summaryrefslogtreecommitdiff
path: root/autoload
diff options
context:
space:
mode:
authorBjorn Neergaard <bjorn@neersighted.com>2018-11-29 14:57:35 -0700
committerBjorn Neergaard <bjorn@neersighted.com>2018-11-29 14:57:35 -0700
commitd2b0ae8108b2ee395d4eb43c49d68b322a023a30 (patch)
treebb8145e69bf1bcb8968b29cb1a68c47ceb83674d /autoload
parentef641dda80f45cb979bc93c2513c6e10cbd8e42a (diff)
parent0a384a49d371838903d8401c5358ec60f3f4266d (diff)
downloadale-d2b0ae8108b2ee395d4eb43c49d68b322a023a30.zip
Merge branch 'master' into sridhars
Diffstat (limited to 'autoload')
-rw-r--r--autoload/ale.vim125
-rw-r--r--autoload/ale/assert.vim70
-rw-r--r--autoload/ale/c.vim175
-rw-r--r--autoload/ale/completion.vim141
-rw-r--r--autoload/ale/cursor.vim102
-rw-r--r--autoload/ale/d.vim16
-rw-r--r--autoload/ale/debugging.vim5
-rw-r--r--autoload/ale/definition.vim74
-rw-r--r--autoload/ale/engine.vim58
-rw-r--r--autoload/ale/engine/ignore.vim4
-rw-r--r--autoload/ale/events.vim16
-rw-r--r--autoload/ale/fix.vim24
-rw-r--r--autoload/ale/fix/registry.vim60
-rw-r--r--autoload/ale/fixers/brittany.vim10
-rw-r--r--autoload/ale/fixers/eslint.vim2
-rw-r--r--autoload/ale/fixers/fixjson.vim1
-rw-r--r--autoload/ale/fixers/generic_python.vim17
-rw-r--r--autoload/ale/fixers/gomod.vim10
-rw-r--r--autoload/ale/fixers/google_java_format.vim10
-rw-r--r--autoload/ale/fixers/hackfmt.vim8
-rw-r--r--autoload/ale/fixers/hfmt.vim2
-rw-r--r--autoload/ale/fixers/hlint.vim13
-rw-r--r--autoload/ale/fixers/importjs.vim5
-rw-r--r--autoload/ale/fixers/jq.vim9
-rw-r--r--autoload/ale/fixers/ocamlformat.vim18
-rw-r--r--autoload/ale/fixers/php_cs_fixer.vim1
-rw-r--r--autoload/ale/fixers/phpcbf.vim1
-rw-r--r--autoload/ale/fixers/prettier.vim42
-rw-r--r--autoload/ale/fixers/puppetlint.vim1
-rw-r--r--autoload/ale/fixers/rubocop.vim13
-rw-r--r--autoload/ale/fixers/scalafmt.vim1
-rw-r--r--autoload/ale/fixers/shfmt.vim18
-rw-r--r--autoload/ale/fixers/sqlfmt.vim13
-rw-r--r--autoload/ale/fixers/stylish_haskell.vim21
-rw-r--r--autoload/ale/fixers/terraform.vim17
-rw-r--r--autoload/ale/fixers/uncrustify.vim16
-rw-r--r--autoload/ale/fixers/xmllint.vim29
-rw-r--r--autoload/ale/go.vim27
-rw-r--r--autoload/ale/handlers/ccls.vim17
-rw-r--r--autoload/ale/handlers/elixir.vim28
-rw-r--r--autoload/ale/handlers/eslint.vim7
-rw-r--r--autoload/ale/handlers/gawk.vim2
-rw-r--r--autoload/ale/handlers/gcc.vim86
-rw-r--r--autoload/ale/handlers/go.vim1
-rw-r--r--autoload/ale/handlers/haskell.vim10
-rw-r--r--autoload/ale/handlers/haskell_stack.vim7
-rw-r--r--autoload/ale/handlers/hlint.vim8
-rw-r--r--autoload/ale/handlers/ols.vim1
-rw-r--r--autoload/ale/handlers/pony.vim1
-rw-r--r--autoload/ale/handlers/redpen.vim11
-rw-r--r--autoload/ale/handlers/rubocop.vim6
-rw-r--r--autoload/ale/handlers/ruby.vim9
-rw-r--r--autoload/ale/handlers/rust.vim10
-rw-r--r--autoload/ale/handlers/sml.vim5
-rw-r--r--autoload/ale/handlers/vale.vim1
-rw-r--r--autoload/ale/hover.vim87
-rw-r--r--autoload/ale/java.vim20
-rw-r--r--autoload/ale/job.vim7
-rw-r--r--autoload/ale/julia.vim19
-rw-r--r--autoload/ale/linter.vim67
-rw-r--r--autoload/ale/list.vim3
-rw-r--r--autoload/ale/loclist_jumping.vim2
-rw-r--r--autoload/ale/lsp.vim464
-rw-r--r--autoload/ale/lsp/message.vim12
-rw-r--r--autoload/ale/lsp/reset.vim2
-rw-r--r--autoload/ale/lsp/response.vim33
-rw-r--r--autoload/ale/lsp_linter.vim62
-rw-r--r--autoload/ale/node.vim5
-rw-r--r--autoload/ale/other_source.vim21
-rw-r--r--autoload/ale/path.vim21
-rw-r--r--autoload/ale/preview.vim7
-rw-r--r--autoload/ale/python.vim8
-rw-r--r--autoload/ale/references.vim60
-rw-r--r--autoload/ale/ruby.vim22
-rw-r--r--autoload/ale/sign.vim2
-rw-r--r--autoload/ale/socket.vim12
-rw-r--r--autoload/ale/symbol.vim109
-rw-r--r--autoload/ale/toggle.vim20
-rw-r--r--autoload/ale/util.vim34
-rw-r--r--autoload/ale/virtualtext.vim136
80 files changed, 1879 insertions, 741 deletions
diff --git a/autoload/ale.vim b/autoload/ale.vim
index 6d1e8521..f6c23d72 100644
--- a/autoload/ale.vim
+++ b/autoload/ale.vim
@@ -10,8 +10,7 @@ let g:ale_echo_msg_warning_str = get(g:, 'ale_echo_msg_warning_str', 'Warning')
let g:ale_linters_ignore = get(g:, 'ale_linters_ignore', {})
let s:lint_timer = -1
-let s:queued_buffer_number = -1
-let s:should_lint_file_for_buffer = {}
+let s:getcmdwintype_exists = exists('*getcmdwintype')
" Return 1 if a file is too large for ALE to handle.
function! ale#FileTooLarge(buffer) abort
@@ -20,14 +19,12 @@ function! ale#FileTooLarge(buffer) abort
return l:max > 0 ? (line2byte(line('$') + 1) > l:max) : 0
endfunction
-let s:getcmdwintype_exists = exists('*getcmdwintype')
-
" A function for checking various conditions whereby ALE just shouldn't
" attempt to do anything, say if particular buffer types are open in Vim.
function! ale#ShouldDoNothing(buffer) abort
" The checks are split into separate if statements to make it possible to
" profile each check individually with Vim's profiling tools.
-
+ "
" Do nothing if ALE is disabled.
if !getbufvar(a:buffer, 'ale_enabled', get(g:, 'ale_enabled', 0))
return 1
@@ -62,6 +59,11 @@ function! ale#ShouldDoNothing(buffer) abort
return 1
endif
+ " Don't start linting and so on when an operator is pending.
+ if ale#util#Mode(1) is# 'no'
+ return 1
+ endif
+
" Do nothing if running in the sandbox.
if ale#util#InSandbox()
return 1
@@ -81,21 +83,47 @@ function! ale#ShouldDoNothing(buffer) abort
return 0
endfunction
+function! s:Lint(buffer, should_lint_file, timer_id) abort
+ " Use the filetype from the buffer
+ let l:filetype = getbufvar(a:buffer, '&filetype')
+ let l:linters = ale#linter#Get(l:filetype)
+
+ " Apply ignore lists for linters only if needed.
+ let l:ignore_config = ale#Var(a:buffer, 'linters_ignore')
+ let l:linters = !empty(l:ignore_config)
+ \ ? ale#engine#ignore#Exclude(l:filetype, l:linters, l:ignore_config)
+ \ : l:linters
+
+ " Tell other sources that they can start checking the buffer now.
+ let g:ale_want_results_buffer = a:buffer
+ silent doautocmd <nomodeline> User ALEWantResults
+ unlet! g:ale_want_results_buffer
+
+ " Don't set up buffer data and so on if there are no linters to run.
+ if !has_key(g:ale_buffer_info, a:buffer) && empty(l:linters)
+ return
+ endif
+
+ " Clear lint_file linters, or only run them if the file exists.
+ let l:lint_file = empty(l:linters)
+ \ || (a:should_lint_file && filereadable(expand('#' . a:buffer . ':p')))
+
+ call ale#engine#RunLinters(a:buffer, l:linters, l:lint_file)
+endfunction
+
" (delay, [linting_flag, buffer_number])
function! ale#Queue(delay, ...) abort
if a:0 > 2
throw 'too many arguments!'
endif
- " Default linting_flag to ''
- let l:linting_flag = get(a:000, 0, '')
- let l:buffer = get(a:000, 1, bufnr(''))
+ let l:buffer = get(a:000, 1, v:null)
- if l:linting_flag isnot# '' && l:linting_flag isnot# 'lint_file'
- throw "linting_flag must be either '' or 'lint_file'"
+ if l:buffer is v:null
+ let l:buffer = bufnr('')
endif
- if type(l:buffer) != type(0)
+ if type(l:buffer) isnot v:t_number
throw 'buffer_number must be a Number'
endif
@@ -103,80 +131,24 @@ function! ale#Queue(delay, ...) abort
return
endif
- " 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 is# 'lint_file'
- let s:should_lint_file_for_buffer[l:buffer] = 1
- endif
+ " Default linting_flag to ''
+ let l:should_lint_file = get(a:000, 0) is# 'lint_file'
if s:lint_timer != -1
call timer_stop(s:lint_timer)
let s:lint_timer = -1
endif
- let l:linters = ale#linter#Get(getbufvar(l:buffer, '&filetype'))
-
- " Don't set up buffer data and so on if there are no linters to run.
- if empty(l:linters)
- " If we have some previous buffer data, then stop any jobs currently
- " running and clear everything.
- if has_key(g:ale_buffer_info, l:buffer)
- call ale#engine#RunLinters(l:buffer, [], 1)
- endif
-
- return
- endif
-
if a:delay > 0
- let s:queued_buffer_number = l:buffer
- let s:lint_timer = timer_start(a:delay, function('ale#Lint'))
+ let s:lint_timer = timer_start(
+ \ a:delay,
+ \ function('s:Lint', [l:buffer, l:should_lint_file])
+ \)
else
- call ale#Lint(-1, l:buffer)
+ call s:Lint(l:buffer, l:should_lint_file, 0)
endif
endfunction
-function! ale#Lint(...) abort
- if a:0 > 1
- " Use the buffer number given as the optional second argument.
- let l:buffer = a:2
- elseif a:0 > 0 && a:1 == s:lint_timer
- " Use the buffer number for the buffer linting was queued for.
- let l:buffer = s:queued_buffer_number
- else
- " Use the current buffer number.
- let l:buffer = bufnr('')
- endif
-
- if ale#ShouldDoNothing(l:buffer)
- return
- endif
-
- " Use the filetype from the buffer
- let l:filetype = getbufvar(l:buffer, '&filetype')
- let l:linters = ale#linter#Get(l: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]
- " Lint files if they exist.
- let l:should_lint_file = filereadable(expand('#' . l:buffer . ':p'))
- endif
-
- " Apply ignore lists for linters only if needed.
- let l:ignore_config = ale#Var(l:buffer, 'linters_ignore')
- let l:linters = !empty(l:ignore_config)
- \ ? ale#engine#ignore#Exclude(l:filetype, l:linters, l:ignore_config)
- \ : l:linters
-
- call ale#engine#RunLinters(l:buffer, l:linters, l:should_lint_file)
-endfunction
-
-" Reset flags indicating that files should be checked for all buffers.
-function! ale#ResetLintFileMarkers() abort
- let s:should_lint_file_for_buffer = {}
-endfunction
-
let g:ale_has_override = get(g:, 'ale_has_override', {})
" Call has(), but check a global Dictionary so we can force flags on or off
@@ -197,6 +169,11 @@ function! ale#Var(buffer, variable_name) abort
return get(l:vars, l:full_name, g:[l:full_name])
endfunction
+" As above, but curry the arguments so only the buffer number is required.
+function! ale#VarFunc(variable_name) abort
+ return {buf -> ale#Var(buf, a:variable_name)}
+endfunction
+
" Initialize a variable with a default value, if it isn't already set.
"
" Every variable name will be prefixed with 'ale_'.
diff --git a/autoload/ale/assert.vim b/autoload/ale/assert.vim
index 55c39ee3..ed08ed09 100644
--- a/autoload/ale/assert.vim
+++ b/autoload/ale/assert.vim
@@ -30,7 +30,7 @@ function! ale#assert#Linter(expected_executable, expected_command) abort
let l:callbacks = map(copy(l:linter.command_chain), 'v:val.callback')
" If the expected command is a string, just check the last one.
- if type(a:expected_command) is type('')
+ if type(a:expected_command) is v:t_string
if len(l:callbacks) is 1
let l:command = call(l:callbacks[0], [l:buffer])
else
@@ -54,9 +54,14 @@ function! ale#assert#Linter(expected_executable, expected_command) abort
endif
else
let l:command = ale#linter#GetCommand(l:buffer, l:linter)
+ endif
+
+ if type(l:command) is v:t_string
" Replace %e with the escaped executable, so tests keep passing after
" linters are changed to use %e.
let l:command = substitute(l:command, '%e', '\=ale#Escape(l:executable)', 'g')
+ else
+ call map(l:command, 'substitute(v:val, ''%e'', ''\=ale#Escape(l:executable)'', ''g'')')
endif
AssertEqual
@@ -80,6 +85,14 @@ function! ale#assert#LSPOptions(expected_options) abort
AssertEqual a:expected_options, l:initialization_options
endfunction
+function! ale#assert#LSPConfig(expected_config) abort
+ let l:buffer = bufnr('')
+ let l:linter = s:GetLinter()
+ let l:config = ale#lsp_linter#GetConfig(l:buffer, l:linter)
+
+ AssertEqual a:expected_config, l:config
+endfunction
+
function! ale#assert#LSPLanguage(expected_language) abort
let l:buffer = bufnr('')
let l:linter = s:GetLinter()
@@ -96,6 +109,14 @@ function! ale#assert#LSPProject(expected_root) abort
AssertEqual a:expected_root, l:root
endfunction
+function! ale#assert#LSPAddress(expected_address) abort
+ let l:buffer = bufnr('')
+ let l:linter = s:GetLinter()
+ let l:address = ale#util#GetFunction(l:linter.address_callback)(l:buffer)
+
+ AssertEqual a:expected_address, l:address
+endfunction
+
" A dummy function for making sure this module is loaded.
function! ale#assert#SetUpLinterTest(filetype, name) abort
" Set up a marker so ALE doesn't create real random temporary filenames.
@@ -126,28 +147,59 @@ function! ale#assert#SetUpLinterTest(filetype, name) abort
execute 'runtime ale_linters/' . a:filetype . '/' . a:name . '.vim'
- call ale#test#SetDirectory('/testplugin/test/command_callback')
+ if !exists('g:dir')
+ call ale#test#SetDirectory('/testplugin/test/command_callback')
+ endif
command! -nargs=+ WithChainResults :call ale#assert#WithChainResults(<args>)
command! -nargs=+ AssertLinter :call ale#assert#Linter(<args>)
command! -nargs=0 AssertLinterNotExecuted :call ale#assert#LinterNotExecuted()
command! -nargs=+ AssertLSPOptions :call ale#assert#LSPOptions(<args>)
+ command! -nargs=+ AssertLSPConfig :call ale#assert#LSPConfig(<args>)
command! -nargs=+ AssertLSPLanguage :call ale#assert#LSPLanguage(<args>)
command! -nargs=+ AssertLSPProject :call ale#assert#LSPProject(<args>)
+ command! -nargs=+ AssertLSPAddress :call ale#assert#LSPAddress(<args>)
endfunction
function! ale#assert#TearDownLinterTest() abort
unlet! g:ale_create_dummy_temporary_file
let s:chain_results = []
- delcommand WithChainResults
- delcommand AssertLinter
- delcommand AssertLinterNotExecuted
- delcommand AssertLSPOptions
- delcommand AssertLSPLanguage
- delcommand AssertLSPProject
+ if exists(':WithChainResults')
+ delcommand WithChainResults
+ endif
+
+ if exists(':AssertLinter')
+ delcommand AssertLinter
+ endif
+
+ if exists(':AssertLinterNotExecuted')
+ delcommand AssertLinterNotExecuted
+ endif
+
+ if exists(':AssertLSPOptions')
+ delcommand AssertLSPOptions
+ endif
+
+ if exists(':AssertLSPConfig')
+ delcommand AssertLSPConfig
+ endif
- call ale#test#RestoreDirectory()
+ if exists(':AssertLSPLanguage')
+ delcommand AssertLSPLanguage
+ endif
+
+ if exists(':AssertLSPProject')
+ delcommand AssertLSPProject
+ endif
+
+ if exists(':AssertLSPAddress')
+ delcommand AssertLSPAddress
+ endif
+
+ if exists('g:dir')
+ call ale#test#RestoreDirectory()
+ endif
Restore
diff --git a/autoload/ale/c.vim b/autoload/ale/c.vim
index fbfe9509..ce5105b6 100644
--- a/autoload/ale/c.vim
+++ b/autoload/ale/c.vim
@@ -2,11 +2,31 @@
" Description: Functions for integrating with C-family linters.
call ale#Set('c_parse_makefile', 0)
+call ale#Set('c_parse_compile_commands', 0)
let s:sep = has('win32') ? '\' : '/'
" Set just so tests can override it.
let g:__ale_c_project_filenames = ['.git/HEAD', 'configure', 'Makefile', 'CMakeLists.txt']
+function! ale#c#GetBuildDirectory(buffer) abort
+ " Don't include build directory for header files, as compile_commands.json
+ " files don't consider headers to be translation units, and provide no
+ " commands for compiling header files.
+ if expand('#' . a:buffer) =~# '\v\.(h|hpp)$'
+ return ''
+ endif
+
+ let l:build_dir = ale#Var(a:buffer, 'c_build_dir')
+
+ " c_build_dir has the priority if defined
+ if !empty(l:build_dir)
+ return l:build_dir
+ endif
+
+ return ale#path#Dirname(ale#c#FindCompileCommands(a:buffer))
+endfunction
+
+
function! ale#c#FindProjectRoot(buffer) abort
for l:project_filename in g:__ale_c_project_filenames
let l:full_path = ale#path#FindNearestFile(a:buffer, l:project_filename)
@@ -26,15 +46,21 @@ function! ale#c#FindProjectRoot(buffer) abort
return ''
endfunction
-function! ale#c#ParseCFlagsToList(path_prefix, cflags) abort
+function! ale#c#ParseCFlags(path_prefix, cflag_line) abort
let l:cflags_list = []
let l:previous_options = []
- for l:option in a:cflags
+ let l:split_lines = split(a:cflag_line, '-')
+ let l:option_index = 0
+
+ while l:option_index < len(l:split_lines)
+ let l:option = l:split_lines[l:option_index]
+ let l:option_index = l:option_index + 1
call add(l:previous_options, l:option)
" Check if cflag contained a '-' and should not have been splitted
let l:option_list = split(l:option, '\zs')
- if l:option_list[-1] isnot# ' '
+
+ if len(l:option_list) > 0 && l:option_list[-1] isnot# ' ' && l:option_index < len(l:split_lines)
continue
endif
@@ -60,34 +86,134 @@ function! ale#c#ParseCFlagsToList(path_prefix, cflags) abort
call add(l:cflags_list, l:option)
endif
endif
- endfor
+ endwhile
- return l:cflags_list
+ return join(l:cflags_list, ' ')
endfunction
-function! ale#c#ParseCFlags(buffer, stdout_make) abort
+function! ale#c#ParseCFlagsFromMakeOutput(buffer, make_output) abort
if !g:ale_c_parse_makefile
- return []
+ return ''
endif
let l:buffer_filename = expand('#' . a:buffer . ':t')
- let l:cflags = []
- for l:lines in split(a:stdout_make, '\\n')
- if stridx(l:lines, l:buffer_filename) >= 0
- let l:cflags = split(l:lines, '-')
+ let l:cflag_line = ''
+
+ " Find a line matching this buffer's filename in the make output.
+ for l:line in a:make_output
+ if stridx(l:line, l:buffer_filename) >= 0
+ let l:cflag_line = l:line
break
endif
endfor
let l:makefile_path = ale#path#FindNearestFile(a:buffer, 'Makefile')
- return ale#c#ParseCFlagsToList(fnamemodify(l:makefile_path, ':p:h'), l:cflags)
+ let l:makefile_dir = fnamemodify(l:makefile_path, ':p:h')
+
+ return ale#c#ParseCFlags(l:makefile_dir, l:cflag_line)
+endfunction
+
+" Given a buffer number, find the build subdirectory with compile commands
+" The subdirectory is returned without the trailing /
+function! ale#c#FindCompileCommands(buffer) abort
+ " Look above the current source file to find compile_commands.json
+ let l:json_file = ale#path#FindNearestFile(a:buffer, 'compile_commands.json')
+
+ if !empty(l:json_file)
+ return l:json_file
+ endif
+
+ " Search in build directories if we can't find it in the project.
+ for l:path in ale#path#Upwards(expand('#' . a:buffer . ':p:h'))
+ for l:dirname in ale#Var(a:buffer, 'c_build_dir_names')
+ let l:c_build_dir = l:path . s:sep . l:dirname
+ let l:json_file = l:c_build_dir . s:sep . 'compile_commands.json'
+
+ if filereadable(l:json_file)
+ return l:json_file
+ endif
+ endfor
+ endfor
+
+ return ''
+endfunction
+
+" Cache compile_commands.json data in a Dictionary, so we don't need to read
+" the same files over and over again. The key in the dictionary will include
+" the last modified time of the file.
+if !exists('s:compile_commands_cache')
+ let s:compile_commands_cache = {}
+endif
+
+function! s:GetListFromCompileCommandsFile(compile_commands_file) abort
+ if empty(a:compile_commands_file)
+ return []
+ endif
+
+ let l:time = getftime(a:compile_commands_file)
+
+ if l:time < 0
+ return []
+ endif
+
+ let l:key = a:compile_commands_file . ':' . l:time
+
+ if has_key(s:compile_commands_cache, l:key)
+ return s:compile_commands_cache[l:key]
+ endif
+
+ let l:data = []
+ silent! let l:data = json_decode(join(readfile(a:compile_commands_file), ''))
+
+ if !empty(l:data)
+ let s:compile_commands_cache[l:key] = l:data
+
+ return l:data
+ endif
+
+ return []
+endfunction
+
+function! ale#c#ParseCompileCommandsFlags(buffer, dir, json_list) abort
+ " Search for an exact file match first.
+ for l:item in a:json_list
+ if bufnr(l:item.file) is a:buffer
+ return ale#c#ParseCFlags(a:dir, l:item.command)
+ endif
+ endfor
+
+ " Look for any file in the same directory if we can't find an exact match.
+ let l:dir = ale#path#Simplify(expand('#' . a:buffer . ':p:h'))
+
+ for l:item in a:json_list
+ if ale#path#Simplify(fnamemodify(l:item.file, ':h')) is? l:dir
+ return ale#c#ParseCFlags(a:dir, l:item.command)
+ endif
+ endfor
+
+ return ''
+endfunction
+
+function! ale#c#FlagsFromCompileCommands(buffer, compile_commands_file) abort
+ let l:dir = ale#path#Dirname(a:compile_commands_file)
+ let l:json_list = s:GetListFromCompileCommandsFile(a:compile_commands_file)
+
+ return ale#c#ParseCompileCommandsFlags(a:buffer, l:dir, l:json_list)
endfunction
function! ale#c#GetCFlags(buffer, output) abort
let l:cflags = ' '
- if g:ale_c_parse_makefile && !empty(a:output)
- let l:cflags = join(ale#c#ParseCFlags(a:buffer, join(a:output, '\n')), ' ') . ' '
+ if ale#Var(a:buffer, 'c_parse_makefile') && !empty(a:output)
+ let l:cflags = ale#c#ParseCFlagsFromMakeOutput(a:buffer, a:output)
+ endif
+
+ if ale#Var(a:buffer, 'c_parse_compile_commands')
+ let l:json_file = ale#c#FindCompileCommands(a:buffer)
+
+ if !empty(l:json_file)
+ let l:cflags = ale#c#FlagsFromCompileCommands(a:buffer, l:json_file)
+ endif
endif
if l:cflags is# ' '
@@ -98,8 +224,9 @@ function! ale#c#GetCFlags(buffer, output) abort
endfunction
function! ale#c#GetMakeCommand(buffer) abort
- if g:ale_c_parse_makefile
+ if ale#Var(a:buffer, 'c_parse_makefile')
let l:makefile_path = ale#path#FindNearestFile(a:buffer, 'Makefile')
+
if !empty(l:makefile_path)
return 'cd '. fnamemodify(l:makefile_path, ':p:h') . ' && make -n'
endif
@@ -154,26 +281,10 @@ function! ale#c#IncludeOptions(include_paths) abort
return ''
endif
- return ' ' . join(l:option_list) . ' '
+ return join(l:option_list)
endfunction
let g:ale_c_build_dir_names = get(g:, 'ale_c_build_dir_names', [
\ 'build',
\ 'bin',
\])
-
-" Given a buffer number, find the build subdirectory with compile commands
-" The subdirectory is returned without the trailing /
-function! ale#c#FindCompileCommands(buffer) abort
- for l:path in ale#path#Upwards(expand('#' . a:buffer . ':p:h'))
- for l:dirname in ale#Var(a:buffer, 'c_build_dir_names')
- let l:c_build_dir = l:path . s:sep . l:dirname
-
- if filereadable(l:c_build_dir . '/compile_commands.json')
- return l:c_build_dir
- endif
- endfor
- endfor
-
- return ''
-endfunction
diff --git a/autoload/ale/completion.vim b/autoload/ale/completion.vim
index 7440f8cd..9dd913f5 100644
--- a/autoload/ale/completion.vim
+++ b/autoload/ale/completion.vim
@@ -39,10 +39,14 @@ let s:LSP_COMPLETION_COLOR_KIND = 16
let s:LSP_COMPLETION_FILE_KIND = 17
let s:LSP_COMPLETION_REFERENCE_KIND = 18
+let s:lisp_regex = '\v[a-zA-Z_\-][a-zA-Z_\-0-9]*$'
+
" Regular expressions for checking the characters in the line before where
" the insert cursor is. If one of these matches, we'll check for completions.
let s:should_complete_map = {
\ '<default>': '\v[a-zA-Z$_][a-zA-Z$_0-9]*$|\.$',
+\ 'clojure': s:lisp_regex,
+\ 'lisp': s:lisp_regex,
\ 'typescript': '\v[a-zA-Z$_][a-zA-Z$_0-9]*$|\.$|''$|"$',
\ 'rust': '\v[a-zA-Z$_][a-zA-Z$_0-9]*$|\.$|::$',
\}
@@ -75,6 +79,7 @@ endfunction
" Check if we should look for completions for a language.
function! ale#completion#GetPrefix(filetype, line, column) abort
let l:regex = s:GetFiletypeValue(s:should_complete_map, a:filetype)
+
" The column we're using completions for is where we are inserting text,
" like so:
" abc
@@ -93,14 +98,15 @@ function! ale#completion#GetTriggerCharacter(filetype, prefix) abort
return ''
endfunction
-function! ale#completion#Filter(buffer, suggestions, prefix) abort
+function! ale#completion#Filter(buffer, filetype, suggestions, prefix) abort
let l:excluded_words = ale#Var(a:buffer, 'completion_excluded_words')
+ let l:triggers = s:GetFiletypeValue(s:trigger_character_map, a:filetype)
" For completing...
" foo.
" ^
" We need to include all of the given suggestions.
- if a:prefix is# '.'
+ if index(l:triggers, a:prefix) >= 0
let l:filtered_suggestions = a:suggestions
else
let l:filtered_suggestions = []
@@ -113,7 +119,7 @@ function! ale#completion#Filter(buffer, suggestions, prefix) abort
for l:item in a:suggestions
" A List of String values or a List of completion item Dictionaries
" is accepted here.
- let l:word = type(l:item) == type('') ? l:item : l:item.word
+ let l:word = type(l:item) is v:t_string ? l:item : l:item.word
" Add suggestions if the suggestion starts with a case-insensitive
" match for the prefix.
@@ -133,7 +139,7 @@ function! ale#completion#Filter(buffer, suggestions, prefix) abort
" Remove suggestions with words in the exclusion List.
call filter(
\ l:filtered_suggestions,
- \ 'index(l:excluded_words, type(v:val) is type('''') ? v:val : v:val.word) < 0',
+ \ 'index(l:excluded_words, type(v:val) is v:t_string ? v:val : v:val.word) < 0',
\)
endif
@@ -214,8 +220,10 @@ function! ale#completion#Show(response, completion_parser) abort
" function, and then start omni-completion.
let b:ale_completion_response = a:response
let b:ale_completion_parser = a:completion_parser
+ " Replace completion options shortly before opening the menu.
call s:ReplaceCompletionOptions()
- call ale#util#FeedKeys("\<Plug>(ale_show_completion_menu)")
+
+ call timer_start(0, {-> ale#util#FeedKeys("\<Plug>(ale_show_completion_menu)")})
endfunction
function! s:CompletionStillValid(request_id) abort
@@ -257,7 +265,7 @@ function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort
call add(l:documentationParts, l:part.text)
endfor
- if l:suggestion.kind is# 'clasName'
+ if l:suggestion.kind is# 'className'
let l:kind = 'f'
elseif l:suggestion.kind is# 'parameterName'
let l:kind = 'f'
@@ -315,10 +323,10 @@ function! ale#completion#ParseLSPCompletions(response) abort
let l:item_list = []
- if type(get(a:response, 'result')) is type([])
+ if type(get(a:response, 'result')) is v:t_list
let l:item_list = a:response.result
- elseif type(get(a:response, 'result')) is type({})
- \&& type(get(a:response.result, 'items')) is type([])
+ elseif type(get(a:response, 'result')) is v:t_dict
+ \&& type(get(a:response.result, 'items')) is v:t_list
let l:item_list = a:response.result.items
endif
@@ -352,17 +360,23 @@ function! ale#completion#ParseLSPCompletions(response) abort
let l:kind = 'v'
endif
+ let l:doc = get(l:item, 'documentation', '')
+
+ if type(l:doc) is v:t_dict && has_key(l:doc, 'value')
+ let l:doc = l:doc.value
+ endif
+
call add(l:results, {
\ 'word': l:word,
\ 'kind': l:kind,
\ 'icase': 1,
\ 'menu': get(l:item, 'detail', ''),
- \ 'info': get(l:item, 'documentation', ''),
+ \ 'info': (type(l:doc) is v:t_string ? l:doc : ''),
\})
endfor
if has_key(l:info, 'prefix')
- return ale#completion#Filter(l:buffer, l:results, l:info.prefix)
+ return ale#completion#Filter(l:buffer, &filetype, l:results, l:info.prefix)
endif
return l:results
@@ -383,6 +397,7 @@ function! ale#completion#HandleTSServerResponse(conn_id, response) abort
if l:command is# 'completions'
let l:names = ale#completion#Filter(
\ l:buffer,
+ \ &filetype,
\ ale#completion#ParseTSServerCompletions(a:response),
\ b:ale_completion_info.prefix,
\)[: g:ale_completion_max_suggestions - 1]
@@ -422,6 +437,58 @@ function! ale#completion#HandleLSPResponse(conn_id, response) abort
\)
endfunction
+function! s:OnReady(linter, lsp_details, ...) abort
+ let l:buffer = a:lsp_details.buffer
+ let l:id = a:lsp_details.connection_id
+
+ " If we have sent a completion request already, don't send another.
+ if b:ale_completion_info.request_id
+ return
+ endif
+
+ let l:Callback = a:linter.lsp is# 'tsserver'
+ \ ? function('ale#completion#HandleTSServerResponse')
+ \ : function('ale#completion#HandleLSPResponse')
+ call ale#lsp#RegisterCallback(l:id, l:Callback)
+
+ if a:linter.lsp is# 'tsserver'
+ let l:message = ale#lsp#tsserver_message#Completions(
+ \ l:buffer,
+ \ b:ale_completion_info.line,
+ \ b:ale_completion_info.column,
+ \ b:ale_completion_info.prefix,
+ \)
+ else
+ " Send a message saying the buffer has changed first, otherwise
+ " completions won't know what text is nearby.
+ call ale#lsp#NotifyForChanges(l:id, l:buffer)
+
+ " For LSP completions, we need to clamp the column to the length of
+ " the line. python-language-server and perhaps others do not implement
+ " this correctly.
+ let l:message = ale#lsp#message#Completion(
+ \ l:buffer,
+ \ b:ale_completion_info.line,
+ \ min([
+ \ b:ale_completion_info.line_length,
+ \ b:ale_completion_info.column,
+ \ ]),
+ \ ale#completion#GetTriggerCharacter(&filetype, b:ale_completion_info.prefix),
+ \)
+ endif
+
+ let l:request_id = ale#lsp#Send(l:id, l:message)
+
+ if l:request_id
+ let b:ale_completion_info.conn_id = l:id
+ let b:ale_completion_info.request_id = l:request_id
+
+ if has_key(a:linter, 'completion_filter')
+ let b:ale_completion_info.completion_filter = a:linter.completion_filter
+ endif
+ endif
+endfunction
+
function! s:GetLSPCompletions(linter) abort
let l:buffer = bufnr('')
let l:lsp_details = ale#lsp_linter#StartLSP(l:buffer, a:linter)
@@ -431,58 +498,10 @@ function! s:GetLSPCompletions(linter) abort
endif
let l:id = l:lsp_details.connection_id
- let l:root = l:lsp_details.project_root
-
- function! OnReady(...) abort closure
- " If we have sent a completion request already, don't send another.
- if b:ale_completion_info.request_id
- return
- endif
-
- let l:Callback = a:linter.lsp is# 'tsserver'
- \ ? function('ale#completion#HandleTSServerResponse')
- \ : function('ale#completion#HandleLSPResponse')
- call ale#lsp#RegisterCallback(l:id, l:Callback)
-
- if a:linter.lsp is# 'tsserver'
- let l:message = ale#lsp#tsserver_message#Completions(
- \ l:buffer,
- \ b:ale_completion_info.line,
- \ b:ale_completion_info.column,
- \ b:ale_completion_info.prefix,
- \)
- else
- " Send a message saying the buffer has changed first, otherwise
- " completions won't know what text is nearby.
- call ale#lsp#NotifyForChanges(l:id, l:root, l:buffer)
-
- " For LSP completions, we need to clamp the column to the length of
- " the line. python-language-server and perhaps others do not implement
- " this correctly.
- let l:message = ale#lsp#message#Completion(
- \ l:buffer,
- \ b:ale_completion_info.line,
- \ min([
- \ b:ale_completion_info.line_length,
- \ b:ale_completion_info.column,
- \ ]),
- \ ale#completion#GetTriggerCharacter(&filetype, b:ale_completion_info.prefix),
- \)
- endif
- let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root)
-
- if l:request_id
- let b:ale_completion_info.conn_id = l:id
- let b:ale_completion_info.request_id = l:request_id
-
- if has_key(a:linter, 'completion_filter')
- let b:ale_completion_info.completion_filter = a:linter.completion_filter
- endif
- endif
- endfunction
+ let l:OnReady = function('s:OnReady', [a:linter, l:lsp_details])
- call ale#lsp#WaitForCapability(l:id, l:root, 'completion', function('OnReady'))
+ call ale#lsp#WaitForCapability(l:id, 'completion', l:OnReady)
endfunction
function! ale#completion#GetCompletions() abort
diff --git a/autoload/ale/cursor.vim b/autoload/ale/cursor.vim
index 73dbebb2..6672c349 100644
--- a/autoload/ale/cursor.vim
+++ b/autoload/ale/cursor.vim
@@ -1,4 +1,6 @@
+scriptencoding utf-8
" Author: w0rp <devw0rp@gmail.com>
+" Author: João Paulo S. de Souza <joao.paulo.silvasouza@hotmail.com>
" Description: Echoes lint message for the current line, if any
" Controls the milliseconds delay before echoing a message.
@@ -24,7 +26,20 @@ function! ale#cursor#TruncatedEcho(original_message) abort
" The message is truncated and saved to the history.
setlocal shortmess+=T
- exec "norm! :echomsg l:message\n"
+
+ try
+ exec "norm! :echomsg l:message\n"
+ catch /^Vim\%((\a\+)\)\=:E523/
+ " Fallback into manual truncate (#1987)
+ let l:winwidth = winwidth(0)
+
+ if l:winwidth < strdisplaywidth(l:message)
+ " Truncate message longer than window width with trailing '...'
+ let l:message = l:message[:l:winwidth - 4] . '...'
+ endif
+
+ exec 'echomsg l:message'
+ endtry
" Reset the cursor position if we moved off the end of the line.
" Using :norm and :echomsg can move the cursor off the end of the
@@ -37,17 +52,6 @@ function! ale#cursor#TruncatedEcho(original_message) abort
endtry
endfunction
-function! s:FindItemAtCursor() abort
- let l:buf = bufnr('')
- let l:info = get(g:ale_buffer_info, l:buf, {})
- let l:loclist = get(l:info, 'loclist', [])
- let l:pos = getcurpos()
- let l:index = ale#util#BinarySearch(l:loclist, l:buf, l:pos[1], l:pos[2])
- let l:loc = l:index >= 0 ? l:loclist[l:index] : {}
-
- return [l:info, l:loc]
-endfunction
-
function! s:StopCursorTimer() abort
if s:cursor_timer != -1
call timer_stop(s:cursor_timer)
@@ -56,42 +60,55 @@ function! s:StopCursorTimer() abort
endfunction
function! ale#cursor#EchoCursorWarning(...) abort
- if !g:ale_echo_cursor
+ let l:buffer = bufnr('')
+
+ if !g:ale_echo_cursor && !g:ale_cursor_detail
return
endif
" Only echo the warnings in normal mode, otherwise we will get problems.
- if mode() isnot# 'n'
+ if mode(1) isnot# 'n'
return
endif
- if ale#ShouldDoNothing(bufnr(''))
+ if ale#ShouldDoNothing(l:buffer)
return
endif
- let l:buffer = bufnr('')
- let [l:info, l:loc] = s:FindItemAtCursor()
+ let [l:info, l:loc] = ale#util#FindItemAtCursor(l:buffer)
+
+ if g:ale_echo_cursor
+ if !empty(l:loc)
+ let l:format = ale#Var(l:buffer, 'echo_msg_format')
+ let l:msg = ale#GetLocItemMessage(l:loc, l:format)
+ call ale#cursor#TruncatedEcho(l:msg)
+ let l:info.echoed = 1
+ elseif get(l:info, 'echoed')
+ " We'll only clear the echoed message when moving off errors once,
+ " so we don't continually clear the echo line.
+ execute 'echo'
+ let l:info.echoed = 0
+ endif
+ endif
- if !empty(l:loc)
- let l:format = ale#Var(l:buffer, 'echo_msg_format')
- let l:msg = ale#GetLocItemMessage(l:loc, l:format)
- call ale#cursor#TruncatedEcho(l:msg)
- let l:info.echoed = 1
- elseif get(l:info, 'echoed')
- " We'll only clear the echoed message when moving off errors once,
- " so we don't continually clear the echo line.
- execute 'echo'
- let l:info.echoed = 0
+ if g:ale_cursor_detail
+ if !empty(l:loc)
+ call s:ShowCursorDetailForItem(l:loc, {'stay_here': 1})
+ else
+ call ale#preview#CloseIfTypeMatches('ale-preview')
+ endif
endif
endfunction
function! ale#cursor#EchoCursorWarningWithDelay() abort
- if !g:ale_echo_cursor
+ let l:buffer = bufnr('')
+
+ if !g:ale_echo_cursor && !g:ale_cursor_detail
return
endif
" Only echo the warnings in normal mode, otherwise we will get problems.
- if mode() isnot# 'n'
+ if mode(1) isnot# 'n'
return
endif
@@ -104,7 +121,7 @@ function! ale#cursor#EchoCursorWarningWithDelay() abort
" we should echo something. Otherwise we can end up doing processing
" the echo message far too frequently.
if l:pos != s:last_pos
- let l:delay = ale#Var(bufnr(''), 'echo_delay')
+ let l:delay = ale#Var(l:buffer, 'echo_delay')
let s:last_pos = l:pos
let s:cursor_timer = timer_start(
@@ -114,24 +131,37 @@ function! ale#cursor#EchoCursorWarningWithDelay() abort
endif
endfunction
+function! s:ShowCursorDetailForItem(loc, options) abort
+ let l:stay_here = get(a:options, 'stay_here', 0)
+
+ let s:last_detailed_line = line('.')
+ let l:message = get(a:loc, 'detail', a:loc.text)
+ let l:lines = split(l:message, "\n")
+ call ale#preview#Show(l:lines, {'stay_here': l:stay_here})
+
+ " Clear the echo message if we manually displayed details.
+ if !l:stay_here
+ execute 'echo'
+ endif
+endfunction
+
function! ale#cursor#ShowCursorDetail() abort
+ let l:buffer = bufnr('')
+
" Only echo the warnings in normal mode, otherwise we will get problems.
if mode() isnot# 'n'
return
endif
- if ale#ShouldDoNothing(bufnr(''))
+ if ale#ShouldDoNothing(l:buffer)
return
endif
call s:StopCursorTimer()
- let [l:info, l:loc] = s:FindItemAtCursor()
+ let [l:info, l:loc] = ale#util#FindItemAtCursor(l:buffer)
if !empty(l:loc)
- let l:message = get(l:loc, 'detail', l:loc.text)
-
- call ale#preview#Show(split(l:message, "\n"))
- execute 'echo'
+ call s:ShowCursorDetailForItem(l:loc, {'stay_here': 0})
endif
endfunction
diff --git a/autoload/ale/d.vim b/autoload/ale/d.vim
new file mode 100644
index 00000000..0e232203
--- /dev/null
+++ b/autoload/ale/d.vim
@@ -0,0 +1,16 @@
+" Author: Auri <me@aurieh.me>
+" Description: Functions for integrating with D linters.
+
+function! ale#d#FindDUBConfig(buffer) abort
+ " Find a DUB configuration file in ancestor paths.
+ " The most DUB-specific names will be tried first.
+ for l:possible_filename in ['dub.sdl', 'dub.json', 'package.json']
+ let l:dub_file = ale#path#FindNearestFile(a:buffer, l:possible_filename)
+
+ if !empty(l:dub_file)
+ return l:dub_file
+ endif
+ endfor
+
+ return ''
+endfunction
diff --git a/autoload/ale/debugging.vim b/autoload/ale/debugging.vim
index 34c13770..6c2bfbee 100644
--- a/autoload/ale/debugging.vim
+++ b/autoload/ale/debugging.vim
@@ -22,14 +22,14 @@ let s:global_variable_list = [
\ 'ale_lint_delay',
\ 'ale_lint_on_enter',
\ 'ale_lint_on_filetype_changed',
+\ 'ale_lint_on_insert_leave',
\ 'ale_lint_on_save',
\ 'ale_lint_on_text_changed',
-\ 'ale_lint_on_insert_leave',
\ 'ale_linter_aliases',
\ 'ale_linters',
\ 'ale_linters_explicit',
-\ 'ale_list_window_size',
\ 'ale_list_vertical',
+\ 'ale_list_window_size',
\ 'ale_loclist_msg_format',
\ 'ale_max_buffer_history_size',
\ 'ale_max_signs',
@@ -52,6 +52,7 @@ let s:global_variable_list = [
\ 'ale_statusline_format',
\ 'ale_type_map',
\ 'ale_use_global_executables',
+\ 'ale_virtualtext_cursor',
\ 'ale_warn_about_trailing_blank_lines',
\ 'ale_warn_about_trailing_whitespace',
\]
diff --git a/autoload/ale/definition.vim b/autoload/ale/definition.vim
index 6c7d7d32..984a4f9d 100644
--- a/autoload/ale/definition.vim
+++ b/autoload/ale/definition.vim
@@ -40,16 +40,16 @@ function! ale#definition#HandleLSPResponse(conn_id, response) abort
" The result can be a Dictionary item, a List of the same, or null.
let l:result = get(a:response, 'result', v:null)
- if type(l:result) is type({})
+ if type(l:result) is v:t_dict
let l:result = [l:result]
- elseif type(l:result) isnot type([])
+ elseif type(l:result) isnot v:t_list
let l:result = []
endif
for l:item in l:result
let l:filename = ale#path#FromURI(l:item.uri)
let l:line = l:item.range.start.line + 1
- let l:column = l:item.range.start.character
+ let l:column = l:item.range.start.character + 1
call ale#util#Open(l:filename, l:line, l:column, l:options)
break
@@ -57,6 +57,39 @@ function! ale#definition#HandleLSPResponse(conn_id, response) abort
endif
endfunction
+function! s:OnReady(linter, lsp_details, line, column, options, ...) abort
+ let l:buffer = a:lsp_details.buffer
+ let l:id = a:lsp_details.connection_id
+
+ let l:Callback = a:linter.lsp is# 'tsserver'
+ \ ? function('ale#definition#HandleTSServerResponse')
+ \ : function('ale#definition#HandleLSPResponse')
+ call ale#lsp#RegisterCallback(l:id, l:Callback)
+
+ if a:linter.lsp is# 'tsserver'
+ let l:message = ale#lsp#tsserver_message#Definition(
+ \ l:buffer,
+ \ a:line,
+ \ a:column
+ \)
+ else
+ " Send a message saying the buffer has changed first, or the
+ " definition position probably won't make sense.
+ call ale#lsp#NotifyForChanges(l:id, l:buffer)
+
+ " For LSP completions, we need to clamp the column to the length of
+ " the line. python-language-server and perhaps others do not implement
+ " this correctly.
+ let l:message = ale#lsp#message#Definition(l:buffer, a:line, a:column)
+ endif
+
+ let l:request_id = ale#lsp#Send(l:id, l:message)
+
+ let s:go_to_definition_map[l:request_id] = {
+ \ 'open_in_tab': get(a:options, 'open_in_tab', 0),
+ \}
+endfunction
+
function! s:GoToLSPDefinition(linter, options) abort
let l:buffer = bufnr('')
let [l:line, l:column] = getcurpos()[1:2]
@@ -71,39 +104,10 @@ function! s:GoToLSPDefinition(linter, options) abort
endif
let l:id = l:lsp_details.connection_id
- let l:root = l:lsp_details.project_root
-
- function! OnReady(...) abort closure
- let l:Callback = a:linter.lsp is# 'tsserver'
- \ ? function('ale#definition#HandleTSServerResponse')
- \ : function('ale#definition#HandleLSPResponse')
- call ale#lsp#RegisterCallback(l:id, l:Callback)
-
- if a:linter.lsp is# 'tsserver'
- let l:message = ale#lsp#tsserver_message#Definition(
- \ l:buffer,
- \ l:line,
- \ l:column
- \)
- else
- " Send a message saying the buffer has changed first, or the
- " definition position probably won't make sense.
- call ale#lsp#NotifyForChanges(l:id, l:root, l:buffer)
-
- " For LSP completions, we need to clamp the column to the length of
- " the line. python-language-server and perhaps others do not implement
- " this correctly.
- let l:message = ale#lsp#message#Definition(l:buffer, l:line, l:column)
- endif
-
- let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root)
-
- let s:go_to_definition_map[l:request_id] = {
- \ 'open_in_tab': get(a:options, 'open_in_tab', 0),
- \}
- endfunction
- call ale#lsp#WaitForCapability(l:id, l:root, 'definition', function('OnReady'))
+ call ale#lsp#WaitForCapability(l:id, 'definition', function('s:OnReady', [
+ \ a:linter, l:lsp_details, l:line, l:column, a:options
+ \]))
endfunction
function! ale#definition#GoTo(options) abort
diff --git a/autoload/ale/engine.vim b/autoload/ale/engine.vim
index ec5ccb6d..b44be73c 100644
--- a/autoload/ale/engine.vim
+++ b/autoload/ale/engine.vim
@@ -18,6 +18,22 @@ if !has_key(s:, 'executable_cache_map')
let s:executable_cache_map = {}
endif
+
+function! ale#engine#CleanupEveryBuffer() abort
+ for l:key in keys(g:ale_buffer_info)
+ " The key could be a filename or a buffer number, so try and
+ " convert it to a number. We need a number for the other
+ " functions.
+ let l:buffer = str2nr(l:key)
+
+ if l:buffer > 0
+ " Stop all jobs and clear the results for everything, and delete
+ " all of the data we stored for the buffer.
+ call ale#engine#Cleanup(l:buffer)
+ endif
+ endfor
+endfunction
+
function! ale#engine#ResetExecutableCache() abort
let s:executable_cache_map = {}
endfunction
@@ -63,6 +79,7 @@ function! ale#engine#InitBufferInfo(buffer) abort
let g:ale_buffer_info[a:buffer] = {
\ 'job_list': [],
\ 'active_linter_list': [],
+ \ 'active_other_sources_list': [],
\ 'loclist': [],
\ 'temporary_file_list': [],
\ 'temporary_directory_list': [],
@@ -81,6 +98,7 @@ function! ale#engine#IsCheckingBuffer(buffer) abort
let l:info = get(g:ale_buffer_info, a:buffer, {})
return !empty(get(l:info, 'active_linter_list', []))
+ \ || !empty(get(l:info, 'active_other_sources_list', []))
endfunction
" Register a temporary file to be managed with the ALE engine for
@@ -161,20 +179,27 @@ function! s:GatherOutput(job_id, line) abort
endif
endfunction
-function! ale#engine#HandleLoclist(linter_name, buffer, loclist) abort
+function! ale#engine#HandleLoclist(linter_name, buffer, loclist, from_other_source) abort
let l:info = get(g:ale_buffer_info, a:buffer, {})
if empty(l:info)
return
endif
- " Remove this linter from the list of active linters.
- " This may have already been done when the job exits.
- call filter(l:info.active_linter_list, 'v:val isnot# a:linter_name')
+ if !a:from_other_source
+ " Remove this linter from the list of active linters.
+ " This may have already been done when the job exits.
+ call filter(l:info.active_linter_list, 'v:val isnot# a:linter_name')
+ endif
" Make some adjustments to the loclists to fix common problems, and also
" to set default values for loclist items.
- let l:linter_loclist = ale#engine#FixLocList(a:buffer, a:linter_name, a:loclist)
+ let l:linter_loclist = ale#engine#FixLocList(
+ \ a:buffer,
+ \ a:linter_name,
+ \ a:from_other_source,
+ \ a:loclist,
+ \)
" Remove previous items for this linter.
call filter(l:info.loclist, 'v:val.linter_name isnot# a:linter_name')
@@ -231,6 +256,7 @@ function! s:HandleExit(job_id, exit_code) abort
if l:next_chain_index < len(get(l:linter, 'command_chain', []))
call s:InvokeChain(l:buffer, l:executable, l:linter, l:next_chain_index, l:output)
+
return
endif
@@ -246,7 +272,7 @@ function! s:HandleExit(job_id, exit_code) abort
let l:loclist = []
endtry
- call ale#engine#HandleLoclist(l:linter.name, l:buffer, l:loclist)
+ call ale#engine#HandleLoclist(l:linter.name, l:buffer, l:loclist, 0)
endfunction
function! ale#engine#SetResults(buffer, loclist) abort
@@ -278,6 +304,12 @@ function! ale#engine#SetResults(buffer, loclist) abort
call ale#cursor#EchoCursorWarning()
endif
+ if g:ale_virtualtext_cursor
+ " Try and show the warning now.
+ " This will only do something meaningful if we're in normal mode.
+ call ale#virtualtext#ShowCursorWarning()
+ endif
+
" Reset the save event marker, used for opening windows, etc.
call setbufvar(a:buffer, 'ale_save_event_fired', 0)
" Set a marker showing how many times a buffer has been checked.
@@ -318,7 +350,7 @@ function! s:RemapItemTypes(type_map, loclist) abort
endfor
endfunction
-function! ale#engine#FixLocList(buffer, linter_name, loclist) abort
+function! ale#engine#FixLocList(buffer, linter_name, from_other_source, loclist) abort
let l:bufnr_map = {}
let l:new_loclist = []
@@ -351,6 +383,10 @@ function! ale#engine#FixLocList(buffer, linter_name, loclist) abort
\ 'linter_name': a:linter_name,
\}
+ if a:from_other_source
+ let l:item.from_other_source = 1
+ endif
+
if has_key(l:old_item, 'code')
let l:item.code = l:old_item.code
endif
@@ -557,7 +593,7 @@ function! s:RunJob(options) abort
if get(g:, 'ale_run_synchronously') == 1
" Run a command synchronously if this test option is set.
let s:job_info_map[l:job_id].output = systemlist(
- \ type(l:command) == type([])
+ \ type(l:command) is v:t_list
\ ? join(l:command[0:1]) . ' ' . ale#Escape(l:command[2])
\ : l:command
\)
@@ -595,9 +631,8 @@ function! ale#engine#ProcessChain(buffer, linter, chain_index, input) abort
\)
endif
+ " If we have a command to run, execute that.
if !empty(l:command)
- " We hit a command to run, so we'll execute that
-
" 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
@@ -675,6 +710,7 @@ endfunction
function! s:RemoveProblemsForDisabledLinters(buffer, linters) abort
" Figure out which linters are still enabled, and remove
" problems for linters which are no longer enabled.
+ " Problems from other sources will be kept.
let l:name_map = {}
for l:linter in a:linters
@@ -683,7 +719,7 @@ function! s:RemoveProblemsForDisabledLinters(buffer, linters) abort
call filter(
\ get(g:ale_buffer_info[a:buffer], 'loclist', []),
- \ 'get(l:name_map, get(v:val, ''linter_name''))',
+ \ 'get(v:val, ''from_other_source'') || get(l:name_map, get(v:val, ''linter_name''))',
\)
endfunction
diff --git a/autoload/ale/engine/ignore.vim b/autoload/ale/engine/ignore.vim
index 65347e21..2db2c6c1 100644
--- a/autoload/ale/engine/ignore.vim
+++ b/autoload/ale/engine/ignore.vim
@@ -4,11 +4,11 @@
" Given a filetype and a configuration for ignoring linters, return a List of
" Strings for linter names to ignore.
function! ale#engine#ignore#GetList(filetype, config) abort
- if type(a:config) is type([])
+ if type(a:config) is v:t_list
return a:config
endif
- if type(a:config) is type({})
+ if type(a:config) is v:t_dict
let l:names_to_remove = []
for l:part in split(a:filetype , '\.')
diff --git a/autoload/ale/events.vim b/autoload/ale/events.vim
index 300aefcc..c3dbd378 100644
--- a/autoload/ale/events.vim
+++ b/autoload/ale/events.vim
@@ -29,7 +29,7 @@ function! ale#events#SaveEvent(buffer) abort
call setbufvar(a:buffer, 'ale_save_event_fired', 1)
endif
- if ale#Var(a:buffer, 'fix_on_save')
+ if ale#Var(a:buffer, 'fix_on_save') && !ale#events#QuitRecently(a:buffer)
let l:will_fix = ale#fix#Fix(a:buffer, 'save_file')
let l:should_lint = l:should_lint && !l:will_fix
endif
@@ -131,13 +131,25 @@ function! ale#events#Init() abort
autocmd InsertLeave * call ale#Queue(0)
endif
- if g:ale_echo_cursor
+ if g:ale_echo_cursor || g:ale_cursor_detail
autocmd CursorMoved,CursorHold * if exists('*ale#engine#Cleanup') | call ale#cursor#EchoCursorWarningWithDelay() | endif
" Look for a warning to echo as soon as we leave Insert mode.
" The script's position variable used when moving the cursor will
" not be changed here.
autocmd InsertLeave * if exists('*ale#engine#Cleanup') | call ale#cursor#EchoCursorWarning() | endif
endif
+
+ if g:ale_virtualtext_cursor
+ autocmd CursorMoved,CursorHold * if exists('*ale#engine#Cleanup') | call ale#virtualtext#ShowCursorWarningWithDelay() | endif
+ " Look for a warning to echo as soon as we leave Insert mode.
+ " The script's position variable used when moving the cursor will
+ " not be changed here.
+ autocmd InsertLeave * if exists('*ale#engine#Cleanup') | call ale#virtualtext#ShowCursorWarning() | endif
+ endif
+
+ if g:ale_close_preview_on_insert
+ autocmd InsertEnter * if exists('*ale#preview#CloseIfTypeMatches') | call ale#preview#CloseIfTypeMatches('ale-preview') | endif
+ endif
endif
augroup END
endfunction
diff --git a/autoload/ale/fix.vim b/autoload/ale/fix.vim
index 8dfdeca8..03652ecf 100644
--- a/autoload/ale/fix.vim
+++ b/autoload/ale/fix.vim
@@ -30,7 +30,14 @@ function! ale#fix#ApplyQueuedFixes() abort
call winrestview(l:save)
endif
- call setline(1, l:data.output)
+ " If the file is in DOS mode, we have to remove carriage returns from
+ " the ends of lines before calling setline(), or we will see them
+ " twice.
+ let l:lines_to_set = getbufvar(l:buffer, '&fileformat') is# 'dos'
+ \ ? map(copy(l:data.output), 'substitute(v:val, ''\r\+$'', '''', '''')')
+ \ : l:data.output
+
+ call setline(1, l:lines_to_set)
if l:data.should_save
if empty(&buftype)
@@ -71,6 +78,7 @@ function! ale#fix#ApplyFixes(buffer, output) abort
if l:data.lines_before != l:lines
call remove(g:ale_fix_buffer_data, a:buffer)
execute 'echoerr ''The file was changed before fixing finished'''
+
return
endif
endif
@@ -275,7 +283,7 @@ function! s:RunJob(options) abort
if get(g:, 'ale_run_synchronously') == 1
" Run a command synchronously if this test option is set.
let l:output = systemlist(
- \ type(l:command) == type([])
+ \ type(l:command) is v:t_list
\ ? join(l:command[0:1]) . ' ' . ale#Escape(l:command[2])
\ : l:command
\)
@@ -313,10 +321,10 @@ function! s:RunFixer(options) abort
\ : call(l:Function, [l:buffer, copy(l:input)])
endif
- if type(l:result) == type(0) && l:result == 0
+ if type(l:result) is v:t_number && l:result == 0
" When `0` is returned, skip this item.
let l:index += 1
- elseif type(l:result) == type([])
+ elseif type(l:result) is v:t_list
let l:input = l:result
let l:index += 1
else
@@ -351,9 +359,9 @@ function! s:RunFixer(options) abort
endfunction
function! s:AddSubCallbacks(full_list, callbacks) abort
- if type(a:callbacks) == type('')
+ if type(a:callbacks) is v:t_string
call add(a:full_list, a:callbacks)
- elseif type(a:callbacks) == type([])
+ elseif type(a:callbacks) is v:t_list
call extend(a:full_list, a:callbacks)
else
return 0
@@ -365,7 +373,7 @@ endfunction
function! s:GetCallbacks(buffer, fixers) abort
if len(a:fixers)
let l:callback_list = a:fixers
- elseif type(get(b:, 'ale_fixers')) is type([])
+ elseif type(get(b:, 'ale_fixers')) is v:t_list
" Lists can be used for buffer-local variables only
let l:callback_list = b:ale_fixers
else
@@ -396,7 +404,7 @@ function! s:GetCallbacks(buffer, fixers) abort
" Variables with capital characters are needed, or Vim will complain about
" funcref variables.
for l:Item in l:callback_list
- if type(l:Item) == type('')
+ if type(l:Item) is v:t_string
let l:Func = ale#fix#registry#GetFunc(l:Item)
if !empty(l:Func)
diff --git a/autoload/ale/fix/registry.vim b/autoload/ale/fix/registry.vim
index e148ecd6..a54be420 100644
--- a/autoload/ale/fix/registry.vim
+++ b/autoload/ale/fix/registry.vim
@@ -56,7 +56,7 @@ let s:default_registry = {
\ },
\ 'prettier': {
\ 'function': 'ale#fixers#prettier#Fix',
-\ 'suggested_filetypes': ['javascript', 'typescript', 'css', 'less', 'scss', 'json', 'json5', 'graphql', 'markdown', 'vue'],
+\ 'suggested_filetypes': ['javascript', 'typescript', 'css', 'less', 'scss', 'json', 'json5', 'graphql', 'markdown', 'vue', 'html', 'yaml'],
\ 'description': 'Apply prettier to a file.',
\ },
\ 'prettier_eslint': {
@@ -145,6 +145,11 @@ let s:default_registry = {
\ 'suggested_filetypes': ['go'],
\ 'description': 'Fix Go files imports with goimports.',
\ },
+\ 'gomod': {
+\ 'function': 'ale#fixers#gomod#Fix',
+\ 'suggested_filetypes': ['gomod'],
+\ 'description': 'Fix Go module files with go mod edit -fmt.',
+\ },
\ 'tslint': {
\ 'function': 'ale#fixers#tslint#Fix',
\ 'suggested_filetypes': ['typescript'],
@@ -157,7 +162,7 @@ let s:default_registry = {
\ },
\ 'hackfmt': {
\ 'function': 'ale#fixers#hackfmt#Fix',
-\ 'suggested_filetypes': ['php'],
+\ 'suggested_filetypes': ['hack'],
\ 'description': 'Fix Hack files with hackfmt.',
\ },
\ 'hfmt': {
@@ -170,6 +175,21 @@ let s:default_registry = {
\ 'suggested_filetypes': ['haskell'],
\ 'description': 'Fix Haskell files with brittany.',
\ },
+\ 'hlint': {
+\ 'function': 'ale#fixers#hlint#Fix',
+\ 'suggested_filetypes': ['haskell'],
+\ 'description': 'Refactor Haskell files with hlint.',
+\ },
+\ 'stylish-haskell': {
+\ 'function': 'ale#fixers#stylish_haskell#Fix',
+\ 'suggested_filetypes': ['haskell'],
+\ 'description': 'Refactor Haskell files with stylish-haskell.',
+\ },
+\ 'ocamlformat': {
+\ 'function': 'ale#fixers#ocamlformat#Fix',
+\ 'suggested_filetypes': ['ocaml'],
+\ 'description': 'Fix OCaml files with ocamlformat.',
+\ },
\ 'refmt': {
\ 'function': 'ale#fixers#refmt#Fix',
\ 'suggested_filetypes': ['reason'],
@@ -180,6 +200,11 @@ let s:default_registry = {
\ 'suggested_filetypes': ['sh'],
\ 'description': 'Fix sh files with shfmt.',
\ },
+\ 'sqlfmt': {
+\ 'function': 'ale#fixers#sqlfmt#Fix',
+\ 'suggested_filetypes': ['sql'],
+\ 'description': 'Fix SQL files with sqlfmt.',
+\ },
\ 'google_java_format': {
\ 'function': 'ale#fixers#google_java_format#Fix',
\ 'suggested_filetypes': ['java'],
@@ -215,6 +240,21 @@ let s:default_registry = {
\ 'suggested_filetypes': ['dart'],
\ 'description': 'Fix Dart files with dartfmt.',
\ },
+\ 'xmllint': {
+\ 'function': 'ale#fixers#xmllint#Fix',
+\ 'suggested_filetypes': ['xml'],
+\ 'description': 'Fix XML files with xmllint.',
+\ },
+\ 'uncrustify': {
+\ 'function': 'ale#fixers#uncrustify#Fix',
+\ 'suggested_filetypes': ['c', 'cpp', 'cs', 'objc', 'objcpp', 'd', 'java', 'p', 'vala' ],
+\ 'description': 'Fix C, C++, C#, ObjectiveC, ObjectiveC++, D, Java, Pawn, and VALA files with uncrustify.',
+\ },
+\ 'terraform': {
+\ 'function': 'ale#fixers#terraform#Fix',
+\ 'suggested_filetypes': ['hcl', 'terraform'],
+\ 'description': 'Fix tf and hcl files with terraform fmt.',
+\ },
\}
" Reset the function registry to the default entries.
@@ -243,34 +283,34 @@ endfunction
" (name, func, filetypes, desc, aliases)
function! ale#fix#registry#Add(name, func, filetypes, desc, ...) abort
" This command will throw from the sandbox.
- let &equalprg=&equalprg
+ let &l:equalprg=&l:equalprg
- if type(a:name) != type('')
+ if type(a:name) isnot v:t_string
throw '''name'' must be a String'
endif
- if type(a:func) != type('')
+ if type(a:func) isnot v:t_string
throw '''func'' must be a String'
endif
- if type(a:filetypes) != type([])
+ if type(a:filetypes) isnot v:t_list
throw '''filetypes'' must be a List'
endif
for l:type in a:filetypes
- if type(l:type) != type('')
+ if type(l:type) isnot v:t_string
throw 'Each entry of ''filetypes'' must be a String'
endif
endfor
- if type(a:desc) != type('')
+ if type(a:desc) isnot v:t_string
throw '''desc'' must be a String'
endif
let l:aliases = get(a:000, 0, [])
- if type(l:aliases) != type([])
- \|| !empty(filter(copy(l:aliases), 'type(v:val) != type('''')'))
+ if type(l:aliases) isnot v:t_list
+ \|| !empty(filter(copy(l:aliases), 'type(v:val) isnot v:t_string'))
throw '''aliases'' must be a List of String values'
endif
diff --git a/autoload/ale/fixers/brittany.vim b/autoload/ale/fixers/brittany.vim
index 57c77325..c2448348 100644
--- a/autoload/ale/fixers/brittany.vim
+++ b/autoload/ale/fixers/brittany.vim
@@ -3,11 +3,17 @@
call ale#Set('haskell_brittany_executable', 'brittany')
-function! ale#fixers#brittany#Fix(buffer) abort
+function! ale#fixers#brittany#GetExecutable(buffer) abort
let l:executable = ale#Var(a:buffer, 'haskell_brittany_executable')
+ return ale#handlers#haskell_stack#EscapeExecutable(l:executable, 'brittany')
+endfunction
+
+function! ale#fixers#brittany#Fix(buffer) abort
+ let l:executable = ale#fixers#brittany#GetExecutable(a:buffer)
+
return {
- \ 'command': ale#Escape(l:executable)
+ \ 'command': l:executable
\ . ' --write-mode inplace'
\ . ' %t',
\ 'read_temporary_file': 1,
diff --git a/autoload/ale/fixers/eslint.vim b/autoload/ale/fixers/eslint.vim
index 36f47510..ea5b2a63 100644
--- a/autoload/ale/fixers/eslint.vim
+++ b/autoload/ale/fixers/eslint.vim
@@ -25,7 +25,7 @@ endfunction
function! ale#fixers#eslint#ProcessEslintDOutput(buffer, output) abort
" If the output is an error message, don't use it.
for l:line in a:output[:10]
- if l:line =~# '^Error:'
+ if l:line =~# '\v^Error:|^Could not connect'
return []
endif
endfor
diff --git a/autoload/ale/fixers/fixjson.vim b/autoload/ale/fixers/fixjson.vim
index 64c6ba81..33ce0af3 100644
--- a/autoload/ale/fixers/fixjson.vim
+++ b/autoload/ale/fixers/fixjson.vim
@@ -17,6 +17,7 @@ function! ale#fixers#fixjson#Fix(buffer) abort
let l:command = l:executable . ' --stdin-filename ' . l:filename
let l:options = ale#Var(a:buffer, 'json_fixjson_options')
+
if l:options isnot# ''
let l:command .= ' ' . l:options
endif
diff --git a/autoload/ale/fixers/generic_python.vim b/autoload/ale/fixers/generic_python.vim
index 124146be..d55a23c3 100644
--- a/autoload/ale/fixers/generic_python.vim
+++ b/autoload/ale/fixers/generic_python.vim
@@ -6,13 +6,28 @@ function! ale#fixers#generic_python#AddLinesBeforeControlStatements(buffer, line
let l:new_lines = []
let l:last_indent_size = 0
let l:last_line_is_blank = 0
+ let l:in_docstring = 0
for l:line in a:lines
let l:indent_size = len(matchstr(l:line, '^ *'))
+ if !l:in_docstring
+ " Make sure it is not just a single line docstring and then verify
+ " it's starting a new docstring
+ if match(l:line, '\v^ *("""|'''''').*("""|'''''')') == -1
+ \&& match(l:line, '\v^ *("""|'''''')') >= 0
+ let l:in_docstring = 1
+ endif
+ else
+ if match(l:line, '\v^ *.*("""|'''''')') >= 0
+ let l:in_docstring = 0
+ endif
+ endif
+
if !l:last_line_is_blank
+ \&& !l:in_docstring
\&& l:indent_size <= l:last_indent_size
- \&& match(l:line, '\v^ *(return|if|for|while|break|continue)') >= 0
+ \&& match(l:line, '\v^ *(return|if|for|while|break|continue)(\(| |$)') >= 0
call add(l:new_lines, '')
endif
diff --git a/autoload/ale/fixers/gomod.vim b/autoload/ale/fixers/gomod.vim
new file mode 100644
index 00000000..68895f9b
--- /dev/null
+++ b/autoload/ale/fixers/gomod.vim
@@ -0,0 +1,10 @@
+call ale#Set('go_go_executable', 'go')
+
+function! ale#fixers#gomod#Fix(buffer) abort
+ let l:executable = ale#Var(a:buffer, 'go_go_executable')
+
+ return {
+ \ 'command': ale#Escape(l:executable) . ' mod edit -fmt %t',
+ \ 'read_temporary_file': 1,
+ \}
+endfunction
diff --git a/autoload/ale/fixers/google_java_format.vim b/autoload/ale/fixers/google_java_format.vim
index 6a2f5491..20086c73 100644
--- a/autoload/ale/fixers/google_java_format.vim
+++ b/autoload/ale/fixers/google_java_format.vim
@@ -1,13 +1,13 @@
" Author: butlerx <butlerx@notthe,cloud>
" Description: Integration of Google-java-format with ALE.
-call ale#Set('google_java_format_executable', 'google-java-format')
-call ale#Set('google_java_format_use_global', get(g:, 'ale_use_global_executables', 0))
-call ale#Set('google_java_format_options', '')
+call ale#Set('java_google_java_format_executable', 'google-java-format')
+call ale#Set('java_google_java_format_use_global', get(g:, 'ale_use_global_executables', 0))
+call ale#Set('java_google_java_format_options', '')
function! ale#fixers#google_java_format#Fix(buffer) abort
- let l:options = ale#Var(a:buffer, 'google_java_format_options')
- let l:executable = ale#Var(a:buffer, 'google_java_format_executable')
+ let l:options = ale#Var(a:buffer, 'java_google_java_format_options')
+ let l:executable = ale#Var(a:buffer, 'java_google_java_format_executable')
if !executable(l:executable)
return 0
diff --git a/autoload/ale/fixers/hackfmt.vim b/autoload/ale/fixers/hackfmt.vim
index b5bf0dc5..bf2d4f71 100644
--- a/autoload/ale/fixers/hackfmt.vim
+++ b/autoload/ale/fixers/hackfmt.vim
@@ -1,12 +1,12 @@
" Author: Sam Howie <samhowie@gmail.com>
" Description: Integration of hackfmt with ALE.
-call ale#Set('php_hackfmt_executable', 'hackfmt')
-call ale#Set('php_hackfmt_options', '')
+call ale#Set('hack_hackfmt_executable', 'hackfmt')
+call ale#Set('hack_hackfmt_options', '')
function! ale#fixers#hackfmt#Fix(buffer) abort
- let l:executable = ale#Var(a:buffer, 'php_hackfmt_executable')
- let l:options = ale#Var(a:buffer, 'php_hackfmt_options')
+ let l:executable = ale#Var(a:buffer, 'hack_hackfmt_executable')
+ let l:options = ale#Var(a:buffer, 'hack_hackfmt_options')
return {
\ 'command': ale#Escape(l:executable)
diff --git a/autoload/ale/fixers/hfmt.vim b/autoload/ale/fixers/hfmt.vim
index ea061da4..0407b713 100644
--- a/autoload/ale/fixers/hfmt.vim
+++ b/autoload/ale/fixers/hfmt.vim
@@ -7,7 +7,7 @@ function! ale#fixers#hfmt#Fix(buffer) abort
let l:executable = ale#Var(a:buffer, 'haskell_hfmt_executable')
return {
- \ 'command': ale#Escape(l:executable)
+ \ 'command': ale#handlers#haskell_stack#EscapeExecutable(l:executable, 'hfmt')
\ . ' -w'
\ . ' %t',
\ 'read_temporary_file': 1,
diff --git a/autoload/ale/fixers/hlint.vim b/autoload/ale/fixers/hlint.vim
new file mode 100644
index 00000000..88779a55
--- /dev/null
+++ b/autoload/ale/fixers/hlint.vim
@@ -0,0 +1,13 @@
+" Author: eborden <evan@evan-borden.com>
+" Description: Integration of hlint refactor with ALE.
+"
+
+function! ale#fixers#hlint#Fix(buffer) abort
+ return {
+ \ 'command': ale#handlers#hlint#GetExecutable(a:buffer)
+ \ . ' --refactor'
+ \ . ' --refactor-options="--inplace"'
+ \ . ' %t',
+ \ 'read_temporary_file': 1,
+ \}
+endfunction
diff --git a/autoload/ale/fixers/importjs.vim b/autoload/ale/fixers/importjs.vim
index e8eedb12..b5487b2c 100644
--- a/autoload/ale/fixers/importjs.vim
+++ b/autoload/ale/fixers/importjs.vim
@@ -1,15 +1,16 @@
" Author: Jeff Willette <jrwillette88@gmail.com>
" Description: Integration of importjs with ALE.
-call ale#Set('js_importjs_executable', 'importjs')
+call ale#Set('javascript_importjs_executable', 'importjs')
function! ale#fixers#importjs#ProcessOutput(buffer, output) abort
let l:result = ale#util#FuzzyJSONDecode(a:output, [])
+
return split(get(l:result, 'fileContent', ''), "\n")
endfunction
function! ale#fixers#importjs#Fix(buffer) abort
- let l:executable = ale#Var(a:buffer, 'js_importjs_executable')
+ let l:executable = ale#Var(a:buffer, 'javascript_importjs_executable')
if !executable(l:executable)
return 0
diff --git a/autoload/ale/fixers/jq.vim b/autoload/ale/fixers/jq.vim
index b0a43fe2..1b743e49 100644
--- a/autoload/ale/fixers/jq.vim
+++ b/autoload/ale/fixers/jq.vim
@@ -1,5 +1,6 @@
call ale#Set('json_jq_executable', 'jq')
call ale#Set('json_jq_options', '')
+call ale#Set('json_jq_filters', '.')
function! ale#fixers#jq#GetExecutable(buffer) abort
return ale#Var(a:buffer, 'json_jq_executable')
@@ -7,9 +8,15 @@ endfunction
function! ale#fixers#jq#Fix(buffer) abort
let l:options = ale#Var(a:buffer, 'json_jq_options')
+ let l:filters = ale#Var(a:buffer, 'json_jq_filters')
+
+ if empty(l:filters)
+ return 0
+ endif
return {
\ 'command': ale#Escape(ale#fixers#jq#GetExecutable(a:buffer))
- \ . ' . ' . l:options,
+ \ . ' ' . l:filters . ' '
+ \ . l:options,
\}
endfunction
diff --git a/autoload/ale/fixers/ocamlformat.vim b/autoload/ale/fixers/ocamlformat.vim
new file mode 100644
index 00000000..9b7c3e12
--- /dev/null
+++ b/autoload/ale/fixers/ocamlformat.vim
@@ -0,0 +1,18 @@
+" Author: Stephen Lumenta <@sbl>
+" Description: Integration of ocamlformat with ALE.
+
+call ale#Set('ocaml_ocamlformat_executable', 'ocamlformat')
+call ale#Set('ocaml_ocamlformat_options', '')
+
+function! ale#fixers#ocamlformat#Fix(buffer) abort
+ let l:filename = expand('#' . a:buffer . ':p')
+ let l:executable = ale#Var(a:buffer, 'ocaml_ocamlformat_executable')
+ let l:options = ale#Var(a:buffer, 'ocaml_ocamlformat_options')
+
+ return {
+ \ 'command': ale#Escape(l:executable)
+ \ . (empty(l:options) ? '' : ' ' . l:options)
+ \ . ' --name=' . ale#Escape(l:filename)
+ \ . ' -'
+ \}
+endfunction
diff --git a/autoload/ale/fixers/php_cs_fixer.vim b/autoload/ale/fixers/php_cs_fixer.vim
index 26b8e5de..5c59e262 100644
--- a/autoload/ale/fixers/php_cs_fixer.vim
+++ b/autoload/ale/fixers/php_cs_fixer.vim
@@ -14,6 +14,7 @@ endfunction
function! ale#fixers#php_cs_fixer#Fix(buffer) abort
let l:executable = ale#fixers#php_cs_fixer#GetExecutable(a:buffer)
+
return {
\ 'command': ale#Escape(l:executable)
\ . ' ' . ale#Var(a:buffer, 'php_cs_fixer_options')
diff --git a/autoload/ale/fixers/phpcbf.vim b/autoload/ale/fixers/phpcbf.vim
index 487f369a..f14b8406 100644
--- a/autoload/ale/fixers/phpcbf.vim
+++ b/autoload/ale/fixers/phpcbf.vim
@@ -18,6 +18,7 @@ function! ale#fixers#phpcbf#Fix(buffer) abort
let l:standard_option = !empty(l:standard)
\ ? '--standard=' . l:standard
\ : ''
+
return {
\ 'command': ale#Escape(l:executable) . ' --stdin-path=%s ' . l:standard_option . ' -'
\}
diff --git a/autoload/ale/fixers/prettier.vim b/autoload/ale/fixers/prettier.vim
index e8f4e92e..58dea159 100644
--- a/autoload/ale/fixers/prettier.vim
+++ b/autoload/ale/fixers/prettier.vim
@@ -27,6 +27,17 @@ function! ale#fixers#prettier#Fix(buffer) abort
\}
endfunction
+function! ale#fixers#prettier#ProcessPrettierDOutput(buffer, output) abort
+ " If the output is an error message, don't use it.
+ for l:line in a:output[:10]
+ if l:line =~# '^\w*Error:'
+ return []
+ endif
+ endfor
+
+ return a:output
+endfunction
+
function! ale#fixers#prettier#ApplyFixForVersion(buffer, version_output) abort
let l:executable = ale#fixers#prettier#GetExecutable(a:buffer)
let l:options = ale#Var(a:buffer, 'javascript_prettier_options')
@@ -36,12 +47,24 @@ function! ale#fixers#prettier#ApplyFixForVersion(buffer, version_output) abort
" Append the --parser flag depending on the current filetype (unless it's
" already set in g:javascript_prettier_options).
if empty(expand('#' . a:buffer . ':e')) && match(l:options, '--parser') == -1
- let l:prettier_parsers = ['typescript', 'css', 'less', 'scss', 'json', 'json5', 'graphql', 'markdown', 'vue']
- let l:parser = 'babylon'
+ let l:prettier_parsers = {
+ \ 'typescript': 'typescript',
+ \ 'css': 'css',
+ \ 'less': 'less',
+ \ 'scss': 'scss',
+ \ 'json': 'json',
+ \ 'json5': 'json5',
+ \ 'graphql': 'graphql',
+ \ 'markdown': 'markdown',
+ \ 'vue': 'vue',
+ \ 'yaml': 'yaml',
+ \ 'html': 'html',
+ \}
+ let l:parser = ''
for l:filetype in split(getbufvar(a:buffer, '&filetype'), '\.')
- if index(l:prettier_parsers, l:filetype) > -1
- let l:parser = l:filetype
+ if has_key(l:prettier_parsers, l:filetype)
+ let l:parser = l:prettier_parsers[l:filetype]
break
endif
endfor
@@ -51,6 +74,17 @@ function! ale#fixers#prettier#ApplyFixForVersion(buffer, version_output) abort
let l:options = (!empty(l:options) ? l:options . ' ' : '') . '--parser ' . l:parser
endif
+ " Special error handling needed for prettier_d
+ if l:executable =~# 'prettier_d$'
+ return {
+ \ 'command': ale#path#BufferCdString(a:buffer)
+ \ . ale#Escape(l:executable)
+ \ . (!empty(l:options) ? ' ' . l:options : '')
+ \ . ' --stdin-filepath %s --stdin',
+ \ 'process_with': 'ale#fixers#prettier#ProcessPrettierDOutput',
+ \}
+ endif
+
" 1.4.0 is the first version with --stdin-filepath
if ale#semver#GTE(l:version, [1, 4, 0])
return {
diff --git a/autoload/ale/fixers/puppetlint.vim b/autoload/ale/fixers/puppetlint.vim
index 81f34e89..bf36e486 100644
--- a/autoload/ale/fixers/puppetlint.vim
+++ b/autoload/ale/fixers/puppetlint.vim
@@ -4,6 +4,7 @@
if !exists('g:ale_puppet_puppetlint_executable')
let g:ale_puppet_puppetlint_executable = 'puppet-lint'
endif
+
if !exists('g:ale_puppet_puppetlint_options')
let g:ale_puppet_puppetlint_options = ''
endif
diff --git a/autoload/ale/fixers/rubocop.vim b/autoload/ale/fixers/rubocop.vim
index 35569b19..33ba6887 100644
--- a/autoload/ale/fixers/rubocop.vim
+++ b/autoload/ale/fixers/rubocop.vim
@@ -1,16 +1,15 @@
+call ale#Set('ruby_rubocop_options', '')
+call ale#Set('ruby_rubocop_executable', 'rubocop')
+
function! ale#fixers#rubocop#GetCommand(buffer) abort
- let l:executable = ale#handlers#rubocop#GetExecutable(a:buffer)
- let l:exec_args = l:executable =~? 'bundle$'
- \ ? ' exec rubocop'
- \ : ''
+ let l:executable = ale#Var(a:buffer, 'ruby_rubocop_executable')
let l:config = ale#path#FindNearestFile(a:buffer, '.rubocop.yml')
let l:options = ale#Var(a:buffer, 'ruby_rubocop_options')
- return ale#Escape(l:executable) . l:exec_args
+ return ale#handlers#ruby#EscapeExecutable(l:executable, 'rubocop')
\ . (!empty(l:config) ? ' --config ' . ale#Escape(l:config) : '')
\ . (!empty(l:options) ? ' ' . l:options : '')
- \ . ' --auto-correct %t'
-
+ \ . ' --auto-correct --force-exclusion %t'
endfunction
function! ale#fixers#rubocop#Fix(buffer) abort
diff --git a/autoload/ale/fixers/scalafmt.vim b/autoload/ale/fixers/scalafmt.vim
index 07d28275..dd0e7745 100644
--- a/autoload/ale/fixers/scalafmt.vim
+++ b/autoload/ale/fixers/scalafmt.vim
@@ -15,7 +15,6 @@ function! ale#fixers#scalafmt#GetCommand(buffer) abort
return ale#Escape(l:executable) . l:exec_args
\ . (empty(l:options) ? '' : ' ' . l:options)
\ . ' %t'
-
endfunction
function! ale#fixers#scalafmt#Fix(buffer) abort
diff --git a/autoload/ale/fixers/shfmt.vim b/autoload/ale/fixers/shfmt.vim
index 882cf3a4..06e8da57 100644
--- a/autoload/ale/fixers/shfmt.vim
+++ b/autoload/ale/fixers/shfmt.vim
@@ -5,13 +5,27 @@ scriptencoding utf-8
call ale#Set('sh_shfmt_executable', 'shfmt')
call ale#Set('sh_shfmt_options', '')
+function! s:DefaultOption(buffer) abort
+ if getbufvar(a:buffer, '&expandtab') == 0
+ " Tab is used by default
+ return ''
+ endif
+
+ let l:tabsize = getbufvar(a:buffer, '&shiftwidth')
+
+ if l:tabsize == 0
+ let l:tabsize = getbufvar(a:buffer, '&tabstop')
+ endif
+
+ return ' -i ' . l:tabsize
+endfunction
+
function! ale#fixers#shfmt#Fix(buffer) abort
let l:executable = ale#Var(a:buffer, 'sh_shfmt_executable')
let l:options = ale#Var(a:buffer, 'sh_shfmt_options')
return {
\ 'command': ale#Escape(l:executable)
- \ . (empty(l:options) ? '' : ' ' . l:options)
+ \ . (empty(l:options) ? s:DefaultOption(a:buffer) : ' ' . l:options)
\}
-
endfunction
diff --git a/autoload/ale/fixers/sqlfmt.vim b/autoload/ale/fixers/sqlfmt.vim
new file mode 100644
index 00000000..c88a8ec2
--- /dev/null
+++ b/autoload/ale/fixers/sqlfmt.vim
@@ -0,0 +1,13 @@
+call ale#Set('sql_sqlfmt_executable', 'sqlfmt')
+call ale#Set('sql_sqlfmt_options', '')
+
+function! ale#fixers#sqlfmt#Fix(buffer) abort
+ let l:executable = ale#Var(a:buffer, 'sql_sqlfmt_executable')
+ let l:options = ale#Var(a:buffer, 'sql_sqlfmt_options')
+
+ return {
+ \ 'command': ale#Escape(l:executable)
+ \ . ' -w'
+ \ . (empty(l:options) ? '' : ' ' . l:options),
+ \}
+endfunction
diff --git a/autoload/ale/fixers/stylish_haskell.vim b/autoload/ale/fixers/stylish_haskell.vim
new file mode 100644
index 00000000..ce71c1ce
--- /dev/null
+++ b/autoload/ale/fixers/stylish_haskell.vim
@@ -0,0 +1,21 @@
+" Author: eborden <evan@evan-borden.com>
+" Description: Integration of stylish-haskell formatting with ALE.
+"
+call ale#Set('haskell_stylish_haskell_executable', 'stylish-haskell')
+
+function! ale#fixers#stylish_haskell#GetExecutable(buffer) abort
+ let l:executable = ale#Var(a:buffer, 'haskell_stylish_haskell_executable')
+
+ return ale#handlers#haskell_stack#EscapeExecutable(l:executable, 'stylish-haskell')
+endfunction
+
+function! ale#fixers#stylish_haskell#Fix(buffer) abort
+ let l:executable = ale#fixers#stylish_haskell#GetExecutable(a:buffer)
+
+ return {
+ \ 'command': l:executable
+ \ . ' --inplace'
+ \ . ' %t',
+ \ 'read_temporary_file': 1,
+ \}
+endfunction
diff --git a/autoload/ale/fixers/terraform.vim b/autoload/ale/fixers/terraform.vim
new file mode 100644
index 00000000..bc05380a
--- /dev/null
+++ b/autoload/ale/fixers/terraform.vim
@@ -0,0 +1,17 @@
+" Author: dsifford <dereksifford@gmail.com>
+" Description: Fixer for terraform and .hcl files
+
+call ale#Set('terraform_fmt_executable', 'terraform')
+call ale#Set('terraform_fmt_options', '')
+
+function! ale#fixers#terraform#Fix(buffer) abort
+ let l:executable = ale#Var(a:buffer, 'terraform_fmt_executable')
+ let l:options = ale#Var(a:buffer, 'terraform_fmt_options')
+
+ return {
+ \ 'command': ale#Escape(l:executable)
+ \ . ' fmt'
+ \ . (empty(l:options) ? '' : ' ' . l:options)
+ \ . ' -'
+ \}
+endfunction
diff --git a/autoload/ale/fixers/uncrustify.vim b/autoload/ale/fixers/uncrustify.vim
new file mode 100644
index 00000000..ffec18ef
--- /dev/null
+++ b/autoload/ale/fixers/uncrustify.vim
@@ -0,0 +1,16 @@
+" Author: Derek P Sifford <dereksifford@gmail.com>
+" Description: Fixer for C, C++, C#, ObjectiveC, D, Java, Pawn, and VALA.
+
+call ale#Set('c_uncrustify_executable', 'uncrustify')
+call ale#Set('c_uncrustify_options', '')
+
+function! ale#fixers#uncrustify#Fix(buffer) abort
+ let l:executable = ale#Var(a:buffer, 'c_uncrustify_executable')
+ let l:options = ale#Var(a:buffer, 'c_uncrustify_options')
+
+ return {
+ \ 'command': ale#Escape(l:executable)
+ \ . ' --no-backup'
+ \ . (empty(l:options) ? '' : ' ' . l:options)
+ \}
+endfunction
diff --git a/autoload/ale/fixers/xmllint.vim b/autoload/ale/fixers/xmllint.vim
new file mode 100644
index 00000000..b14ffd36
--- /dev/null
+++ b/autoload/ale/fixers/xmllint.vim
@@ -0,0 +1,29 @@
+" Author: Cyril Roelandt <tipecaml@gmail.com>
+" Description: Integration of xmllint with ALE.
+
+call ale#Set('xml_xmllint_executable', 'xmllint')
+call ale#Set('xml_xmllint_options', '')
+call ale#Set('xml_xmllint_indentsize', 2)
+
+function! ale#fixers#xmllint#Fix(buffer) abort
+ let l:executable = ale#Escape(ale#Var(a:buffer, 'xml_xmllint_executable'))
+ let l:filename = ale#Escape(bufname(a:buffer))
+ let l:command = l:executable . ' --format ' . l:filename
+
+ let l:indent = ale#Var(a:buffer, 'xml_xmllint_indentsize')
+
+ if l:indent isnot# ''
+ let l:env = ale#Env('XMLLINT_INDENT', repeat(' ', l:indent))
+ let l:command = l:env . l:command
+ endif
+
+ let l:options = ale#Var(a:buffer, 'xml_xmllint_options')
+
+ if l:options isnot# ''
+ let l:command .= ' ' . l:options
+ endif
+
+ return {
+ \ 'command': l:command
+ \}
+endfunction
diff --git a/autoload/ale/go.vim b/autoload/ale/go.vim
new file mode 100644
index 00000000..a166480a
--- /dev/null
+++ b/autoload/ale/go.vim
@@ -0,0 +1,27 @@
+" Author: Horacio Sanson https://github.com/hsanson
+" Description: Functions for integrating with Go tools
+
+" Find the nearest dir listed in GOPATH and assume it the root of the go
+" project.
+function! ale#go#FindProjectRoot(buffer) abort
+ let l:sep = has('win32') ? ';' : ':'
+
+ let l:filename = ale#path#Simplify(expand('#' . a:buffer . ':p'))
+
+ for l:name in split($GOPATH, l:sep)
+ let l:path_dir = ale#path#Simplify(l:name)
+
+ " Use the directory from GOPATH if the current filename starts with it.
+ if l:filename[: len(l:path_dir) - 1] is? l:path_dir
+ return l:path_dir
+ endif
+ endfor
+
+ let l:default_go_path = ale#path#Simplify(expand('~/go'))
+
+ if isdirectory(l:default_go_path)
+ return l:default_go_path
+ endif
+
+ return ''
+endfunction
diff --git a/autoload/ale/handlers/ccls.vim b/autoload/ale/handlers/ccls.vim
new file mode 100644
index 00000000..29dd6aed
--- /dev/null
+++ b/autoload/ale/handlers/ccls.vim
@@ -0,0 +1,17 @@
+scriptencoding utf-8
+" Author: Ye Jingchen <ye.jingchen@gmail.com>
+" Description: Utilities for ccls
+
+function! ale#handlers#ccls#GetProjectRoot(buffer) abort
+ let l:project_root = ale#path#FindNearestFile(a:buffer, '.ccls-root')
+
+ if empty(l:project_root)
+ let l:project_root = ale#path#FindNearestFile(a:buffer, 'compile_commands.json')
+ endif
+
+ if empty(l:project_root)
+ let l:project_root = ale#path#FindNearestFile(a:buffer, '.ccls')
+ endif
+
+ return !empty(l:project_root) ? fnamemodify(l:project_root, ':h') : ''
+endfunction
diff --git a/autoload/ale/handlers/elixir.vim b/autoload/ale/handlers/elixir.vim
new file mode 100644
index 00000000..2fddf8e7
--- /dev/null
+++ b/autoload/ale/handlers/elixir.vim
@@ -0,0 +1,28 @@
+" Author: Matteo Centenaro (bugant) - https://github.com/bugant
+" Author: Jon Parise <jon@indelible.org>
+" Description: Functions for working with Elixir projects
+
+" Find the root directory for an elixir project that uses mix.
+function! ale#handlers#elixir#FindMixProjectRoot(buffer) abort
+ let l:mix_file = ale#path#FindNearestFile(a:buffer, 'mix.exs')
+
+ if !empty(l:mix_file)
+ return fnamemodify(l:mix_file, ':p:h')
+ endif
+
+ return '.'
+endfunction
+
+" Similar to ale#handlers#elixir#FindMixProjectRoot but also continue the
+" search upward for a potential umbrella project root. If an umbrella root
+" does not exist, the initial project root will be returned.
+function! ale#handlers#elixir#FindMixUmbrellaRoot(buffer) abort
+ let l:app_root = ale#handlers#elixir#FindMixProjectRoot(a:buffer)
+ let l:umbrella_root = fnamemodify(l:app_root, ':h:h')
+
+ if filereadable(l:umbrella_root . '/mix.exs')
+ return l:umbrella_root
+ endif
+
+ return l:app_root
+endfunction
diff --git a/autoload/ale/handlers/eslint.vim b/autoload/ale/handlers/eslint.vim
index bc10ec21..eda033e4 100644
--- a/autoload/ale/handlers/eslint.vim
+++ b/autoload/ale/handlers/eslint.vim
@@ -99,6 +99,13 @@ function! ale#handlers#eslint#Handle(buffer, lines) abort
\}]
endif
+ if a:lines == ['Could not connect']
+ return [{
+ \ 'lnum': 1,
+ \ 'text': 'Could not connect to eslint_d. Try updating eslint_d or killing it.',
+ \}]
+ endif
+
" Matches patterns line the following:
"
" /path/to/some-filename.js:47:14: Missing trailing comma. [Warning/comma-dangle]
diff --git a/autoload/ale/handlers/gawk.vim b/autoload/ale/handlers/gawk.vim
index 942bc2b2..50bc4c45 100644
--- a/autoload/ale/handlers/gawk.vim
+++ b/autoload/ale/handlers/gawk.vim
@@ -9,9 +9,11 @@ function! ale#handlers#gawk#HandleGawkFormat(buffer, lines) abort
for l:match in ale#util#GetMatches(a:lines, l:pattern)
let l:ecode = 'E'
+
if l:match[2] is? 'warning:'
let l:ecode = 'W'
endif
+
call add(l:output, {
\ 'lnum': l:match[1] + 0,
\ 'col': 0,
diff --git a/autoload/ale/handlers/gcc.vim b/autoload/ale/handlers/gcc.vim
index 4b53652a..72d639da 100644
--- a/autoload/ale/handlers/gcc.vim
+++ b/autoload/ale/handlers/gcc.vim
@@ -5,6 +5,13 @@ scriptencoding utf-8
let s:pragma_error = '#pragma once in main file'
+" Look for lines like the following.
+"
+" <stdin>:8:5: warning: conversion lacks type at end of format [-Wformat=]
+" <stdin>:10:27: error: invalid operands to binary - (have ‘int’ and ‘char *’)
+" -:189:7: note: $/${} is unnecessary on arithmetic variables. [SC2004]
+let s:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):(\d+)?:? ([^:]+): (.+)$'
+
function! s:IsHeaderFile(filename) abort
return a:filename =~? '\v\.(h|hpp)$'
endfunction
@@ -18,16 +25,63 @@ function! s:RemoveUnicodeQuotes(text) abort
return l:text
endfunction
+" Report problems inside of header files just for gcc and clang
+function! s:ParseProblemsInHeaders(buffer, lines) abort
+ let l:output = []
+ let l:include_item = {}
+
+ for l:line in a:lines[: -2]
+ let l:include_match = matchlist(l:line, '\v^In file included from')
+
+ if !empty(l:include_item)
+ let l:pattern_match = matchlist(l:line, s:pattern)
+
+ if !empty(l:pattern_match) && l:pattern_match[1] is# '<stdin>'
+ if has_key(l:include_item, 'lnum')
+ call add(l:output, l:include_item)
+ endif
+
+ let l:include_item = {}
+
+ continue
+ endif
+
+ let l:include_item.detail .= "\n" . l:line
+ endif
+
+ if !empty(l:include_match)
+ if empty(l:include_item)
+ let l:include_item = {
+ \ 'text': 'Error found in header. See :ALEDetail',
+ \ 'detail': l:line,
+ \}
+ endif
+ endif
+
+ if !empty(l:include_item)
+ let l:stdin_match = matchlist(l:line, '\vfrom \<stdin\>:(\d+):(\d*):?$')
+
+ if !empty(l:stdin_match)
+ let l:include_item.lnum = str2nr(l:stdin_match[1])
+
+ if str2nr(l:stdin_match[2])
+ let l:include_item.col = str2nr(l:stdin_match[2])
+ endif
+ endif
+ endif
+ endfor
+
+ if !empty(l:include_item) && has_key(l:include_item, 'lnum')
+ call add(l:output, l:include_item)
+ endif
+
+ return l:output
+endfunction
+
function! ale#handlers#gcc#HandleGCCFormat(buffer, lines) abort
- " Look for lines like the following.
- "
- " <stdin>:8:5: warning: conversion lacks type at end of format [-Wformat=]
- " <stdin>:10:27: error: invalid operands to binary - (have ‘int’ and ‘char *’)
- " -:189:7: note: $/${} is unnecessary on arithmetic variables. [SC2004]
- let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):(\d+)?:? ([^:]+): (.+)$'
let l:output = []
- for l:match in ale#util#GetMatches(a:lines, l:pattern)
+ for l:match in ale#util#GetMatches(a:lines, s:pattern)
" Filter out the pragma errors
if s:IsHeaderFile(bufname(bufnr('')))
\&& l:match[5][:len(s:pragma_error) - 1] is# s:pragma_error
@@ -38,9 +92,12 @@ function! ale#handlers#gcc#HandleGCCFormat(buffer, lines) abort
" the previous error parsed in output
if l:match[4] is# 'note'
if !empty(l:output)
- let l:output[-1]['detail'] =
- \ get(l:output[-1], 'detail', '')
- \ . s:RemoveUnicodeQuotes(l:match[0]) . "\n"
+ if !has_key(l:output[-1], 'detail')
+ let l:output[-1].detail = l:output[-1].text
+ endif
+
+ let l:output[-1].detail = l:output[-1].detail . "\n"
+ \ . s:RemoveUnicodeQuotes(l:match[0])
endif
continue
@@ -67,3 +124,12 @@ function! ale#handlers#gcc#HandleGCCFormat(buffer, lines) abort
return l:output
endfunction
+
+" Handle problems with the GCC format, but report problems inside of headers.
+function! ale#handlers#gcc#HandleGCCFormatWithIncludes(buffer, lines) abort
+ let l:output = ale#handlers#gcc#HandleGCCFormat(a:buffer, a:lines)
+
+ call extend(l:output, s:ParseProblemsInHeaders(a:buffer, a:lines))
+
+ return l:output
+endfunction
diff --git a/autoload/ale/handlers/go.vim b/autoload/ale/handlers/go.vim
index 224df664..f17cd862 100644
--- a/autoload/ale/handlers/go.vim
+++ b/autoload/ale/handlers/go.vim
@@ -21,5 +21,6 @@ function! ale#handlers#go#Handler(buffer, lines) abort
\ 'type': 'E',
\})
endfor
+
return l:output
endfunction
diff --git a/autoload/ale/handlers/haskell.vim b/autoload/ale/handlers/haskell.vim
index 9223b650..9e495b36 100644
--- a/autoload/ale/handlers/haskell.vim
+++ b/autoload/ale/handlers/haskell.vim
@@ -1,5 +1,15 @@
" Author: w0rp <devw0rp@gmail.com>
" Description: Error handling for the format GHC outputs.
+"
+function! ale#handlers#haskell#GetStackExecutable(bufnr) abort
+ if ale#path#FindNearestFile(a:bufnr, 'stack.yaml') isnot# ''
+ return 'stack'
+ endif
+
+ " if there is no stack.yaml file, we don't use stack even if it exists,
+ " so we return '', because executable('') apparently always fails
+ return ''
+endfunction
" Remember the directory used for temporary files for Vim.
let s:temp_dir = fnamemodify(ale#util#Tempname(), ':h')
diff --git a/autoload/ale/handlers/haskell_stack.vim b/autoload/ale/handlers/haskell_stack.vim
new file mode 100644
index 00000000..108301a9
--- /dev/null
+++ b/autoload/ale/handlers/haskell_stack.vim
@@ -0,0 +1,7 @@
+function! ale#handlers#haskell_stack#EscapeExecutable(executable, stack_exec) abort
+ let l:exec_args = a:executable =~? 'stack$'
+ \ ? ' exec ' . ale#Escape(a:stack_exec) . ' --'
+ \ : ''
+
+ return ale#Escape(a:executable) . l:exec_args
+endfunction
diff --git a/autoload/ale/handlers/hlint.vim b/autoload/ale/handlers/hlint.vim
new file mode 100644
index 00000000..b9a8c322
--- /dev/null
+++ b/autoload/ale/handlers/hlint.vim
@@ -0,0 +1,8 @@
+call ale#Set('haskell_hlint_executable', 'hlint')
+call ale#Set('haskell_hlint_options', get(g:, 'hlint_options', ''))
+
+function! ale#handlers#hlint#GetExecutable(buffer) abort
+ let l:executable = ale#Var(a:buffer, 'haskell_hlint_executable')
+
+ return ale#handlers#haskell_stack#EscapeExecutable(l:executable, 'hlint')
+endfunction
diff --git a/autoload/ale/handlers/ols.vim b/autoload/ale/handlers/ols.vim
index 1dda7f92..74130a26 100644
--- a/autoload/ale/handlers/ols.vim
+++ b/autoload/ale/handlers/ols.vim
@@ -3,6 +3,7 @@
function! ale#handlers#ols#GetExecutable(buffer) abort
let l:ols_setting = ale#handlers#ols#GetLanguage(a:buffer) . '_ols'
+
return ale#node#FindExecutable(a:buffer, l:ols_setting, [
\ 'node_modules/.bin/ocaml-language-server',
\])
diff --git a/autoload/ale/handlers/pony.vim b/autoload/ale/handlers/pony.vim
index 0ac18e76..ea84ac4b 100644
--- a/autoload/ale/handlers/pony.vim
+++ b/autoload/ale/handlers/pony.vim
@@ -14,7 +14,6 @@ endfunction
function! ale#handlers#pony#HandlePonycFormat(buffer, lines) abort
" Look for lines like the following.
" /home/code/pony/classes/Wombat.pony:22:30: can't lookup private fields from outside the type
-
let l:pattern = '\v^([^:]+):(\d+):(\d+)?:? (.+)$'
let l:output = []
diff --git a/autoload/ale/handlers/redpen.vim b/autoload/ale/handlers/redpen.vim
index c136789c..84e331ed 100644
--- a/autoload/ale/handlers/redpen.vim
+++ b/autoload/ale/handlers/redpen.vim
@@ -6,15 +6,18 @@ function! ale#handlers#redpen#HandleRedpenOutput(buffer, lines) abort
" element.
let l:res = json_decode(join(a:lines))[0]
let l:output = []
+
for l:err in l:res.errors
let l:item = {
\ 'text': l:err.message,
\ 'type': 'W',
\ 'code': l:err.validator,
\}
+
if has_key(l:err, 'startPosition')
let l:item.lnum = l:err.startPosition.lineNum
let l:item.col = l:err.startPosition.offset + 1
+
if has_key(l:err, 'endPosition')
let l:item.end_lnum = l:err.endPosition.lineNum
let l:item.end_col = l:err.endPosition.offset
@@ -28,29 +31,35 @@ function! ale#handlers#redpen#HandleRedpenOutput(buffer, lines) abort
" Adjust column number for multibyte string
let l:line = getline(l:item.lnum)
+
if l:line is# ''
let l:line = l:err.sentence
endif
+
let l:line = split(l:line, '\zs')
if l:item.col >= 2
let l:col = 0
+
for l:strlen in map(l:line[0:(l:item.col - 2)], 'strlen(v:val)')
let l:col = l:col + l:strlen
endfor
+
let l:item.col = l:col + 1
endif
if has_key(l:item, 'end_col')
let l:col = 0
+
for l:strlen in map(l:line[0:(l:item.end_col - 1)], 'strlen(v:val)')
let l:col = l:col + l:strlen
endfor
+
let l:item.end_col = l:col
endif
call add(l:output, l:item)
endfor
+
return l:output
endfunction
-
diff --git a/autoload/ale/handlers/rubocop.vim b/autoload/ale/handlers/rubocop.vim
deleted file mode 100644
index f6367cf5..00000000
--- a/autoload/ale/handlers/rubocop.vim
+++ /dev/null
@@ -1,6 +0,0 @@
-call ale#Set('ruby_rubocop_options', '')
-call ale#Set('ruby_rubocop_executable', 'rubocop')
-
-function! ale#handlers#rubocop#GetExecutable(buffer) abort
- return ale#Var(a:buffer, 'ruby_rubocop_executable')
-endfunction
diff --git a/autoload/ale/handlers/ruby.vim b/autoload/ale/handlers/ruby.vim
index 555c13b1..c28b8b75 100644
--- a/autoload/ale/handlers/ruby.vim
+++ b/autoload/ale/handlers/ruby.vim
@@ -13,8 +13,10 @@ function! s:HandleSyntaxError(buffer, lines) abort
for l:line in a:lines
let l:match = matchlist(l:line, l:pattern)
+
if len(l:match) == 0
let l:match = matchlist(l:line, l:column)
+
if len(l:match) != 0
let l:output[len(l:output) - 1]['col'] = len(l:match[1])
endif
@@ -35,3 +37,10 @@ function! ale#handlers#ruby#HandleSyntaxErrors(buffer, lines) abort
return s:HandleSyntaxError(a:buffer, a:lines)
endfunction
+function! ale#handlers#ruby#EscapeExecutable(executable, bundle_exec) abort
+ let l:exec_args = a:executable =~? 'bundle'
+ \ ? ' exec ' . a:bundle_exec
+ \ : ''
+
+ return ale#Escape(a:executable) . l:exec_args
+endfunction
diff --git a/autoload/ale/handlers/rust.vim b/autoload/ale/handlers/rust.vim
index 537bc731..c6a4b670 100644
--- a/autoload/ale/handlers/rust.vim
+++ b/autoload/ale/handlers/rust.vim
@@ -7,6 +7,10 @@ if !exists('g:ale_rust_ignore_error_codes')
let g:ale_rust_ignore_error_codes = []
endif
+if !exists('g:ale_rust_ignore_secondary_spans')
+ let g:ale_rust_ignore_secondary_spans = 0
+endif
+
function! s:FindSpan(buffer, span) abort
if ale#path#IsBufferPath(a:buffer, a:span.file_name) || a:span.file_name is# '<anon>'
return a:span
@@ -32,7 +36,7 @@ function! ale#handlers#rust#HandleRustErrors(buffer, lines) abort
let l:error = json_decode(l:errorline)
- if has_key(l:error, 'message') && type(l:error.message) == type({})
+ if has_key(l:error, 'message') && type(l:error.message) is v:t_dict
let l:error = l:error.message
endif
@@ -47,6 +51,10 @@ function! ale#handlers#rust#HandleRustErrors(buffer, lines) abort
for l:root_span in l:error.spans
let l:span = s:FindSpan(a:buffer, l:root_span)
+ if ale#Var(a:buffer, 'rust_ignore_secondary_spans') && !get(l:span, 'is_primary', 1)
+ continue
+ endif
+
if !empty(l:span)
call add(l:output, {
\ 'lnum': l:span.line_start,
diff --git a/autoload/ale/handlers/sml.vim b/autoload/ale/handlers/sml.vim
index 377eade5..92c5f83b 100644
--- a/autoload/ale/handlers/sml.vim
+++ b/autoload/ale/handlers/sml.vim
@@ -11,8 +11,10 @@ function! ale#handlers#sml#GetCmFile(buffer) abort
let l:as_list = 1
let l:cmfile = ''
+
for l:path in ale#path#Upwards(expand('#' . a:buffer . ':p:h'))
let l:results = glob(l:path . '/' . l:pattern, 0, l:as_list)
+
if len(l:results) > 0
" If there is more than one CM file, we take the first one
" See :help ale-sml-smlnj for how to configure this.
@@ -46,6 +48,7 @@ endfunction
function! ale#handlers#sml#GetExecutableSmlnjCm(buffer) abort
return s:GetExecutable(a:buffer, 'smlnj-cm')
endfunction
+
function! ale#handlers#sml#GetExecutableSmlnjFile(buffer) abort
return s:GetExecutable(a:buffer, 'smlnj-file')
endfunction
@@ -53,7 +56,6 @@ endfunction
function! ale#handlers#sml#Handle(buffer, lines) abort
" Try to match basic sml errors
" TODO(jez) We can get better errorfmt strings from Syntastic
-
let l:out = []
let l:pattern = '^.*\:\([0-9\.]\+\)\ \(\w\+\)\:\ \(.*\)'
let l:pattern2 = '^.*\:\([0-9]\+\)\.\?\([0-9]\+\).* \(\(Warning\|Error\): .*\)'
@@ -83,7 +85,6 @@ function! ale#handlers#sml#Handle(buffer, lines) abort
\})
continue
endif
-
endfor
return l:out
diff --git a/autoload/ale/handlers/vale.vim b/autoload/ale/handlers/vale.vim
index 9dc0872f..2da72fc7 100644
--- a/autoload/ale/handlers/vale.vim
+++ b/autoload/ale/handlers/vale.vim
@@ -23,6 +23,7 @@ function! ale#handlers#vale#Handle(buffer, lines) abort
endif
let l:output = []
+
for l:error in l:errors[keys(l:errors)[0]]
call add(l:output, {
\ 'lnum': l:error['Line'],
diff --git a/autoload/ale/hover.vim b/autoload/ale/hover.vim
index 5e97e16e..69db276e 100644
--- a/autoload/ale/hover.vim
+++ b/autoload/ale/hover.vim
@@ -63,19 +63,19 @@ function! ale#hover#HandleLSPResponse(conn_id, response) abort
let l:result = l:result.contents
- if type(l:result) is type('')
+ if type(l:result) is v:t_string
" The result can be just a string.
let l:result = [l:result]
endif
- if type(l:result) is type({})
+ if type(l:result) is v:t_dict
" If the result is an object, then it's markup content.
let l:result = [l:result.value]
endif
- if type(l:result) is type([])
+ if type(l:result) is v:t_list
" Replace objects with text values.
- call map(l:result, 'type(v:val) is type('''') ? v:val : v:val.value')
+ call map(l:result, 'type(v:val) is v:t_string ? v:val : v:val.value')
let l:str = join(l:result, "\n")
let l:str = substitute(l:str, '^\s*\(.\{-}\)\s*$', '\1', '')
@@ -92,7 +92,44 @@ function! ale#hover#HandleLSPResponse(conn_id, response) abort
endif
endfunction
-function! s:ShowDetails(linter, buffer, line, column, opt) abort
+function! s:OnReady(linter, lsp_details, line, column, opt, ...) abort
+ let l:buffer = a:lsp_details.buffer
+ let l:id = a:lsp_details.connection_id
+
+ let l:Callback = a:linter.lsp is# 'tsserver'
+ \ ? function('ale#hover#HandleTSServerResponse')
+ \ : function('ale#hover#HandleLSPResponse')
+ call ale#lsp#RegisterCallback(l:id, l:Callback)
+
+ if a:linter.lsp is# 'tsserver'
+ let l:column = a:column
+
+ let l:message = ale#lsp#tsserver_message#Quickinfo(
+ \ l:buffer,
+ \ a:line,
+ \ l:column
+ \)
+ else
+ " Send a message saying the buffer has changed first, or the
+ " hover position probably won't make sense.
+ call ale#lsp#NotifyForChanges(l:id, l:buffer)
+
+ let l:column = min([a:column, len(getbufline(l:buffer, a:line)[0])])
+
+ let l:message = ale#lsp#message#Hover(l:buffer, a:line, l:column)
+ endif
+
+ let l:request_id = ale#lsp#Send(l:id, l:message)
+
+ let s:hover_map[l:request_id] = {
+ \ 'buffer': l:buffer,
+ \ 'line': a:line,
+ \ 'column': l:column,
+ \ 'hover_from_balloonexpr': get(a:opt, 'called_from_balloonexpr', 0),
+ \}
+endfunction
+
+function! s:ShowDetails(linter, buffer, line, column, opt, ...) abort
let l:lsp_details = ale#lsp_linter#StartLSP(a:buffer, a:linter)
if empty(l:lsp_details)
@@ -100,44 +137,10 @@ function! s:ShowDetails(linter, buffer, line, column, opt) abort
endif
let l:id = l:lsp_details.connection_id
- let l:root = l:lsp_details.project_root
- let l:language_id = l:lsp_details.language_id
-
- function! OnReady(...) abort closure
- let l:Callback = a:linter.lsp is# 'tsserver'
- \ ? function('ale#hover#HandleTSServerResponse')
- \ : function('ale#hover#HandleLSPResponse')
- call ale#lsp#RegisterCallback(l:id, l:Callback)
-
- if a:linter.lsp is# 'tsserver'
- let l:column = a:column
-
- let l:message = ale#lsp#tsserver_message#Quickinfo(
- \ a:buffer,
- \ a:line,
- \ l:column
- \)
- else
- " Send a message saying the buffer has changed first, or the
- " hover position probably won't make sense.
- call ale#lsp#NotifyForChanges(l:id, l:root, a:buffer)
-
- let l:column = min([a:column, len(getbufline(a:buffer, a:line)[0])])
-
- let l:message = ale#lsp#message#Hover(a:buffer, a:line, l:column)
- endif
-
- let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root)
-
- let s:hover_map[l:request_id] = {
- \ 'buffer': a:buffer,
- \ 'line': a:line,
- \ 'column': l:column,
- \ 'hover_from_balloonexpr': get(a:opt, 'called_from_balloonexpr', 0),
- \}
- endfunction
- call ale#lsp#WaitForCapability(l:id, l:root, 'hover', function('OnReady'))
+ call ale#lsp#WaitForCapability(l:id, 'hover', function('s:OnReady', [
+ \ a:linter, l:lsp_details, a:line, a:column, a:opt
+ \]))
endfunction
" Obtain Hover information for the specified position
diff --git a/autoload/ale/java.vim b/autoload/ale/java.vim
new file mode 100644
index 00000000..b7fd10bd
--- /dev/null
+++ b/autoload/ale/java.vim
@@ -0,0 +1,20 @@
+" Author: Horacio Sanson https://github.com/hsanson
+" Description: Functions for integrating with Java tools
+
+" Find the nearest dir contining a gradle or pom file and asume it
+" the root of a java app.
+function! ale#java#FindProjectRoot(buffer) abort
+ let l:gradle_root = ale#gradle#FindProjectRoot(a:buffer)
+
+ if !empty(l:gradle_root)
+ return l:gradle_root
+ endif
+
+ let l:maven_pom_file = ale#path#FindNearestFile(a:buffer, 'pom.xml')
+
+ if !empty(l:maven_pom_file)
+ return fnamemodify(l:maven_pom_file, ':h')
+ endif
+
+ return ''
+endfunction
diff --git a/autoload/ale/job.vim b/autoload/ale/job.vim
index e0266cba..0117c7dd 100644
--- a/autoload/ale/job.vim
+++ b/autoload/ale/job.vim
@@ -249,6 +249,11 @@ function! ale#job#Start(command, options) abort
let l:job_options.exit_cb = function('s:VimExitCallback')
endif
+ " Use non-blocking writes for Vim versions that support the option.
+ if has('patch-8.1.350')
+ let l:job_options.noblock = 1
+ endif
+
" Vim 8 will read the stdin from the file's buffer.
let l:job_info.job = job_start(a:command, l:job_options)
let l:job_id = ale#job#ParseVim8ProcessID(string(l:job_info.job))
@@ -278,11 +283,13 @@ function! ale#job#IsRunning(job_id) abort
try
" In NeoVim, if the job isn't running, jobpid() will throw.
call jobpid(a:job_id)
+
return 1
catch
endtry
elseif has_key(s:job_map, a:job_id)
let l:job = s:job_map[a:job_id].job
+
return job_status(l:job) is# 'run'
endif
diff --git a/autoload/ale/julia.vim b/autoload/ale/julia.vim
new file mode 100644
index 00000000..18dd9ad7
--- /dev/null
+++ b/autoload/ale/julia.vim
@@ -0,0 +1,19 @@
+" Author: Bartolomeo Stellato bartolomeo.stellato@gmail.com
+" Description: Functions for integrating with Julia tools
+
+" Find the nearest dir containing a julia project
+let s:__ale_julia_project_filenames = ['REQUIRE', 'Manifest.toml', 'Project.toml']
+
+function! ale#julia#FindProjectRoot(buffer) abort
+ for l:project_filename in s:__ale_julia_project_filenames
+ let l:full_path = ale#path#FindNearestFile(a:buffer, l:project_filename)
+
+ if !empty(l:full_path)
+ let l:path = fnamemodify(l:full_path, ':p:h')
+
+ return l:path
+ endif
+ endfor
+
+ return ''
+endfunction
diff --git a/autoload/ale/linter.vim b/autoload/ale/linter.vim
index aa602f7e..1cbc9ffe 100644
--- a/autoload/ale/linter.vim
+++ b/autoload/ale/linter.vim
@@ -16,6 +16,7 @@ let s:default_ale_linter_aliases = {
\ 'systemverilog': 'verilog',
\ 'verilog_systemverilog': ['verilog_systemverilog', 'verilog'],
\ 'vimwiki': 'markdown',
+\ 'vue': ['vue', 'javascript'],
\ 'zsh': 'sh',
\}
@@ -26,17 +27,22 @@ let s:default_ale_linter_aliases = {
"
" Only cargo is enabled for Rust by default.
" rpmlint is disabled by default because it can result in code execution.
+" hhast is disabled by default because it executes code in the project root.
"
" NOTE: Update the g:ale_linters documentation when modifying this.
let s:default_ale_linters = {
\ 'csh': ['shell'],
+\ 'elixir': ['credo', 'dialyxir', 'dogma', 'elixir-ls'],
\ 'go': ['gofmt', 'golint', 'go vet'],
+\ 'hack': ['hack'],
\ 'help': [],
\ 'perl': ['perlcritic'],
+\ 'perl6': [],
\ 'python': ['flake8', 'mypy', 'pylint'],
\ 'rust': ['cargo'],
\ 'spec': [],
\ 'text': [],
+\ 'vue': ['eslint', 'vls'],
\ 'zsh': ['shell'],
\}
@@ -51,17 +57,17 @@ endfunction
" Do not call this function.
function! ale#linter#GetLintersLoaded() abort
" This command will throw from the sandbox.
- let &equalprg=&equalprg
+ let &l:equalprg=&l:equalprg
return s:linters
endfunction
function! s:IsCallback(value) abort
- return type(a:value) == type('') || type(a:value) == type(function('type'))
+ return type(a:value) is v:t_string || type(a:value) is v:t_func
endfunction
function! s:IsBoolean(value) abort
- return type(a:value) == type(0) && (a:value == 0 || a:value == 1)
+ return type(a:value) is v:t_number && (a:value == 0 || a:value == 1)
endfunction
function! s:LanguageGetter(buffer) dict abort
@@ -69,7 +75,7 @@ function! s:LanguageGetter(buffer) dict abort
endfunction
function! ale#linter#PreProcess(filetype, linter) abort
- if type(a:linter) != type({})
+ if type(a:linter) isnot v:t_dict
throw 'The linter object must be a Dictionary'
endif
@@ -79,7 +85,7 @@ function! ale#linter#PreProcess(filetype, linter) abort
\ 'lsp': get(a:linter, 'lsp', ''),
\}
- if type(l:obj.name) != type('')
+ if type(l:obj.name) isnot v:t_string
throw '`name` must be defined to name the linter'
endif
@@ -97,7 +103,7 @@ function! ale#linter#PreProcess(filetype, linter) abort
endif
if index(['', 'socket', 'stdio', 'tsserver'], l:obj.lsp) < 0
- throw '`lsp` must be either `''lsp''` or `''tsserver''` if defined'
+ throw '`lsp` must be either `''lsp''`, `''stdio''`, `''socket''` or `''tsserver''` if defined'
endif
if !l:needs_executable
@@ -114,7 +120,7 @@ function! ale#linter#PreProcess(filetype, linter) abort
elseif has_key(a:linter, 'executable')
let l:obj.executable = a:linter.executable
- if type(l:obj.executable) != type('')
+ if type(l:obj.executable) isnot v:t_string
throw '`executable` must be a string if defined'
endif
else
@@ -130,7 +136,7 @@ function! ale#linter#PreProcess(filetype, linter) abort
elseif has_key(a:linter, 'command_chain')
let l:obj.command_chain = a:linter.command_chain
- if type(l:obj.command_chain) != type([])
+ if type(l:obj.command_chain) isnot v:t_list
throw '`command_chain` must be a List'
endif
@@ -148,7 +154,7 @@ function! ale#linter#PreProcess(filetype, linter) abort
endif
if has_key(l:link, 'output_stream')
- if type(l:link.output_stream) != type('')
+ if type(l:link.output_stream) isnot v:t_string
\|| index(['stdout', 'stderr', 'both'], l:link.output_stream) < 0
throw l:err_prefix . '`output_stream` flag must be '
\ . "'stdout', 'stderr', or 'both'"
@@ -170,7 +176,7 @@ function! ale#linter#PreProcess(filetype, linter) abort
elseif has_key(a:linter, 'command')
let l:obj.command = a:linter.command
- if type(l:obj.command) != type('')
+ if type(l:obj.command) isnot v:t_string
throw '`command` must be a string if defined'
endif
else
@@ -217,7 +223,7 @@ function! ale#linter#PreProcess(filetype, linter) abort
" Default to using the filetype as the language.
let l:obj.language = get(a:linter, 'language', a:filetype)
- if type(l:obj.language) != type('')
+ if type(l:obj.language) isnot v:t_string
throw '`language` must be a string'
endif
@@ -253,11 +259,29 @@ function! ale#linter#PreProcess(filetype, linter) abort
elseif has_key(a:linter, 'initialization_options')
let l:obj.initialization_options = a:linter.initialization_options
endif
+
+ if has_key(a:linter, 'lsp_config_callback')
+ if has_key(a:linter, 'lsp_config')
+ throw 'Only one of `lsp_config` or `lsp_config_callback` should be set'
+ endif
+
+ let l:obj.lsp_config_callback = a:linter.lsp_config_callback
+
+ if !s:IsCallback(l:obj.lsp_config_callback)
+ throw '`lsp_config_callback` must be a callback if defined'
+ endif
+ elseif has_key(a:linter, 'lsp_config')
+ if type(a:linter.lsp_config) isnot v:t_dict
+ throw '`lsp_config` must be a Dictionary'
+ endif
+
+ let l:obj.lsp_config = a:linter.lsp_config
+ endif
endif
let l:obj.output_stream = get(a:linter, 'output_stream', 'stdout')
- if type(l:obj.output_stream) != type('')
+ if type(l:obj.output_stream) isnot v:t_string
\|| index(['stdout', 'stderr', 'both'], l:obj.output_stream) < 0
throw "`output_stream` must be 'stdout', 'stderr', or 'both'"
endif
@@ -283,8 +307,8 @@ function! ale#linter#PreProcess(filetype, linter) abort
let l:obj.aliases = get(a:linter, 'aliases', [])
- if type(l:obj.aliases) != type([])
- \|| len(filter(copy(l:obj.aliases), 'type(v:val) != type('''')')) > 0
+ if type(l:obj.aliases) isnot v:t_list
+ \|| len(filter(copy(l:obj.aliases), 'type(v:val) isnot v:t_string')) > 0
throw '`aliases` must be a List of String values'
endif
@@ -293,7 +317,7 @@ endfunction
function! ale#linter#Define(filetype, linter) abort
" This command will throw from the sandbox.
- let &equalprg=&equalprg
+ let &l:equalprg=&l:equalprg
if !has_key(s:linters, a:filetype)
let s:linters[a:filetype] = []
@@ -335,8 +359,9 @@ endfunction
function! s:GetAliasedFiletype(original_filetype) abort
let l:buffer_aliases = get(b:, 'ale_linter_aliases', {})
- " b:ale_linter_aliases can be set to a List.
- if type(l:buffer_aliases) is type([])
+ " b:ale_linter_aliases can be set to a List or String.
+ if type(l:buffer_aliases) is v:t_list
+ \|| type(l:buffer_aliases) is v:t_string
return l:buffer_aliases
endif
@@ -360,7 +385,7 @@ endfunction
function! ale#linter#ResolveFiletype(original_filetype) abort
let l:filetype = s:GetAliasedFiletype(a:original_filetype)
- if type(l:filetype) != type([])
+ if type(l:filetype) isnot v:t_list
return [l:filetype]
endif
@@ -376,7 +401,7 @@ function! s:GetLinterNames(original_filetype) abort
endif
" b:ale_linters can be set to a List.
- if type(l:buffer_ale_linters) is type([])
+ if type(l:buffer_ale_linters) is v:t_list
return l:buffer_ale_linters
endif
@@ -414,9 +439,9 @@ function! ale#linter#Get(original_filetypes) abort
let l:all_linters = ale#linter#GetAll(l:filetype)
let l:filetype_linters = []
- if type(l:linter_names) == type('') && l:linter_names is# 'all'
+ if type(l:linter_names) is v:t_string && l:linter_names is# 'all'
let l:filetype_linters = l:all_linters
- elseif type(l:linter_names) == type([])
+ elseif type(l:linter_names) is v:t_list
" Select only the linters we or the user has specified.
for l:linter in l:all_linters
let l:name_list = [l:linter.name] + l:linter.aliases
diff --git a/autoload/ale/list.vim b/autoload/ale/list.vim
index 35304a09..3417575c 100644
--- a/autoload/ale/list.vim
+++ b/autoload/ale/list.vim
@@ -25,6 +25,7 @@ function! ale#list#IsQuickfixOpen() abort
return 1
endif
endfor
+
return 0
endfunction
@@ -112,9 +113,11 @@ function! s:SetListsImpl(timer_id, buffer, loclist) abort
" open windows vertically instead of default horizontally
let l:open_type = ''
+
if ale#Var(a:buffer, 'list_vertical') == 1
let l:open_type = 'vert '
endif
+
if g:ale_set_quickfix
if !ale#list#IsQuickfixOpen()
silent! execute l:open_type . 'copen ' . str2nr(ale#Var(a:buffer, 'list_window_size'))
diff --git a/autoload/ale/loclist_jumping.vim b/autoload/ale/loclist_jumping.vim
index 7ed9e6ba..fd5ff922 100644
--- a/autoload/ale/loclist_jumping.vim
+++ b/autoload/ale/loclist_jumping.vim
@@ -66,6 +66,7 @@ function! ale#loclist_jumping#Jump(direction, wrap) abort
let l:nearest = ale#loclist_jumping#FindNearest(a:direction, a:wrap)
if !empty(l:nearest)
+ normal! m`
call cursor(l:nearest)
endif
endfunction
@@ -82,6 +83,7 @@ function! ale#loclist_jumping#JumpToIndex(index) abort
let l:item = l:loclist[a:index]
if !empty(l:item)
+ normal! m`
call cursor([l:item.lnum, l:item.col])
endif
endfunction
diff --git a/autoload/ale/lsp.vim b/autoload/ale/lsp.vim
index 312319ab..f55096c2 100644
--- a/autoload/ale/lsp.vim
+++ b/autoload/ale/lsp.vim
@@ -1,62 +1,70 @@
" Author: w0rp <devw0rp@gmail.com>
" Description: Language Server Protocol client code
-" A List of connections, used for tracking servers which have been connected
-" to, and programs which are run.
-let s:connections = get(s:, 'connections', [])
+" A Dictionary for tracking connections.
+let s:connections = get(s:, 'connections', {})
let g:ale_lsp_next_message_id = 1
-" Exposed only so tests can get at it.
-" Do not call this function basically anywhere.
-function! ale#lsp#NewConnection(initialization_options) abort
- " id: The job ID as a Number, or the server address as a string.
- " data: The message data received so far.
- " executable: An executable only set for program connections.
- " open_documents: A Dictionary mapping buffers to b:changedtick, keeping
- " track of when documents were opened, and when we last changed them.
- " callback_list: A list of callbacks for handling LSP responses.
- " initialization_options: Options to send to the server.
- " capabilities: Features the server supports.
- let l:conn = {
- \ 'is_tsserver': 0,
- \ 'id': '',
- \ 'data': '',
- \ 'projects': {},
- \ 'open_documents': {},
- \ 'callback_list': [],
- \ 'initialization_options': a:initialization_options,
- \ 'capabilities': {
- \ 'hover': 0,
- \ 'references': 0,
- \ 'completion': 0,
- \ 'completion_trigger_characters': [],
- \ 'definition': 0,
- \ },
- \}
-
- call add(s:connections, l:conn)
+" Given an id, which can be an executable or address, and a project path,
+" create a new connection if needed. Return a unique ID for the connection.
+function! ale#lsp#Register(executable_or_address, project, init_options) abort
+ let l:conn_id = a:executable_or_address . ':' . a:project
+
+ if !has_key(s:connections, l:conn_id)
+ " is_tsserver: 1 if the connection is for tsserver.
+ " data: The message data received so far.
+ " root: The project root.
+ " open_documents: A Dictionary mapping buffers to b:changedtick, keeping
+ " track of when documents were opened, and when we last changed them.
+ " initialized: 0 if the connection is ready, 1 otherwise.
+ " init_request_id: The ID for the init request.
+ " init_options: Options to send to the server.
+ " config: Configuration settings to send to the server.
+ " callback_list: A list of callbacks for handling LSP responses.
+ " message_queue: Messages queued for sending to callbacks.
+ " capabilities_queue: The list of callbacks to call with capabilities.
+ " capabilities: Features the server supports.
+ let s:connections[l:conn_id] = {
+ \ 'id': l:conn_id,
+ \ 'is_tsserver': 0,
+ \ 'data': '',
+ \ 'root': a:project,
+ \ 'open_documents': {},
+ \ 'initialized': 0,
+ \ 'init_request_id': 0,
+ \ 'init_options': a:init_options,
+ \ 'config': {},
+ \ 'callback_list': [],
+ \ 'message_queue': [],
+ \ 'capabilities_queue': [],
+ \ 'capabilities': {
+ \ 'hover': 0,
+ \ 'references': 0,
+ \ 'completion': 0,
+ \ 'completion_trigger_characters': [],
+ \ 'definition': 0,
+ \ 'symbol_search': 0,
+ \ },
+ \}
+ endif
- return l:conn
+ return l:conn_id
endfunction
" Remove an LSP connection with a given ID. This is only for tests.
function! ale#lsp#RemoveConnectionWithID(id) abort
- call filter(s:connections, 'v:val.id isnot a:id')
+ if has_key(s:connections, a:id)
+ call remove(s:connections, a:id)
+ endif
endfunction
-function! s:FindConnection(key, value) abort
- for l:conn in s:connections
- if has_key(l:conn, a:key) && get(l:conn, a:key) is# a:value
- return l:conn
- endif
- endfor
-
- return {}
-endfunction
+" This is only needed for tests
+function! ale#lsp#MarkDocumentAsOpen(id, buffer) abort
+ let l:conn = get(s:connections, a:id, {})
-" Get the capabilities for a connection, or an empty Dictionary.
-function! ale#lsp#GetConnectionCapabilities(id) abort
- return get(s:FindConnection('id', a:id), 'capabilities', {})
+ if !empty(l:conn)
+ let l:conn.open_documents[a:buffer] = -1
+ endif
endfunction
function! ale#lsp#GetNextMessageID() abort
@@ -94,13 +102,14 @@ function! s:CreateTSServerMessageData(message) abort
endif
let l:data = json_encode(l:obj) . "\n"
+
return [l:is_notification ? 0 : l:obj.seq, l:data]
endfunction
" Given a List of one or two items, [method_name] or [method_name, params],
" return a List containing [message_id, message_data]
function! ale#lsp#CreateMessageData(message) abort
- if a:message[1] =~# '^ts@'
+ if a:message[1][:2] is# 'ts@'
return s:CreateTSServerMessageData(a:message)
endif
@@ -167,53 +176,10 @@ function! ale#lsp#ReadMessageData(data) abort
return [l:remainder, l:response_list]
endfunction
-function! s:FindProjectWithInitRequestID(conn, init_request_id) abort
- for l:project_root in keys(a:conn.projects)
- let l:project = a:conn.projects[l:project_root]
-
- if l:project.init_request_id == a:init_request_id
- return l:project
- endif
- endfor
-
- return {}
-endfunction
-
-function! s:MarkProjectAsInitialized(conn, project) abort
- let a:project.initialized = 1
-
- " After the server starts, send messages we had queued previously.
- for l:message_data in a:project.message_queue
- call s:SendMessageData(a:conn, l:message_data)
- endfor
-
- " Remove the messages now.
- let a:conn.message_queue = []
-
- " Call capabilities callbacks queued for the project.
- for [l:capability, l:Callback] in a:project.capabilities_queue
- if a:conn.is_tsserver || a:conn.capabilities[l:capability]
- call call(l:Callback, [a:conn.id, a:project.root])
- endif
- endfor
-
- " Clear the queued callbacks now.
- let a:project.capabilities_queue = []
-endfunction
-
-function! s:HandleInitializeResponse(conn, response) abort
- let l:request_id = a:response.request_id
- let l:project = s:FindProjectWithInitRequestID(a:conn, l:request_id)
-
- if !empty(l:project)
- call s:MarkProjectAsInitialized(a:conn, l:project)
- endif
-endfunction
-
" Update capabilities from the server, so we know which features the server
" supports.
function! s:UpdateCapabilities(conn, capabilities) abort
- if type(a:capabilities) != type({})
+ if type(a:capabilities) isnot v:t_dict
return
endif
@@ -229,10 +195,10 @@ function! s:UpdateCapabilities(conn, capabilities) abort
let a:conn.capabilities.completion = 1
endif
- if type(get(a:capabilities, 'completionProvider')) is type({})
+ if type(get(a:capabilities, 'completionProvider')) is v:t_dict
let l:chars = get(a:capabilities.completionProvider, 'triggerCharacters')
- if type(l:chars) is type([])
+ if type(l:chars) is v:t_list
let a:conn.capabilities.completion_trigger_characters = l:chars
endif
endif
@@ -240,180 +206,164 @@ function! s:UpdateCapabilities(conn, capabilities) abort
if get(a:capabilities, 'definitionProvider') is v:true
let a:conn.capabilities.definition = 1
endif
-endfunction
-function! ale#lsp#HandleOtherInitializeResponses(conn, response) abort
- let l:uninitialized_projects = []
+ if get(a:capabilities, 'workspaceSymbolProvider') is v:true
+ let a:conn.capabilities.symbol_search = 1
+ endif
+endfunction
- for [l:key, l:value] in items(a:conn.projects)
- if l:value.initialized == 0
- call add(l:uninitialized_projects, [l:key, l:value])
- endif
- endfor
+" Update a connection's configuration dictionary and notify LSP servers
+" of any changes since the last update. Returns 1 if a configuration
+" update was sent; otherwise 0 will be returned.
+function! ale#lsp#UpdateConfig(conn_id, buffer, config) abort
+ let l:conn = get(s:connections, a:conn_id, {})
- if empty(l:uninitialized_projects)
- return
+ if empty(l:conn) || a:config ==# l:conn.config " no-custom-checks
+ return 0
endif
- if get(a:response, 'method', '') is# ''
- if has_key(get(a:response, 'result', {}), 'capabilities')
- call s:UpdateCapabilities(a:conn, a:response.result.capabilities)
+ let l:conn.config = a:config
+ let l:message = ale#lsp#message#DidChangeConfiguration(a:buffer, a:config)
- for [l:dir, l:project] in l:uninitialized_projects
- call s:MarkProjectAsInitialized(a:conn, l:project)
- endfor
- endif
- elseif get(a:response, 'method', '') is# 'textDocument/publishDiagnostics'
- let l:filename = ale#path#FromURI(a:response.params.uri)
+ call ale#lsp#Send(a:conn_id, l:message)
- for [l:dir, l:project] in l:uninitialized_projects
- if l:filename[:len(l:dir) - 1] is# l:dir
- call s:MarkProjectAsInitialized(a:conn, l:project)
- endif
- endfor
- endif
+ return 1
endfunction
-function! ale#lsp#HandleMessage(conn, message) abort
- if type(a:message) != type('')
- " Ignore messages that aren't strings.
- return
+
+function! ale#lsp#HandleInitResponse(conn, response) abort
+ if get(a:response, 'method', '') is# 'initialize'
+ let a:conn.initialized = 1
+ elseif type(get(a:response, 'result')) is v:t_dict
+ \&& has_key(a:response.result, 'capabilities')
+ call s:UpdateCapabilities(a:conn, a:response.result.capabilities)
+
+ let a:conn.initialized = 1
endif
- let a:conn.data .= a:message
+ if !a:conn.initialized
+ return
+ endif
- " Parse the objects now if we can, and keep the remaining text.
- let [a:conn.data, l:response_list] = ale#lsp#ReadMessageData(a:conn.data)
+ " After the server starts, send messages we had queued previously.
+ for l:message_data in a:conn.message_queue
+ call s:SendMessageData(a:conn, l:message_data)
+ endfor
- " Call our callbacks.
- for l:response in l:response_list
- if get(l:response, 'method', '') is# 'initialize'
- call s:HandleInitializeResponse(a:conn, l:response)
- else
- call ale#lsp#HandleOtherInitializeResponses(a:conn, l:response)
+ " Remove the messages now.
+ let a:conn.message_queue = []
- " Call all of the registered handlers with the response.
- for l:Callback in a:conn.callback_list
- call ale#util#GetFunction(l:Callback)(a:conn.id, l:response)
- endfor
+ " Call capabilities callbacks queued for the project.
+ for [l:capability, l:Callback] in a:conn.capabilities_queue
+ if a:conn.capabilities[l:capability]
+ call call(l:Callback, [a:conn.id])
endif
endfor
+
+ let a:conn.capabilities_queue = []
endfunction
-function! s:HandleChannelMessage(channel_id, message) abort
- let l:address = ale#socket#GetAddress(a:channel_id)
- let l:conn = s:FindConnection('id', l:address)
+function! ale#lsp#HandleMessage(conn_id, message) abort
+ let l:conn = get(s:connections, a:conn_id, {})
- call ale#lsp#HandleMessage(l:conn, a:message)
-endfunction
+ if empty(l:conn)
+ return
+ endif
-function! s:HandleCommandMessage(job_id, message) abort
- let l:conn = s:FindConnection('id', a:job_id)
+ if type(a:message) isnot v:t_string
+ " Ignore messages that aren't strings.
+ return
+ endif
- call ale#lsp#HandleMessage(l:conn, a:message)
-endfunction
+ let l:conn.data .= a:message
-" Given a connection ID, mark it as a tsserver connection, so it will be
-" handled that way.
-function! ale#lsp#MarkConnectionAsTsserver(conn_id) abort
- let l:conn = s:FindConnection('id', a:conn_id)
+ " Parse the objects now if we can, and keep the remaining text.
+ let [l:conn.data, l:response_list] = ale#lsp#ReadMessageData(l:conn.data)
- if !empty(l:conn)
- let l:conn.is_tsserver = 1
+ " Look for initialize responses first.
+ if !l:conn.initialized
+ for l:response in l:response_list
+ call ale#lsp#HandleInitResponse(l:conn, l:response)
+ endfor
endif
-endfunction
-" Register a project for an LSP connection.
-"
-" This function will throw if the connection doesn't exist.
-function! ale#lsp#RegisterProject(conn_id, project_root) abort
- let l:conn = s:FindConnection('id', a:conn_id)
-
- " Empty strings can't be used for Dictionary keys in NeoVim, due to E713.
- " This appears to be a nonsensical bug in NeoVim.
- let l:key = empty(a:project_root) ? '<<EMPTY>>' : a:project_root
-
- if !has_key(l:conn.projects, l:key)
- " Tools without project roots are ready right away, like tsserver.
- let l:conn.projects[l:key] = {
- \ 'root': a:project_root,
- \ 'initialized': empty(a:project_root),
- \ 'init_request_id': 0,
- \ 'message_queue': [],
- \ 'capabilities_queue': [],
- \}
+ " If the connection is marked as initialized, call the callbacks with the
+ " responses.
+ if l:conn.initialized
+ for l:response in l:response_list
+ " Call all of the registered handlers with the response.
+ for l:Callback in l:conn.callback_list
+ call ale#util#GetFunction(l:Callback)(a:conn_id, l:response)
+ endfor
+ endfor
endif
endfunction
-function! ale#lsp#GetProject(conn, project_root) abort
- if empty(a:conn)
- return {}
- endif
-
- let l:key = empty(a:project_root) ? '<<EMPTY>>' : a:project_root
-
- return get(a:conn.projects, l:key, {})
+" Given a connection ID, mark it as a tsserver connection, so it will be
+" handled that way.
+function! ale#lsp#MarkConnectionAsTsserver(conn_id) abort
+ let l:conn = s:connections[a:conn_id]
+ let l:conn.is_tsserver = 1
+ let l:conn.initialized = 1
+ " Set capabilities which are supported by tsserver.
+ let l:conn.capabilities.hover = 1
+ let l:conn.capabilities.references = 1
+ let l:conn.capabilities.completion = 1
+ let l:conn.capabilities.completion_trigger_characters = ['.']
+ let l:conn.capabilities.definition = 1
+ let l:conn.capabilities.symbol_search = 1
endfunction
-" Start a program for LSP servers which run with executables.
+" Start a program for LSP servers.
"
-" The job ID will be returned for for the program if it ran, otherwise
-" 0 will be returned.
-function! ale#lsp#StartProgram(executable, command, init_options) abort
- if !executable(a:executable)
- return 0
- endif
-
- let l:conn = s:FindConnection('executable', a:executable)
+" 1 will be returned if the program is running, or 0 if the program could
+" not be started.
+function! ale#lsp#StartProgram(conn_id, executable, command) abort
+ let l:conn = s:connections[a:conn_id]
- " Get the current connection or a new one.
- let l:conn = !empty(l:conn) ? l:conn : ale#lsp#NewConnection(a:init_options)
- let l:conn.executable = a:executable
-
- if !has_key(l:conn, 'id') || !ale#job#IsRunning(l:conn.id)
+ if !has_key(l:conn, 'job_id') || !ale#job#IsRunning(l:conn.job_id)
let l:options = {
\ 'mode': 'raw',
- \ 'out_cb': function('s:HandleCommandMessage'),
+ \ 'out_cb': {_, message -> ale#lsp#HandleMessage(a:conn_id, message)},
\}
let l:job_id = ale#job#Start(a:command, l:options)
else
- let l:job_id = l:conn.id
+ let l:job_id = l:conn.job_id
endif
- if l:job_id <= 0
- return 0
+ if l:job_id > 0
+ let l:conn.job_id = l:job_id
endif
- let l:conn.id = l:job_id
-
- return l:job_id
+ return l:job_id > 0
endfunction
-" Connect to an address and set up a callback for handling responses.
-function! ale#lsp#ConnectToAddress(address, init_options) abort
- let l:conn = s:FindConnection('id', a:address)
- " Get the current connection or a new one.
- let l:conn = !empty(l:conn) ? l:conn : ale#lsp#NewConnection(a:init_options)
+" Connect to an LSP server via TCP.
+"
+" 1 will be returned if the connection is running, or 0 if the connection could
+" not be opened.
+function! ale#lsp#ConnectToAddress(conn_id, address) abort
+ let l:conn = s:connections[a:conn_id]
if !has_key(l:conn, 'channel_id') || !ale#socket#IsOpen(l:conn.channel_id)
- let l:conn.channel_id = ale#socket#Open(a:address, {
- \ 'callback': function('s:HandleChannelMessage'),
+ let l:channel_id = ale#socket#Open(a:address, {
+ \ 'callback': {_, mess -> ale#lsp#HandleMessage(a:conn_id, mess)},
\})
+ else
+ let l:channel_id = l:conn.channel_id
endif
- if l:conn.channel_id < 0
- return ''
+ if l:channel_id >= 0
+ let l:conn.channel_id = l:channel_id
endif
- let l:conn.id = a:address
-
- return a:address
+ return l:channel_id >= 0
endfunction
" Given a connection ID and a callback, register that callback for handling
" messages if the connection exists.
function! ale#lsp#RegisterCallback(conn_id, callback) abort
- let l:conn = s:FindConnection('id', a:conn_id)
+ let l:conn = get(s:connections, a:conn_id, {})
if !empty(l:conn)
" Add the callback to the List if it's not there already.
@@ -421,23 +371,33 @@ function! ale#lsp#RegisterCallback(conn_id, callback) abort
endif
endfunction
-" Stop all LSP connections, closing all jobs and channels, and removing any
-" queued messages.
-function! ale#lsp#StopAll() abort
- for l:conn in s:connections
+" Stop a single LSP connection.
+function! ale#lsp#Stop(conn_id) abort
+ if has_key(s:connections, a:conn_id)
+ let l:conn = remove(s:connections, a:conn_id)
+
if has_key(l:conn, 'channel_id')
call ale#socket#Close(l:conn.channel_id)
- else
- call ale#job#Stop(l:conn.id)
+ elseif has_key(l:conn, 'job_id')
+ call ale#job#Stop(l:conn.job_id)
endif
- endfor
+ endif
+endfunction
- let s:connections = []
+function! ale#lsp#CloseDocument(conn_id) abort
+endfunction
+
+" Stop all LSP connections, closing all jobs and channels, and removing any
+" queued messages.
+function! ale#lsp#StopAll() abort
+ for l:conn_id in keys(s:connections)
+ call ale#lsp#Stop(l:conn_id)
+ endfor
endfunction
function! s:SendMessageData(conn, data) abort
- if has_key(a:conn, 'executable')
- call ale#job#SendRaw(a:conn.id, a:data)
+ if has_key(a:conn, 'job_id')
+ call ale#job#SendRaw(a:conn.job_id, a:data)
elseif has_key(a:conn, 'channel_id') && ale#socket#IsOpen(a:conn.channel_id)
" Send the message to the server
call ale#socket#Send(a:conn.channel_id, a:data)
@@ -454,38 +414,32 @@ endfunction
" Returns -1 when a message is sent, but no response is expected
" 0 when the message is not sent and
" >= 1 with the message ID when a response is expected.
-function! ale#lsp#Send(conn_id, message, ...) abort
- let l:project_root = get(a:000, 0, '')
+function! ale#lsp#Send(conn_id, message) abort
+ let l:conn = get(s:connections, a:conn_id, {})
- let l:conn = s:FindConnection('id', a:conn_id)
- let l:project = ale#lsp#GetProject(l:conn, l:project_root)
-
- if empty(l:project)
+ if empty(l:conn)
return 0
endif
" If we haven't initialized the server yet, then send the message for it.
- if !l:project.initialized
- " Only send the init message once.
- if !l:project.init_request_id
- let [l:init_id, l:init_data] = ale#lsp#CreateMessageData(
- \ ale#lsp#message#Initialize(l:project_root, l:conn.initialization_options),
- \)
+ if !l:conn.initialized && !l:conn.init_request_id
+ let [l:init_id, l:init_data] = ale#lsp#CreateMessageData(
+ \ ale#lsp#message#Initialize(l:conn.root, l:conn.init_options),
+ \)
- let l:project.init_request_id = l:init_id
+ let l:conn.init_request_id = l:init_id
- call s:SendMessageData(l:conn, l:init_data)
- endif
+ call s:SendMessageData(l:conn, l:init_data)
endif
let [l:id, l:data] = ale#lsp#CreateMessageData(a:message)
- if l:project.initialized
+ if l:conn.initialized
" Send the message now.
call s:SendMessageData(l:conn, l:data)
else
" Add the message we wanted to send to a List to send later.
- call add(l:project.message_queue, l:data)
+ call add(l:conn.message_queue, l:data)
endif
return l:id == 0 ? -1 : l:id
@@ -493,11 +447,10 @@ endfunction
" Notify LSP servers or tsserver if a document is opened, if needed.
" If a document is opened, 1 will be returned, otherwise 0 will be returned.
-function! ale#lsp#OpenDocument(conn_id, project_root, buffer, language_id) abort
- let l:conn = s:FindConnection('id', a:conn_id)
+function! ale#lsp#OpenDocument(conn_id, buffer, language_id) abort
+ let l:conn = get(s:connections, a:conn_id, {})
let l:opened = 0
- " FIXME: Return 1 if the document is already open?
if !empty(l:conn) && !has_key(l:conn.open_documents, a:buffer)
if l:conn.is_tsserver
let l:message = ale#lsp#tsserver_message#Open(a:buffer)
@@ -505,7 +458,7 @@ function! ale#lsp#OpenDocument(conn_id, project_root, buffer, language_id) abort
let l:message = ale#lsp#message#DidOpen(a:buffer, a:language_id)
endif
- call ale#lsp#Send(a:conn_id, l:message, a:project_root)
+ call ale#lsp#Send(a:conn_id, l:message)
let l:conn.open_documents[a:buffer] = getbufvar(a:buffer, 'changedtick')
let l:opened = 1
endif
@@ -515,8 +468,8 @@ endfunction
" Notify LSP servers or tsserver that a document has changed, if needed.
" If a notification is sent, 1 will be returned, otherwise 0 will be returned.
-function! ale#lsp#NotifyForChanges(conn_id, project_root, buffer) abort
- let l:conn = s:FindConnection('id', a:conn_id)
+function! ale#lsp#NotifyForChanges(conn_id, buffer) abort
+ let l:conn = get(s:connections, a:conn_id, {})
let l:notified = 0
if !empty(l:conn) && has_key(l:conn.open_documents, a:buffer)
@@ -529,7 +482,7 @@ function! ale#lsp#NotifyForChanges(conn_id, project_root, buffer) abort
let l:message = ale#lsp#message#DidChange(a:buffer)
endif
- call ale#lsp#Send(a:conn_id, l:message, a:project_root)
+ call ale#lsp#Send(a:conn_id, l:message)
let l:conn.open_documents[a:buffer] = l:new_tick
let l:notified = 1
endif
@@ -540,25 +493,24 @@ endfunction
" Given some LSP details that must contain at least `connection_id` and
" `project_root` keys,
-function! ale#lsp#WaitForCapability(conn_id, project_root, capability, callback) abort
- let l:conn = s:FindConnection('id', a:conn_id)
- let l:project = ale#lsp#GetProject(l:conn, a:project_root)
+function! ale#lsp#WaitForCapability(conn_id, capability, callback) abort
+ let l:conn = get(s:connections, a:conn_id, {})
- if empty(l:project)
- return 0
+ if empty(l:conn)
+ return
endif
- if type(get(l:conn.capabilities, a:capability, v:null)) isnot type(0)
+ if type(get(l:conn.capabilities, a:capability, v:null)) isnot v:t_number
throw 'Invalid capability ' . a:capability
endif
- if l:project.initialized
- if l:conn.is_tsserver || l:conn.capabilities[a:capability]
+ if l:conn.initialized
+ if l:conn.capabilities[a:capability]
" The project has been initialized, so call the callback now.
- call call(a:callback, [a:conn_id, a:project_root])
+ call call(a:callback, [a:conn_id])
endif
else
" Call the callback later, once we have the information we need.
- call add(l:project.capabilities_queue, [a:capability, a:callback])
+ call add(l:conn.capabilities_queue, [a:capability, a:callback])
endif
endfunction
diff --git a/autoload/ale/lsp/message.vim b/autoload/ale/lsp/message.vim
index 9e05156d..9fffb83a 100644
--- a/autoload/ale/lsp/message.vim
+++ b/autoload/ale/lsp/message.vim
@@ -130,6 +130,12 @@ function! ale#lsp#message#References(buffer, line, column) abort
\}]
endfunction
+function! ale#lsp#message#Symbol(query) abort
+ return [0, 'workspace/symbol', {
+ \ 'query': a:query,
+ \}]
+endfunction
+
function! ale#lsp#message#Hover(buffer, line, column) abort
return [0, 'textDocument/hover', {
\ 'textDocument': {
@@ -138,3 +144,9 @@ function! ale#lsp#message#Hover(buffer, line, column) abort
\ 'position': {'line': a:line - 1, 'character': a:column},
\}]
endfunction
+
+function! ale#lsp#message#DidChangeConfiguration(buffer, config) abort
+ return [0, 'workspace/didChangeConfiguration', {
+ \ 'settings': a:config,
+ \}]
+endfunction
diff --git a/autoload/ale/lsp/reset.vim b/autoload/ale/lsp/reset.vim
index c7c97a47..2fc7f0a2 100644
--- a/autoload/ale/lsp/reset.vim
+++ b/autoload/ale/lsp/reset.vim
@@ -17,7 +17,7 @@ function! ale#lsp#reset#StopAllLSPs() abort
for l:linter in ale#linter#Get(getbufvar(l:buffer, '&filetype'))
if !empty(l:linter.lsp)
- call ale#engine#HandleLoclist(l:linter.name, l:buffer, [])
+ call ale#engine#HandleLoclist(l:linter.name, l:buffer, [], 0)
endif
endfor
endfor
diff --git a/autoload/ale/lsp/response.vim b/autoload/ale/lsp/response.vim
index a0e1984d..08b36808 100644
--- a/autoload/ale/lsp/response.vim
+++ b/autoload/ale/lsp/response.vim
@@ -47,7 +47,23 @@ function! ale#lsp#response#ReadDiagnostics(response) abort
endif
if has_key(l:diagnostic, 'code')
- let l:loclist_item.nr = l:diagnostic.code
+ if type(l:diagnostic.code) == v:t_string
+ let l:loclist_item.code = l:diagnostic.code
+ elseif type(l:diagnostic.code) == v:t_number && l:diagnostic.code != -1
+ let l:loclist_item.code = string(l:diagnostic.code)
+ let l:loclist_item.nr = l:diagnostic.code
+ endif
+ endif
+
+ if has_key(l:diagnostic, 'relatedInformation')
+ let l:related = deepcopy(l:diagnostic.relatedInformation)
+ call map(l:related, {key, val ->
+ \ ale#path#FromURI(val.location.uri) .
+ \ ':' . (val.location.range.start.line + 1) .
+ \ ':' . (val.location.range.start.character + 1) .
+ \ ":\n\t" . val.message
+ \ })
+ let l:loclist_item.detail = l:diagnostic.message . "\n" . join(l:related, "\n")
endif
if has_key(l:diagnostic, 'source')
@@ -74,7 +90,12 @@ function! ale#lsp#response#ReadTSServerDiagnostics(response) abort
\}
if has_key(l:diagnostic, 'code')
- let l:loclist_item.nr = l:diagnostic.code
+ if type(l:diagnostic.code) == v:t_string
+ let l:loclist_item.code = l:diagnostic.code
+ elseif type(l:diagnostic.code) == v:t_number && l:diagnostic.code != -1
+ let l:loclist_item.code = string(l:diagnostic.code)
+ let l:loclist_item.nr = l:diagnostic.code
+ endif
endif
if get(l:diagnostic, 'category') is# 'warning'
@@ -92,7 +113,7 @@ function! ale#lsp#response#ReadTSServerDiagnostics(response) abort
endfunction
function! ale#lsp#response#GetErrorMessage(response) abort
- if type(get(a:response, 'error', 0)) isnot type({})
+ if type(get(a:response, 'error', 0)) isnot v:t_dict
return ''
endif
@@ -112,12 +133,12 @@ function! ale#lsp#response#GetErrorMessage(response) abort
" Include the traceback or error data as details, if present.
let l:error_data = get(a:response.error, 'data', {})
- if type(l:error_data) is type('')
+ if type(l:error_data) is v:t_string
let l:message .= "\n" . l:error_data
- else
+ elseif type(l:error_data) is v:t_dict
let l:traceback = get(l:error_data, 'traceback', [])
- if type(l:traceback) is type([]) && !empty(l:traceback)
+ if type(l:traceback) is v:t_list && !empty(l:traceback)
let l:message .= "\n" . join(l:traceback, "\n")
endif
endif
diff --git a/autoload/ale/lsp_linter.vim b/autoload/ale/lsp_linter.vim
index 87aee759..42d67398 100644
--- a/autoload/ale/lsp_linter.vim
+++ b/autoload/ale/lsp_linter.vim
@@ -38,7 +38,7 @@ function! s:HandleLSPDiagnostics(conn_id, response) abort
let l:loclist = ale#lsp#response#ReadDiagnostics(a:response)
- call ale#engine#HandleLoclist(l:linter_name, l:buffer, l:loclist)
+ call ale#engine#HandleLoclist(l:linter_name, l:buffer, l:loclist, 0)
endfunction
function! s:HandleTSServerDiagnostics(response, error_type) abort
@@ -55,20 +55,33 @@ function! s:HandleTSServerDiagnostics(response, error_type) abort
endif
let l:thislist = ale#lsp#response#ReadTSServerDiagnostics(a:response)
+ let l:no_changes = 0
" tsserver sends syntax and semantic errors in separate messages, so we
" have to collect the messages separately for each buffer and join them
" back together again.
if a:error_type is# 'syntax'
+ if len(l:thislist) is 0 && len(get(l:info, 'syntax_loclist', [])) is 0
+ let l:no_changes = 1
+ endif
+
let l:info.syntax_loclist = l:thislist
else
+ if len(l:thislist) is 0 && len(get(l:info, 'semantic_loclist', [])) is 0
+ let l:no_changes = 1
+ endif
+
let l:info.semantic_loclist = l:thislist
endif
+ if l:no_changes
+ return
+ endif
+
let l:loclist = get(l:info, 'semantic_loclist', [])
\ + get(l:info, 'syntax_loclist', [])
- call ale#engine#HandleLoclist(l:linter_name, l:buffer, l:loclist)
+ call ale#engine#HandleLoclist(l:linter_name, l:buffer, l:loclist, 0)
endfunction
function! s:HandleLSPErrorMessage(linter_name, response) abort
@@ -99,9 +112,10 @@ endfunction
function! ale#lsp_linter#HandleLSPResponse(conn_id, response) abort
let l:method = get(a:response, 'method', '')
- let l:linter_name = get(s:lsp_linter_map, a:conn_id, '')
if get(a:response, 'jsonrpc', '') is# '2.0' && has_key(a:response, 'error')
+ let l:linter_name = get(s:lsp_linter_map, a:conn_id, '')
+
call s:HandleLSPErrorMessage(l:linter_name, a:response)
elseif l:method is# 'textDocument/publishDiagnostics'
call s:HandleLSPDiagnostics(a:conn_id, a:response)
@@ -126,6 +140,18 @@ function! ale#lsp_linter#GetOptions(buffer, linter) abort
return l:initialization_options
endfunction
+function! ale#lsp_linter#GetConfig(buffer, linter) abort
+ let l:config = {}
+
+ if has_key(a:linter, 'lsp_config_callback')
+ let l:config = ale#util#GetFunction(a:linter.lsp_config_callback)(a:buffer)
+ elseif has_key(a:linter, 'lsp_config')
+ let l:config = a:linter.lsp_config
+ endif
+
+ return l:config
+endfunction
+
" Given a buffer, an LSP linter, start up an LSP linter and get ready to
" receive messages for the document.
function! ale#lsp_linter#StartLSP(buffer, linter) abort
@@ -143,7 +169,8 @@ function! ale#lsp_linter#StartLSP(buffer, linter) abort
if a:linter.lsp is# 'socket'
let l:address = ale#linter#GetAddress(a:buffer, a:linter)
- let l:conn_id = ale#lsp#ConnectToAddress(l:address, l:init_options)
+ let l:conn_id = ale#lsp#Register(l:address, l:root, l:init_options)
+ let l:ready = ale#lsp#ConnectToAddress(l:conn_id, l:address)
else
let l:executable = ale#linter#GetExecutable(a:buffer, a:linter)
@@ -151,18 +178,16 @@ function! ale#lsp_linter#StartLSP(buffer, linter) abort
return {}
endif
+ let l:conn_id = ale#lsp#Register(l:executable, l:root, l:init_options)
+
let l:command = ale#linter#GetCommand(a:buffer, a:linter)
" Format the command, so %e can be formatted into it.
let l:command = ale#command#FormatCommand(a:buffer, l:executable, l:command, 0)[1]
let l:command = ale#job#PrepareCommand(a:buffer, l:command)
- let l:conn_id = ale#lsp#StartProgram(
- \ l:executable,
- \ l:command,
- \ l:init_options,
- \)
+ let l:ready = ale#lsp#StartProgram(l:conn_id, l:executable, l:command)
endif
- if empty(l:conn_id)
+ if !l:ready
if g:ale_history_enabled && !empty(l:command)
call ale#history#Add(a:buffer, 'failed', l:conn_id, l:command)
endif
@@ -175,9 +200,7 @@ function! ale#lsp_linter#StartLSP(buffer, linter) abort
call ale#lsp#MarkConnectionAsTsserver(l:conn_id)
endif
- " Register the project now the connection is ready.
- call ale#lsp#RegisterProject(l:conn_id, l:root)
-
+ let l:config = ale#lsp_linter#GetConfig(a:buffer, a:linter)
let l:language_id = ale#util#GetFunction(a:linter.language_callback)(a:buffer)
let l:details = {
@@ -188,7 +211,9 @@ function! ale#lsp_linter#StartLSP(buffer, linter) abort
\ 'language_id': l:language_id,
\}
- if ale#lsp#OpenDocument(l:conn_id, l:root, a:buffer, l:language_id)
+ call ale#lsp#UpdateConfig(l:conn_id, a:buffer, l:config)
+
+ if ale#lsp#OpenDocument(l:conn_id, a:buffer, l:language_id)
if g:ale_history_enabled && !empty(l:command)
call ale#history#Add(a:buffer, 'started', l:conn_id, l:command)
endif
@@ -196,7 +221,7 @@ function! ale#lsp_linter#StartLSP(buffer, linter) abort
" The change message needs to be sent for tsserver before doing anything.
if a:linter.lsp is# 'tsserver'
- call ale#lsp#NotifyForChanges(l:conn_id, l:root, a:buffer)
+ call ale#lsp#NotifyForChanges(l:conn_id, a:buffer)
endif
return l:details
@@ -211,7 +236,6 @@ function! ale#lsp_linter#CheckWithLSP(buffer, linter) abort
endif
let l:id = l:lsp_details.connection_id
- let l:root = l:lsp_details.project_root
" Register a callback now for handling errors now.
let l:Callback = function('ale#lsp_linter#HandleLSPResponse')
@@ -222,16 +246,16 @@ function! ale#lsp_linter#CheckWithLSP(buffer, linter) abort
if a:linter.lsp is# 'tsserver'
let l:message = ale#lsp#tsserver_message#Geterr(a:buffer)
- let l:notified = ale#lsp#Send(l:id, l:message, l:root) != 0
+ let l:notified = ale#lsp#Send(l:id, l:message) != 0
else
- let l:notified = ale#lsp#NotifyForChanges(l:id, l:root, a:buffer)
+ let l:notified = ale#lsp#NotifyForChanges(l:id, a:buffer)
endif
" If this was a file save event, also notify the server of that.
if a:linter.lsp isnot# 'tsserver'
\&& getbufvar(a:buffer, 'ale_save_event_fired', 0)
let l:save_message = ale#lsp#message#DidSave(a:buffer)
- let l:notified = ale#lsp#Send(l:id, l:save_message, l:root) != 0
+ let l:notified = ale#lsp#Send(l:id, l:save_message) != 0
endif
if l:notified
diff --git a/autoload/ale/node.vim b/autoload/ale/node.vim
index f75280b7..5c579c75 100644
--- a/autoload/ale/node.vim
+++ b/autoload/ale/node.vim
@@ -23,6 +23,11 @@ function! ale#node#FindExecutable(buffer, base_var_name, path_list) abort
return ale#Var(a:buffer, a:base_var_name . '_executable')
endfunction
+" As above, but curry the arguments so only the buffer number is required.
+function! ale#node#FindExecutableFunc(base_var_name, path_list) abort
+ return {buf -> ale#node#FindExecutable(buf, a:base_var_name, a:path_list)}
+endfunction
+
" Create a executable string which executes a Node.js script command with a
" Node.js executable if needed.
"
diff --git a/autoload/ale/other_source.vim b/autoload/ale/other_source.vim
new file mode 100644
index 00000000..1a092034
--- /dev/null
+++ b/autoload/ale/other_source.vim
@@ -0,0 +1,21 @@
+" Tell ALE that another source has started checking a buffer.
+function! ale#other_source#StartChecking(buffer, linter_name) abort
+ call ale#engine#InitBufferInfo(a:buffer)
+ let l:list = g:ale_buffer_info[a:buffer].active_other_sources_list
+
+ call add(l:list, a:linter_name)
+ call uniq(sort(l:list))
+endfunction
+
+" Show some results, and stop checking a buffer.
+" To clear results or cancel checking a buffer, an empty List can be given.
+function! ale#other_source#ShowResults(buffer, linter_name, loclist) abort
+ call ale#engine#InitBufferInfo(a:buffer)
+ let l:info = g:ale_buffer_info[a:buffer]
+
+ " Remove this linter name from the active list.
+ let l:list = l:info.active_other_sources_list
+ call filter(l:list, 'v:val isnot# a:linter_name')
+
+ call ale#engine#HandleLoclist(a:linter_name, a:buffer, a:loclist, 1)
+endfunction
diff --git a/autoload/ale/path.vim b/autoload/ale/path.vim
index 45da3709..89b119f4 100644
--- a/autoload/ale/path.vim
+++ b/autoload/ale/path.vim
@@ -65,7 +65,11 @@ endfunction
" Output 'cd <directory> && '
" This function can be used changing the directory for a linter command.
function! ale#path#CdString(directory) abort
- return 'cd ' . ale#Escape(a:directory) . ' && '
+ if has('win32')
+ return 'cd /d ' . ale#Escape(a:directory) . ' && '
+ else
+ return 'cd ' . ale#Escape(a:directory) . ' && '
+ endif
endfunction
" Output 'cd <buffer_filename_directory> && '
@@ -105,6 +109,21 @@ function! ale#path#GetAbsPath(base_directory, filename) abort
return ale#path#Simplify(a:base_directory . l:sep . a:filename)
endfunction
+" Given a path, return the directory name for that path, with no trailing
+" slashes. If the argument is empty(), return an empty string.
+function! ale#path#Dirname(path) abort
+ if empty(a:path)
+ return ''
+ endif
+
+ " For /foo/bar/ we need :h:h to get /foo
+ if a:path[-1:] is# '/'
+ return fnamemodify(a:path, ':h:h')
+ endif
+
+ return fnamemodify(a:path, ':h')
+endfunction
+
" Given a buffer number and a relative or absolute path, return 1 if the
" two paths represent the same file on disk.
function! ale#path#IsBufferPath(buffer, complex_filename) abort
diff --git a/autoload/ale/preview.vim b/autoload/ale/preview.vim
index aefbb691..1f50e0ad 100644
--- a/autoload/ale/preview.vim
+++ b/autoload/ale/preview.vim
@@ -15,13 +15,13 @@ function! ale#preview#Show(lines, ...) abort
setlocal modifiable
setlocal noreadonly
setlocal nobuflisted
- let &l:filetype = get(l:options, 'filetype', 'ale-preview')
setlocal buftype=nofile
setlocal bufhidden=wipe
:%d
call setline(1, a:lines)
setlocal nomodifiable
setlocal readonly
+ let &l:filetype = get(l:options, 'filetype', 'ale-preview')
if get(l:options, 'stay_here')
wincmd p
@@ -46,11 +46,14 @@ function! ale#preview#ShowSelection(item_list) abort
" Create lines to display to users.
for l:item in a:item_list
+ let l:match = get(l:item, 'match', '')
+
call add(
\ l:lines,
\ l:item.filename
\ . ':' . l:item.line
- \ . ':' . l:item.column,
+ \ . ':' . l:item.column
+ \ . (!empty(l:match) ? ' ' . l:match : ''),
\)
endfor
diff --git a/autoload/ale/python.vim b/autoload/ale/python.vim
index bc1cc980..8d6bf1f0 100644
--- a/autoload/ale/python.vim
+++ b/autoload/ale/python.vim
@@ -1,6 +1,8 @@
" Author: w0rp <devw0rp@gmail.com>
" Description: Functions for integrating with Python linters.
+call ale#Set('python_auto_pipenv', '0')
+
let s:sep = has('win32') ? '\' : '/'
" bin is used for Unix virtualenv directories, and Scripts is for Windows.
let s:bin_dir = has('unix') ? 'bin' : 'Scripts'
@@ -24,6 +26,7 @@ function! ale#python#FindProjectRootIni(buffer) abort
\|| filereadable(l:path . '/mypy.ini')
\|| filereadable(l:path . '/pycodestyle.cfg')
\|| filereadable(l:path . '/flake8.cfg')
+ \|| filereadable(l:path . '/.flake8rc')
\|| filereadable(l:path . '/Pipfile')
\|| filereadable(l:path . '/Pipfile.lock')
return l:path
@@ -106,3 +109,8 @@ function! ale#python#FindExecutable(buffer, base_var_name, path_list) abort
return ale#Var(a:buffer, a:base_var_name . '_executable')
endfunction
+
+" Detects whether a pipenv environment is present.
+function! ale#python#PipenvPresent(buffer) abort
+ return findfile('Pipfile.lock', expand('#' . a:buffer . ':p:h') . ';') isnot# ''
+endfunction
diff --git a/autoload/ale/references.vim b/autoload/ale/references.vim
index 3a710b7b..d00a1fa9 100644
--- a/autoload/ale/references.vim
+++ b/autoload/ale/references.vim
@@ -64,6 +64,35 @@ function! ale#references#HandleLSPResponse(conn_id, response) abort
endif
endfunction
+function! s:OnReady(linter, lsp_details, line, column, ...) abort
+ let l:buffer = a:lsp_details.buffer
+ let l:id = a:lsp_details.connection_id
+
+ let l:Callback = a:linter.lsp is# 'tsserver'
+ \ ? function('ale#references#HandleTSServerResponse')
+ \ : function('ale#references#HandleLSPResponse')
+
+ call ale#lsp#RegisterCallback(l:id, l:Callback)
+
+ if a:linter.lsp is# 'tsserver'
+ let l:message = ale#lsp#tsserver_message#References(
+ \ l:buffer,
+ \ a:line,
+ \ a:column
+ \)
+ else
+ " Send a message saying the buffer has changed first, or the
+ " references position probably won't make sense.
+ call ale#lsp#NotifyForChanges(l:id, l:buffer)
+
+ let l:message = ale#lsp#message#References(l:buffer, a:line, a:column)
+ endif
+
+ let l:request_id = ale#lsp#Send(l:id, l:message)
+
+ let s:references_map[l:request_id] = {}
+endfunction
+
function! s:FindReferences(linter) abort
let l:buffer = bufnr('')
let [l:line, l:column] = getcurpos()[1:2]
@@ -79,35 +108,10 @@ function! s:FindReferences(linter) abort
endif
let l:id = l:lsp_details.connection_id
- let l:root = l:lsp_details.project_root
-
- function! OnReady(...) abort closure
- let l:Callback = a:linter.lsp is# 'tsserver'
- \ ? function('ale#references#HandleTSServerResponse')
- \ : function('ale#references#HandleLSPResponse')
-
- call ale#lsp#RegisterCallback(l:id, l:Callback)
-
- if a:linter.lsp is# 'tsserver'
- let l:message = ale#lsp#tsserver_message#References(
- \ l:buffer,
- \ l:line,
- \ l:column
- \)
- else
- " Send a message saying the buffer has changed first, or the
- " references position probably won't make sense.
- call ale#lsp#NotifyForChanges(l:id, l:root, l:buffer)
-
- let l:message = ale#lsp#message#References(l:buffer, l:line, l:column)
- endif
-
- let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root)
-
- let s:references_map[l:request_id] = {}
- endfunction
- call ale#lsp#WaitForCapability(l:id, l:root, 'references', function('OnReady'))
+ call ale#lsp#WaitForCapability(l:id, 'references', function('s:OnReady', [
+ \ a:linter, l:lsp_details, l:line, l:column
+ \]))
endfunction
function! ale#references#Find() abort
diff --git a/autoload/ale/ruby.vim b/autoload/ale/ruby.vim
index b981ded6..5f0aa50d 100644
--- a/autoload/ale/ruby.vim
+++ b/autoload/ale/ruby.vim
@@ -20,3 +20,25 @@ function! ale#ruby#FindRailsRoot(buffer) abort
return ''
endfunction
+
+" Find the nearest dir containing a potential ruby project.
+function! ale#ruby#FindProjectRoot(buffer) abort
+ let l:dir = ale#ruby#FindRailsRoot(a:buffer)
+
+ if isdirectory(l:dir)
+ return l:dir
+ endif
+
+ for l:name in ['.solargraph.yml', 'Rakefile', 'Gemfile']
+ let l:dir = fnamemodify(
+ \ ale#path#FindNearestFile(a:buffer, l:name),
+ \ ':h'
+ \)
+
+ if l:dir isnot# '.' && isdirectory(l:dir)
+ return l:dir
+ endif
+ endfor
+
+ return ''
+endfunction
diff --git a/autoload/ale/sign.vim b/autoload/ale/sign.vim
index a0dde359..af863682 100644
--- a/autoload/ale/sign.vim
+++ b/autoload/ale/sign.vim
@@ -211,7 +211,7 @@ function! s:BuildSignMap(buffer, current_sign_list, grouped_items) abort
if l:max_signs is 0
let l:selected_grouped_items = []
- elseif type(l:max_signs) is type(0) && l:max_signs > 0
+ elseif type(l:max_signs) is v:t_number && l:max_signs > 0
let l:selected_grouped_items = a:grouped_items[:l:max_signs - 1]
else
let l:selected_grouped_items = a:grouped_items
diff --git a/autoload/ale/socket.vim b/autoload/ale/socket.vim
index 0ca4dea6..7e069fb5 100644
--- a/autoload/ale/socket.vim
+++ b/autoload/ale/socket.vim
@@ -55,11 +55,18 @@ function! ale#socket#Open(address, options) abort
if !has('nvim')
" Vim
- let l:channel_info.channel = ch_open(a:address, {
+ let l:channel_options = {
\ 'mode': l:mode,
\ 'waittime': 0,
\ 'callback': function('s:VimOutputCallback'),
- \})
+ \}
+
+ " Use non-blocking writes for Vim versions that support the option.
+ if has('patch-8.1.350')
+ let l:channel_options.noblock = 1
+ endif
+
+ let l:channel_info.channel = ch_open(a:address, l:channel_options)
let l:vim_info = ch_info(l:channel_info.channel)
let l:channel_id = !empty(l:vim_info) ? l:vim_info.id : -1
elseif exists('*chansend') && exists('*sockconnect')
@@ -104,6 +111,7 @@ function! ale#socket#IsOpen(channel_id) abort
endif
let l:channel = s:channel_map[a:channel_id].channel
+
return ch_status(l:channel) is# 'open'
endfunction
diff --git a/autoload/ale/symbol.vim b/autoload/ale/symbol.vim
new file mode 100644
index 00000000..5180cb86
--- /dev/null
+++ b/autoload/ale/symbol.vim
@@ -0,0 +1,109 @@
+let s:symbol_map = {}
+
+" Used to get the symbol map in tests.
+function! ale#symbol#GetMap() abort
+ return deepcopy(s:symbol_map)
+endfunction
+
+" Used to set the symbol map in tests.
+function! ale#symbol#SetMap(map) abort
+ let s:symbol_map = a:map
+endfunction
+
+function! ale#symbol#ClearLSPData() abort
+ let s:symbol_map = {}
+endfunction
+
+function! ale#symbol#HandleLSPResponse(conn_id, response) abort
+ if has_key(a:response, 'id')
+ \&& has_key(s:symbol_map, a:response.id)
+ let l:options = remove(s:symbol_map, a:response.id)
+
+ let l:result = get(a:response, 'result', v:null)
+ let l:item_list = []
+
+ if type(l:result) is v:t_list
+ " Each item looks like this:
+ " {
+ " 'name': 'foo',
+ " 'kind': 123,
+ " 'deprecated': v:false,
+ " 'location': {
+ " 'uri': 'file://...',
+ " 'range': {
+ " 'start': {'line': 0, 'character': 0},
+ " 'end': {'line': 0, 'character': 0},
+ " },
+ " },
+ " 'containerName': 'SomeContainer',
+ " }
+ for l:response_item in l:result
+ let l:location = l:response_item.location
+
+ call add(l:item_list, {
+ \ 'filename': ale#path#FromURI(l:location.uri),
+ \ 'line': l:location.range.start.line + 1,
+ \ 'column': l:location.range.start.character + 1,
+ \ 'match': l:response_item.name,
+ \})
+ endfor
+ endif
+
+ if empty(l:item_list)
+ call ale#util#Execute('echom ''No symbols found.''')
+ else
+ call ale#preview#ShowSelection(l:item_list)
+ endif
+ endif
+endfunction
+
+function! s:OnReady(linter, lsp_details, query, ...) abort
+ let l:buffer = a:lsp_details.buffer
+
+ " If we already made a request, stop here.
+ if getbufvar(l:buffer, 'ale_symbol_request_made', 0)
+ return
+ endif
+
+ let l:id = a:lsp_details.connection_id
+
+ let l:Callback = function('ale#symbol#HandleLSPResponse')
+ call ale#lsp#RegisterCallback(l:id, l:Callback)
+
+ let l:message = ale#lsp#message#Symbol(a:query)
+ let l:request_id = ale#lsp#Send(l:id, l:message)
+
+ call setbufvar(l:buffer, 'ale_symbol_request_made', 1)
+ let s:symbol_map[l:request_id] = {
+ \ 'buffer': l:buffer,
+ \}
+endfunction
+
+function! s:Search(linter, buffer, query) abort
+ let l:lsp_details = ale#lsp_linter#StartLSP(a:buffer, a:linter)
+
+ if !empty(l:lsp_details)
+ call ale#lsp#WaitForCapability(
+ \ l:lsp_details.connection_id,
+ \ 'symbol_search',
+ \ function('s:OnReady', [a:linter, l:lsp_details, a:query]),
+ \)
+ endif
+endfunction
+
+function! ale#symbol#Search(query) abort
+ if type(a:query) isnot v:t_string || empty(a:query)
+ throw 'A non-empty string must be provided!'
+ endif
+
+ let l:buffer = bufnr('')
+
+ " Set a flag so we only make one request.
+ call setbufvar(l:buffer, 'ale_symbol_request_made', 0)
+
+ for l:linter in ale#linter#Get(getbufvar(l:buffer, '&filetype'))
+ if !empty(l:linter.lsp) && l:linter.lsp isnot# 'tsserver'
+ call s:Search(l:linter, l:buffer, a:query)
+ endif
+ endfor
+endfunction
diff --git a/autoload/ale/toggle.vim b/autoload/ale/toggle.vim
index da108782..8e642b3f 100644
--- a/autoload/ale/toggle.vim
+++ b/autoload/ale/toggle.vim
@@ -15,21 +15,6 @@ function! s:DisablePostamble() abort
endif
endfunction
-function! s:CleanupEveryBuffer() abort
- for l:key in keys(g:ale_buffer_info)
- " The key could be a filename or a buffer number, so try and
- " convert it to a number. We need a number for the other
- " functions.
- let l:buffer = str2nr(l:key)
-
- if l:buffer > 0
- " Stop all jobs and clear the results for everything, and delete
- " all of the data we stored for the buffer.
- call ale#engine#Cleanup(l:buffer)
- endif
- endfor
-endfunction
-
function! ale#toggle#Toggle() abort
let g:ale_enabled = !get(g:, 'ale_enabled')
@@ -40,7 +25,7 @@ function! ale#toggle#Toggle() abort
call ale#balloon#Enable()
endif
else
- call s:CleanupEveryBuffer()
+ call ale#engine#CleanupEveryBuffer()
call s:DisablePostamble()
if exists('*ale#balloon#Disable')
@@ -64,7 +49,7 @@ function! ale#toggle#Disable() abort
endfunction
function! ale#toggle#Reset() abort
- call s:CleanupEveryBuffer()
+ call ale#engine#CleanupEveryBuffer()
call ale#highlight#UpdateHighlights()
endfunction
@@ -76,6 +61,7 @@ function! ale#toggle#ToggleBuffer(buffer) abort
" linting locally when linting is disabled globally
if l:enabled && !g:ale_enabled
execute 'echom ''ALE cannot be enabled locally when disabled globally'''
+
return
endif
diff --git a/autoload/ale/util.vim b/autoload/ale/util.vim
index fb6dc085..bb478957 100644
--- a/autoload/ale/util.vim
+++ b/autoload/ale/util.vim
@@ -54,6 +54,7 @@ endif
function! ale#util#JoinNeovimOutput(job, last_line, data, mode, callback) abort
if a:mode is# 'raw'
call a:callback(a:job, join(a:data, "\n"))
+
return ''
endif
@@ -79,7 +80,7 @@ function! ale#util#GetLineCount(buffer) abort
endfunction
function! ale#util#GetFunction(string_or_ref) abort
- if type(a:string_or_ref) == type('')
+ if type(a:string_or_ref) is v:t_string
return function(a:string_or_ref)
endif
@@ -88,12 +89,12 @@ endfunction
function! ale#util#Open(filename, line, column, options) abort
if get(a:options, 'open_in_tab', 0)
- call ale#util#Execute('tabedit ' . fnameescape(a:filename))
- else
+ call ale#util#Execute('tabedit +' . a:line . ' ' . fnameescape(a:filename))
+ elseif bufnr(a:filename) isnot bufnr('')
" Open another file only if we need to.
- if bufnr(a:filename) isnot bufnr('')
- call ale#util#Execute('edit ' . fnameescape(a:filename))
- endif
+ call ale#util#Execute('edit +' . a:line . ' ' . fnameescape(a:filename))
+ else
+ normal! m`
endif
call cursor(a:line, a:column)
@@ -268,7 +269,7 @@ endfunction
" See :help sandbox
function! ale#util#InSandbox() abort
try
- let &equalprg=&equalprg
+ let &l:equalprg=&l:equalprg
catch /E48/
" E48 is the sandbox error.
return 1
@@ -303,8 +304,8 @@ endfunction
" Only the first pattern which matches a line will be returned.
function! ale#util#GetMatches(lines, patterns) abort
let l:matches = []
- let l:lines = type(a:lines) == type([]) ? a:lines : [a:lines]
- let l:patterns = type(a:patterns) == type([]) ? a:patterns : [a:patterns]
+ let l:lines = type(a:lines) is v:t_list ? a:lines : [a:lines]
+ let l:patterns = type(a:patterns) is v:t_list ? a:patterns : [a:patterns]
for l:line in l:lines
for l:pattern in l:patterns
@@ -382,7 +383,7 @@ function! ale#util#FuzzyJSONDecode(data, default) abort
return a:default
endif
- let l:str = type(a:data) == type('') ? a:data : join(a:data, '')
+ let l:str = type(a:data) is v:t_string ? a:data : join(a:data, '')
try
let l:result = json_decode(l:str)
@@ -404,7 +405,7 @@ endfunction
" the buffer.
function! ale#util#Writefile(buffer, lines, filename) abort
let l:corrected_lines = getbufvar(a:buffer, '&fileformat') is# 'dos'
- \ ? map(copy(a:lines), 'v:val . "\r"')
+ \ ? map(copy(a:lines), 'substitute(v:val, ''\r*$'', ''\r'', '''')')
\ : a:lines
call writefile(l:corrected_lines, a:filename) " no-custom-checks
@@ -451,3 +452,14 @@ function! ale#util#Col(str, chr) abort
return strlen(join(split(a:str, '\zs')[0:a:chr - 2], '')) + 1
endfunction
+
+function! ale#util#FindItemAtCursor(buffer) abort
+ let l:info = get(g:ale_buffer_info, a:buffer, {})
+ let l:loclist = get(l:info, 'loclist', [])
+ let l:pos = getcurpos()
+ let l:index = ale#util#BinarySearch(l:loclist, a:buffer, l:pos[1], l:pos[2])
+ let l:loc = l:index >= 0 ? l:loclist[l:index] : {}
+
+ return [l:info, l:loc]
+endfunction
+
diff --git a/autoload/ale/virtualtext.vim b/autoload/ale/virtualtext.vim
new file mode 100644
index 00000000..c4ce37dd
--- /dev/null
+++ b/autoload/ale/virtualtext.vim
@@ -0,0 +1,136 @@
+scriptencoding utf-8
+" Author: w0rp <devw0rp@gmail.com>
+" Author: Luan Santos <cfcluan@gmail.com>
+" Description: Shows lint message for the current line as virtualtext, if any
+
+" Controls the milliseconds delay before showing a message.
+let g:ale_virtualtext_delay = get(g:, 'ale_virtualtext_delay', 10)
+let s:cursor_timer = -1
+let s:last_pos = [0, 0, 0]
+
+if has('nvim-0.3.2')
+ let s:ns_id = nvim_create_namespace('ale')
+endif
+
+if !hlexists('ALEVirtualTextError')
+ highlight link ALEVirtualTextError ALEError
+endif
+
+if !hlexists('ALEVirtualTextStyleError')
+ highlight link ALEVirtualTextStyleError ALEVirtualTextError
+endif
+
+if !hlexists('ALEVirtualTextWarning')
+ highlight link ALEVirtualTextWarning ALEWarning
+endif
+
+if !hlexists('ALEVirtualTextStyleWarning')
+ highlight link ALEVirtualTextStyleWarning ALEVirtualTextWarning
+endif
+
+if !hlexists('ALEVirtualTextInfo')
+ highlight link ALEVirtualTextInfo ALEVirtualTextWarning
+endif
+
+function! ale#virtualtext#Clear() abort
+ if !has('nvim-0.3.2')
+ return
+ endif
+
+ let l:buffer = bufnr('')
+
+ call nvim_buf_clear_highlight(l:buffer, s:ns_id, 0, -1)
+endfunction
+
+function! ale#virtualtext#ShowMessage(message, hl_group) abort
+ if !has('nvim-0.3.2')
+ return
+ endif
+
+ let l:cursor_position = getcurpos()
+ let l:line = line('.')
+ let l:buffer = bufnr('')
+ let l:prefix = get(g:, 'ale_virtualtext_prefix', '> ')
+
+ call nvim_buf_set_virtual_text(l:buffer, s:ns_id, l:line-1, [[l:prefix.a:message, a:hl_group]], {})
+endfunction
+
+function! s:StopCursorTimer() abort
+ if s:cursor_timer != -1
+ call timer_stop(s:cursor_timer)
+ let s:cursor_timer = -1
+ endif
+endfunction
+
+function! ale#virtualtext#ShowCursorWarning(...) abort
+ if !g:ale_virtualtext_cursor
+ return
+ endif
+
+ let l:buffer = bufnr('')
+
+ if mode(1) isnot# 'n'
+ return
+ endif
+
+ if ale#ShouldDoNothing(l:buffer)
+ return
+ endif
+
+ let [l:info, l:loc] = ale#util#FindItemAtCursor(l:buffer)
+
+ call ale#virtualtext#Clear()
+
+ if !empty(l:loc)
+ let l:msg = get(l:loc, 'detail', l:loc.text)
+ let l:hl_group = 'ALEVirtualTextInfo'
+ let l:type = get(l:loc, 'type', 'E')
+
+ if l:type is# 'E'
+ if get(l:loc, 'sub_type', '') is# 'style'
+ let l:hl_group = 'ALEVirtualTextStyleError'
+ else
+ let l:hl_group = 'ALEVirtualTextError'
+ endif
+ elseif l:type is# 'W'
+ if get(l:loc, 'sub_type', '') is# 'style'
+ let l:hl_group = 'ALEVirtualTextStyleWarning'
+ else
+ let l:hl_group = 'ALEVirtualTextWarning'
+ endif
+ endif
+
+ call ale#virtualtext#ShowMessage(l:msg, l:hl_group)
+ endif
+endfunction
+
+function! ale#virtualtext#ShowCursorWarningWithDelay() abort
+ let l:buffer = bufnr('')
+
+ if !g:ale_virtualtext_cursor
+ return
+ endif
+
+ if mode(1) isnot# 'n'
+ return
+ endif
+
+ call s:StopCursorTimer()
+
+ let l:pos = getcurpos()[0:2]
+
+ " Check the current buffer, line, and column number against the last
+ " recorded position. If the position has actually changed, *then*
+ " we should show something. Otherwise we can end up doing processing
+ " the show message far too frequently.
+ if l:pos != s:last_pos
+ let l:delay = ale#Var(l:buffer, 'virtualtext_delay')
+
+ let s:last_pos = l:pos
+ let s:cursor_timer = timer_start(
+ \ l:delay,
+ \ function('ale#virtualtext#ShowCursorWarning')
+ \)
+ endif
+endfunction
+