summaryrefslogtreecommitdiff
path: root/autoload
diff options
context:
space:
mode:
Diffstat (limited to 'autoload')
-rw-r--r--autoload/ale/engine.vim125
-rw-r--r--autoload/ale/handlers/rust.vim90
-rw-r--r--autoload/ale/linter.vim32
-rw-r--r--autoload/ale/loclist_jumping.vim16
-rw-r--r--autoload/ale/semver.vim29
-rw-r--r--autoload/ale/util.vim2
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