mirror of
https://github.com/dense-analysis/ale
synced 2025-01-05 13:39:58 +00:00
Implement command chaining. Tests and documentation to come.
This commit is contained in:
parent
88f4598ea2
commit
f950c29035
@ -106,11 +106,17 @@ function! s:HandleExit(job) abort
|
||||
let l:linter = l:job_info.linter
|
||||
let l:output = l:job_info.output
|
||||
let l:buffer = l:job_info.buffer
|
||||
let l:next_chain_index = l:job_info.next_chain_index
|
||||
|
||||
" Call the same function for stopping jobs again to clean up the job
|
||||
" which just closed.
|
||||
call s:StopPreviousJobs(l:buffer, l:linter)
|
||||
|
||||
if l:next_chain_index < len(get(l:linter, 'command_chain', []))
|
||||
call s:InvokeChain(l:buffer, l:linter, l:next_chain_index, l:output)
|
||||
return
|
||||
endif
|
||||
|
||||
let l:linter_loclist = ale#util#GetFunction(l:linter.callback)(l:buffer, l:output)
|
||||
|
||||
" Make some adjustments to the loclists to fix common problems.
|
||||
@ -182,31 +188,27 @@ function! s:FixLocList(buffer, loclist) abort
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! ale#engine#Invoke(buffer, linter) abort
|
||||
" Stop previous jobs for the same linter.
|
||||
call s:StopPreviousJobs(a:buffer, a:linter)
|
||||
|
||||
if has_key(a:linter, 'command_callback')
|
||||
" If there is a callback for generating a command, call that instead.
|
||||
let l:command = ale#util#GetFunction(a:linter.command_callback)(a:buffer)
|
||||
else
|
||||
let l:command = a:linter.command
|
||||
endif
|
||||
function! s:RunJob(command, generic_job_options) abort
|
||||
let l:buffer = a:generic_job_options.buffer
|
||||
let l:linter = a:generic_job_options.linter
|
||||
let l:output_stream = a:generic_job_options.output_stream
|
||||
let l:next_chain_index = a:generic_job_options.next_chain_index
|
||||
let l:command = a:command
|
||||
|
||||
if l:command =~# '%s'
|
||||
" If there is a '%s' in the command string, replace it with the name
|
||||
" of the file.
|
||||
let l:command = printf(l:command, shellescape(fnamemodify(bufname(a:buffer), ':p')))
|
||||
let l:command = printf(l:command, shellescape(fnamemodify(bufname(l:buffer), ':p')))
|
||||
endif
|
||||
|
||||
if has('nvim')
|
||||
if a:linter.output_stream ==# 'stderr'
|
||||
if l:output_stream ==# 'stderr'
|
||||
" Read from stderr instead of stdout.
|
||||
let l:job = jobstart(l:command, {
|
||||
\ 'on_stderr': 's:GatherOutputNeoVim',
|
||||
\ 'on_exit': 's:HandleExitNeoVim',
|
||||
\})
|
||||
elseif a:linter.output_stream ==# 'both'
|
||||
elseif l:output_stream ==# 'both'
|
||||
let l:job = jobstart(l:command, {
|
||||
\ 'on_stdout': 's:GatherOutputNeoVim',
|
||||
\ 'on_stderr': 's:GatherOutputNeoVim',
|
||||
@ -226,10 +228,10 @@ function! ale#engine#Invoke(buffer, linter) abort
|
||||
\ 'close_cb': function('s:HandleExitVim'),
|
||||
\}
|
||||
|
||||
if a:linter.output_stream ==# 'stderr'
|
||||
if l:output_stream ==# 'stderr'
|
||||
" Read from stderr instead of stdout.
|
||||
let l:job_options.err_cb = function('s:GatherOutputVim')
|
||||
elseif a:linter.output_stream ==# 'both'
|
||||
elseif l:output_stream ==# 'both'
|
||||
" Read from both streams.
|
||||
let l:job_options.out_cb = function('s:GatherOutputVim')
|
||||
let l:job_options.err_cb = function('s:GatherOutputVim')
|
||||
@ -249,7 +251,7 @@ function! ale#engine#Invoke(buffer, linter) abort
|
||||
" On Unix machines, we can send the Vim buffer directly.
|
||||
" This is faster than reading the lines ourselves.
|
||||
let l:job_options.in_io = 'buffer'
|
||||
let l:job_options.in_buf = a:buffer
|
||||
let l:job_options.in_buf = l:buffer
|
||||
endif
|
||||
|
||||
" Vim 8 will read the stdin from the file's buffer.
|
||||
@ -259,24 +261,25 @@ function! ale#engine#Invoke(buffer, linter) abort
|
||||
" Only proceed if the job is being run.
|
||||
if has('nvim') || (l:job !=# 'no process' && job_status(l:job) ==# 'run')
|
||||
" Add the job to the list of jobs, so we can track them.
|
||||
call add(g:ale_buffer_info[a:buffer].job_list, l:job)
|
||||
call add(g:ale_buffer_info[l:buffer].job_list, l:job)
|
||||
|
||||
" Store the ID for the job in the map to read back again.
|
||||
let s:job_info_map[s:GetJobID(l:job)] = {
|
||||
\ 'linter': a:linter,
|
||||
\ 'buffer': a:buffer,
|
||||
\ 'linter': l:linter,
|
||||
\ 'buffer': l:buffer,
|
||||
\ 'output': [],
|
||||
\ 'next_chain_index': l:next_chain_index,
|
||||
\}
|
||||
|
||||
if has('nvim')
|
||||
" In NeoVim, we have to send the buffer lines ourselves.
|
||||
let l:input = join(getbufline(a:buffer, 1, '$'), "\n") . "\n"
|
||||
let l:input = join(getbufline(l:buffer, 1, '$'), "\n") . "\n"
|
||||
|
||||
call jobsend(l:job, l:input)
|
||||
call jobclose(l:job, 'stdin')
|
||||
elseif has('win32')
|
||||
" On some Vim versions, we have to send the buffer data ourselves.
|
||||
let l:input = join(getbufline(a:buffer, 1, '$'), "\n") . "\n"
|
||||
let l:input = join(getbufline(l:buffer, 1, '$'), "\n") . "\n"
|
||||
let l:channel = job_getchannel(l:job)
|
||||
|
||||
if ch_status(l:channel) ==# 'open'
|
||||
@ -287,6 +290,49 @@ function! ale#engine#Invoke(buffer, linter) abort
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:InvokeChain(buffer, linter, chain_index, input) abort
|
||||
let l:output_stream = get(a:linter, 'output_stream', 'stdout')
|
||||
|
||||
if has_key(a:linter, 'command_chain')
|
||||
" Run a chain of commands, one asychronous command after the other,
|
||||
" so that many programs can be run in a sequence.
|
||||
let l:chain_item = a:linter.command_chain[a:chain_index]
|
||||
|
||||
" The chain item can override the output_stream option.
|
||||
if has_key(l:chain_item)
|
||||
let l:output_stream = l:chain_item.output_stream
|
||||
endif
|
||||
|
||||
let l:callback = ale#util#GetFunction(a:linter.callback)
|
||||
|
||||
if a:chain_index == 0
|
||||
" The first callback in the chain takes only a buffer number.
|
||||
let l:command = l:callback(a:buffer)
|
||||
else
|
||||
" The second callback in the chain takes some input too.
|
||||
let l:command = l:callback(a:buffer, a:input)
|
||||
endif
|
||||
elseif has_key(a:linter, 'command_callback')
|
||||
" If there is a callback for generating a command, call that instead.
|
||||
let l:command = ale#util#GetFunction(a:linter.command_callback)(a:buffer)
|
||||
else
|
||||
let l:command = a:linter.command
|
||||
endif
|
||||
|
||||
call s:RunJob(l:command, {
|
||||
\ 'buffer': a:buffer,
|
||||
\ 'linter': a:linter,
|
||||
\ 'output_stream': l:output_stream,
|
||||
\ 'next_chain_index': a:chain_index + 1,
|
||||
\})
|
||||
endfunction
|
||||
|
||||
function! ale#engine#Invoke(buffer, linter) abort
|
||||
" Stop previous jobs for the same linter.
|
||||
call s:StopPreviousJobs(a:buffer, a:linter)
|
||||
call s:InvokeChain(a:buffer, a:linter, 0, [])
|
||||
endfunction
|
||||
|
||||
" Given a buffer number, return the warnings and errors for a given buffer.
|
||||
function! ale#engine#GetLoclist(buffer) abort
|
||||
if !has_key(g:ale_buffer_info, a:buffer)
|
||||
|
@ -25,35 +25,77 @@ function! ale#linter#Reset() abort
|
||||
let s:linters = {}
|
||||
endfunction
|
||||
|
||||
function! s:IsCallback(value) abort
|
||||
return type(a:value) == type('') || type(a:value) == type(function('type'))
|
||||
endfunction
|
||||
|
||||
function! ale#linter#PreProcess(linter) abort
|
||||
if type(a:linter) != type({})
|
||||
throw 'The linter object must be a Dictionary'
|
||||
endif
|
||||
|
||||
let l:obj = {
|
||||
\ 'name': get(a:linter, 'name'),
|
||||
\ 'callback': get(a:linter, 'callback'),
|
||||
\}
|
||||
|
||||
if type(l:obj.name) != type('')
|
||||
throw '`name` must be defined to name the linter'
|
||||
endif
|
||||
|
||||
if !s:IsCallback(l:obj.callback)
|
||||
throw '`callback` must be defined with a callback to accept output'
|
||||
endif
|
||||
|
||||
if has_key(a:linter, 'executable_callback')
|
||||
let l:obj.executable_callback = a:linter.executable_callback
|
||||
|
||||
if !s:IsCallback(l:obj.executable_callback)
|
||||
throw '`executable_callback` must be a callback if defined'
|
||||
endif
|
||||
elseif has_key(a:linter, 'executable')
|
||||
let l:obj.executable = a:linter.executable
|
||||
|
||||
if type(l:obj.executable) != type('')
|
||||
throw '`executable` must be a string if defined'
|
||||
endif
|
||||
else
|
||||
throw 'Either `executable` or `executable_callback` must be defined'
|
||||
endif
|
||||
|
||||
if has_key(a:linter, 'command_callback')
|
||||
let l:obj.command_callback = a:linter.command_callback
|
||||
|
||||
if !s:IsCallback(l:obj.command_callback)
|
||||
throw '`command_callback` must be a callback if defined'
|
||||
endif
|
||||
elseif has_key(a:linter, 'command')
|
||||
let l:obj.command = a:linter.command
|
||||
|
||||
if type(l:obj.command) != type('')
|
||||
throw '`command` must be a string if defined'
|
||||
endif
|
||||
else
|
||||
throw 'Either `command`, `executable_callback`, `command_chain` '
|
||||
\ . 'must be defined'
|
||||
endif
|
||||
|
||||
let l:obj.output_stream = get(a:linter, 'output_stream', 'stdout')
|
||||
|
||||
if type(l:obj.output_stream) != type('')
|
||||
\|| index(['stdout', 'stderr', 'both'], l:obj.output_stream) < 0
|
||||
throw "`output_stream` must be 'stdout', 'stderr', or 'both'"
|
||||
endif
|
||||
|
||||
return l:obj
|
||||
endfunction
|
||||
|
||||
function! ale#linter#Define(filetype, linter) abort
|
||||
if !has_key(s:linters, a:filetype)
|
||||
let s:linters[a:filetype] = []
|
||||
endif
|
||||
|
||||
let l:new_linter = {
|
||||
\ 'name': a:linter.name,
|
||||
\ 'callback': a:linter.callback,
|
||||
\}
|
||||
|
||||
if has_key(a:linter, 'executable_callback')
|
||||
let l:new_linter.executable_callback = a:linter.executable_callback
|
||||
else
|
||||
let l:new_linter.executable = a:linter.executable
|
||||
endif
|
||||
|
||||
if has_key(a:linter, 'command_callback')
|
||||
let l:new_linter.command_callback = a:linter.command_callback
|
||||
else
|
||||
let l:new_linter.command = a:linter.command
|
||||
endif
|
||||
|
||||
if has_key(a:linter, 'output_stream')
|
||||
let l:new_linter.output_stream = a:linter.output_stream
|
||||
else
|
||||
let l:new_linter.output_stream = 'stdout'
|
||||
endif
|
||||
|
||||
" TODO: Assert the value of the output_stream to be something sensible.
|
||||
let l:new_linter = ale#linter#PreProcess(a:linter)
|
||||
|
||||
call add(s:linters[a:filetype], l:new_linter)
|
||||
endfunction
|
||||
|
Loading…
Reference in New Issue
Block a user