summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--autoload/ale.vim35
-rw-r--r--autoload/ale/engine.vim27
-rw-r--r--doc/ale.txt18
-rw-r--r--test/test_lint_file_linters.vader174
4 files changed, 243 insertions, 11 deletions
diff --git a/autoload/ale.vim b/autoload/ale.vim
index ce2589bb..830a281d 100644
--- a/autoload/ale.vim
+++ b/autoload/ale.vim
@@ -3,7 +3,7 @@
" Manages execution of linters when requested by autocommands
let s:lint_timer = -1
-let s:should_lint_file = 0
+let s:should_lint_file_for_buffer = {}
" A function for checking various conditions whereby ALE just shouldn't
" attempt to do anything, say if particular buffer types are open in Vim.
@@ -20,8 +20,8 @@ function! ale#Queue(delay, ...) abort
throw 'too many arguments!'
endif
- " Default run_file_linters to 0
- let l:linting_flag = len(a:0) > 1 ? a:1 : ''
+ " Default linting_flag to ''
+ let l:linting_flag = get(a:000, 0, '')
if l:linting_flag !=# '' && l:linting_flag !=# 'lint_file'
throw "linting_flag must be either '' or 'lint_file'"
@@ -31,8 +31,11 @@ function! ale#Queue(delay, ...) abort
return
endif
- " Remember the event used for linting.
- let s:should_lint_file = l:linting_flag ==# 'lint_file'
+ " Remember that we want to check files for this buffer.
+ " We will remember this until we finally run the linters, via any event.
+ if l:linting_flag ==# 'lint_file'
+ let s:should_lint_file_for_buffer[bufnr('%')] = 1
+ endif
if s:lint_timer != -1
call timer_stop(s:lint_timer)
@@ -59,6 +62,13 @@ function! ale#Lint(...) abort
let l:buffer = bufnr('%')
let l:linters = ale#linter#Get(&filetype)
+ let l:should_lint_file = 0
+
+ " Check if we previously requested checking the file.
+ if has_key(s:should_lint_file_for_buffer, l:buffer)
+ unlet s:should_lint_file_for_buffer[l:buffer]
+ let l:should_lint_file = 1
+ endif
" Initialise the buffer information if needed.
call ale#engine#InitBufferInfo(l:buffer)
@@ -66,7 +76,22 @@ function! ale#Lint(...) abort
" Clear the new loclist again, so we will work with all new items.
let g:ale_buffer_info[l:buffer].new_loclist = []
+ if l:should_lint_file
+ " Clear loclist items for files if we are checking files again.
+ let g:ale_buffer_info[l:buffer].lint_file_loclist = []
+ else
+ " Otherwise, don't run any `lint_file` linters
+ " We will continue running any linters which are currently checking
+ " the file, and the items will be mixed together with any new items.
+ call filter(l:linters, '!v:val.lint_file')
+ endif
+
for l:linter in l:linters
call ale#engine#Invoke(l:buffer, l:linter)
endfor
endfunction
+
+" Reset flags indicating that files should be checked for all buffers.
+function! ale#ResetLintFileMarkers() abort
+ let s:should_lint_file_for_buffer = {}
+endfunction
diff --git a/autoload/ale/engine.vim b/autoload/ale/engine.vim
index 8f5a06a0..3e440d1f 100644
--- a/autoload/ale/engine.vim
+++ b/autoload/ale/engine.vim
@@ -226,7 +226,13 @@ function! s:HandleExit(job) abort
let l:linter_loclist = ale#engine#FixLocList(l:buffer, l:linter, l:linter_loclist)
" Add the loclist items from the linter.
- call extend(g:ale_buffer_info[l:buffer].new_loclist, l:linter_loclist)
+ " loclist items for files which are checked go into a different list,
+ " and are kept between runs.
+ if l:linter.lint_file
+ call extend(g:ale_buffer_info[l:buffer].lint_file_loclist, l:linter_loclist)
+ else
+ call extend(g:ale_buffer_info[l:buffer].new_loclist, l:linter_loclist)
+ endif
if !empty(g:ale_buffer_info[l:buffer].job_list)
" Wait for all jobs to complete before doing anything else.
@@ -237,14 +243,18 @@ function! s:HandleExit(job) abort
" now that all jobs have completed.
call ale#engine#RemoveManagedFiles(l:buffer)
+ " Combine the lint_file List and the List for everything else.
+ let l:combined_list = g:ale_buffer_info[l:buffer].lint_file_loclist
+ \ + g:ale_buffer_info[l:buffer].new_loclist
+
" Sort the loclist again.
" We need a sorted list so we can run a binary search against it
" for efficient lookup of the messages in the cursor handler.
- call sort(g:ale_buffer_info[l:buffer].new_loclist, 'ale#util#LocItemCompare')
+ call sort(l:combined_list, 'ale#util#LocItemCompare')
" Now swap the old and new loclists, after we have collected everything
" and sorted the list again.
- let g:ale_buffer_info[l:buffer].loclist = g:ale_buffer_info[l:buffer].new_loclist
+ let g:ale_buffer_info[l:buffer].loclist = l:combined_list
let g:ale_buffer_info[l:buffer].new_loclist = []
call ale#engine#SetResults(l:buffer, g:ale_buffer_info[l:buffer].loclist)
@@ -255,6 +265,7 @@ endfunction
function! ale#engine#SetResults(buffer, loclist) abort
" Set signs first. This could potentially fix some line numbers.
+ " The List could be sorted again here by SetSigns.
if g:ale_set_signs
call ale#sign#SetSigns(a:buffer, a:loclist)
endif
@@ -698,10 +709,14 @@ function! ale#engine#WaitForJobs(deadline) abort
" for command_chain linters.
let l:has_new_jobs = 0
+ " Check again to see if any jobs are running.
for l:info in values(g:ale_buffer_info)
- if !empty(l:info.job_list)
- let l:has_new_jobs = 1
- endif
+ for l:job in l:info.job_list
+ if job_status(l:job) ==# 'run'
+ let l:has_new_jobs = 1
+ break
+ endif
+ endfor
endfor
if l:has_new_jobs
diff --git a/doc/ale.txt b/doc/ale.txt
index c9b51f01..bf700e0a 100644
--- a/doc/ale.txt
+++ b/doc/ale.txt
@@ -1407,6 +1407,24 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()*
if a command manually reads from a temporary file
instead, etc.
+ `lint_file` A |Number| (`0` or `1`) indicating whether a command
+ should read the file instead of the Vim buffer. This
+ option can be used for linters which must check the
+ file on disk, and which cannot check a Vim buffer
+ instead.
+
+ Linters set with this option will not be run as a
+ user types, per |g:ale_lint_on_text_changed|. Linters
+ will instead be run only when events occur against
+ the file on disk, including |g:ale_lint_on_enter|
+ and |g:ale_lint_on_save|. Linters with this option
+ set to `1` will also be run when linters are run
+ manually, per |ALELint-autocmd|.
+
+ When this option is set to `1`, `read_buffer` will
+ be set automatically to `0`. The two options cannot
+ be used together.
+
Only one of `command`, `command_callback`, or `command_chain` should be
specified. `command_callback` is generally recommended when a command string
needs to be generated dynamically, or any global options are used.
diff --git a/test/test_lint_file_linters.vader b/test/test_lint_file_linters.vader
new file mode 100644
index 00000000..d80fe2c6
--- /dev/null
+++ b/test/test_lint_file_linters.vader
@@ -0,0 +1,174 @@
+Before:
+ let g:buffer_result = [
+ \ {
+ \ 'lnum': 1,
+ \ 'col': 1,
+ \ 'text': 'buffer error',
+ \ 'type': 'E',
+ \ },
+ \ {
+ \ 'lnum': 2,
+ \ 'col': 1,
+ \ 'text': 'buffer warning',
+ \ 'type': 'W',
+ \ },
+ \]
+
+ function! LintFileCallback(buffer, output)
+ return [
+ \ {
+ \ 'lnum': 1,
+ \ 'col': 3,
+ \ 'text': 'file warning',
+ \ 'type': 'W',
+ \ },
+ \ {
+ \ 'lnum': 2,
+ \ 'col': 3,
+ \ 'text': 'file error',
+ \ 'type': 'E',
+ \ },
+ \]
+ endfunction
+
+ function! BufferCallback(buffer, output)
+ return deepcopy(g:buffer_result)
+ endfunction
+
+ function! GetSimplerLoclist()
+ let l:loclist = []
+
+ for l:item in getloclist(0)
+ call add(l:loclist, {
+ \ 'lnum': l:item.lnum,
+ \ 'col': l:item.col,
+ \ 'text': l:item.text,
+ \ 'type': l:item.type,
+ \})
+ endfor
+
+ return l:loclist
+ endfunction
+
+ call ale#linter#Define('foobar', {
+ \ 'name': 'lint_file_linter',
+ \ 'callback': 'LintFileCallback',
+ \ 'executable': 'echo',
+ \ 'command': 'echo',
+ \ 'lint_file': 1,
+ \})
+
+ call ale#linter#Define('foobar', {
+ \ 'name': 'buffer_linter',
+ \ 'callback': 'BufferCallback',
+ \ 'executable': 'echo',
+ \ 'command': 'echo',
+ \ 'read_buffer': 0,
+ \})
+
+After:
+ unlet g:buffer_result
+ let g:ale_buffer_info = {}
+ call ale#linter#Reset()
+ call setloclist(0, [])
+ delfunction LintFileCallback
+ delfunction BufferCallback
+
+Given foobar (Some imaginary filetype):
+ foo
+ bar
+ baz
+
+Execute(Running linters without 'lint_file' should run only buffer linters):
+ call ale#ResetLintFileMarkers()
+ let g:ale_buffer_info = {}
+ call ale#Queue(0)
+ call ale#engine#WaitForJobs(2000)
+
+ AssertEqual [
+ \ {
+ \ 'lnum': 1,
+ \ 'col': 1,
+ \ 'text': 'buffer error',
+ \ 'type': 'E',
+ \ },
+ \ {
+ \ 'lnum': 2,
+ \ 'col': 1,
+ \ 'text': 'buffer warning',
+ \ 'type': 'W',
+ \ },
+ \], GetSimplerLoclist()
+
+Execute(Running linters with 'lint_file' should run all linters):
+ call ale#ResetLintFileMarkers()
+ let g:ale_buffer_info = {}
+ call ale#Queue(0, 'lint_file')
+ call ale#engine#WaitForJobs(2000)
+
+ AssertEqual [
+ \ {
+ \ 'lnum': 1,
+ \ 'col': 1,
+ \ 'text': 'buffer error',
+ \ 'type': 'E',
+ \ },
+ \ {
+ \ 'lnum': 1,
+ \ 'col': 3,
+ \ 'text': 'file warning',
+ \ 'type': 'W',
+ \ },
+ \ {
+ \ 'lnum': 2,
+ \ 'col': 1,
+ \ 'text': 'buffer warning',
+ \ 'type': 'W',
+ \ },
+ \ {
+ \ 'lnum': 2,
+ \ 'col': 3,
+ \ 'text': 'file error',
+ \ 'type': 'E',
+ \ },
+ \], GetSimplerLoclist()
+
+Execute(Linter errors from files should be kept):
+ call ale#ResetLintFileMarkers()
+ let g:ale_buffer_info = {}
+ call ale#Queue(0, 'lint_file')
+ call ale#engine#WaitForJobs(2000)
+
+ " Change the results for the buffer callback.
+ let g:buffer_result = [
+ \ {
+ \ 'lnum': 1,
+ \ 'col': 1,
+ \ 'text': 'new buffer error',
+ \ 'type': 'E',
+ \ },
+ \]
+
+ call ale#Queue(0)
+ call ale#engine#WaitForJobs(2000)
+
+ AssertEqual [
+ \ {
+ \ 'lnum': 1,
+ \ 'col': 1,
+ \ 'text': 'new buffer error',
+ \ 'type': 'E',
+ \ },
+ \ {
+ \ 'lnum': 1,
+ \ 'col': 3,
+ \ 'text': 'file warning',
+ \ 'type': 'W',
+ \ },
+ \ {
+ \ 'lnum': 2,
+ \ 'col': 3,
+ \ 'text': 'file error',
+ \ 'type': 'E',
+ \ },
+ \], GetSimplerLoclist()