summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--autoload/ale/engine.vim88
-rw-r--r--autoload/ale/linter.vim72
2 files changed, 124 insertions, 36 deletions
diff --git a/autoload/ale/engine.vim b/autoload/ale/engine.vim
index 731bacf1..960b993e 100644
--- a/autoload/ale/engine.vim
+++ b/autoload/ale/engine.vim
@@ -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)
diff --git a/autoload/ale/linter.vim b/autoload/ale/linter.vim
index 757e6a40..802d8646 100644
--- a/autoload/ale/linter.vim
+++ b/autoload/ale/linter.vim
@@ -25,35 +25,77 @@ function! ale#linter#Reset() abort
let s:linters = {}
endfunction
-function! ale#linter#Define(filetype, linter) abort
- if !has_key(s:linters, a:filetype)
- let s:linters[a:filetype] = []
+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:new_linter = {
- \ 'name': a:linter.name,
- \ 'callback': a:linter.callback,
+ 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:new_linter.executable_callback = 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
- let l:new_linter.executable = a:linter.executable
+ throw 'Either `executable` or `executable_callback` must be defined'
endif
if has_key(a:linter, 'command_callback')
- let l:new_linter.command_callback = 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
- let l:new_linter.command = a:linter.command
+ throw 'Either `command`, `executable_callback`, `command_chain` '
+ \ . 'must be defined'
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'
+ 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
- " 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