summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Dockerfile2
-rw-r--r--README.md18
-rw-r--r--ale_linters/haskell/cabal_ghc.vim3
-rw-r--r--ale_linters/haskell/stack_ghc.vim3
-rw-r--r--ale_linters/kotlin/kotlinc.vim2
-rw-r--r--autoload/ale/c.vim25
-rw-r--r--autoload/ale/completion.vim101
-rw-r--r--autoload/ale/definition.vim19
-rw-r--r--autoload/ale/engine.vim6
-rw-r--r--autoload/ale/fix.vim28
-rw-r--r--autoload/ale/fix/registry.vim5
-rw-r--r--autoload/ale/fixers/styler.vim16
-rw-r--r--autoload/ale/highlight.vim140
-rw-r--r--autoload/ale/lsp/response.vim2
-rw-r--r--autoload/ale/lsp_linter.vim15
-rw-r--r--autoload/ale/sign.vim2
-rw-r--r--autoload/ale/util.vim2
-rw-r--r--autoload/ale/virtualtext.vim2
-rw-r--r--doc/ale-java.txt7
-rw-r--r--doc/ale-r.txt16
-rw-r--r--doc/ale-supported-languages-and-tools.txt1
-rw-r--r--doc/ale.txt96
-rw-r--r--plugin/ale.vim2
-rw-r--r--rplugin/python3/deoplete/sources/ale.py50
-rw-r--r--supported-tools.md1
-rw-r--r--test/command_callback/test_haskell_cabal_ghc_command_callbacks.vader4
-rw-r--r--test/command_callback/test_haskell_stack_ghc_command_callback.vader4
-rw-r--r--test/command_callback/test_kotlinc_command_callback.vader9
-rw-r--r--test/completion/test_completion_events.vader64
-rw-r--r--test/completion/test_lsp_completion_messages.vader10
-rw-r--r--test/fix/test_ale_fix_ignore.vader110
-rw-r--r--test/fixers/test_styler_fixer_callback.vader21
-rw-r--r--test/lsp/test_read_lsp_diagnostics.vader20
-rw-r--r--test/python/test_deoplete_source.py130
-rwxr-xr-xtest/script/custom-checks10
-rw-r--r--test/test_c_flag_parsing.vader98
-rw-r--r--test/test_engine_lsp_response_handling.vader44
-rw-r--r--test/test_go_to_definition.vader9
-rw-r--r--test/test_nvim_api_highlight.vader160
-rw-r--r--test/test_redundant_tsserver_rendering_avoided.vader2
41 files changed, 1140 insertions, 121 deletions
diff --git a/.gitignore b/.gitignore
index 9b539d7f..ae9f65fb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,8 @@
# Ignore all hidden files everywhere.
# Use `git add -f` to add hidden files.
.*
+__pycache__
+*.pyc
/doc/tags
/init.vim
/test/ale-info-test-file
diff --git a/Dockerfile b/Dockerfile
index 6111f9ba..58ab0771 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,7 +1,7 @@
FROM tweekmonster/vim-testbed:latest
RUN install_vim -tag v8.0.0027 -build \
- -tag v8.1.0204 -build \
+ -tag v8.1.0519 -build \
-tag neovim:v0.2.0 -build \
-tag neovim:v0.3.0 -build
diff --git a/README.md b/README.md
index 433bba11..a35a62ab 100644
--- a/README.md
+++ b/README.md
@@ -3,8 +3,8 @@
![ALE Logo by Mark Grealish - https://www.bhalash.com/](img/logo.jpg?raw=true)
-ALE (Asynchronous Lint Engine) is a plugin for providing linting (checking
-syntax and semantics) in NeoVim 0.2.0+ and Vim 8 while you edit your text files,
+ALE (Asynchronous Lint Engine) is a plugin providing linting (syntax checking
+and semantic errors) in NeoVim 0.2.0+ and Vim 8 while you edit your text files,
and acts as a Vim [Language Server Protocol](https://langserver.org/) client.
<img src="img/example.gif?raw=true" alt="A linting example with the darkspectrum color scheme in GVim." title="A linting example with the darkspectrum color scheme in GVim.">
@@ -26,7 +26,7 @@ features, including:
* Diagnostics (via Language Server Protocol linters)
* Go To Definition (`:ALEGoToDefinition`)
-* Completion (`let g:ale_completion_enabled = 1` before ALE is loaded)
+* Completion (Built in completion support, or with Deoplete)
* Finding references (`:ALEFindReferences`)
* Hover information (`:ALEHover`)
* Symbol search (`:ALESymbolSearch`)
@@ -159,6 +159,18 @@ ALE offers some support for completion via hijacking of omnicompletion while you
type. All of ALE's completion information must come from Language Server
Protocol linters, or from `tsserver` for TypeScript.
+ALE integrates with [Deoplete](https://github.com/Shougo/deoplete.nvim) as a
+completion source, named `'ale'`. You can configure Deoplete to only use ALE as
+the source of completion information, or mix it with other sources.
+
+```vim
+" Use ALE and also some plugin 'foobar' as completion sources for all code.
+let g:deoplete#sources = {'_': ['ale', 'foobar']}
+```
+
+ALE also offers its own automatic completion support, which does not require any
+other plugins, and can be enabled by changing a setting before ALE is loaded.
+
```vim
" Enable completion where available.
" This setting must be set before ALE is loaded.
diff --git a/ale_linters/haskell/cabal_ghc.vim b/ale_linters/haskell/cabal_ghc.vim
index 59978e7e..f3f248f5 100644
--- a/ale_linters/haskell/cabal_ghc.vim
+++ b/ale_linters/haskell/cabal_ghc.vim
@@ -4,7 +4,8 @@
call ale#Set('haskell_cabal_ghc_options', '-fno-code -v0')
function! ale_linters#haskell#cabal_ghc#GetCommand(buffer) abort
- return 'cabal exec -- ghc '
+ return ale#path#BufferCdString(a:buffer)
+ \ . 'cabal exec -- ghc '
\ . ale#Var(a:buffer, 'haskell_cabal_ghc_options')
\ . ' %t'
endfunction
diff --git a/ale_linters/haskell/stack_ghc.vim b/ale_linters/haskell/stack_ghc.vim
index 06af7f6d..c345fe43 100644
--- a/ale_linters/haskell/stack_ghc.vim
+++ b/ale_linters/haskell/stack_ghc.vim
@@ -4,7 +4,8 @@
call ale#Set('haskell_stack_ghc_options', '-fno-code -v0')
function! ale_linters#haskell#stack_ghc#GetCommand(buffer) abort
- return ale#handlers#haskell#GetStackExecutable(a:buffer)
+ return ale#path#BufferCdString(a:buffer)
+ \ . ale#handlers#haskell#GetStackExecutable(a:buffer)
\ . ' ghc -- '
\ . ale#Var(a:buffer, 'haskell_stack_ghc_options')
\ . ' %t'
diff --git a/ale_linters/kotlin/kotlinc.vim b/ale_linters/kotlin/kotlinc.vim
index fddd6625..3c6854fa 100644
--- a/ale_linters/kotlin/kotlinc.vim
+++ b/ale_linters/kotlin/kotlinc.vim
@@ -12,6 +12,8 @@ let g:ale_kotlin_kotlinc_module_filename = get(g:, 'ale_kotlin_kotlinc_module_fi
let s:classpath_sep = has('unix') ? ':' : ';'
function! ale_linters#kotlin#kotlinc#RunWithImportPaths(buffer) abort
+ let l:command = ''
+
" exec maven/gradle only if classpath is not set
if ale#Var(a:buffer, 'kotlin_kotlinc_classpath') isnot# ''
return ale_linters#kotlin#kotlinc#GetCommand(a:buffer, [], {})
diff --git a/autoload/ale/c.vim b/autoload/ale/c.vim
index eeb512ba..a9289e22 100644
--- a/autoload/ale/c.vim
+++ b/autoload/ale/c.vim
@@ -219,9 +219,32 @@ function! ale#c#ParseCompileCommandsFlags(buffer, file_lookup, dir_lookup) abort
" Search for an exact file match first.
let l:basename = tolower(expand('#' . a:buffer . ':t'))
let l:file_list = get(a:file_lookup, l:basename, [])
+ " A source file matching the header filename.
+ let l:source_file = ''
+
+ if empty(l:file_list) && l:basename =~? '\.h$\|\.hpp$'
+ for l:suffix in ['.c', '.cpp']
+ let l:key = fnamemodify(l:basename, ':r') . l:suffix
+ let l:file_list = get(a:file_lookup, l:key, [])
+
+ if !empty(l:file_list)
+ let l:source_file = l:key
+ break
+ endif
+ endfor
+ endif
for l:item in l:file_list
- if bufnr(l:item.file) is a:buffer && has_key(l:item, 'command')
+ " Load the flags for this file, or for a source file matching the
+ " header file.
+ if has_key(l:item, 'command')
+ \&& (
+ \ bufnr(l:item.file) is a:buffer
+ \ || (
+ \ !empty(l:source_file)
+ \ && l:item.file[-len(l:source_file):] is? l:source_file
+ \ )
+ \)
return ale#c#ParseCFlags(l:item.directory, l:item.command)
endif
endfor
diff --git a/autoload/ale/completion.vim b/autoload/ale/completion.vim
index 682f4c43..1d42c489 100644
--- a/autoload/ale/completion.vim
+++ b/autoload/ale/completion.vim
@@ -159,18 +159,20 @@ function! ale#completion#Filter(buffer, filetype, suggestions, prefix) abort
endfunction
function! s:ReplaceCompletionOptions() abort
- " Remember the old omnifunc value, if there is one.
- " If we don't store an old one, we'll just never reset the option.
- " This will stop some random exceptions from appearing.
- if !exists('b:ale_old_omnifunc') && !empty(&l:omnifunc)
- let b:ale_old_omnifunc = &l:omnifunc
- endif
-
- let &l:omnifunc = 'ale#completion#OmniFunc'
+ let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '')
+
+ if l:source is# 'ale-automatic' || l:source is# 'ale-manual'
+ " Remember the old omnifunc value, if there is one.
+ " If we don't store an old one, we'll just never reset the option.
+ " This will stop some random exceptions from appearing.
+ if !exists('b:ale_old_omnifunc') && !empty(&l:omnifunc)
+ let b:ale_old_omnifunc = &l:omnifunc
+ endif
- let l:info = get(b:, 'ale_completion_info', {})
+ let &l:omnifunc = 'ale#completion#OmniFunc'
+ endif
- if !get(l:info, 'manual')
+ if l:source is# 'ale-automatic'
if !exists('b:ale_old_completeopt')
let b:ale_old_completeopt = &l:completeopt
endif
@@ -199,31 +201,49 @@ function! ale#completion#RestoreCompletionOptions() abort
endif
endfunction
-function! ale#completion#OmniFunc(findstart, base) abort
- if a:findstart
- let l:line = b:ale_completion_info.line
- let l:column = b:ale_completion_info.column
- let l:regex = s:GetFiletypeValue(s:omni_start_map, &filetype)
- let l:up_to_column = getline(l:line)[: l:column - 2]
- let l:match = matchstr(l:up_to_column, l:regex)
+function! ale#completion#GetCompletionPosition() abort
+ if !exists('b:ale_completion_info')
+ return 0
+ endif
- return l:column - len(l:match) - 1
- else
- " Parse a new response if there is one.
- if exists('b:ale_completion_response')
- \&& exists('b:ale_completion_parser')
- let l:response = b:ale_completion_response
- let l:parser = b:ale_completion_parser
+ let l:line = b:ale_completion_info.line
+ let l:column = b:ale_completion_info.column
+ let l:regex = s:GetFiletypeValue(s:omni_start_map, &filetype)
+ let l:up_to_column = getline(l:line)[: l:column - 2]
+ let l:match = matchstr(l:up_to_column, l:regex)
+
+ return l:column - len(l:match) - 1
+endfunction
- unlet b:ale_completion_response
- unlet b:ale_completion_parser
+function! ale#completion#GetCompletionResult() abort
+ " Parse a new response if there is one.
+ if exists('b:ale_completion_response')
+ \&& exists('b:ale_completion_parser')
+ let l:response = b:ale_completion_response
+ let l:parser = b:ale_completion_parser
- let b:ale_completion_result = function(l:parser)(l:response)
- endif
+ unlet b:ale_completion_response
+ unlet b:ale_completion_parser
+
+ let b:ale_completion_result = function(l:parser)(l:response)
+ endif
+
+ if exists('b:ale_completion_result')
+ return b:ale_completion_result
+ endif
+
+ return v:null
+endfunction
+
+function! ale#completion#OmniFunc(findstart, base) abort
+ if a:findstart
+ return ale#completion#GetCompletionPosition()
+ else
+ let l:result = ale#completion#GetCompletionResult()
call s:ReplaceCompletionOptions()
- return get(b:, 'ale_completion_result', [])
+ return l:result isnot v:null ? l:result : []
endif
endfunction
@@ -239,7 +259,14 @@ function! ale#completion#Show(response, completion_parser) abort
" Replace completion options shortly before opening the menu.
call s:ReplaceCompletionOptions()
- call timer_start(0, {-> ale#util#FeedKeys("\<Plug>(ale_show_completion_menu)")})
+ let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '')
+
+ if l:source is# 'ale-automatic' || l:source is# 'ale-manual'
+ call timer_start(
+ \ 0,
+ \ {-> ale#util#FeedKeys("\<Plug>(ale_show_completion_menu)")}
+ \)
+ endif
endfunction
function! s:CompletionStillValid(request_id) abort
@@ -249,7 +276,10 @@ function! s:CompletionStillValid(request_id) abort
\&& has_key(b:, 'ale_completion_info')
\&& b:ale_completion_info.request_id == a:request_id
\&& b:ale_completion_info.line == l:line
- \&& b:ale_completion_info.column == l:column
+ \&& (
+ \ b:ale_completion_info.column == l:column
+ \ || b:ale_completion_info.source is# 'deoplete'
+ \)
endfunction
function! ale#completion#ParseTSServerCompletions(response) abort
@@ -519,12 +549,12 @@ endfunction
" This function can be used to manually trigger autocomplete, even when
" g:ale_completion_enabled is set to false
-function! ale#completion#GetCompletions(manual) abort
+function! ale#completion#GetCompletions(source) abort
let [l:line, l:column] = getpos('.')[1:2]
let l:prefix = ale#completion#GetPrefix(&filetype, l:line, l:column)
- if !a:manual && empty(l:prefix)
+ if a:source is# 'ale-automatic' && empty(l:prefix)
return
endif
@@ -537,8 +567,9 @@ function! ale#completion#GetCompletions(manual) abort
\ 'prefix': l:prefix,
\ 'conn_id': 0,
\ 'request_id': 0,
- \ 'manual': a:manual,
+ \ 'source': a:source,
\}
+ unlet! b:ale_completion_result
let l:buffer = bufnr('')
let l:Callback = function('s:OnReady')
@@ -562,7 +593,7 @@ function! s:TimerHandler(...) abort
" When running the timer callback, we have to be sure that the cursor
" hasn't moved from where it was when we requested completions by typing.
if s:timer_pos == [l:line, l:column] && ale#util#Mode() is# 'i'
- call ale#completion#GetCompletions(0)
+ call ale#completion#GetCompletions('ale-automatic')
endif
endfunction
diff --git a/autoload/ale/definition.vim b/autoload/ale/definition.vim
index 521f0b45..3915cac1 100644
--- a/autoload/ale/definition.vim
+++ b/autoload/ale/definition.vim
@@ -3,6 +3,9 @@
let s:go_to_definition_map = {}
+" Enable automatic updates of the tagstack
+let g:ale_update_tagstack = get(g:, 'ale_update_tagstack', 1)
+
" Used to get the definition map in tests.
function! ale#definition#GetMap() abort
return deepcopy(s:go_to_definition_map)
@@ -17,6 +20,20 @@ function! ale#definition#ClearLSPData() abort
let s:go_to_definition_map = {}
endfunction
+function! ale#definition#UpdateTagStack() abort
+ let l:should_update_tagstack = exists('*gettagstack') && exists('*settagstack') && g:ale_update_tagstack
+
+ if l:should_update_tagstack
+ " Grab the old location (to jump back to) and the word under the
+ " cursor (as a label for the tagstack)
+ let l:old_location = [bufnr('%'), line('.'), col('.'), 0]
+ let l:tagname = expand('<cword>')
+ let l:winid = win_getid()
+ call settagstack(l:winid, {'items': [{'from': l:old_location, 'tagname': l:tagname}]}, 'a')
+ call settagstack(l:winid, {'curidx': len(gettagstack(l:winid)['items']) + 1})
+ endif
+endfunction
+
function! ale#definition#HandleTSServerResponse(conn_id, response) abort
if get(a:response, 'command', '') is# 'definition'
\&& has_key(s:go_to_definition_map, a:response.request_seq)
@@ -27,6 +44,7 @@ function! ale#definition#HandleTSServerResponse(conn_id, response) abort
let l:line = a:response.body[0].start.line
let l:column = a:response.body[0].start.offset
+ call ale#definition#UpdateTagStack()
call ale#util#Open(l:filename, l:line, l:column, l:options)
endif
endif
@@ -51,6 +69,7 @@ function! ale#definition#HandleLSPResponse(conn_id, response) abort
let l:line = l:item.range.start.line + 1
let l:column = l:item.range.start.character + 1
+ call ale#definition#UpdateTagStack()
call ale#util#Open(l:filename, l:line, l:column, l:options)
break
endfor
diff --git a/autoload/ale/engine.vim b/autoload/ale/engine.vim
index 6b312b4f..7db808d6 100644
--- a/autoload/ale/engine.vim
+++ b/autoload/ale/engine.vim
@@ -39,8 +39,8 @@ function! ale#engine#MarkLinterActive(info, linter) abort
endif
endfunction
-function! ale#engine#MarkLinterInactive(info, linter) abort
- call filter(a:info.active_linter_list, 'v:val.name isnot# a:linter.name')
+function! ale#engine#MarkLinterInactive(info, linter_name) abort
+ call filter(a:info.active_linter_list, 'v:val.name isnot# a:linter_name')
endfunction
function! ale#engine#ResetExecutableCache() abort
@@ -195,7 +195,7 @@ function! s:HandleExit(job_info, buffer, output, data) abort
let l:next_chain_index = a:job_info.next_chain_index
" Remove this job from the list.
- call ale#engine#MarkLinterInactive(l:buffer_info, l:linter)
+ call ale#engine#MarkLinterInactive(l:buffer_info, l:linter.name)
" Stop here if we land in the handle for a job completing if we're in
" a sandbox.
diff --git a/autoload/ale/fix.vim b/autoload/ale/fix.vim
index c0f05611..92ae3e14 100644
--- a/autoload/ale/fix.vim
+++ b/autoload/ale/fix.vim
@@ -1,3 +1,5 @@
+call ale#Set('fix_on_save_ignore', {})
+
" Apply fixes queued up for buffers which may be hidden.
" Vim doesn't let you modify hidden buffers.
function! ale#fix#ApplyQueuedFixes() abort
@@ -265,7 +267,21 @@ function! s:AddSubCallbacks(full_list, callbacks) abort
return 1
endfunction
-function! s:GetCallbacks(buffer, fixers) abort
+function! s:IgnoreFixers(callback_list, filetype, config) abort
+ if type(a:config) is v:t_list
+ let l:ignore_list = a:config
+ else
+ let l:ignore_list = []
+
+ for l:part in split(a:filetype , '\.')
+ call extend(l:ignore_list, get(a:config, l:part, []))
+ endfor
+ endif
+
+ call filter(a:callback_list, 'index(l:ignore_list, v:val) < 0')
+endfunction
+
+function! s:GetCallbacks(buffer, fixing_flag, fixers) abort
if len(a:fixers)
let l:callback_list = a:fixers
elseif type(get(b:, 'ale_fixers')) is v:t_list
@@ -290,8 +306,12 @@ function! s:GetCallbacks(buffer, fixers) abort
endif
endif
- if empty(l:callback_list)
- return []
+ if a:fixing_flag is# 'save_file'
+ let l:config = ale#Var(a:buffer, 'fix_on_save_ignore')
+
+ if !empty(l:config)
+ call s:IgnoreFixers(l:callback_list, &filetype, l:config)
+ endif
endif
let l:corrected_list = []
@@ -339,7 +359,7 @@ function! ale#fix#Fix(buffer, fixing_flag, ...) abort
endif
try
- let l:callback_list = s:GetCallbacks(a:buffer, a:000)
+ let l:callback_list = s:GetCallbacks(a:buffer, a:fixing_flag, a:000)
catch /E700\|BADNAME/
let l:function_name = join(split(split(v:exception, ':')[3]))
let l:echo_message = printf(
diff --git a/autoload/ale/fix/registry.vim b/autoload/ale/fix/registry.vim
index 430dc7db..4d28272a 100644
--- a/autoload/ale/fix/registry.vim
+++ b/autoload/ale/fix/registry.vim
@@ -290,6 +290,11 @@ let s:default_registry = {
\ 'suggested_filetypes': ['kt'],
\ 'description': 'Fix Kotlin files with ktlint.',
\ },
+\ 'styler': {
+\ 'function': 'ale#fixers#styler#Fix',
+\ 'suggested_filetypes': ['r'],
+\ 'description': 'Fix R files with styler.',
+\ },
\ 'latexindent': {
\ 'function': 'ale#fixers#latexindent#Fix',
\ 'suggested_filetypes': ['tex'],
diff --git a/autoload/ale/fixers/styler.vim b/autoload/ale/fixers/styler.vim
new file mode 100644
index 00000000..7ff3275c
--- /dev/null
+++ b/autoload/ale/fixers/styler.vim
@@ -0,0 +1,16 @@
+" Author: tvatter <thibault.vatter@gmail.com>
+" Description: Fixing R files with styler.
+
+call ale#Set('r_styler_executable', 'Rscript')
+call ale#Set('r_styler_options', 'tidyverse_style')
+
+function! ale#fixers#styler#Fix(buffer) abort
+ return {
+ \ 'command': 'Rscript --vanilla -e '
+ \ . '"suppressPackageStartupMessages(library(styler));'
+ \ . 'style_file(commandArgs(TRUE), style = '
+ \ . ale#Var(a:buffer, 'r_styler_options') . ')"'
+ \ . ' %t',
+ \ 'read_temporary_file': 1,
+ \}
+endfunction
diff --git a/autoload/ale/highlight.vim b/autoload/ale/highlight.vim
index ae1f3e7d..3e09f26d 100644
--- a/autoload/ale/highlight.vim
+++ b/autoload/ale/highlight.vim
@@ -26,6 +26,41 @@ endif
let s:MAX_POS_VALUES = 8
let s:MAX_COL_SIZE = 1073741824 " pow(2, 30)
+" Check if we have neovim's buffer highlight API
+"
+" Below we define some functions' implementation conditionally if this API
+" exists or not.
+"
+" The API itself is more ergonomic and neovim performs highlights positions
+" rebases during edits so we see less stalled highlights.
+let s:nvim_api = exists('*nvim_buf_add_highlight') && exists('*nvim_buf_clear_namespace')
+
+function! ale#highlight#HasNeovimApi() abort
+ return s:nvim_api
+endfunction
+
+function! ale#highlight#nvim_buf_clear_namespace(...) abort
+ return call('nvim_buf_clear_namespace', a:000)
+endfunction
+
+function! ale#highlight#nvim_buf_add_highlight(...) abort
+ return call('nvim_buf_add_highlight', a:000)
+endfunction
+
+function! s:ale_nvim_highlight_id(bufnr) abort
+ let l:id = getbufvar(a:bufnr, 'ale_nvim_highlight_id', -1)
+
+ if l:id is -1
+ " NOTE: This will highlight nothing but will allocate new id
+ let l:id = ale#highlight#nvim_buf_add_highlight(
+ \ a:bufnr, 0, '', 0, 0, -1
+ \)
+ call setbufvar(a:bufnr, 'ale_nvim_highlight_id', l:id)
+ endif
+
+ return l:id
+endfunction
+
function! ale#highlight#CreatePositions(line, col, end_line, end_col) abort
if a:line >= a:end_line
" For single lines, just return the one position.
@@ -49,12 +84,90 @@ endfunction
" Given a loclist for current items to highlight, remove all highlights
" except these which have matching loclist item entries.
+
function! ale#highlight#RemoveHighlights() abort
- for l:match in getmatches()
- if l:match.group =~# '^ALE'
- call matchdelete(l:match.id)
+ if ale#highlight#HasNeovimApi()
+ if get(b:, 'ale_nvim_highlight_id', 0)
+ let l:bufnr = bufnr('%')
+ " NOTE: 0, -1 means from 0 line till the end of buffer
+ call ale#highlight#nvim_buf_clear_namespace(
+ \ l:bufnr,
+ \ b:ale_nvim_highlight_id,
+ \ 0, -1
+ \)
endif
- endfor
+ else
+ for l:match in getmatches()
+ if l:match.group =~# '^ALE'
+ call matchdelete(l:match.id)
+ endif
+ endfor
+ endif
+endfunction
+
+function! s:highlight_line(bufnr, lnum, group) abort
+ if ale#highlight#HasNeovimApi()
+ let l:highlight_id = s:ale_nvim_highlight_id(a:bufnr)
+ call ale#highlight#nvim_buf_add_highlight(
+ \ a:bufnr, l:highlight_id, a:group,
+ \ a:lnum, 0, -1
+ \)
+ else
+ call matchaddpos(a:group, [a:lnum])
+ endif
+endfunction
+
+function! s:highlight_range(bufnr, range, group) abort
+ if ale#highlight#HasNeovimApi()
+ let l:highlight_id = s:ale_nvim_highlight_id(a:bufnr)
+ " NOTE: lines and columns indicies are 0-based in nvim_buf_* API.
+ let l:lnum = a:range.lnum - 1
+ let l:end_lnum = a:range.end_lnum - 1
+ let l:col = a:range.col - 1
+ let l:end_col = a:range.end_col
+
+ if l:lnum >= l:end_lnum
+ " For single lines, just return the one position.
+ call ale#highlight#nvim_buf_add_highlight(
+ \ a:bufnr, l:highlight_id, a:group,
+ \ l:lnum, l:col, l:end_col
+ \)
+ else
+ " highlight first line from start till the line end
+ call ale#highlight#nvim_buf_add_highlight(
+ \ a:bufnr, l:highlight_id, a:group,
+ \ l:lnum, l:col, -1
+ \)
+
+ " highlight all lines between the first and last entirely
+ let l:cur = l:lnum + 1
+
+ while l:cur < l:end_lnum
+ call ale#highlight#nvim_buf_add_highlight(
+ \ a:bufnr, l:highlight_id, a:group,
+ \ l:cur, 0, -1
+ \ )
+ let l:cur += 1
+ endwhile
+
+ call ale#highlight#nvim_buf_add_highlight(
+ \ a:bufnr, l:highlight_id, a:group,
+ \ l:end_lnum, 0, l:end_col
+ \)
+ endif
+ else
+ " Set all of the positions, which are chunked into Lists which
+ " are as large as will be accepted by matchaddpos.
+ call map(
+ \ ale#highlight#CreatePositions(
+ \ a:range.lnum,
+ \ a:range.col,
+ \ a:range.end_lnum,
+ \ a:range.end_col
+ \ ),
+ \ 'matchaddpos(a:group, v:val)'
+ \)
+ endif
endfunction
function! ale#highlight#UpdateHighlights() abort
@@ -79,17 +192,14 @@ function! ale#highlight#UpdateHighlights() abort
let l:group = 'ALEError'
endif
- let l:line = l:item.lnum
- let l:col = l:item.col
- let l:end_line = get(l:item, 'end_lnum', l:line)
- let l:end_col = get(l:item, 'end_col', l:col)
+ let l:range = {
+ \ 'lnum': l:item.lnum,
+ \ 'col': l:item.col,
+ \ 'end_lnum': get(l:item, 'end_lnum', l:item.lnum),
+ \ 'end_col': get(l:item, 'end_col', l:item.col)
+ \}
- " Set all of the positions, which are chunked into Lists which
- " are as large as will be accepted by matchaddpos.
- call map(
- \ ale#highlight#CreatePositions(l:line, l:col, l:end_line, l:end_col),
- \ 'matchaddpos(l:group, v:val)'
- \)
+ call s:highlight_range(l:item.bufnr, l:range, l:group)
endfor
" If highlights are enabled and signs are not enabled, we should still
@@ -111,7 +221,7 @@ function! ale#highlight#UpdateHighlights() abort
endif
if l:available_groups[l:group]
- call matchaddpos(l:group, [l:item.lnum])
+ call s:highlight_line(l:item.bufnr, l:item.lnum, l:group)
endif
endfor
endif
diff --git a/autoload/ale/lsp/response.vim b/autoload/ale/lsp/response.vim
index b91d875b..9ce05260 100644
--- a/autoload/ale/lsp/response.vim
+++ b/autoload/ale/lsp/response.vim
@@ -28,7 +28,7 @@ function! ale#lsp#response#ReadDiagnostics(response) abort
for l:diagnostic in a:response.params.diagnostics
let l:severity = get(l:diagnostic, 'severity', 0)
let l:loclist_item = {
- \ 'text': l:diagnostic.message,
+ \ 'text': substitute(l:diagnostic.message, '\(\r\n\|\n\|\r\)', ' ', 'g'),
\ 'type': 'E',
\ 'lnum': l:diagnostic.range.start.line + 1,
\ 'col': l:diagnostic.range.start.character + 1,
diff --git a/autoload/ale/lsp_linter.vim b/autoload/ale/lsp_linter.vim
index d544916a..3a596d62 100644
--- a/autoload/ale/lsp_linter.vim
+++ b/autoload/ale/lsp_linter.vim
@@ -27,12 +27,13 @@ function! s:HandleLSPDiagnostics(conn_id, response) abort
let l:linter_name = s:lsp_linter_map[a:conn_id]
let l:filename = ale#path#FromURI(a:response.params.uri)
let l:buffer = bufnr(l:filename)
+ let l:info = get(g:ale_buffer_info, l:buffer, {})
- if s:ShouldIgnore(l:buffer, l:linter_name)
+ if empty(l:info)
return
endif
- if l:buffer <= 0
+ if s:ShouldIgnore(l:buffer, l:linter_name)
return
endif
@@ -50,6 +51,8 @@ function! s:HandleTSServerDiagnostics(response, error_type) abort
return
endif
+ call ale#engine#MarkLinterInactive(l:info, l:linter_name)
+
if s:ShouldIgnore(l:buffer, l:linter_name)
return
endif
@@ -376,6 +379,10 @@ function! s:CheckWithLSP(linter, details) abort
if a:linter.lsp is# 'tsserver'
let l:message = ale#lsp#tsserver_message#Geterr(l:buffer)
let l:notified = ale#lsp#Send(l:id, l:message) != 0
+
+ if l:notified
+ call ale#engine#MarkLinterActive(l:info, a:linter)
+ endif
else
let l:notified = ale#lsp#NotifyForChanges(l:id, l:buffer)
endif
@@ -386,10 +393,6 @@ function! s:CheckWithLSP(linter, details) abort
let l:save_message = ale#lsp#message#DidSave(l:buffer)
let l:notified = ale#lsp#Send(l:id, l:save_message) != 0
endif
-
- if l:notified
- call ale#engine#MarkLinterActive(l:info, a:linter)
- endif
endfunction
function! ale#lsp_linter#CheckWithLSP(buffer, linter) abort
diff --git a/autoload/ale/sign.vim b/autoload/ale/sign.vim
index 933fc055..7395b0e2 100644
--- a/autoload/ale/sign.vim
+++ b/autoload/ale/sign.vim
@@ -66,7 +66,7 @@ endif
" Spaces and backslashes need to be escaped for signs.
function! s:EscapeSignText(sign_text) abort
- return substitute(a:sign_text, '\\\| ', '\\\0', 'g')
+ return substitute(substitute(a:sign_text, ' *$', '', ''), '\\\| ', '\\\0', 'g')
endfunction
" Signs show up on the left for error markers.
diff --git a/autoload/ale/util.vim b/autoload/ale/util.vim
index e57307e4..e7563608 100644
--- a/autoload/ale/util.vim
+++ b/autoload/ale/util.vim
@@ -422,7 +422,7 @@ function! ale#util#Writefile(buffer, lines, filename) abort
\ ? map(copy(a:lines), 'substitute(v:val, ''\r*$'', ''\r'', '''')')
\ : a:lines
- call writefile(l:corrected_lines, a:filename) " no-custom-checks
+ call writefile(l:corrected_lines, a:filename, 'S') " no-custom-checks
endfunction
if !exists('s:patial_timers')
diff --git a/autoload/ale/virtualtext.vim b/autoload/ale/virtualtext.vim
index 532427fb..d7c5360e 100644
--- a/autoload/ale/virtualtext.vim
+++ b/autoload/ale/virtualtext.vim
@@ -81,7 +81,7 @@ function! ale#virtualtext#ShowCursorWarning(...) abort
call ale#virtualtext#Clear()
if !empty(l:loc)
- let l:msg = get(l:loc, 'detail', l:loc.text)
+ let l:msg = l:loc.text
let l:hl_group = 'ALEVirtualTextInfo'
let l:type = get(l:loc, 'type', 'E')
diff --git a/doc/ale-java.txt b/doc/ale-java.txt
index 35967f03..30ac316b 100644
--- a/doc/ale-java.txt
+++ b/doc/ale-java.txt
@@ -114,9 +114,14 @@ Note: currently, the build can only run when launched with JDK 8. JDK 9 or more
recent versions can be used to run the server though.
After build completes the files required to run the language server will be
-located inside the folder |org.eclipse.jdt.ls.product/target/repository|.
+located inside the folder `org.eclipse.jdt.ls.product/target/repository`.
Ensure to set |g:ale_java_eclipselsp_path| to the absolute path of that folder.
+You could customize compiler options and code assists of the server.
+Under your project folder, modify the file
+`.settings/org.eclipse.jdt.core.prefs` with options presented at
+https://help.eclipse.org/neon/topic/org.eclipse.jdt.doc.isv/reference/api/org/eclipse/jdt/core/JavaCore.html.
+
g:ale_java_eclipselsp_path *g:ale_java_eclipselsp_path*
*b:ale_java_eclipselsp_path*
diff --git a/doc/ale-r.txt b/doc/ale-r.txt
index f85f48fd..b5ccebe5 100644
--- a/doc/ale-r.txt
+++ b/doc/ale-r.txt
@@ -25,5 +25,21 @@ g:ale_r_lintr_lint_package *g:ale_r_lintr_lint_package*
of `lintr::lint`. This prevents erroneous namespace warnings when linting
package files.
+
+===============================================================================
+styler *ale-r-styler*
+
+g:ale_r_styler_options *g:ale_r_styler_options*
+ *b:ale_r_styler_options*
+ Type: |String|
+ Default: `'styler::tidyverse_style'`
+
+ This option can be configured to change the options for styler.
+
+ The value of this option will be used as the `style` argument for the
+ `styler::style_file` options. Consult the styler documentation
+ for more information.
+
+
===============================================================================
vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl:
diff --git a/doc/ale-supported-languages-and-tools.txt b/doc/ale-supported-languages-and-tools.txt
index 89d5ee3b..607c65c2 100644
--- a/doc/ale-supported-languages-and-tools.txt
+++ b/doc/ale-supported-languages-and-tools.txt
@@ -358,6 +358,7 @@ Notes:
* `qmllint`
* R
* `lintr`
+ * `styler`
* Racket
* `raco`
* ReasonML
diff --git a/doc/ale.txt b/doc/ale.txt
index dce7a8ae..755064c1 100644
--- a/doc/ale.txt
+++ b/doc/ale.txt
@@ -1,4 +1,4 @@
-*ale.txt* For Vim version 8.0.
+*ale.txt* Plugin to lint and fix files asynchronously
*ale*
ALE - Asynchronous Lint Engine
@@ -309,6 +309,9 @@ by default.
|g:ale_fix_on_save| - Fix files when they are saved.
+Fixers can be disabled on save with |g:ale_fix_on_save_ignore|. They will
+still be run when you manually run |ALEFix|.
+
===============================================================================
5. Language Server Protocol Support *ale-lsp*
@@ -318,40 +321,44 @@ servers. LSP linters can be used in combination with any other linter, and
will automatically connect to LSP servers when needed. ALE also supports
`tsserver` for TypeScript, which uses a different but very similar protocol.
-ALE supports the following LSP/tsserver features:
-
-1. Diagnostics/linting - Enabled via selecting linters as usual.
-2. Completion
-3. Go to definition
-
-------------------------------------------------------------------------------
5.1 Completion *ale-completion*
-ALE offers limited support for automatic completion of code while you type.
+ALE offers support for automatic completion of code while you type.
Completion is only supported while at least one LSP linter is enabled. ALE
will only suggest symbols provided by the LSP servers.
-Suggestions will be made while you type after completion is enabled.
-Completion can be enabled by setting |g:ale_completion_enabled| to `1`. This
-setting must be set to `1` before ALE is loaded. The delay for completion can
-be configured with |g:ale_completion_delay|. ALE will only suggest so many
-possible matches for completion. The maximum number of items can be controlled
-with |g:ale_completion_max_suggestions|.
+ *ale-deoplete-integration*
+
+ALE integrates with Deoplete for offering automatic completion data. ALE's
+completion source for Deoplete is named `'ale'`, and should enabled
+automatically if Deoplete is enabled and configured correctly. Deoplete
+integration should not be combined with ALE's own implementation.
+
+ALE also offers its own completion implementation, which does not require any
+other plugins. Suggestions will be made while you type after completion is
+enabled. Completion can be enabled by setting |g:ale_completion_enabled| to
+`1`. This setting must be set to `1` before ALE is loaded. The delay for
+completion can be configured with |g:ale_completion_delay|.
+
+ALE will only suggest so many possible matches for completion. The maximum
+number of items can be controlled with |g:ale_completion_max_suggestions|.
If you don't like some of the suggestions you see, you can filter them out
with |g:ale_completion_excluded_words| or |b:ale_completion_excluded_words|.
The |ALEComplete| command can be used to show completion suggestions manually,
-even when |g:ale_completion_enabled| is set to `0`.
+even when |g:ale_completion_enabled| is set to `0`. For manually requesting
+completion information with Deoplete, consult Deoplete's documentation.
*ale-completion-completeopt-bug*
-Automatic completion replaces |completeopt| before opening the omnicomplete
-menu with <C-x><C-o>. In some versions of Vim, the value set for the option
-will not be respected. If you experience issues with Vim automatically
-inserting text while you type, set the following option in vimrc, and your
-issues should go away. >
+ALE Automatic completion implementation replaces |completeopt| before opening
+the omnicomplete menu with <C-x><C-o>. In some versions of Vim, the value set
+for the option will not be respected. If you experience issues with Vim
+automatically inserting text while you type, set the following option in
+vimrc, and your issues should go away. >
set completeopt=menu,menuone,preview,noselect,noinsert
<
@@ -368,6 +375,9 @@ information returned by LSP servers. The following commands are supported:
|ALEGoToDefinitionInSplit| - The same, but in a new split.
|ALEGoToDefinitionInVSplit| - The same, but in a new vertical split.
+ALE will update Vim's |tagstack| automatically unless |g:ale_update_tagstack| is
+set to `0`.
+
-------------------------------------------------------------------------------
5.3 Go To Type Definition *ale-go-to-type-definition*
@@ -770,6 +780,43 @@ b:ale_fix_on_save *b:ale_fix_on_save*
Fixing files can be disabled or enabled for individual buffers by setting
`b:ale_fix_on_save` to `0` or `1`.
+ Some fixers can be excluded from being run automatically when you save files
+ with the |g:ale_fix_on_save_ignore| setting.
+
+
+g:ale_fix_on_save_ignore *g:ale_fix_on_save_ignore*
+b:ale_fix_on_save_ignore *b:ale_fix_on_save_ignore*
+
+ Type: |Dictionary| or |List|
+ Default: `{}`
+
+ Given a |Dictionary| mapping filetypes to |Lists| of fixers to ignore, or
+ just a |List| of fixers to ignore, exclude those fixers from being run
+ automatically when files are saved.
+
+ You can disable some fixers in your ftplugin file: >
+
+ " Disable fixers 'b' and 'c' when fixing on safe for this buffer.
+ let b:ale_fix_on_save_ignore = ['b', 'c']
+ " Alternatively, define ignore lists for different filetypes.
+ let b:ale_fix_on_save_ignore = {'foo': ['b'], 'bar': ['c']}
+<
+ You can disable some fixers globally per filetype like so: >
+
+ let g:ale_fixers = {'foo': ['a', 'b'], 'bar': ['c', 'd']}
+ let g:ale_fix_on_save = 1
+ " For filetype `foo.bar`, only fixers 'b' and 'd' will be run on save.
+ let g:ale_fix_on_save_ignore = {'foo': ['a'], 'bar': ['c']}
+ " Alternatively, disable these fixers on save for all filetypes.
+ let g:ale_fix_on_save_ignore = ['a', 'c']
+<
+ You can ignore fixers based on matching |Funcref| values too: >
+
+ let g:AddBar = {buffer, lines -> lines + ['bar']}
+ let g:ale_fixers = {'foo': g:AddBar}
+ " The lambda fixer will be ignored, as it will be found in the ignore list.
+ let g:ale_fix_on_save_ignore = [g:AddBar]
+<
g:ale_history_enabled *g:ale_history_enabled*
@@ -1454,6 +1501,14 @@ g:ale_sign_warning *g:ale_sign_warning*
The sign for warnings in the sign gutter.
+g:ale_update_tagstack *g:ale_update_tagstack*
+ *b:ale_update_tagstack*
+ Type: |Number|
+ Default: `1`
+
+ This option can be set to disable updating Vim's |tagstack| automatically.
+
+
g:ale_type_map *g:ale_type_map*
*b:ale_type_map*
Type: |Dictionary|
@@ -2110,6 +2165,7 @@ documented in additional help files.
qmlfmt................................|ale-qml-qmlfmt|
r.......................................|ale-r-options|
lintr.................................|ale-r-lintr|
+ styler................................|ale-r-styler|
reasonml................................|ale-reasonml-options|
merlin................................|ale-reasonml-merlin|
ols...................................|ale-reasonml-ols|
diff --git a/plugin/ale.vim b/plugin/ale.vim
index ad3d3e56..cf39d632 100644
--- a/plugin/ale.vim
+++ b/plugin/ale.vim
@@ -216,7 +216,7 @@ command! -bar ALEDocumentation :call ale#hover#ShowDocumentationAtCursor()
" Search for appearances of a symbol, such as a type name or function name.
command! -nargs=1 ALESymbolSearch :call ale#symbol#Search(<q-args>)
-command! -bar ALEComplete :call ale#completion#GetCompletions(1)
+command! -bar ALEComplete :call ale#completion#GetCompletions('ale-manual')
" <Plug> mappings for commands
nnoremap <silent> <Plug>(ale_previous) :ALEPrevious<Return>
diff --git a/rplugin/python3/deoplete/sources/ale.py b/rplugin/python3/deoplete/sources/ale.py
new file mode 100644
index 00000000..1addfae3
--- /dev/null
+++ b/rplugin/python3/deoplete/sources/ale.py
@@ -0,0 +1,50 @@
+"""
+A Deoplete source for ALE completion via tsserver and LSP.
+"""
+__author__ = 'Joao Paulo, w0rp'
+
+try:
+ from deoplete.source.base import Base
+except ImportError:
+ # Mock the Base class if deoplete isn't available, as mock isn't available
+ # in the Docker image.
+ class Base(object):
+ def __init__(self, vim):
+ pass
+
+
+# Make sure this code is valid in Python 2, used for running unit tests.
+class Source(Base):
+
+ def __init__(self, vim):
+ super(Source, self).__init__(vim)
+
+ self.name = 'ale'
+ self.mark = '[L]'
+ self.rank = 100
+ self.is_bytepos = True
+ self.min_pattern_length = 1
+
+ # Returns an integer for the start position, as with omnifunc.
+ def get_completion_position(self):
+ return self.vim.call('ale#completion#GetCompletionPosition')
+
+ def gather_candidates(self, context):
+ if context.get('is_refresh'):
+ context['is_async'] = False
+
+ if context['is_async']:
+ # Result is the same as for omnifunc, or None.
+ result = self.vim.call('ale#completion#GetCompletionResult')
+
+ if result is not None:
+ context['is_async'] = False
+
+ return result
+ else:
+ context['is_async'] = True
+
+ # Request some completion results.
+ self.vim.call('ale#completion#GetCompletions', 'deoplete')
+
+ return []
diff --git a/supported-tools.md b/supported-tools.md
index 0b6e04c8..1c02e77c 100644
--- a/supported-tools.md
+++ b/supported-tools.md
@@ -367,6 +367,7 @@ formatting.
* [qmllint](https://github.com/qt/qtdeclarative/tree/5.11/tools/qmllint)
* R
* [lintr](https://github.com/jimhester/lintr)
+ * [styler](https://github.com/r-lib/styler)
* Racket
* [raco](https://docs.racket-lang.org/raco/)
* ReasonML
diff --git a/test/command_callback/test_haskell_cabal_ghc_command_callbacks.vader b/test/command_callback/test_haskell_cabal_ghc_command_callbacks.vader
index 650aefa3..9e3712d6 100644
--- a/test/command_callback/test_haskell_cabal_ghc_command_callbacks.vader
+++ b/test/command_callback/test_haskell_cabal_ghc_command_callbacks.vader
@@ -13,11 +13,11 @@ After:
Execute(The options should be used in the command):
AssertEqual
- \ 'cabal exec -- ghc -fno-code -v0 %t',
+ \ ale#path#BufferCdString(bufnr('')) . 'cabal exec -- ghc -fno-code -v0 %t',
\ ale_linters#haskell#cabal_ghc#GetCommand(bufnr(''))
let b:ale_haskell_cabal_ghc_options = 'foobar'
AssertEqual
- \ 'cabal exec -- ghc foobar %t',
+ \ ale#path#BufferCdString(bufnr('')) . 'cabal exec -- ghc foobar %t',
\ ale_linters#haskell#cabal_ghc#GetCommand(bufnr(''))
diff --git a/test/command_callback/test_haskell_stack_ghc_command_callback.vader b/test/command_callback/test_haskell_stack_ghc_command_callback.vader
index f58f8b5f..aa13fb2b 100644
--- a/test/command_callback/test_haskell_stack_ghc_command_callback.vader
+++ b/test/command_callback/test_haskell_stack_ghc_command_callback.vader
@@ -10,8 +10,8 @@ Execute(The linter should not be executed when there's no stack.yaml file):
Execute(The linter should be executed when there is a stack.yaml file):
call ale#test#SetFilename('stack_ghc_paths/test.hs')
- AssertLinter 'stack', 'stack ghc -- -fno-code -v0 %t'
+ AssertLinter 'stack', ale#path#BufferCdString(bufnr('')) . 'stack ghc -- -fno-code -v0 %t'
let b:ale_haskell_stack_ghc_options = 'foobar'
- AssertLinter 'stack', 'stack ghc -- foobar %t'
+ AssertLinter 'stack', ale#path#BufferCdString(bufnr('')) . 'stack ghc -- foobar %t'
diff --git a/test/command_callback/test_kotlinc_command_callback.vader b/test/command_callback/test_kotlinc_command_callback.vader
new file mode 100644
index 00000000..fe94bffa
--- /dev/null
+++ b/test/command_callback/test_kotlinc_command_callback.vader
@@ -0,0 +1,9 @@
+Before:
+ call ale#assert#SetUpLinterTest('kotlin', 'kotlinc')
+ call ale#test#SetFilename('test.kt')
+
+After:
+ call ale#assert#TearDownLinterTest()
+
+Execute(The default command should be correct):
+ AssertLinter 'kotlinc', 'kotlinc ' . ale#Escape(expand('%:p'))
diff --git a/test/completion/test_completion_events.vader b/test/completion/test_completion_events.vader
index d04a8085..f79773cc 100644
--- a/test/completion/test_completion_events.vader
+++ b/test/completion/test_completion_events.vader
@@ -27,7 +27,7 @@ Before:
let g:get_completions_called = 0
" We just want to check if the function is called.
- function! ale#completion#GetCompletions(manual)
+ function! ale#completion#GetCompletions(source)
let g:get_completions_called = 1
endfunction
@@ -57,6 +57,7 @@ After:
unlet! b:ale_completion_info
unlet! b:ale_completion_response
unlet! b:ale_completion_parser
+ unlet! b:ale_completion_result
unlet! b:ale_complete_done_time
delfunction CheckCompletionCalled
@@ -86,7 +87,7 @@ Execute(ale#completion#GetCompletions should not be called when the cursor posit
call setpos('.', [bufnr(''), 1, 2, 0])
" We just want to check if the function is called.
- function! ale#completion#GetCompletions(manual)
+ function! ale#completion#GetCompletions(source)
let g:get_completions_called = 1
endfunction
@@ -105,7 +106,7 @@ Execute(ale#completion#GetCompletions should not be called if you switch to norm
let g:fake_mode = 'n'
" We just want to check if the function is called.
- function! ale#completion#GetCompletions(manual)
+ function! ale#completion#GetCompletions(source)
let g:get_completions_called = 1
endfunction
@@ -124,6 +125,7 @@ Execute(Completion should not be done shortly after the CompleteDone function):
Execute(ale#completion#Show() should remember the omnifunc setting and replace it):
let &l:omnifunc = 'FooBar'
+ let b:ale_completion_info = {'source': 'ale-automatic'}
call ale#completion#Show('Response', 'Parser')
AssertEqual 'FooBar', b:ale_old_omnifunc
@@ -136,6 +138,7 @@ Execute(ale#completion#Show() should remember the omnifunc setting and replace i
Execute(ale#completion#Show() should remember the completeopt setting and replace it):
let &l:completeopt = 'menu'
+ let b:ale_completion_info = {'source': 'ale-automatic'}
call ale#completion#Show('Response', 'Parser')
AssertEqual 'menu', b:ale_old_completeopt
@@ -148,6 +151,7 @@ Execute(ale#completion#Show() should remember the completeopt setting and replac
Execute(ale#completion#Show() should set the preview option if it's set):
let &l:completeopt = 'menu,preview'
+ let b:ale_completion_info = {'source': 'ale-automatic'}
call ale#completion#Show('Response', 'Parser')
AssertEqual 'menu,preview', b:ale_old_completeopt
@@ -158,7 +162,7 @@ Execute(ale#completion#Show() should set the preview option if it's set):
AssertEqual [["\<Plug>(ale_show_completion_menu)"]], g:feedkeys_calls
Execute(ale#completion#Show() should not replace the completeopt setting for manual completion):
- let b:ale_completion_info = {'manual': 1}
+ let b:ale_completion_info = {'source': 'ale-manual'}
let &l:completeopt = 'menu,preview'
@@ -173,6 +177,7 @@ Execute(ale#completion#Show() should not replace the completeopt setting for man
Execute(ale#completion#OmniFunc() should also remember the completeopt setting and replace it):
let &l:completeopt = 'menu'
+ let b:ale_completion_info = {'source': 'ale-automatic'}
call ale#completion#OmniFunc(0, '')
AssertEqual 'menu', b:ale_old_completeopt
@@ -181,18 +186,35 @@ Execute(ale#completion#OmniFunc() should also remember the completeopt setting a
Execute(ale#completion#OmniFunc() should set the preview option if it's set):
let &l:completeopt = 'menu,preview'
+ let b:ale_completion_info = {'source': 'ale-automatic'}
call ale#completion#OmniFunc(0, '')
AssertEqual 'menu,preview', b:ale_old_completeopt
AssertEqual 'menu,menuone,preview,noselect,noinsert', &l:completeopt
-Execute(ale#completion#Show() should make the correct feedkeys() call):
+Execute(ale#completion#Show() should make the correct feedkeys() call for automatic completion):
+ let b:ale_completion_info = {'source': 'ale-automatic'}
call ale#completion#Show('Response', 'Parser')
AssertEqual [], g:feedkeys_calls
sleep 1ms
AssertEqual [["\<Plug>(ale_show_completion_menu)"]], g:feedkeys_calls
+Execute(ale#completion#Show() should make the correct feedkeys() call for manual completion):
+ let b:ale_completion_info = {'source': 'ale-automatic'}
+ call ale#completion#Show('Response', 'Parser')
+
+ AssertEqual [], g:feedkeys_calls
+ sleep 1ms
+ AssertEqual [["\<Plug>(ale_show_completion_menu)"]], g:feedkeys_calls
+
+Execute(ale#completion#Show() should not call feedkeys() for other sources):
+ let b:ale_completion_info = {'source': 'deoplete'}
+ call ale#completion#Show('Response', 'Parser')
+
+ sleep 1ms
+ AssertEqual [], g:feedkeys_calls
+
Execute(ale#completion#Show() shouldn't do anything if you switch back to normal mode):
let &l:completeopt = 'menu,preview'
let g:fake_mode = 'n'
@@ -247,9 +269,10 @@ Execute(The completion request_id should be reset when queuing again):
AssertEqual 0, b:ale_completion_info.request_id
-Execute(b:ale_completion_info should be set up correctly when requesting completions):
+Execute(b:ale_completion_info should be set up correctly when requesting completions automatically):
+ let b:ale_completion_result = []
call setpos('.', [bufnr(''), 3, 14, 0])
- call ale#completion#GetCompletions(0)
+ call ale#completion#GetCompletions('ale-automatic')
AssertEqual
\ {
@@ -259,11 +282,13 @@ Execute(b:ale_completion_info should be set up correctly when requesting complet
\ 'line_length': 14,
\ 'line': 3,
\ 'prefix': 'ab',
- \ 'manual': 0,
+ \ 'source': 'ale-automatic',
\ },
\ b:ale_completion_info
+ Assert !exists('b:ale_completion_result')
-Execute(b:ale_completion_info should be set up correctly when requesting completions):
+Execute(b:ale_completion_info should be set up correctly when requesting completions manually):
+ let b:ale_completion_result = []
call setpos('.', [bufnr(''), 3, 14, 0])
ALEComplete
@@ -275,9 +300,28 @@ Execute(b:ale_completion_info should be set up correctly when requesting complet
\ 'line_length': 14,
\ 'line': 3,
\ 'prefix': 'ab',
- \ 'manual': 1,
+ \ 'source': 'ale-manual',
+ \ },
+ \ b:ale_completion_info
+ Assert !exists('b:ale_completion_result')
+
+Execute(b:ale_completion_info should be set up correctly for other sources):
+ let b:ale_completion_result = []
+ call setpos('.', [bufnr(''), 3, 14, 0])
+ call ale#completion#GetCompletions('deoplete')
+
+ AssertEqual
+ \ {
+ \ 'request_id': 0,
+ \ 'conn_id': 0,
+ \ 'column': 14,
+ \ 'line_length': 14,
+ \ 'line': 3,
+ \ 'prefix': 'ab',
+ \ 'source': 'deoplete',
\ },
\ b:ale_completion_info
+ Assert !exists('b:ale_completion_result')
Execute(The correct keybinds should be configured):
redir => g:output
diff --git a/test/completion/test_lsp_completion_messages.vader b/test/completion/test_lsp_completion_messages.vader
index dce61e36..25536436 100644
--- a/test/completion/test_lsp_completion_messages.vader
+++ b/test/completion/test_lsp_completion_messages.vader
@@ -102,7 +102,7 @@ Execute(The right message should be sent for the initial tsserver request):
" The cursor position needs to match what was saved before.
call setpos('.', [bufnr(''), 1, 3, 0])
- call ale#completion#GetCompletions(0)
+ call ale#completion#GetCompletions('ale-automatic')
" We shouldn't register the callback yet.
AssertEqual '''''', string(g:Callback)
@@ -129,7 +129,7 @@ Execute(The right message should be sent for the initial tsserver request):
\ 'request_id': 1,
\ 'line': 1,
\ 'prefix': 'fo',
- \ 'manual': 0,
+ \ 'source': 'ale-automatic',
\ },
\ get(b:, 'ale_completion_info', {})
@@ -191,7 +191,7 @@ Execute(The right message should be sent for the initial LSP request):
" The cursor position needs to match what was saved before.
call setpos('.', [bufnr(''), 1, 5, 0])
- call ale#completion#GetCompletions(0)
+ call ale#completion#GetCompletions('ale-automatic')
" We shouldn't register the callback yet.
AssertEqual '''''', string(g:Callback)
@@ -234,7 +234,7 @@ Execute(The right message should be sent for the initial LSP request):
\ 'request_id': 1,
\ 'line': 1,
\ 'prefix': 'fo',
- \ 'manual': 0,
+ \ 'source': 'ale-automatic',
\ 'completion_filter': 'ale#completion#python#CompletionItemFilter',
\ },
\ get(b:, 'ale_completion_info', {})
@@ -260,7 +260,7 @@ Execute(Two completion requests shouldn't be sent in a row):
" The cursor position needs to match what was saved before.
call setpos('.', [bufnr(''), 1, 5, 0])
- call ale#completion#GetCompletions(0)
+ call ale#completion#GetCompletions('ale-automatic')
" We shouldn't register the callback yet.
AssertEqual '''''', string(g:Callback)
diff --git a/test/fix/test_ale_fix_ignore.vader b/test/fix/test_ale_fix_ignore.vader
new file mode 100644
index 00000000..5eb9b9ab
--- /dev/null
+++ b/test/fix/test_ale_fix_ignore.vader
@@ -0,0 +1,110 @@
+Before:
+ Save g:ale_fixers
+ Save g:ale_fix_on_save
+ Save g:ale_fix_on_save_ignore
+
+ let g:ale_fix_on_save = 1
+ let g:ale_fixers = {'abc': ['a', 'b'], 'xyz': ['c', 'd']}
+ unlet! b:ale_fixers
+ unlet! b:ale_fix_on_save_ignore
+
+ function FixerA(buffer, lines) abort
+ return a:lines + ['a']
+ endfunction
+
+ function FixerB(buffer, lines) abort
+ return a:lines + ['b']
+ endfunction
+
+ function FixerC(buffer, lines) abort
+ return a:lines + ['c']
+ endfunction
+
+ function FixerD(buffer, lines) abort
+ return a:lines + ['d']
+ endfunction
+
+ set filetype=abc.xyz
+ let g:test_filename = tempname()
+ execute 'noautocmd silent file ' . fnameescape(g:test_filename)
+
+ call ale#fix#registry#Add('a', 'FixerA', ['abc'], '')
+ call ale#fix#registry#Add('b', 'FixerB', ['abc'], '')
+ call ale#fix#registry#Add('c', 'FixerC', ['xyz'], '')
+ call ale#fix#registry#Add('d', 'FixerD', ['xyz'], '')
+
+After:
+ Restore
+
+ if exists('g:test_filename') && filereadable(g:test_filename)
+ call delete(g:test_filename)
+ endif
+
+ unlet! b:ale_fixers
+ unlet! b:ale_fix_on_save_ignore
+ unlet! g:test_filename
+
+ delfunction FixerA
+ delfunction FixerB
+ delfunction FixerC
+ delfunction FixerD
+
+ call ale#fix#registry#ResetToDefaults()
+
+Given abc.xyz (An empty file):
+Execute(Ignoring with a filetype in a global Dictionary should work):
+ let g:ale_fix_on_save_ignore = {'abc': ['b'], 'xyz': ['c']}
+
+ call ale#events#SaveEvent(bufnr(''))
+
+ AssertEqual ['', 'a', 'd'], getline(1, '$')
+
+Execute(Ignoring with a filetype in a global List should work):
+ let g:ale_fix_on_save_ignore = ['b', 'c']
+
+ call ale#events#SaveEvent(bufnr(''))
+
+ AssertEqual ['', 'a', 'd'], getline(1, '$')
+
+Execute(Ignoring with a filetype in a local Dictionary should work):
+ let g:ale_fix_on_save_ignore = {'abc': ['b'], 'xyz': ['c']}
+ " The local Dictionary should entirely replace the global one.
+ let b:ale_fix_on_save_ignore = {'abc': ['b']}
+
+ call ale#events#SaveEvent(bufnr(''))
+
+ AssertEqual ['', 'a', 'c', 'd'], getline(1, '$')
+
+Execute(Ignoring with a filetype in a local List should work):
+ let g:ale_fix_on_save_ignore = {'abc': ['b'], 'xyz': ['c']}
+ " The local List should entirely replace the global Dictionary.
+ let b:ale_fix_on_save_ignore = ['b']
+
+ call ale#events#SaveEvent(bufnr(''))
+
+ AssertEqual ['', 'a', 'c', 'd'], getline(1, '$')
+
+Execute(Ignoring functions by reference with a Dictionary should work):
+ let g:ale_fixers = {
+ \ 'abc': [function('FixerA'), function('FixerB')],
+ \ 'xyz': [function('FixerC'), function('FixerD')],
+ \}
+ let b:ale_fix_on_save_ignore = {
+ \ 'abc': [function('FixerB')],
+ \ 'xyz': [function('FixerC')],
+ \}
+
+ call ale#events#SaveEvent(bufnr(''))
+
+ AssertEqual ['', 'a', 'd'], getline(1, '$')
+
+Execute(Ignoring functions by reference with a List should work):
+ let g:ale_fixers = {
+ \ 'abc': [function('FixerA'), function('FixerB')],
+ \ 'xyz': [function('FixerC'), function('FixerD')],
+ \}
+ let b:ale_fix_on_save_ignore = [function('FixerB'), function('FixerC')]
+
+ call ale#events#SaveEvent(bufnr(''))
+
+ AssertEqual ['', 'a', 'd'], getline(1, '$')
diff --git a/test/fixers/test_styler_fixer_callback.vader b/test/fixers/test_styler_fixer_callback.vader
new file mode 100644
index 00000000..85e45c1d
--- /dev/null
+++ b/test/fixers/test_styler_fixer_callback.vader
@@ -0,0 +1,21 @@
+Before:
+ call ale#test#SetDirectory('/testplugin/test/fixers')
+
+After:
+ Restore
+
+ call ale#test#RestoreDirectory()
+
+Execute(The styler callback should include custom styler options):
+ let g:ale_r_styler_options = "a_custom_option"
+
+ AssertEqual
+ \ {
+ \ 'command': 'Rscript --vanilla -e '
+ \ . '"suppressPackageStartupMessages(library(styler));'
+ \ . 'style_file(commandArgs(TRUE), style = '
+ \ . 'a_custom_option)"'
+ \ . ' %t',
+ \ 'read_temporary_file': 1,
+ \ },
+ \ ale#fixers#styler#Fix(bufnr(''))
diff --git a/test/lsp/test_read_lsp_diagnostics.vader b/test/lsp/test_read_lsp_diagnostics.vader
index c197e0c6..377e73d9 100644
--- a/test/lsp/test_read_lsp_diagnostics.vader
+++ b/test/lsp/test_read_lsp_diagnostics.vader
@@ -109,6 +109,26 @@ Execute(ale#lsp#response#ReadDiagnostics() should include sources in detail):
\ }
\ ]}})
+Execute(ale#lsp#response#ReadDiagnostics() should keep detail with line breaks but replace with spaces in text):
+ AssertEqual [
+ \ {
+ \ 'type': 'E',
+ \ 'text': 'cannot borrow `cap` as mutable more than once at a time mutable borrow starts here in previous iteration of loop',
+ \ 'detail': "[rustc] cannot borrow `cap` as mutable\r\nmore than once at a time\n\nmutable borrow starts here\rin previous iteration of loop",
+ \ 'lnum': 10,
+ \ 'col': 15,
+ \ 'end_lnum': 12,
+ \ 'end_col': 22,
+ \ }
+ \ ],
+ \ ale#lsp#response#ReadDiagnostics({'params': {'uri': 'filename.ts', 'diagnostics': [
+ \ {
+ \ 'range': Range(9, 14, 11, 22),
+ \ 'message': "cannot borrow `cap` as mutable\r\nmore than once at a time\n\nmutable borrow starts here\rin previous iteration of loop",
+ \ 'source': 'rustc',
+ \ }
+ \ ]}})
+
Execute(ale#lsp#response#ReadDiagnostics() should consider -1 to be a meaningless code):
AssertEqual [
\ {
diff --git a/test/python/test_deoplete_source.py b/test/python/test_deoplete_source.py
new file mode 100644
index 00000000..960abe3a
--- /dev/null
+++ b/test/python/test_deoplete_source.py
@@ -0,0 +1,130 @@
+import unittest
+import imp
+
+ale_module = imp.load_source(
+ 'deoplete.sources.ale',
+ '/testplugin/rplugin/python3/deoplete/sources/ale.py',
+)
+
+
+class VimMock(object):
+ def __init__(self, call_list, call_results):
+ self.__call_list = call_list
+ self.__call_results = call_results
+
+ def call(self, function, *args):
+ self.__call_list.append((function, args))
+
+ return self.__call_results.get(function, 0)
+
+
+class DeopleteSourceTest(unittest.TestCase):
+ def setUp(self):
+ super(DeopleteSourceTest, self).setUp()
+
+ self.call_list = []
+ self.call_results = {}
+ self.source = ale_module.Source('vim')
+ self.source.vim = VimMock(self.call_list, self.call_results)
+
+ def test_attributes(self):
+ """
+ Check all of the attributes we set.
+ """
+ attributes = dict(
+ (key, getattr(self.source, key))
+ for key in
+ dir(self.source)
+ if not key.startswith('__')
+ and key != 'vim'
+ and not hasattr(getattr(self.source, key), '__self__')
+ )
+
+ self.assertEqual(attributes, {
+ 'is_bytepos': True,
+ 'mark': '[L]',
+ 'min_pattern_length': 1,
+ 'name': 'ale',
+ 'rank': 100,
+ })
+
+ def test_completion_position(self):
+ self.call_results['ale#completion#GetCompletionPosition'] = 2
+
+ self.assertEqual(self.source.get_completion_position(), 2)
+ self.assertEqual(self.call_list, [
+ ('ale#completion#GetCompletionPosition', ()),
+ ])
+
+ def test_request_completion_results(self):
+ context = {'is_async': False}
+
+ self.assertEqual(self.source.gather_candidates(context), [])
+ self.assertEqual(context, {'is_async': True})
+ self.assertEqual(self.call_list, [
+ ('ale#completion#GetCompletions', ('deoplete',)),
+ ])
+
+ def test_refresh_completion_results(self):
+ context = {'is_async': False}
+
+ self.assertEqual(self.source.gather_candidates(context), [])
+ self.assertEqual(context, {'is_async': True})
+ self.assertEqual(self.call_list, [
+ ('ale#completion#GetCompletions', ('deoplete',)),
+ ])
+
+ context = {'is_async': True, 'is_refresh': True}
+
+ self.assertEqual(self.source.gather_candidates(context), [])
+ self.assertEqual(context, {'is_async': True, 'is_refresh': True})
+ self.assertEqual(self.call_list, [
+ ('ale#completion#GetCompletions', ('deoplete',)),
+ ('ale#completion#GetCompletions', ('deoplete',)),
+ ])
+
+ def test_poll_no_result(self):
+ context = {'is_async': True}
+ self.call_results['ale#completion#GetCompletionResult'] = None
+
+ self.assertEqual(self.source.gather_candidates(context), [])
+ self.assertEqual(context, {'is_async': True})
+ self.assertEqual(self.call_list, [
+ ('ale#completion#GetCompletionResult', ()),
+ ])
+
+ def test_poll_empty_result_ready(self):
+ context = {'is_async': True}
+ self.call_results['ale#completion#GetCompletionResult'] = []
+
+ self.assertEqual(self.source.gather_candidates(context), [])
+ self.assertEqual(context, {'is_async': False})
+ self.assertEqual(self.call_list, [
+ ('ale#completion#GetCompletionResult', ()),
+ ])
+
+ def test_poll_non_empty_result_ready(self):
+ context = {'is_async': True}
+ self.call_results['ale#completion#GetCompletionResult'] = [
+ {
+ 'word': 'foobar',
+ 'kind': 'v',
+ 'icase': 1,
+ 'menu': '',
+ 'info': '',
+ },
+ ]
+
+ self.assertEqual(self.source.gather_candidates(context), [
+ {
+ 'word': 'foobar',
+ 'kind': 'v',
+ 'icase': 1,
+ 'menu': '',
+ 'info': '',
+ },
+ ])
+ self.assertEqual(context, {'is_async': False})
+ self.assertEqual(self.call_list, [
+ ('ale#completion#GetCompletionResult', ()),
+ ])
diff --git a/test/script/custom-checks b/test/script/custom-checks
index d4027fec..20dbfb80 100755
--- a/test/script/custom-checks
+++ b/test/script/custom-checks
@@ -67,4 +67,14 @@ echo
test/script/check-toc || exit_code=$?
+echo '========================================'
+echo 'Check Python code'
+echo '========================================'
+echo
+
+docker run --rm -v "$PWD:/testplugin" "$DOCKER_RUN_IMAGE" \
+ python -W ignore -m unittest discover /testplugin/test/python \
+ || exit_code=$?
+echo
+
exit $exit_code
diff --git a/test/test_c_flag_parsing.vader b/test/test_c_flag_parsing.vader
index 340f3ccf..045554a3 100644
--- a/test/test_c_flag_parsing.vader
+++ b/test/test_c_flag_parsing.vader
@@ -153,10 +153,10 @@ Execute(ParseCompileCommandsFlags should tolerate empty values):
AssertEqual '', ale#c#ParseCompileCommandsFlags(bufnr(''), {}, {})
Execute(ParseCompileCommandsFlags should parse some basic flags):
- noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'))
+ silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'))
AssertEqual
- \ '-I' . '/usr/include/xmms2',
+ \ '-I/usr/include/xmms2',
\ ale#c#ParseCompileCommandsFlags(bufnr(''), { "xmms2-mpris.c": [
\ {
\ 'directory': '/foo/bar/xmms2-mpris',
@@ -168,7 +168,7 @@ Execute(ParseCompileCommandsFlags should parse some basic flags):
\ ] }, {})
Execute(ParseCompileCommandsFlags should tolerate items without commands):
- noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'))
+ silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'))
AssertEqual
\ '',
@@ -180,10 +180,10 @@ Execute(ParseCompileCommandsFlags should tolerate items without commands):
\ ] }, {})
Execute(ParseCompileCommandsFlags should fall back to files in the same directory):
- noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'))
+ silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'))
AssertEqual
- \ '-I' . '/usr/include/xmms2',
+ \ '-I/usr/include/xmms2',
\ ale#c#ParseCompileCommandsFlags(bufnr(''), {}, { "src": [
\ {
\ 'directory': '/foo/bar/xmms2-mpris',
@@ -194,6 +194,94 @@ Execute(ParseCompileCommandsFlags should fall back to files in the same director
\ },
\ ] })
+Execute(ParseCompileCommandsFlags should take commands from matching .c files for .h files):
+ silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.h'))
+
+ AssertEqual
+ \ '-I/usr/include/xmms2',
+ \ ale#c#ParseCompileCommandsFlags(
+ \ bufnr(''),
+ \ {
+ \ 'xmms2-mpris.c': [
+ \ {
+ \ 'directory': '/foo/bar/xmms2-mpris',
+ \ 'file': (has('win32') ? 'C:' : '') . '/foo/bar/xmms2-mpris/src/xmms2-mpris.c',
+ \ 'command': '/usr/bin/cc -I' . '/usr/include/xmms2'
+ \ . ' -o CMakeFiles/xmms2-mpris.dir/src/xmms2-mpris.c.o'
+ \ . ' -c ' . '/foo/bar/xmms2-mpris/src/xmms2-mpris.c',
+ \ },
+ \ ],
+ \ },
+ \ {
+ \ },
+ \ )
+
+Execute(ParseCompileCommandsFlags should take commands from matching .cpp files for .hpp files):
+ silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.hpp'))
+
+ AssertEqual
+ \ '-I/usr/include/xmms2',
+ \ ale#c#ParseCompileCommandsFlags(
+ \ bufnr(''),
+ \ {
+ \ 'xmms2-mpris.cpp': [
+ \ {
+ \ 'directory': '/foo/bar/xmms2-mpris',
+ \ 'file': (has('win32') ? 'C:' : '') . '/foo/bar/xmms2-mpris/src/xmms2-mpris.cpp',
+ \ 'command': '/usr/bin/cc -I' . '/usr/include/xmms2'
+ \ . ' -o CMakeFiles/xmms2-mpris.dir/src/xmms2-mpris.c.o'
+ \ . ' -c ' . '/foo/bar/xmms2-mpris/src/xmms2-mpris.cpp',
+ \ },
+ \ ],
+ \ },
+ \ {
+ \ },
+ \ )
+
+Execute(ParseCompileCommandsFlags should take commands from matching .cpp files for .h files):
+ silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.h'))
+
+ AssertEqual
+ \ '-I/usr/include/xmms2',
+ \ ale#c#ParseCompileCommandsFlags(
+ \ bufnr(''),
+ \ {
+ \ 'xmms2-mpris.cpp': [
+ \ {
+ \ 'directory': '/foo/bar/xmms2-mpris',
+ \ 'file': (has('win32') ? 'C:' : '') . '/foo/bar/xmms2-mpris/src/xmms2-mpris.cpp',
+ \ 'command': '/usr/bin/cc -I' . '/usr/include/xmms2'
+ \ . ' -o CMakeFiles/xmms2-mpris.dir/src/xmms2-mpris.c.o'
+ \ . ' -c ' . '/foo/bar/xmms2-mpris/src/xmms2-mpris.cpp',
+ \ },
+ \ ],
+ \ },
+ \ {
+ \ },
+ \ )
+
+Execute(ParseCompileCommandsFlags should not take commands from .c files for .h files with different names):
+ silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/other.h'))
+
+ AssertEqual
+ \ '',
+ \ ale#c#ParseCompileCommandsFlags(
+ \ bufnr(''),
+ \ {
+ \ 'xmms2-mpris.c': [
+ \ {
+ \ 'directory': '/foo/bar/xmms2-mpris',
+ \ 'file': (has('win32') ? 'C:' : '') . '/foo/bar/xmms2-mpris/src/xmms2-mpris.c',
+ \ 'command': '/usr/bin/cc -I' . '/usr/include/xmms2'
+ \ . ' -o CMakeFiles/xmms2-mpris.dir/src/xmms2-mpris.c.o'
+ \ . ' -c ' . '/foo/bar/xmms2-mpris/src/xmms2-mpris.c',
+ \ },
+ \ ],
+ \ },
+ \ {
+ \ },
+ \ )
+
Execute(ParseCFlags should handle parenthesis and quotes):
AssertEqual
\ '-Dgoal=9 -Dtest1="('' '')" file1.o -Dtest2=''(` `)'' file2.o -Dtest3=`(" ")` file3.o',
diff --git a/test/test_engine_lsp_response_handling.vader b/test/test_engine_lsp_response_handling.vader
index 04f12ad6..84febe39 100644
--- a/test/test_engine_lsp_response_handling.vader
+++ b/test/test_engine_lsp_response_handling.vader
@@ -5,12 +5,15 @@ Before:
let g:ale_buffer_info = {}
unlet! g:ale_lsp_error_messages
+ unlet! b:ale_linters
call ale#test#SetDirectory('/testplugin/test')
After:
Restore
+ unlet! b:ale_linters
+
call ale#test#RestoreDirectory()
call ale#linter#Reset()
call ale#lsp_linter#ClearLSPData()
@@ -162,6 +165,47 @@ Execute(tsserver semantic error responses should be handled correctly):
\ ],
\ ale#test#GetLoclistWithoutModule()
+Execute(tsserver errors should mark tsserver no longer active):
+ let b:ale_linters = ['tsserver']
+ runtime ale_linters/typescript/tsserver.vim
+ call ale#test#SetFilename('filename.ts')
+ call ale#engine#InitBufferInfo(bufnr(''))
+
+ let g:ale_buffer_info[bufnr('')].active_linter_list = ale#linter#Get('typescript')
+ Assert !empty(g:ale_buffer_info[bufnr('')].active_linter_list)
+
+ call ale#lsp_linter#HandleLSPResponse(1, {
+ \ 'seq': 0,
+ \ 'type': 'event',
+ \ 'event': 'semanticDiag',
+ \ 'body': {
+ \ 'file': g:dir . '/filename.ts',
+ \ 'diagnostics':[],
+ \ },
+ \})
+
+ AssertEqual [], g:ale_buffer_info[bufnr('')].active_linter_list
+
+Execute(LSP errors should mark linters no longer active):
+ let b:ale_linters = ['pyls']
+ runtime ale_linters/python/pyls.vim
+ call ale#test#SetFilename('filename.py')
+ call ale#engine#InitBufferInfo(bufnr(''))
+ call ale#lsp_linter#SetLSPLinterMap({1: 'pyls'})
+
+ let g:ale_buffer_info[bufnr('')].active_linter_list = ale#linter#Get('python')
+ Assert !empty(g:ale_buffer_info[bufnr('')].active_linter_list)
+
+ call ale#lsp_linter#HandleLSPResponse(1, {
+ \ 'method': 'textDocument/publishDiagnostics',
+ \ 'params': {
+ \ 'uri': ale#path#ToURI(g:dir . '/filename.py'),
+ \ 'diagnostics': [],
+ \ },
+ \})
+
+ AssertEqual [], g:ale_buffer_info[bufnr('')].active_linter_list
+
Execute(LSP errors should be logged in the history):
call ale#lsp_linter#SetLSPLinterMap({'347': 'foobar'})
call ale#lsp_linter#HandleLSPResponse(347, {
diff --git a/test/test_go_to_definition.vader b/test/test_go_to_definition.vader
index 452b7692..3479d7b5 100644
--- a/test/test_go_to_definition.vader
+++ b/test/test_go_to_definition.vader
@@ -78,6 +78,15 @@ After:
Execute(Other messages for the tsserver handler should be ignored):
call ale#definition#HandleTSServerResponse(1, {'command': 'foo'})
+Execute(Tagstack should be incremented if supported):
+ if exists('*gettagstack') && exists('*settagstack')
+ let original_stack_depth = gettagstack().length
+ endif
+ call ale#definition#UpdateTagStack()
+ if exists('*gettagstack') && exists('*settagstack')
+ AssertEqual original_stack_depth + 1, gettagstack().length
+ endif
+
Execute(Failed definition responses should be handled correctly):
call ale#definition#SetMap({3: {'open_in': 'current-buffer'}})
call ale#definition#HandleTSServerResponse(
diff --git a/test/test_nvim_api_highlight.vader b/test/test_nvim_api_highlight.vader
new file mode 100644
index 00000000..fd535914
--- /dev/null
+++ b/test/test_nvim_api_highlight.vader
@@ -0,0 +1,160 @@
+Before:
+ Save g:ale_enabled
+ Save g:ale_set_signs
+
+ let g:nvim_buf_clear_namespace_calls = []
+ let g:nvim_buf_add_highlight_calls = []
+
+ call ale#test#SetDirectory('/testplugin/test/highlight')
+ call ale#test#SetFilename('dummy.txt')
+
+ runtime autoload/ale/highlight.vim
+
+ let g:ale_set_signs = 1
+ let g:ale_enabled = 1
+ let b:ale_nvim_highlight_id = 42
+ let b:ale_highlight_items = []
+
+ function! ale#highlight#nvim_buf_clear_namespace(...) abort
+ call add(g:nvim_buf_clear_namespace_calls, a:000)
+ endfunction
+
+ function! ale#highlight#nvim_buf_add_highlight(...) abort
+ call add(g:nvim_buf_add_highlight_calls, a:000)
+ return 42 " returns namespace id
+ endfunction
+
+After:
+ Restore
+
+ unlet! b:ale_enabled
+ unlet! b:ale_nvim_highlight_id
+ unlet! b:ale_highlight_items
+
+ unlet! g:nvim_buf_clear_namespace_calls
+ unlet! g:nvim_buf_add_highlight_calls
+
+ call ale#test#RestoreDirectory()
+ call ale#linter#Reset()
+
+ runtime autoload/ale/highlight.vim
+
+Given foobar (Some imaginary filetype):
+ <contents>
+
+Execute(Check usage of nvim_buf_clear_namespace):
+ if ale#highlight#HasNeovimApi()
+ call ale#highlight#SetHighlights(bufnr(''), [])
+
+ AssertEqual 1, len(g:nvim_buf_clear_namespace_calls)
+ AssertEqual
+ \ [[bufnr(''), 42, 0, -1]],
+ \ g:nvim_buf_clear_namespace_calls
+ endif
+
+Execute(Check usage of nvim_buf_add_highlight / single char):
+ if ale#highlight#HasNeovimApi()
+ call ale#highlight#SetHighlights(bufnr(''), [
+ \ {
+ \ 'bufnr': bufnr(''),
+ \ 'type': 'E',
+ \ 'lnum': 2,
+ \ 'col': 4,
+ \ }
+ \])
+
+ " Highlights are cleared on update
+ AssertEqual 1, len(g:nvim_buf_clear_namespace_calls)
+ AssertEqual [[bufnr(''), 42, 0, -1]], g:nvim_buf_clear_namespace_calls
+
+ " Should highlight a single char by lnum, col
+ AssertEqual 1, len(g:nvim_buf_add_highlight_calls)
+ AssertEqual
+ \ [[bufnr(''), 42, 'ALEError', 1, 3, 4]],
+ \ g:nvim_buf_add_highlight_calls
+ endif
+
+Execute(Check usage of nvim_buf_add_highlight / single line span):
+ if ale#highlight#HasNeovimApi()
+ call ale#highlight#SetHighlights(bufnr(''), [
+ \ {
+ \ 'bufnr': bufnr(''),
+ \ 'type': 'E',
+ \ 'lnum': 2,
+ \ 'col': 4,
+ \ 'end_lnum': 2,
+ \ 'end_col': 10,
+ \ }
+ \])
+
+ " Highlights are cleared on update
+ AssertEqual 1, len(g:nvim_buf_clear_namespace_calls)
+ AssertEqual [[bufnr(''), 42, 0, -1]], g:nvim_buf_clear_namespace_calls
+
+ " Should highlight a span between col and end_col on lnum
+ AssertEqual 1, len(g:nvim_buf_add_highlight_calls)
+ AssertEqual
+ \ [[bufnr(''), 42, 'ALEError', 1, 3, 10]],
+ \ g:nvim_buf_add_highlight_calls
+ endif
+
+Execute(Check usage of nvim_buf_add_highlight / multiple lines span):
+ if ale#highlight#HasNeovimApi()
+ call ale#highlight#SetHighlights(bufnr(''), [
+ \ {
+ \ 'bufnr': bufnr(''),
+ \ 'type': 'E',
+ \ 'lnum': 2,
+ \ 'col': 4,
+ \ 'end_lnum': 5,
+ \ 'end_col': 10,
+ \ }
+ \])
+
+ " Highlights are cleared on update
+ AssertEqual 1, len(g:nvim_buf_clear_namespace_calls)
+ AssertEqual [[bufnr(''), 42, 0, -1]], g:nvim_buf_clear_namespace_calls
+
+ " Should highlight all lines from lnum till end_lnum
+ AssertEqual 4, len(g:nvim_buf_add_highlight_calls)
+ AssertEqual
+ \ [
+ \ [bufnr(''), 42, 'ALEError', 1, 3, -1],
+ \ [bufnr(''), 42, 'ALEError', 2, 0, -1],
+ \ [bufnr(''), 42, 'ALEError', 3, 0, -1],
+ \ [bufnr(''), 42, 'ALEError', 4, 0, 10]
+ \ ],
+ \ g:nvim_buf_add_highlight_calls
+ endif
+
+Execute(Check usage of nvim_buf_add_highlight / line highights):
+ let g:ale_set_signs = 0
+
+ if ale#highlight#HasNeovimApi()
+ call ale#highlight#SetHighlights(bufnr(''), [
+ \ {
+ \ 'bufnr': bufnr(''),
+ \ 'type': 'E',
+ \ 'lnum': 2,
+ \ 'col': 4,
+ \ 'end_lnum': 5,
+ \ 'end_col': 10,
+ \ }
+ \])
+
+ " Highlights are cleared on update
+ AssertEqual 1, len(g:nvim_buf_clear_namespace_calls)
+ AssertEqual [[bufnr(''), 42, 0, -1]], g:nvim_buf_clear_namespace_calls
+
+ " Now the last highlight should be put on the entire line
+ AssertEqual 5, len(g:nvim_buf_add_highlight_calls)
+ AssertEqual
+ \ [
+ \ [bufnr(''), 42, 'ALEError', 1, 3, -1],
+ \ [bufnr(''), 42, 'ALEError', 2, 0, -1],
+ \ [bufnr(''), 42, 'ALEError', 3, 0, -1],
+ \ [bufnr(''), 42, 'ALEError', 4, 0, 10],
+ \ [bufnr(''), 42, 'ALEErrorLine', 2, 0, -1]
+ \ ],
+ \ g:nvim_buf_add_highlight_calls
+ endif
diff --git a/test/test_redundant_tsserver_rendering_avoided.vader b/test/test_redundant_tsserver_rendering_avoided.vader
index a292b014..6e0b2d30 100644
--- a/test/test_redundant_tsserver_rendering_avoided.vader
+++ b/test/test_redundant_tsserver_rendering_avoided.vader
@@ -42,7 +42,7 @@ Before:
runtime autoload/ale/engine.vim
- let g:ale_buffer_info = {bufnr(''): {'loclist': []}}
+ let g:ale_buffer_info = {bufnr(''): {'loclist': [], 'active_linter_list': []}}
let g:ale_handle_loclist_called = 0
function! ale#engine#HandleLoclist(linter_name, buffer, loclist, from_other_source) abort