mirror of
https://github.com/dense-analysis/ale.git
synced 2026-01-08 12:34:51 +08:00
Merge branch 'master' into sridhars
This commit is contained in:
125
autoload/ale.vim
125
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_'.
|
||||
|
||||
@@ -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
|
||||
|
||||
call ale#test#RestoreDirectory()
|
||||
if exists(':AssertLinter')
|
||||
delcommand AssertLinter
|
||||
endif
|
||||
|
||||
if exists(':AssertLinterNotExecuted')
|
||||
delcommand AssertLinterNotExecuted
|
||||
endif
|
||||
|
||||
if exists(':AssertLSPOptions')
|
||||
delcommand AssertLSPOptions
|
||||
endif
|
||||
|
||||
if exists(':AssertLSPConfig')
|
||||
delcommand AssertLSPConfig
|
||||
endif
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:OnReady = function('s:OnReady', [a:linter, l:lsp_details])
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
@@ -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 !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_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 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
|
||||
|
||||
16
autoload/ale/d.vim
Normal file
16
autoload/ale/d.vim
Normal file
@@ -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
|
||||
@@ -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',
|
||||
\]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 , '\.')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
10
autoload/ale/fixers/gomod.vim
Normal file
10
autoload/ale/fixers/gomod.vim
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
13
autoload/ale/fixers/hlint.vim
Normal file
13
autoload/ale/fixers/hlint.vim
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
18
autoload/ale/fixers/ocamlformat.vim
Normal file
18
autoload/ale/fixers/ocamlformat.vim
Normal file
@@ -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
|
||||
@@ -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')
|
||||
|
||||
@@ -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 . ' -'
|
||||
\}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
13
autoload/ale/fixers/sqlfmt.vim
Normal file
13
autoload/ale/fixers/sqlfmt.vim
Normal file
@@ -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
|
||||
21
autoload/ale/fixers/stylish_haskell.vim
Normal file
21
autoload/ale/fixers/stylish_haskell.vim
Normal file
@@ -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
|
||||
17
autoload/ale/fixers/terraform.vim
Normal file
17
autoload/ale/fixers/terraform.vim
Normal file
@@ -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
|
||||
16
autoload/ale/fixers/uncrustify.vim
Normal file
16
autoload/ale/fixers/uncrustify.vim
Normal file
@@ -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
|
||||
29
autoload/ale/fixers/xmllint.vim
Normal file
29
autoload/ale/fixers/xmllint.vim
Normal file
@@ -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
|
||||
27
autoload/ale/go.vim
Normal file
27
autoload/ale/go.vim
Normal file
@@ -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
|
||||
17
autoload/ale/handlers/ccls.vim
Normal file
17
autoload/ale/handlers/ccls.vim
Normal file
@@ -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
|
||||
28
autoload/ale/handlers/elixir.vim
Normal file
28
autoload/ale/handlers/elixir.vim
Normal file
@@ -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
|
||||
@@ -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]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -21,5 +21,6 @@ function! ale#handlers#go#Handler(buffer, lines) abort
|
||||
\ 'type': 'E',
|
||||
\})
|
||||
endfor
|
||||
|
||||
return l:output
|
||||
endfunction
|
||||
|
||||
@@ -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')
|
||||
|
||||
7
autoload/ale/handlers/haskell_stack.vim
Normal file
7
autoload/ale/handlers/haskell_stack.vim
Normal file
@@ -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
|
||||
8
autoload/ale/handlers/hlint.vim
Normal file
8
autoload/ale/handlers/hlint.vim
Normal file
@@ -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
|
||||
@@ -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',
|
||||
\])
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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
|
||||
|
||||
20
autoload/ale/java.vim
Normal file
20
autoload/ale/java.vim
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
19
autoload/ale/julia.vim
Normal file
19
autoload/ale/julia.vim
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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'))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
\ },
|
||||
\}
|
||||
" 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
|
||||
|
||||
call add(s:connections, l:conn)
|
||||
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
|
||||
" This is only needed for tests
|
||||
function! ale#lsp#MarkDocumentAsOpen(id, buffer) abort
|
||||
let l:conn = get(s:connections, a:id, {})
|
||||
|
||||
return {}
|
||||
endfunction
|
||||
|
||||
" 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
|
||||
|
||||
if get(a:capabilities, 'workspaceSymbolProvider') is v:true
|
||||
let a:conn.capabilities.symbol_search = 1
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#HandleOtherInitializeResponses(conn, response) abort
|
||||
let l:uninitialized_projects = []
|
||||
" 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, {})
|
||||
|
||||
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
|
||||
if empty(l:conn) || a:config ==# l:conn.config " no-custom-checks
|
||||
return 0
|
||||
endif
|
||||
|
||||
if empty(l:uninitialized_projects)
|
||||
let l:conn.config = a:config
|
||||
let l:message = ale#lsp#message#DidChangeConfiguration(a:buffer, a:config)
|
||||
|
||||
call ale#lsp#Send(a:conn_id, l:message)
|
||||
|
||||
return 1
|
||||
endfunction
|
||||
|
||||
|
||||
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
|
||||
|
||||
if !a:conn.initialized
|
||||
return
|
||||
endif
|
||||
|
||||
if get(a:response, 'method', '') is# ''
|
||||
if has_key(get(a:response, 'result', {}), 'capabilities')
|
||||
call s:UpdateCapabilities(a:conn, a:response.result.capabilities)
|
||||
" 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
|
||||
|
||||
for [l:dir, l:project] in l:uninitialized_projects
|
||||
call s:MarkProjectAsInitialized(a:conn, l:project)
|
||||
endfor
|
||||
" Remove the messages now.
|
||||
let a:conn.message_queue = []
|
||||
|
||||
" 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
|
||||
elseif get(a:response, 'method', '') is# 'textDocument/publishDiagnostics'
|
||||
let l:filename = ale#path#FromURI(a:response.params.uri)
|
||||
endfor
|
||||
|
||||
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
|
||||
let a:conn.capabilities_queue = []
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#HandleMessage(conn, message) abort
|
||||
if type(a:message) != type('')
|
||||
function! ale#lsp#HandleMessage(conn_id, message) abort
|
||||
let l:conn = get(s:connections, a:conn_id, {})
|
||||
|
||||
if empty(l:conn)
|
||||
return
|
||||
endif
|
||||
|
||||
if type(a:message) isnot v:t_string
|
||||
" Ignore messages that aren't strings.
|
||||
return
|
||||
endif
|
||||
|
||||
let a:conn.data .= a:message
|
||||
let l:conn.data .= a:message
|
||||
|
||||
" 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)
|
||||
let [l:conn.data, l:response_list] = ale#lsp#ReadMessageData(l:conn.data)
|
||||
|
||||
" 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)
|
||||
" 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
|
||||
|
||||
" 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 a:conn.callback_list
|
||||
call ale#util#GetFunction(l:Callback)(a:conn.id, l:response)
|
||||
for l:Callback in l:conn.callback_list
|
||||
call ale#util#GetFunction(l:Callback)(a:conn_id, l:response)
|
||||
endfor
|
||||
endif
|
||||
endfor
|
||||
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)
|
||||
|
||||
call ale#lsp#HandleMessage(l:conn, a:message)
|
||||
endfunction
|
||||
|
||||
function! s:HandleCommandMessage(job_id, message) abort
|
||||
let l:conn = s:FindConnection('id', a:job_id)
|
||||
|
||||
call ale#lsp#HandleMessage(l:conn, a:message)
|
||||
endfor
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" 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)
|
||||
|
||||
if !empty(l:conn)
|
||||
let l:conn.is_tsserver = 1
|
||||
endif
|
||||
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
|
||||
|
||||
" Register a project for an LSP connection.
|
||||
" Start a program for LSP servers.
|
||||
"
|
||||
" 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)
|
||||
" 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]
|
||||
|
||||
" 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': [],
|
||||
\}
|
||||
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, {})
|
||||
endfunction
|
||||
|
||||
" Start a program for LSP servers which run with executables.
|
||||
"
|
||||
" 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)
|
||||
|
||||
" 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 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)
|
||||
elseif has_key(l:conn, 'job_id')
|
||||
call ale#job#Stop(l:conn.job_id)
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
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 in s:connections
|
||||
if has_key(l:conn, 'channel_id')
|
||||
call ale#socket#Close(l:conn.channel_id)
|
||||
else
|
||||
call ale#job#Stop(l:conn.id)
|
||||
endif
|
||||
for l:conn_id in keys(s:connections)
|
||||
call ale#lsp#Stop(l:conn_id)
|
||||
endfor
|
||||
|
||||
let s:connections = []
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
"
|
||||
|
||||
21
autoload/ale/other_source.vim
Normal file
21
autoload/ale/other_source.vim
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
109
autoload/ale/symbol.vim
Normal file
109
autoload/ale/symbol.vim
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
136
autoload/ale/virtualtext.vim
Normal file
136
autoload/ale/virtualtext.vim
Normal file
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user