diff options
Diffstat (limited to 'autoload')
-rw-r--r-- | autoload/ale/engine.vim | 125 | ||||
-rw-r--r-- | autoload/ale/handlers/rust.vim | 90 | ||||
-rw-r--r-- | autoload/ale/linter.vim | 32 | ||||
-rw-r--r-- | autoload/ale/loclist_jumping.vim | 16 | ||||
-rw-r--r-- | autoload/ale/semver.vim | 29 | ||||
-rw-r--r-- | autoload/ale/util.vim | 2 |
6 files changed, 257 insertions, 37 deletions
diff --git a/autoload/ale/engine.vim b/autoload/ale/engine.vim index 08852e00..55633ae0 100644 --- a/autoload/ale/engine.vim +++ b/autoload/ale/engine.vim @@ -53,6 +53,11 @@ function! ale#engine#ClearJob(job) abort endfunction function! s:StopPreviousJobs(buffer, linter) abort + if !has_key(g:ale_buffer_info, a:buffer) + " Do nothing if we didn't run anything for the buffer. + return + endif + let l:new_job_list = [] for l:job in g:ale_buffer_info[a:buffer].job_list @@ -210,6 +215,7 @@ function! s:RunJob(command, generic_job_options) abort 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:read_buffer = a:generic_job_options.read_buffer let l:command = a:command if l:command =~# '%s' @@ -265,10 +271,12 @@ function! s:RunJob(command, generic_job_options) abort " Execute the command with the shell, to fix escaping issues. let l:command = split(&shell) + split(&shellcmdflag) + [l:command] - " 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 = l:buffer + if l:read_buffer + " 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 = l:buffer + endif endif " Vim 8 will read the stdin from the file's buffer. @@ -288,20 +296,22 @@ function! s:RunJob(command, generic_job_options) abort \ '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(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(l:buffer, 1, '$'), "\n") . "\n" - let l:channel = job_getchannel(l:job) - - if ch_status(l:channel) ==# 'open' - call ch_sendraw(l:channel, l:input) - call ch_close_in(l:channel) + if l:read_buffer + if has('nvim') + " In NeoVim, we have to send the buffer lines ourselves. + 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(l:buffer, 1, '$'), "\n") . "\n" + let l:channel = job_getchannel(l:job) + + if ch_status(l:channel) ==# 'open' + call ch_sendraw(l:channel, l:input) + call ch_close_in(l:channel) + endif endif endif endif @@ -309,25 +319,48 @@ endfunction function! s:InvokeChain(buffer, linter, chain_index, input) abort let l:output_stream = get(a:linter, 'output_stream', 'stdout') + let l:chain_index = a:chain_index + let l:input = a:input 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] + while l:chain_index < len(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[l:chain_index] + + " The chain item can override the output_stream option. + if has_key(l:chain_item, 'output_stream') + let l:output_stream = l:chain_item.output_stream + endif - " 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 + if l:chain_index == 0 + " The first callback in the chain takes only a buffer number. + let l:command = ale#util#GetFunction(l:chain_item.callback)( + \ a:buffer + \) + else + " The second callback in the chain takes some input too. + let l:command = ale#util#GetFunction(l:chain_item.callback)( + \ a:buffer, + \ l:input + \) + endif + + if !empty(l:command) + " We hit a command to run, so we'll execute that + break + endif - let l:callback = ale#util#GetFunction(a:linter.callback) + " Command chain items can return an empty string to indicate that + " a command should be skipped, so we should try the next item + " with no input. + let l:input = [] + let l:chain_index += 1 + endwhile - 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) + if empty(l:command) + " Don't run any jobs if the last command is an empty string. + return endif elseif has_key(a:linter, 'command_callback') " If there is a callback for generating a command, call that instead. @@ -336,11 +369,14 @@ function! s:InvokeChain(buffer, linter, chain_index, input) abort let l:command = a:linter.command endif + let l:is_last_job = l:chain_index >= len(get(a:linter, 'command_chain', [])) - 1 + call s:RunJob(l:command, { \ 'buffer': a:buffer, \ 'linter': a:linter, \ 'output_stream': l:output_stream, - \ 'next_chain_index': a:chain_index + 1, + \ 'next_chain_index': l:chain_index + 1, + \ 'read_buffer': l:is_last_job, \}) endfunction @@ -406,4 +442,27 @@ function! ale#engine#WaitForJobs(deadline) abort " prevents the occasional failure where this function exits after jobs " end, but before handlers are run. sleep 10ms + + " We must check the buffer data again to see if new jobs started + " for command_chain linters. + let l:has_new_jobs = 0 + + for l:info in values(g:ale_buffer_info) + if !empty(l:info.job_list) + let l:has_new_jobs = 1 + endif + endfor + + if l:has_new_jobs + " We have to wait more. Offset the timeout by the time taken so far. + let l:now = system('date +%s%3N') + 0 + let l:new_deadline = a:deadline - (l:now - l:start_time) + + if l:new_deadline <= 0 + " Enough time passed already, so stop immediately. + throw 'Jobs did not complete on time!' + endif + + call ale#engine#WaitForJobs(l:new_deadline) + endif endfunction diff --git a/autoload/ale/handlers/rust.vim b/autoload/ale/handlers/rust.vim new file mode 100644 index 00000000..c00c2276 --- /dev/null +++ b/autoload/ale/handlers/rust.vim @@ -0,0 +1,90 @@ +" Author: Daniel Schemala <istjanichtzufassen@gmail.com>, +" w0rp <devw0rp@gmail.com> +" +" Description: This file implements handlers specific to Rust. + +if !exists('g:ale_rust_ignore_error_codes') + let g:ale_rust_ignore_error_codes = [] +endif + +" returns: a list [lnum, col] with the location of the error or [] +function! s:FindErrorInExpansion(span, file_name) abort + if a:span.file_name ==# a:file_name + return [a:span.line_start, a:span.byte_start] + endif + + if !empty(a:span.expansion) + return s:FindErrorInExpansion(a:span.expansion.span, a:file_name) + endif + + return [] +endfunction + +" A handler function which accepts a file name, to make unit testing easier. +function! ale#handlers#rust#HandleRustErrorsForFile(buffer, full_filename, lines) abort + let l:filename = fnamemodify(a:full_filename, ':t') + let l:output = [] + + for l:errorline in a:lines + " ignore everything that is not Json + if l:errorline !~# '^{' + continue + endif + + let l:error = json_decode(l:errorline) + + if has_key(l:error, 'message') && type(l:error.message) == type({}) + let l:error = l:error.message + endif + + if !has_key(l:error, 'code') + continue + endif + + if !empty(l:error.code) && index(g:ale_rust_ignore_error_codes, l:error.code.code) > -1 + continue + endif + + for l:span in l:error.spans + let l:span_filename = fnamemodify(l:span.file_name, ':t') + + if ( + \ l:span.is_primary + \ && (l:span_filename ==# l:filename || l:span_filename ==# '<anon>') + \) + call add(l:output, { + \ 'bufnr': a:buffer, + \ 'lnum': l:span.line_start, + \ 'vcol': 0, + \ 'col': l:span.byte_start, + \ 'nr': -1, + \ 'text': l:error.message, + \ 'type': toupper(l:error.level[0]), + \}) + else + " when the error is caused in the expansion of a macro, we have + " to bury deeper + let l:root_cause = s:FindErrorInExpansion(l:span, l:filename) + + if !empty(l:root_cause) + call add(l:output, { + \ 'bufnr': a:buffer, + \ 'lnum': l:root_cause[0], + \ 'vcol': 0, + \ 'col': l:root_cause[1], + \ 'nr': -1, + \ 'text': l:error.message, + \ 'type': toupper(l:error.level[0]), + \}) + endif + endif + endfor + endfor + + return l:output +endfunction + +" A handler for output for Rust linters. +function! ale#handlers#rust#HandleRustErrors(buffer, lines) abort + return ale#handlers#rust#HandleRustErrorsForFile(a:buffer, bufname(a:buffer), a:lines) +endfunction diff --git a/autoload/ale/linter.vim b/autoload/ale/linter.vim index 823359ce..0a5b9ae3 100644 --- a/autoload/ale/linter.vim +++ b/autoload/ale/linter.vim @@ -67,7 +67,37 @@ function! ale#linter#PreProcess(linter) abort throw 'Either `executable` or `executable_callback` must be defined' endif - if has_key(a:linter, 'command_callback') + if has_key(a:linter, 'command_chain') + let l:obj.command_chain = a:linter.command_chain + + if type(l:obj.command_chain) != type([]) + throw '`command_chain` must be a List' + endif + + if empty(l:obj.command_chain) + throw '`command_chain` must contain at least one item' + endif + + let l:link_index = 0 + + for l:link in l:obj.command_chain + let l:err_prefix = 'The `command_chain` item ' . l:link_index . ' ' + + if !s:IsCallback(get(l:link, 'callback')) + throw l:err_prefix . 'must define a `callback` function' + endif + + if has_key(l:link, 'output_stream') + if type(l:link.output_stream) != type('') + \|| index(['stdout', 'stderr', 'both'], l:link.output_stream) < 0 + throw l:err_prefix . '`output_stream` flag must be ' + \ . "'stdout', 'stderr', or 'both'" + endif + endif + + let l:link_index += 1 + endfor + elseif has_key(a:linter, 'command_callback') let l:obj.command_callback = a:linter.command_callback if !s:IsCallback(l:obj.command_callback) diff --git a/autoload/ale/loclist_jumping.vim b/autoload/ale/loclist_jumping.vim index b9ea9d13..b4d1993f 100644 --- a/autoload/ale/loclist_jumping.vim +++ b/autoload/ale/loclist_jumping.vim @@ -1,11 +1,23 @@ " Author: w0rp <devw0rp@gmail.com> " Description: This file implements functions for jumping around in a file -" based on errors and warnings in the loclist. +" based on errors and warnings in the loclist or quickfix list. + +function! s:GetCurrentList() abort + if g:ale_set_loclist + return getloclist(winnr()) + elseif g:ale_set_quickfix + let l:buffer = bufnr('%') + + return filter(getqflist(), 'get(v:val, ''bufnr'', -1) == ' . l:buffer) + endif + + return [] +endfunction function! s:GetSortedLoclist() abort let l:loclist = [] - for l:item in getloclist(winnr()) + for l:item in s:GetCurrentList() if l:item.lnum < 1 " Remove items we can't even jump to. continue diff --git a/autoload/ale/semver.vim b/autoload/ale/semver.vim new file mode 100644 index 00000000..b153dd1d --- /dev/null +++ b/autoload/ale/semver.vim @@ -0,0 +1,29 @@ +" Given some text, parse a semantic versioning string from the text +" into a triple of integeers [major, minor, patch]. +" +" If no match can be performed, then an empty List will be returned instead. +function! ale#semver#Parse(text) abort + let l:match = matchlist(a:text, '^ *\(\d\+\)\.\(\d\+\)\.\(\d\+\)') + + if empty(l:match) + return [] + endif + + return [l:match[1] + 0, l:match[2] + 0, l:match[3] + 0] +endfunction + +" Given two triples of integers [major, minor, patch], compare the triples +" and return 1 if the lhs is greater than or equal to the rhs. +function! ale#semver#GreaterOrEqual(lhs, rhs) abort + if a:lhs[0] > a:rhs[0] + return 1 + elseif a:lhs[0] == a:rhs[0] + if a:lhs[1] > a:rhs[1] + return 1 + elseif a:lhs[1] == a:rhs[1] + return a:lhs[2] >= a:rhs[2] + endif + endif + + return 0 +endfunction diff --git a/autoload/ale/util.vim b/autoload/ale/util.vim index 27517ef7..6c6f20b0 100644 --- a/autoload/ale/util.vim +++ b/autoload/ale/util.vim @@ -8,7 +8,7 @@ function! s:FindWrapperScript() abort if filereadable(l:path) if has('win32') - return l:path . '.bat' + return l:path . '.exe' endif return l:path |