mirror of
https://github.com/amix/vimrc
synced 2025-06-16 01:25:00 +08:00
Updated plugins
This commit is contained in:
@ -169,11 +169,6 @@ 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_'.
|
||||
|
@ -26,6 +26,11 @@ function! ale#assert#Linter(expected_executable, expected_command) abort
|
||||
let l:linter = s:GetLinter()
|
||||
let l:executable = ale#linter#GetExecutable(l:buffer, l:linter)
|
||||
|
||||
while ale#command#IsDeferred(l:executable)
|
||||
call ale#test#FlushJobs()
|
||||
let l:executable = l:executable.value
|
||||
endwhile
|
||||
|
||||
if has_key(l:linter, 'command_chain')
|
||||
let l:callbacks = map(copy(l:linter.command_chain), 'v:val.callback')
|
||||
|
||||
@ -54,13 +59,18 @@ function! ale#assert#Linter(expected_executable, expected_command) abort
|
||||
endif
|
||||
else
|
||||
let l:command = ale#linter#GetCommand(l:buffer, l:linter)
|
||||
|
||||
while ale#command#IsDeferred(l:command)
|
||||
call ale#test#FlushJobs()
|
||||
let l:command = l:command.value
|
||||
endwhile
|
||||
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
|
||||
elseif type(l:command) is v:t_list
|
||||
call map(l:command, 'substitute(v:val, ''%e'', ''\=ale#Escape(l:executable)'', ''g'')')
|
||||
endif
|
||||
|
||||
@ -104,7 +114,7 @@ endfunction
|
||||
function! ale#assert#LSPProject(expected_root) abort
|
||||
let l:buffer = bufnr('')
|
||||
let l:linter = s:GetLinter()
|
||||
let l:root = ale#util#GetFunction(l:linter.project_root_callback)(l:buffer)
|
||||
let l:root = ale#lsp_linter#FindProjectRoot(l:buffer, l:linter)
|
||||
|
||||
AssertEqual a:expected_root, l:root
|
||||
endfunction
|
||||
@ -112,11 +122,22 @@ 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)
|
||||
let l:address = ale#linter#GetAddress(l:buffer, l:linter)
|
||||
|
||||
AssertEqual a:expected_address, l:address
|
||||
endfunction
|
||||
|
||||
function! ale#assert#SetUpLinterTestCommands() abort
|
||||
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
|
||||
|
||||
" 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.
|
||||
@ -129,6 +150,12 @@ function! ale#assert#SetUpLinterTest(filetype, name) abort
|
||||
let l:prefix = 'ale_' . a:filetype . '_' . a:name
|
||||
let b:filter_expr = 'v:val[: len(l:prefix) - 1] is# l:prefix'
|
||||
|
||||
Save g:ale_lsp_root
|
||||
let g:ale_lsp_root = {}
|
||||
|
||||
Save b:ale_lsp_root
|
||||
unlet! b:ale_lsp_root
|
||||
|
||||
Save g:ale_c_build_dir
|
||||
unlet! g:ale_c_build_dir
|
||||
|
||||
@ -151,14 +178,7 @@ function! ale#assert#SetUpLinterTest(filetype, name) abort
|
||||
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>)
|
||||
call ale#assert#SetUpLinterTestCommands()
|
||||
endfunction
|
||||
|
||||
function! ale#assert#TearDownLinterTest() abort
|
||||
|
@ -46,61 +46,76 @@ function! ale#c#FindProjectRoot(buffer) abort
|
||||
return ''
|
||||
endfunction
|
||||
|
||||
function! ale#c#ParseCFlags(path_prefix, cflag_line) abort
|
||||
let l:cflags_list = []
|
||||
let l:previous_options = ''
|
||||
function! ale#c#AreSpecialCharsBalanced(option) abort
|
||||
" Escape \"
|
||||
let l:option_escaped = substitute(a:option, '\\"', '', 'g')
|
||||
|
||||
let l:split_lines = split(a:cflag_line, ' ')
|
||||
" Retain special chars only
|
||||
let l:special_chars = substitute(l:option_escaped, '[^"''()`]', '', 'g')
|
||||
let l:special_chars = split(l:special_chars, '\zs')
|
||||
|
||||
" Check if they are balanced
|
||||
let l:stack = []
|
||||
|
||||
for l:char in l:special_chars
|
||||
if l:char is# ')'
|
||||
if len(l:stack) == 0 || get(l:stack, -1) isnot# '('
|
||||
return 0
|
||||
endif
|
||||
|
||||
call remove(l:stack, -1)
|
||||
elseif l:char is# '('
|
||||
call add(l:stack, l:char)
|
||||
else
|
||||
if len(l:stack) > 0 && get(l:stack, -1) is# l:char
|
||||
call remove(l:stack, -1)
|
||||
else
|
||||
call add(l:stack, l:char)
|
||||
endif
|
||||
endif
|
||||
endfor
|
||||
|
||||
return len(l:stack) == 0
|
||||
endfunction
|
||||
|
||||
function! ale#c#ParseCFlags(path_prefix, cflag_line) abort
|
||||
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:previous_options . l:split_lines[l:option_index]
|
||||
let l:option_index = l:option_index + 1
|
||||
let l:next_option_index = l:option_index + 1
|
||||
|
||||
" Check if cflag contained an unmatched characters and should not have been splitted
|
||||
let l:option_special = substitute(l:option, '\\"', '', 'g')
|
||||
let l:option_special = substitute(l:option_special, '[^"''()`]', '', 'g')
|
||||
let l:option_special = substitute(l:option_special, '""', '', 'g')
|
||||
let l:option_special = substitute(l:option_special, '''''', '', 'g')
|
||||
let l:option_special = substitute(l:option_special, '``', '', 'g')
|
||||
let l:option_special = substitute(l:option_special, '((', '(', 'g')
|
||||
let l:option_special = substitute(l:option_special, '))', ')', 'g')
|
||||
let l:option_special = substitute(l:option_special, '()', '', 'g')
|
||||
" Join space-separated option
|
||||
while l:next_option_index < len(l:split_lines)
|
||||
\&& stridx(l:split_lines[l:next_option_index], '-') != 0
|
||||
let l:next_option_index += 1
|
||||
endwhile
|
||||
|
||||
if len(l:option_special) > 0 && l:option_index < len(l:split_lines)
|
||||
let l:previous_options = l:option . ' '
|
||||
continue
|
||||
endif
|
||||
let l:option = join(l:split_lines[l:option_index : l:next_option_index-1], ' ')
|
||||
call remove(l:split_lines, l:option_index, l:next_option_index-1)
|
||||
call insert(l:split_lines, l:option, l:option_index)
|
||||
|
||||
" Check if there was spaces after -D/-I and the flag should not have been splitted
|
||||
if l:option is# '-D' || l:option is# '-I'
|
||||
let l:previous_options = l:option
|
||||
continue
|
||||
endif
|
||||
|
||||
let l:previous_options = ''
|
||||
|
||||
|
||||
" Fix relative paths if needed
|
||||
if stridx(l:option, '-I') >= 0 &&
|
||||
\ stridx(l:option, '-I' . s:sep) < 0
|
||||
let l:rel_path = join(split(l:option, '\zs')[2:], '')
|
||||
let l:rel_path = substitute(l:rel_path, '"', '', 'g')
|
||||
let l:rel_path = substitute(l:rel_path, '''', '', 'g')
|
||||
let l:option = ale#Escape('-I' . a:path_prefix .
|
||||
\ s:sep . l:rel_path)
|
||||
endif
|
||||
|
||||
" Parse the cflag
|
||||
if stridx(l:option, '-I') >= 0 ||
|
||||
\ stridx(l:option, '-D') >= 0
|
||||
if index(l:cflags_list, l:option) < 0
|
||||
call add(l:cflags_list, l:option)
|
||||
" Ignore invalid or conflicting options
|
||||
if stridx(l:option, '-') != 0
|
||||
\|| stridx(l:option, '-o') == 0
|
||||
\|| stridx(l:option, '-c') == 0
|
||||
call remove(l:split_lines, l:option_index)
|
||||
let l:option_index = l:option_index - 1
|
||||
" Fix relative path
|
||||
elseif stridx(l:option, '-I') == 0
|
||||
if !(stridx(l:option, ':') == 2+1 || stridx(l:option, '/') == 2+0)
|
||||
let l:option = '-I' . a:path_prefix . s:sep . l:option[2:]
|
||||
call remove(l:split_lines, l:option_index)
|
||||
call insert(l:split_lines, l:option, l:option_index)
|
||||
endif
|
||||
endif
|
||||
|
||||
let l:option_index = l:option_index + 1
|
||||
endwhile
|
||||
|
||||
return join(l:cflags_list, ' ')
|
||||
call uniq(l:split_lines)
|
||||
|
||||
return join(l:split_lines, ' ')
|
||||
endfunction
|
||||
|
||||
function! ale#c#ParseCFlagsFromMakeOutput(buffer, make_output) abort
|
||||
@ -187,7 +202,7 @@ function! s:GetLookupFromCompileCommandsFile(compile_commands_file) abort
|
||||
let l:file_lookup[l:basename] = get(l:file_lookup, l:basename, []) + [l:entry]
|
||||
|
||||
let l:dirbasename = tolower(fnamemodify(l:entry.directory, ':p:h:t'))
|
||||
let l:dir_lookup[l:dirbasename] = get(l:dir_lookup, l:basename, []) + [l:entry]
|
||||
let l:dir_lookup[l:dirbasename] = get(l:dir_lookup, l:dirbasename, []) + [l:entry]
|
||||
endfor
|
||||
|
||||
if !empty(l:file_lookup) && !empty(l:dir_lookup)
|
||||
@ -200,14 +215,14 @@ function! s:GetLookupFromCompileCommandsFile(compile_commands_file) abort
|
||||
return l:empty
|
||||
endfunction
|
||||
|
||||
function! ale#c#ParseCompileCommandsFlags(buffer, dir, file_lookup, dir_lookup) abort
|
||||
function! ale#c#ParseCompileCommandsFlags(buffer, file_lookup, dir_lookup) abort
|
||||
" Search for an exact file match first.
|
||||
let l:basename = tolower(expand('#' . a:buffer . ':t'))
|
||||
let l:file_list = get(a:file_lookup, l:basename, [])
|
||||
|
||||
for l:item in l:file_list
|
||||
if bufnr(l:item.file) is a:buffer
|
||||
return ale#c#ParseCFlags(a:dir, l:item.command)
|
||||
if bufnr(l:item.file) is a:buffer && has_key(l:item, 'command')
|
||||
return ale#c#ParseCFlags(l:item.directory, l:item.command)
|
||||
endif
|
||||
endfor
|
||||
|
||||
@ -219,7 +234,8 @@ function! ale#c#ParseCompileCommandsFlags(buffer, dir, file_lookup, dir_lookup)
|
||||
|
||||
for l:item in l:dir_list
|
||||
if ale#path#Simplify(fnamemodify(l:item.file, ':h')) is? l:dir
|
||||
return ale#c#ParseCFlags(a:dir, l:item.command)
|
||||
\&& has_key(l:item, 'command')
|
||||
return ale#c#ParseCFlags(l:item.directory, l:item.command)
|
||||
endif
|
||||
endfor
|
||||
|
||||
@ -227,12 +243,11 @@ function! ale#c#ParseCompileCommandsFlags(buffer, dir, file_lookup, dir_lookup)
|
||||
endfunction
|
||||
|
||||
function! ale#c#FlagsFromCompileCommands(buffer, compile_commands_file) abort
|
||||
let l:dir = ale#path#Dirname(a:compile_commands_file)
|
||||
let l:lookups = s:GetLookupFromCompileCommandsFile(a:compile_commands_file)
|
||||
let l:file_lookup = l:lookups[0]
|
||||
let l:dir_lookup = l:lookups[1]
|
||||
|
||||
return ale#c#ParseCompileCommandsFlags(a:buffer, l:dir, l:file_lookup, l:dir_lookup)
|
||||
return ale#c#ParseCompileCommandsFlags(a:buffer, l:file_lookup, l:dir_lookup)
|
||||
endfunction
|
||||
|
||||
function! ale#c#GetCFlags(buffer, output) abort
|
||||
|
@ -1,6 +1,117 @@
|
||||
" Author: w0rp <devw0rp@gmail.com>
|
||||
" Description: Special command formatting for creating temporary files and
|
||||
" passing buffer filenames easily.
|
||||
" Description: Functions for formatting command strings, running commands, and
|
||||
" managing files during linting and fixing cycles.
|
||||
|
||||
" This dictionary holds lists of files and directories to remove later.
|
||||
if !exists('s:buffer_data')
|
||||
let s:buffer_data = {}
|
||||
endif
|
||||
|
||||
" Used to get the data in tests.
|
||||
function! ale#command#GetData() abort
|
||||
return deepcopy(s:buffer_data)
|
||||
endfunction
|
||||
|
||||
function! ale#command#ClearData() abort
|
||||
let s:buffer_data = {}
|
||||
endfunction
|
||||
|
||||
function! ale#command#InitData(buffer) abort
|
||||
if !has_key(s:buffer_data, a:buffer)
|
||||
let s:buffer_data[a:buffer] = {
|
||||
\ 'jobs': {},
|
||||
\ 'file_list': [],
|
||||
\ 'directory_list': [],
|
||||
\}
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! ale#command#ManageFile(buffer, file) abort
|
||||
call ale#command#InitData(a:buffer)
|
||||
call add(s:buffer_data[a:buffer].file_list, a:file)
|
||||
endfunction
|
||||
|
||||
function! ale#command#ManageDirectory(buffer, directory) abort
|
||||
call ale#command#InitData(a:buffer)
|
||||
call add(s:buffer_data[a:buffer].directory_list, a:directory)
|
||||
endfunction
|
||||
|
||||
function! ale#command#CreateFile(buffer) abort
|
||||
" This variable can be set to 1 in tests to stub this out.
|
||||
if get(g:, 'ale_create_dummy_temporary_file')
|
||||
return 'TEMP'
|
||||
endif
|
||||
|
||||
let l:temporary_file = ale#util#Tempname()
|
||||
call ale#command#ManageFile(a:buffer, l:temporary_file)
|
||||
|
||||
return l:temporary_file
|
||||
endfunction
|
||||
|
||||
" Create a new temporary directory and manage it in one go.
|
||||
function! ale#command#CreateDirectory(buffer) abort
|
||||
" This variable can be set to 1 in tests to stub this out.
|
||||
if get(g:, 'ale_create_dummy_temporary_file')
|
||||
return 'TEMP_DIR'
|
||||
endif
|
||||
|
||||
let l:temporary_directory = ale#util#Tempname()
|
||||
" Create the temporary directory for the file, unreadable by 'other'
|
||||
" users.
|
||||
call mkdir(l:temporary_directory, '', 0750)
|
||||
call ale#command#ManageDirectory(a:buffer, l:temporary_directory)
|
||||
|
||||
return l:temporary_directory
|
||||
endfunction
|
||||
|
||||
function! ale#command#RemoveManagedFiles(buffer) abort
|
||||
let l:info = get(s:buffer_data, a:buffer, {})
|
||||
|
||||
if !empty(l:info) && empty(l:info.jobs)
|
||||
" We can't delete anything in a sandbox, so wait until we escape from
|
||||
" it to delete temporary files and directories.
|
||||
if ale#util#InSandbox()
|
||||
return
|
||||
endif
|
||||
|
||||
" Delete files with a call akin to a plan `rm` command.
|
||||
for l:filename in l:info.file_list
|
||||
call delete(l:filename)
|
||||
endfor
|
||||
|
||||
" Delete directories like `rm -rf`.
|
||||
" Directories are handled differently from files, so paths that are
|
||||
" intended to be single files can be set up for automatic deletion
|
||||
" without accidentally deleting entire directories.
|
||||
for l:directory in l:info.directory_list
|
||||
call delete(l:directory, 'rf')
|
||||
endfor
|
||||
|
||||
call remove(s:buffer_data, a:buffer)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! ale#command#CreateTempFile(buffer, temporary_file, input) abort
|
||||
if empty(a:temporary_file)
|
||||
" There is no file, so we didn't create anything.
|
||||
return 0
|
||||
endif
|
||||
|
||||
" Use an existing list of lines of input if we have it, or get the lines
|
||||
" from the file.
|
||||
let l:lines = a:input isnot v:null ? a:input : getbufline(a:buffer, 1, '$')
|
||||
|
||||
let l:temporary_directory = fnamemodify(a:temporary_file, ':h')
|
||||
" Create the temporary directory for the file, unreadable by 'other'
|
||||
" users.
|
||||
call mkdir(l:temporary_directory, '', 0750)
|
||||
" Automatically delete the directory later.
|
||||
call ale#command#ManageDirectory(a:buffer, l:temporary_directory)
|
||||
" Write the buffer out to a file.
|
||||
call ale#util#Writefile(a:buffer, l:lines, a:temporary_file)
|
||||
|
||||
return 1
|
||||
endfunction
|
||||
|
||||
function! s:TemporaryFilename(buffer) abort
|
||||
let l:filename = fnamemodify(bufname(a:buffer), ':t')
|
||||
@ -16,11 +127,17 @@ function! s:TemporaryFilename(buffer) abort
|
||||
return ale#util#Tempname() . (has('win32') ? '\' : '/') . l:filename
|
||||
endfunction
|
||||
|
||||
" Given part of a command, replace any % with %%, so that no characters in
|
||||
" the string will be replaced with filenames, etc.
|
||||
function! ale#command#EscapeCommandPart(command_part) abort
|
||||
return substitute(a:command_part, '%', '%%', 'g')
|
||||
endfunction
|
||||
|
||||
" Given a command string, replace every...
|
||||
" %s -> with the current filename
|
||||
" %t -> with the name of an unused file in a temporary directory
|
||||
" %% -> with a literal %
|
||||
function! ale#command#FormatCommand(buffer, executable, command, pipe_file_if_needed) abort
|
||||
function! ale#command#FormatCommand(buffer, executable, command, pipe_file_if_needed, input) abort
|
||||
let l:temporary_file = ''
|
||||
let l:command = a:command
|
||||
|
||||
@ -40,7 +157,7 @@ function! ale#command#FormatCommand(buffer, executable, command, pipe_file_if_ne
|
||||
let l:command = substitute(l:command, '%s', '\=ale#Escape(l:filename)', 'g')
|
||||
endif
|
||||
|
||||
if l:command =~# '%t'
|
||||
if a:input isnot v:false && l:command =~# '%t'
|
||||
" Create a temporary filename, <temp_dir>/<original_basename>
|
||||
" The file itself will not be created by this function.
|
||||
let l:temporary_file = s:TemporaryFilename(a:buffer)
|
||||
@ -58,5 +175,189 @@ function! ale#command#FormatCommand(buffer, executable, command, pipe_file_if_ne
|
||||
let l:command = l:command . ' < ' . ale#Escape(l:temporary_file)
|
||||
endif
|
||||
|
||||
return [l:temporary_file, l:command]
|
||||
let l:file_created = ale#command#CreateTempFile(
|
||||
\ a:buffer,
|
||||
\ l:temporary_file,
|
||||
\ a:input,
|
||||
\)
|
||||
|
||||
return [l:temporary_file, l:command, l:file_created]
|
||||
endfunction
|
||||
|
||||
function! ale#command#StopJobs(buffer, job_type) abort
|
||||
let l:info = get(s:buffer_data, a:buffer, {})
|
||||
|
||||
if !empty(l:info)
|
||||
let l:new_map = {}
|
||||
|
||||
for [l:job_id, l:job_type] in items(l:info.jobs)
|
||||
let l:job_id = str2nr(l:job_id)
|
||||
|
||||
if a:job_type is# 'all' || a:job_type is# l:job_type
|
||||
call ale#job#Stop(l:job_id)
|
||||
else
|
||||
let l:new_map[l:job_id] = l:job_type
|
||||
endif
|
||||
endfor
|
||||
|
||||
let l:info.jobs = l:new_map
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:GatherOutput(line_list, job_id, line) abort
|
||||
call add(a:line_list, a:line)
|
||||
endfunction
|
||||
|
||||
function! s:ExitCallback(buffer, line_list, Callback, data) abort
|
||||
if !has_key(s:buffer_data, a:buffer)
|
||||
return
|
||||
endif
|
||||
|
||||
let l:jobs = s:buffer_data[a:buffer].jobs
|
||||
|
||||
if !has_key(l:jobs, a:data.job_id)
|
||||
return
|
||||
endif
|
||||
|
||||
let l:job_type = remove(l:jobs, a:data.job_id)
|
||||
|
||||
if g:ale_history_enabled
|
||||
call ale#history#SetExitCode(a:buffer, a:data.job_id, a:data.exit_code)
|
||||
|
||||
" Log the output of the command for ALEInfo if we should.
|
||||
if g:ale_history_log_output && a:data.log_output is 1
|
||||
call ale#history#RememberOutput(
|
||||
\ a:buffer,
|
||||
\ a:data.job_id,
|
||||
\ a:line_list[:]
|
||||
\)
|
||||
endif
|
||||
endif
|
||||
|
||||
" If the callback starts any new jobs, use the same job type for them.
|
||||
call setbufvar(a:buffer, 'ale_job_type', l:job_type)
|
||||
let l:value = a:Callback(a:buffer, a:line_list, {
|
||||
\ 'exit_code': a:data.exit_code,
|
||||
\ 'temporary_file': a:data.temporary_file,
|
||||
\})
|
||||
|
||||
let l:result = a:data.result
|
||||
let l:result.value = l:value
|
||||
|
||||
if get(l:result, 'result_callback', v:null) isnot v:null
|
||||
call call(l:result.result_callback, [l:value])
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! ale#command#Run(buffer, command, Callback, ...) abort
|
||||
let l:options = get(a:000, 0, {})
|
||||
|
||||
if len(a:000) > 1
|
||||
throw 'Too many arguments!'
|
||||
endif
|
||||
|
||||
let l:output_stream = get(l:options, 'output_stream', 'stdout')
|
||||
let l:line_list = []
|
||||
|
||||
let [l:temporary_file, l:command, l:file_created] = ale#command#FormatCommand(
|
||||
\ a:buffer,
|
||||
\ get(l:options, 'executable', ''),
|
||||
\ a:command,
|
||||
\ get(l:options, 'read_buffer', 0),
|
||||
\ get(l:options, 'input', v:null),
|
||||
\)
|
||||
let l:command = ale#job#PrepareCommand(a:buffer, l:command)
|
||||
let l:job_options = {
|
||||
\ 'exit_cb': {job_id, exit_code -> s:ExitCallback(
|
||||
\ a:buffer,
|
||||
\ l:line_list,
|
||||
\ a:Callback,
|
||||
\ {
|
||||
\ 'job_id': job_id,
|
||||
\ 'exit_code': exit_code,
|
||||
\ 'temporary_file': l:temporary_file,
|
||||
\ 'log_output': get(l:options, 'log_output', 1),
|
||||
\ 'result': l:result,
|
||||
\ }
|
||||
\ )},
|
||||
\ 'mode': 'nl',
|
||||
\}
|
||||
|
||||
if l:output_stream is# 'stdout'
|
||||
let l:job_options.out_cb = function('s:GatherOutput', [l:line_list])
|
||||
elseif l:output_stream is# 'stderr'
|
||||
let l:job_options.err_cb = function('s:GatherOutput', [l:line_list])
|
||||
elseif l:output_stream is# 'both'
|
||||
let l:job_options.out_cb = function('s:GatherOutput', [l:line_list])
|
||||
let l:job_options.err_cb = function('s:GatherOutput', [l:line_list])
|
||||
endif
|
||||
|
||||
let l:status = 'failed'
|
||||
|
||||
if get(g:, 'ale_run_synchronously') == 1
|
||||
if get(g:, 'ale_emulate_job_failure') == 1
|
||||
let l:job_id = 0
|
||||
else
|
||||
" Generate a fake job ID for tests.
|
||||
let s:fake_job_id = get(s:, 'fake_job_id', 0) + 1
|
||||
let l:job_id = s:fake_job_id
|
||||
endif
|
||||
elseif has('win32')
|
||||
let l:job_id = ale#job#StartWithCmd(l:command, l:job_options)
|
||||
else
|
||||
let l:job_id = ale#job#Start(l:command, l:job_options)
|
||||
endif
|
||||
|
||||
if l:job_id
|
||||
let l:status = 'started'
|
||||
let l:job_type = getbufvar(a:buffer, 'ale_job_type', 'all')
|
||||
|
||||
call ale#command#InitData(a:buffer)
|
||||
let s:buffer_data[a:buffer].jobs[l:job_id] = l:job_type
|
||||
endif
|
||||
|
||||
if g:ale_history_enabled
|
||||
call ale#history#Add(a:buffer, l:status, l:job_id, l:command)
|
||||
endif
|
||||
|
||||
if !l:job_id
|
||||
return 0
|
||||
endif
|
||||
|
||||
" We'll return this Dictionary. A `result_callback` can be assigned to it
|
||||
" later for capturing the result of a:Callback.
|
||||
"
|
||||
" The `_deferred_job_id` is used for both checking the type of object, and
|
||||
" for checking the job ID and status.
|
||||
let l:result = {'_deferred_job_id': l:job_id}
|
||||
|
||||
if get(g:, 'ale_run_synchronously') == 1 && l:job_id
|
||||
" Run a command synchronously if this test option is set.
|
||||
call extend(l:line_list, systemlist(
|
||||
\ type(l:command) is v:t_list
|
||||
\ ? join(l:command[0:1]) . ' ' . ale#Escape(l:command[2])
|
||||
\ : l:command
|
||||
\))
|
||||
|
||||
" Don't capture output when the callbacks aren't set.
|
||||
if !has_key(l:job_options, 'out_cb')
|
||||
\&& !has_key(l:job_options, 'err_cb')
|
||||
let l:line_list = []
|
||||
endif
|
||||
|
||||
if !exists('g:ale_run_synchronously_callbacks')
|
||||
let g:ale_run_synchronously_callbacks = []
|
||||
endif
|
||||
|
||||
call add(
|
||||
\ g:ale_run_synchronously_callbacks,
|
||||
\ {-> l:job_options.exit_cb(l:job_id, v:shell_error)}
|
||||
\)
|
||||
endif
|
||||
|
||||
return l:result
|
||||
endfunction
|
||||
|
||||
function! ale#command#IsDeferred(value) abort
|
||||
return type(a:value) is v:t_dict && has_key(a:value, '_deferred_job_id')
|
||||
endfunction
|
||||
|
@ -89,6 +89,10 @@ function! ale#completion#GetPrefix(filetype, line, column) abort
|
||||
endfunction
|
||||
|
||||
function! ale#completion#GetTriggerCharacter(filetype, prefix) abort
|
||||
if empty(a:prefix)
|
||||
return ''
|
||||
endif
|
||||
|
||||
let l:char_list = s:GetFiletypeValue(s:trigger_character_map, a:filetype)
|
||||
|
||||
if index(l:char_list, a:prefix) >= 0
|
||||
@ -100,33 +104,38 @@ endfunction
|
||||
|
||||
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 index(l:triggers, a:prefix) >= 0
|
||||
if empty(a:prefix)
|
||||
let l:filtered_suggestions = a:suggestions
|
||||
else
|
||||
let l:filtered_suggestions = []
|
||||
let l:triggers = s:GetFiletypeValue(s:trigger_character_map, a:filetype)
|
||||
|
||||
" Filter suggestions down to those starting with the prefix we used for
|
||||
" finding suggestions in the first place.
|
||||
"
|
||||
" Some completion tools will include suggestions which don't even start
|
||||
" with the characters we have already typed.
|
||||
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) is v:t_string ? l:item : l:item.word
|
||||
" For completing...
|
||||
" foo.
|
||||
" ^
|
||||
" We need to include all of the given suggestions.
|
||||
if index(l:triggers, a:prefix) >= 0 || empty(a:prefix)
|
||||
let l:filtered_suggestions = a:suggestions
|
||||
else
|
||||
let l:filtered_suggestions = []
|
||||
|
||||
" Add suggestions if the suggestion starts with a case-insensitive
|
||||
" match for the prefix.
|
||||
if l:word[: len(a:prefix) - 1] is? a:prefix
|
||||
call add(l:filtered_suggestions, l:item)
|
||||
endif
|
||||
endfor
|
||||
" Filter suggestions down to those starting with the prefix we
|
||||
" used for finding suggestions in the first place.
|
||||
"
|
||||
" Some completion tools will include suggestions which don't even
|
||||
" start with the characters we have already typed.
|
||||
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) is v:t_string ? l:item : l:item.word
|
||||
|
||||
" Add suggestions if the suggestion starts with a
|
||||
" case-insensitive match for the prefix.
|
||||
if l:word[: len(a:prefix) - 1] is? a:prefix
|
||||
call add(l:filtered_suggestions, l:item)
|
||||
endif
|
||||
endfor
|
||||
endif
|
||||
endif
|
||||
|
||||
if !empty(l:excluded_words)
|
||||
@ -227,7 +236,7 @@ function! ale#completion#Show(response, completion_parser) abort
|
||||
endfunction
|
||||
|
||||
function! s:CompletionStillValid(request_id) abort
|
||||
let [l:line, l:column] = getcurpos()[1:2]
|
||||
let [l:line, l:column] = getpos('.')[1:2]
|
||||
|
||||
return ale#util#Mode() is# 'i'
|
||||
\&& has_key(b:, 'ale_completion_info')
|
||||
@ -307,7 +316,7 @@ function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort
|
||||
endfunction
|
||||
|
||||
function! ale#completion#NullFilter(buffer, item) abort
|
||||
return 1
|
||||
return 1
|
||||
endfunction
|
||||
|
||||
function! ale#completion#ParseLSPCompletions(response) abort
|
||||
@ -437,10 +446,15 @@ function! ale#completion#HandleLSPResponse(conn_id, response) abort
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! s:OnReady(linter, lsp_details, ...) abort
|
||||
let l:buffer = a:lsp_details.buffer
|
||||
function! s:OnReady(linter, lsp_details) abort
|
||||
let l:id = a:lsp_details.connection_id
|
||||
|
||||
if !ale#lsp#HasCapability(l:id, 'completion')
|
||||
return
|
||||
endif
|
||||
|
||||
let l:buffer = a:lsp_details.buffer
|
||||
|
||||
" If we have sent a completion request already, don't send another.
|
||||
if b:ale_completion_info.request_id
|
||||
return
|
||||
@ -472,7 +486,7 @@ function! s:OnReady(linter, lsp_details, ...) abort
|
||||
\ min([
|
||||
\ b:ale_completion_info.line_length,
|
||||
\ b:ale_completion_info.column,
|
||||
\ ]),
|
||||
\ ]) + 1,
|
||||
\ ale#completion#GetTriggerCharacter(&filetype, b:ale_completion_info.prefix),
|
||||
\)
|
||||
endif
|
||||
@ -489,37 +503,22 @@ function! s:OnReady(linter, lsp_details, ...) abort
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:GetLSPCompletions(linter) abort
|
||||
let l:buffer = bufnr('')
|
||||
let l:lsp_details = ale#lsp_linter#StartLSP(l:buffer, a:linter)
|
||||
|
||||
if empty(l:lsp_details)
|
||||
return 0
|
||||
endif
|
||||
|
||||
let l:id = l:lsp_details.connection_id
|
||||
|
||||
let l:OnReady = function('s:OnReady', [a:linter, l:lsp_details])
|
||||
|
||||
call ale#lsp#WaitForCapability(l:id, 'completion', l:OnReady)
|
||||
endfunction
|
||||
|
||||
function! ale#completion#GetCompletions() abort
|
||||
if !g:ale_completion_enabled
|
||||
return
|
||||
endif
|
||||
|
||||
call ale#completion#AlwaysGetCompletions()
|
||||
call ale#completion#AlwaysGetCompletions(1)
|
||||
endfunction
|
||||
|
||||
" This function can be used to manually trigger autocomplete, even when
|
||||
" g:ale_completion_enabled is set to false
|
||||
function! ale#completion#AlwaysGetCompletions() abort
|
||||
let [l:line, l:column] = getcurpos()[1:2]
|
||||
function! ale#completion#AlwaysGetCompletions(need_prefix) abort
|
||||
let [l:line, l:column] = getpos('.')[1:2]
|
||||
|
||||
let l:prefix = ale#completion#GetPrefix(&filetype, l:line, l:column)
|
||||
|
||||
if empty(l:prefix)
|
||||
if a:need_prefix && empty(l:prefix)
|
||||
return
|
||||
endif
|
||||
|
||||
@ -534,9 +533,12 @@ function! ale#completion#AlwaysGetCompletions() abort
|
||||
\ 'request_id': 0,
|
||||
\}
|
||||
|
||||
let l:buffer = bufnr('')
|
||||
let l:Callback = function('s:OnReady')
|
||||
|
||||
for l:linter in ale#linter#Get(&filetype)
|
||||
if !empty(l:linter.lsp)
|
||||
call s:GetLSPCompletions(l:linter)
|
||||
call ale#lsp_linter#StartLSP(l:buffer, l:linter, l:Callback)
|
||||
endif
|
||||
endfor
|
||||
endfunction
|
||||
@ -544,7 +546,7 @@ endfunction
|
||||
function! s:TimerHandler(...) abort
|
||||
let s:timer_id = -1
|
||||
|
||||
let [l:line, l:column] = getcurpos()[1:2]
|
||||
let [l:line, l:column] = getpos('.')[1:2]
|
||||
|
||||
" When running the timer callback, we have to be sure that the cursor
|
||||
" hasn't moved from where it was when we requested completions by typing.
|
||||
@ -567,7 +569,7 @@ function! ale#completion#Queue() abort
|
||||
return
|
||||
endif
|
||||
|
||||
let s:timer_pos = getcurpos()[1:2]
|
||||
let s:timer_pos = getpos('.')[1:2]
|
||||
|
||||
if s:timer_pos == s:last_done_pos
|
||||
" Do not ask for completions if the cursor rests on the position we
|
||||
@ -591,7 +593,7 @@ function! ale#completion#Done() abort
|
||||
|
||||
call ale#completion#RestoreCompletionOptions()
|
||||
|
||||
let s:last_done_pos = getcurpos()[1:2]
|
||||
let s:last_done_pos = getpos('.')[1:2]
|
||||
endfunction
|
||||
|
||||
function! s:Setup(enabled) abort
|
||||
|
@ -22,7 +22,7 @@ function! ale#cursor#TruncatedEcho(original_message) abort
|
||||
let l:shortmess_options = &l:shortmess
|
||||
|
||||
try
|
||||
let l:cursor_position = getcurpos()
|
||||
let l:cursor_position = getpos('.')
|
||||
|
||||
" The message is truncated and saved to the history.
|
||||
setlocal shortmess+=T
|
||||
@ -44,7 +44,7 @@ function! ale#cursor#TruncatedEcho(original_message) abort
|
||||
" 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
|
||||
" line.
|
||||
if l:cursor_position != getcurpos()
|
||||
if l:cursor_position != getpos('.')
|
||||
call setpos('.', l:cursor_position)
|
||||
endif
|
||||
finally
|
||||
@ -114,7 +114,7 @@ function! ale#cursor#EchoCursorWarningWithDelay() abort
|
||||
|
||||
call s:StopCursorTimer()
|
||||
|
||||
let l:pos = getcurpos()[0:2]
|
||||
let l:pos = getpos('.')[0:2]
|
||||
|
||||
" Check the current buffer, line, and column number against the last
|
||||
" recorded position. If the position has actually changed, *then*
|
||||
|
@ -31,6 +31,7 @@ let s:global_variable_list = [
|
||||
\ 'ale_list_vertical',
|
||||
\ 'ale_list_window_size',
|
||||
\ 'ale_loclist_msg_format',
|
||||
\ 'ale_lsp_root',
|
||||
\ 'ale_max_buffer_history_size',
|
||||
\ 'ale_max_signs',
|
||||
\ 'ale_maximum_file_size',
|
||||
|
@ -57,10 +57,15 @@ 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
|
||||
function! s:OnReady(line, column, options, capability, linter, lsp_details) abort
|
||||
let l:id = a:lsp_details.connection_id
|
||||
|
||||
if !ale#lsp#HasCapability(l:id, a:capability)
|
||||
return
|
||||
endif
|
||||
|
||||
let l:buffer = a:lsp_details.buffer
|
||||
|
||||
let l:Callback = a:linter.lsp is# 'tsserver'
|
||||
\ ? function('ale#definition#HandleTSServerResponse')
|
||||
\ : function('ale#definition#HandleLSPResponse')
|
||||
@ -80,7 +85,14 @@ function! s:OnReady(linter, lsp_details, line, column, options, ...) abort
|
||||
" 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)
|
||||
if a:capability is# 'definition'
|
||||
let l:message = ale#lsp#message#Definition(l:buffer, a:line, a:column)
|
||||
elseif a:capability is# 'typeDefinition'
|
||||
let l:message = ale#lsp#message#TypeDefinition(l:buffer, a:line, a:column)
|
||||
else
|
||||
" XXX: log here?
|
||||
return
|
||||
endif
|
||||
endif
|
||||
|
||||
let l:request_id = ale#lsp#Send(l:id, l:message)
|
||||
@ -90,30 +102,36 @@ function! s:OnReady(linter, lsp_details, line, column, options, ...) abort
|
||||
\}
|
||||
endfunction
|
||||
|
||||
function! s:GoToLSPDefinition(linter, options) abort
|
||||
function! s:GoToLSPDefinition(linter, options, capability) abort
|
||||
let l:buffer = bufnr('')
|
||||
let [l:line, l:column] = getcurpos()[1:2]
|
||||
let l:lsp_details = ale#lsp_linter#StartLSP(l:buffer, a:linter)
|
||||
let [l:line, l:column] = getpos('.')[1:2]
|
||||
let l:column = min([l:column, len(getline(l:line))])
|
||||
|
||||
if a:linter.lsp isnot# 'tsserver'
|
||||
let l:column = min([l:column, len(getline(l:line))])
|
||||
endif
|
||||
|
||||
if empty(l:lsp_details)
|
||||
return 0
|
||||
endif
|
||||
|
||||
let l:id = l:lsp_details.connection_id
|
||||
|
||||
call ale#lsp#WaitForCapability(l:id, 'definition', function('s:OnReady', [
|
||||
\ a:linter, l:lsp_details, l:line, l:column, a:options
|
||||
\]))
|
||||
let l:Callback = function(
|
||||
\ 's:OnReady',
|
||||
\ [l:line, l:column, a:options, a:capability]
|
||||
\)
|
||||
call ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback)
|
||||
endfunction
|
||||
|
||||
function! ale#definition#GoTo(options) abort
|
||||
for l:linter in ale#linter#Get(&filetype)
|
||||
if !empty(l:linter.lsp)
|
||||
call s:GoToLSPDefinition(l:linter, a:options)
|
||||
call s:GoToLSPDefinition(l:linter, a:options, 'definition')
|
||||
endif
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! ale#definition#GoToType(options) abort
|
||||
for l:linter in ale#linter#Get(&filetype)
|
||||
if !empty(l:linter.lsp)
|
||||
" TODO: handle typeDefinition for tsserver if supported by the
|
||||
" protocol
|
||||
if l:linter.lsp is# 'tsserver'
|
||||
continue
|
||||
endif
|
||||
|
||||
call s:GoToLSPDefinition(l:linter, a:options, 'typeDefinition')
|
||||
endif
|
||||
endfor
|
||||
endfunction
|
||||
|
@ -5,20 +5,10 @@
|
||||
" Remapping of linter problems.
|
||||
let g:ale_type_map = get(g:, 'ale_type_map', {})
|
||||
|
||||
" Stores information for each job including:
|
||||
"
|
||||
" linter: The linter dictionary for the job.
|
||||
" buffer: The buffer number for the job.
|
||||
" output: The array of lines for the output of the job.
|
||||
if !has_key(s:, 'job_info_map')
|
||||
let s:job_info_map = {}
|
||||
endif
|
||||
|
||||
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
|
||||
@ -34,6 +24,25 @@ function! ale#engine#CleanupEveryBuffer() abort
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! ale#engine#MarkLinterActive(info, linter) abort
|
||||
let l:found = 0
|
||||
|
||||
for l:other_linter in a:info.active_linter_list
|
||||
if l:other_linter.name is# a:linter.name
|
||||
let l:found = 1
|
||||
break
|
||||
endif
|
||||
endfor
|
||||
|
||||
if !l:found
|
||||
call add(a:info.active_linter_list, a:linter)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! ale#engine#MarkLinterInactive(info, linter) abort
|
||||
call filter(a:info.active_linter_list, 'v:val.name isnot# a:linter.name')
|
||||
endfunction
|
||||
|
||||
function! ale#engine#ResetExecutableCache() abort
|
||||
let s:executable_cache_map = {}
|
||||
endfunction
|
||||
@ -71,18 +80,12 @@ endfunction
|
||||
|
||||
function! ale#engine#InitBufferInfo(buffer) abort
|
||||
if !has_key(g:ale_buffer_info, a:buffer)
|
||||
" job_list will hold the list of job IDs
|
||||
" active_linter_list will hold the list of active linter names
|
||||
" loclist holds the loclist items after all jobs have completed.
|
||||
" temporary_file_list holds temporary files to be cleaned up
|
||||
" temporary_directory_list holds temporary directories to be cleaned up
|
||||
let g:ale_buffer_info[a:buffer] = {
|
||||
\ 'job_list': [],
|
||||
\ 'active_linter_list': [],
|
||||
\ 'active_other_sources_list': [],
|
||||
\ 'loclist': [],
|
||||
\ 'temporary_file_list': [],
|
||||
\ 'temporary_directory_list': [],
|
||||
\}
|
||||
|
||||
return 1
|
||||
@ -104,79 +107,25 @@ endfunction
|
||||
" Register a temporary file to be managed with the ALE engine for
|
||||
" a current job run.
|
||||
function! ale#engine#ManageFile(buffer, filename) abort
|
||||
call ale#engine#InitBufferInfo(a:buffer)
|
||||
call add(g:ale_buffer_info[a:buffer].temporary_file_list, a:filename)
|
||||
" TODO: Emit deprecation warning here later.
|
||||
call ale#command#ManageFile(a:buffer, a:filename)
|
||||
endfunction
|
||||
|
||||
" Same as the above, but manage an entire directory.
|
||||
function! ale#engine#ManageDirectory(buffer, directory) abort
|
||||
call ale#engine#InitBufferInfo(a:buffer)
|
||||
call add(g:ale_buffer_info[a:buffer].temporary_directory_list, a:directory)
|
||||
" TODO: Emit deprecation warning here later.
|
||||
call ale#command#ManageDirectory(a:buffer, a:directory)
|
||||
endfunction
|
||||
|
||||
function! ale#engine#CreateFile(buffer) abort
|
||||
" This variable can be set to 1 in tests to stub this out.
|
||||
if get(g:, 'ale_create_dummy_temporary_file')
|
||||
return 'TEMP'
|
||||
endif
|
||||
|
||||
let l:temporary_file = ale#util#Tempname()
|
||||
call ale#engine#ManageFile(a:buffer, l:temporary_file)
|
||||
|
||||
return l:temporary_file
|
||||
" TODO: Emit deprecation warning here later.
|
||||
return ale#command#CreateFile(a:buffer)
|
||||
endfunction
|
||||
|
||||
" Create a new temporary directory and manage it in one go.
|
||||
function! ale#engine#CreateDirectory(buffer) abort
|
||||
" This variable can be set to 1 in tests to stub this out.
|
||||
if get(g:, 'ale_create_dummy_temporary_file')
|
||||
return 'TEMP_DIR'
|
||||
endif
|
||||
|
||||
let l:temporary_directory = ale#util#Tempname()
|
||||
" Create the temporary directory for the file, unreadable by 'other'
|
||||
" users.
|
||||
call mkdir(l:temporary_directory, '', 0750)
|
||||
call ale#engine#ManageDirectory(a:buffer, l:temporary_directory)
|
||||
|
||||
return l:temporary_directory
|
||||
endfunction
|
||||
|
||||
function! ale#engine#RemoveManagedFiles(buffer) abort
|
||||
let l:info = get(g:ale_buffer_info, a:buffer, {})
|
||||
|
||||
" We can't delete anything in a sandbox, so wait until we escape from
|
||||
" it to delete temporary files and directories.
|
||||
if ale#util#InSandbox()
|
||||
return
|
||||
endif
|
||||
|
||||
" Delete files with a call akin to a plan `rm` command.
|
||||
if has_key(l:info, 'temporary_file_list')
|
||||
for l:filename in l:info.temporary_file_list
|
||||
call delete(l:filename)
|
||||
endfor
|
||||
|
||||
let l:info.temporary_file_list = []
|
||||
endif
|
||||
|
||||
" Delete directories like `rm -rf`.
|
||||
" Directories are handled differently from files, so paths that are
|
||||
" intended to be single files can be set up for automatic deletion without
|
||||
" accidentally deleting entire directories.
|
||||
if has_key(l:info, 'temporary_directory_list')
|
||||
for l:directory in l:info.temporary_directory_list
|
||||
call delete(l:directory, 'rf')
|
||||
endfor
|
||||
|
||||
let l:info.temporary_directory_list = []
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:GatherOutput(job_id, line) abort
|
||||
if has_key(s:job_info_map, a:job_id)
|
||||
call add(s:job_info_map[a:job_id].output, a:line)
|
||||
endif
|
||||
" TODO: Emit deprecation warning here later.
|
||||
return ale#command#CreateDirectory(a:buffer)
|
||||
endfunction
|
||||
|
||||
function! ale#engine#HandleLoclist(linter_name, buffer, loclist, from_other_source) abort
|
||||
@ -189,7 +138,7 @@ function! ale#engine#HandleLoclist(linter_name, buffer, loclist, from_other_sour
|
||||
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')
|
||||
call filter(l:info.active_linter_list, 'v:val.name isnot# a:linter_name')
|
||||
endif
|
||||
|
||||
" Make some adjustments to the loclists to fix common problems, and also
|
||||
@ -222,27 +171,19 @@ function! ale#engine#HandleLoclist(linter_name, buffer, loclist, from_other_sour
|
||||
call ale#engine#SetResults(a:buffer, l:info.loclist)
|
||||
endfunction
|
||||
|
||||
function! s:HandleExit(job_id, exit_code) abort
|
||||
if !has_key(s:job_info_map, a:job_id)
|
||||
function! s:HandleExit(job_info, buffer, output, data) abort
|
||||
let l:buffer_info = get(g:ale_buffer_info, a:buffer)
|
||||
|
||||
if empty(l:buffer_info)
|
||||
return
|
||||
endif
|
||||
|
||||
let l:job_info = s:job_info_map[a:job_id]
|
||||
let l:linter = l:job_info.linter
|
||||
let l:output = l:job_info.output
|
||||
let l:buffer = l:job_info.buffer
|
||||
let l:executable = l:job_info.executable
|
||||
let l:next_chain_index = l:job_info.next_chain_index
|
||||
|
||||
if g:ale_history_enabled
|
||||
call ale#history#SetExitCode(l:buffer, a:job_id, a:exit_code)
|
||||
endif
|
||||
let l:linter = a:job_info.linter
|
||||
let l:executable = a:job_info.executable
|
||||
let l:next_chain_index = a:job_info.next_chain_index
|
||||
|
||||
" Remove this job from the list.
|
||||
call ale#job#Stop(a:job_id)
|
||||
call remove(s:job_info_map, a:job_id)
|
||||
call filter(g:ale_buffer_info[l:buffer].job_list, 'v:val isnot# a:job_id')
|
||||
call filter(g:ale_buffer_info[l:buffer].active_linter_list, 'v:val isnot# l:linter.name')
|
||||
call ale#engine#MarkLinterInactive(l:buffer_info, l:linter)
|
||||
|
||||
" Stop here if we land in the handle for a job completing if we're in
|
||||
" a sandbox.
|
||||
@ -250,29 +191,32 @@ function! s:HandleExit(job_id, exit_code) abort
|
||||
return
|
||||
endif
|
||||
|
||||
if has('nvim') && !empty(l:output) && empty(l:output[-1])
|
||||
call remove(l:output, -1)
|
||||
if has('nvim') && !empty(a:output) && empty(a:output[-1])
|
||||
call remove(a:output, -1)
|
||||
endif
|
||||
|
||||
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)
|
||||
let [l:command, l:options] = ale#engine#ProcessChain(
|
||||
\ a:buffer,
|
||||
\ l:executable,
|
||||
\ l:linter,
|
||||
\ l:next_chain_index,
|
||||
\ a:output,
|
||||
\)
|
||||
|
||||
call s:RunJob(l:command, l:options)
|
||||
|
||||
return
|
||||
endif
|
||||
|
||||
" Log the output of the command for ALEInfo if we should.
|
||||
if g:ale_history_enabled && g:ale_history_log_output
|
||||
call ale#history#RememberOutput(l:buffer, a:job_id, l:output[:])
|
||||
endif
|
||||
|
||||
try
|
||||
let l:loclist = ale#util#GetFunction(l:linter.callback)(l:buffer, l:output)
|
||||
let l:loclist = ale#util#GetFunction(l:linter.callback)(a:buffer, a:output)
|
||||
" Handle the function being unknown, or being deleted.
|
||||
catch /E700/
|
||||
let l:loclist = []
|
||||
endtry
|
||||
|
||||
call ale#engine#HandleLoclist(l:linter.name, l:buffer, l:loclist, 0)
|
||||
call ale#engine#HandleLoclist(l:linter.name, a:buffer, l:loclist, 0)
|
||||
endfunction
|
||||
|
||||
function! ale#engine#SetResults(buffer, loclist) abort
|
||||
@ -321,7 +265,7 @@ function! ale#engine#SetResults(buffer, loclist) abort
|
||||
|
||||
" Automatically remove all managed temporary files and directories
|
||||
" now that all jobs have completed.
|
||||
call ale#engine#RemoveManagedFiles(a:buffer)
|
||||
call ale#command#RemoveManagedFiles(a:buffer)
|
||||
|
||||
" Call user autocommands. This allows users to hook into ALE's lint cycle.
|
||||
silent doautocmd <nomodeline> User ALELintPost
|
||||
@ -472,33 +416,23 @@ endfunction
|
||||
" Given part of a command, replace any % with %%, so that no characters in
|
||||
" the string will be replaced with filenames, etc.
|
||||
function! ale#engine#EscapeCommandPart(command_part) abort
|
||||
return substitute(a:command_part, '%', '%%', 'g')
|
||||
endfunction
|
||||
|
||||
function! s:CreateTemporaryFileForJob(buffer, temporary_file) abort
|
||||
if empty(a:temporary_file)
|
||||
" There is no file, so we didn't create anything.
|
||||
return 0
|
||||
endif
|
||||
|
||||
let l:temporary_directory = fnamemodify(a:temporary_file, ':h')
|
||||
" Create the temporary directory for the file, unreadable by 'other'
|
||||
" users.
|
||||
call mkdir(l:temporary_directory, '', 0750)
|
||||
" Automatically delete the directory later.
|
||||
call ale#engine#ManageDirectory(a:buffer, l:temporary_directory)
|
||||
" Write the buffer out to a file.
|
||||
let l:lines = getbufline(a:buffer, 1, '$')
|
||||
call ale#util#Writefile(a:buffer, l:lines, a:temporary_file)
|
||||
|
||||
return 1
|
||||
" TODO: Emit deprecation warning here later.
|
||||
return ale#command#EscapeCommandPart(a:command_part)
|
||||
endfunction
|
||||
|
||||
" Run a job.
|
||||
"
|
||||
" Returns 1 when the job was started successfully.
|
||||
function! s:RunJob(options) abort
|
||||
let l:command = a:options.command
|
||||
" Returns 1 when a job was started successfully.
|
||||
function! s:RunJob(command, options) abort
|
||||
if ale#command#IsDeferred(a:command)
|
||||
let a:command.result_callback = {
|
||||
\ command -> s:RunJob(command, a:options)
|
||||
\}
|
||||
|
||||
return 1
|
||||
endif
|
||||
|
||||
let l:command = a:command
|
||||
|
||||
if empty(l:command)
|
||||
return 0
|
||||
@ -512,201 +446,106 @@ function! s:RunJob(options) abort
|
||||
let l:read_buffer = a:options.read_buffer
|
||||
let l:info = g:ale_buffer_info[l:buffer]
|
||||
|
||||
let [l:temporary_file, l:command] = ale#command#FormatCommand(
|
||||
\ l:buffer,
|
||||
\ l:executable,
|
||||
\ l:command,
|
||||
\ l:read_buffer,
|
||||
\)
|
||||
|
||||
if s:CreateTemporaryFileForJob(l:buffer, l:temporary_file)
|
||||
" If a temporary filename has been formatted in to the command, then
|
||||
" we do not need to send the Vim buffer to the command.
|
||||
let l:read_buffer = 0
|
||||
endif
|
||||
|
||||
" Add a newline to commands which need it.
|
||||
" This is only used for Flow for now, and is not documented.
|
||||
if l:linter.add_newline
|
||||
if has('win32')
|
||||
let l:command = l:command . '; echo.'
|
||||
else
|
||||
let l:command = l:command . '; echo'
|
||||
endif
|
||||
endif
|
||||
|
||||
let l:command = ale#job#PrepareCommand(l:buffer, l:command)
|
||||
let l:job_options = {
|
||||
\ 'mode': 'nl',
|
||||
\ 'exit_cb': function('s:HandleExit'),
|
||||
\}
|
||||
|
||||
if l:output_stream is# 'stderr'
|
||||
let l:job_options.err_cb = function('s:GatherOutput')
|
||||
elseif l:output_stream is# 'both'
|
||||
let l:job_options.out_cb = function('s:GatherOutput')
|
||||
let l:job_options.err_cb = function('s:GatherOutput')
|
||||
else
|
||||
let l:job_options.out_cb = function('s:GatherOutput')
|
||||
endif
|
||||
|
||||
if get(g:, 'ale_run_synchronously') == 1
|
||||
" Find a unique Job value to use, which will be the same as the ID for
|
||||
" running commands synchronously. This is only for test code.
|
||||
let l:job_id = len(s:job_info_map) + 1
|
||||
|
||||
while has_key(s:job_info_map, l:job_id)
|
||||
let l:job_id += 1
|
||||
endwhile
|
||||
else
|
||||
let l:job_id = ale#job#Start(l:command, l:job_options)
|
||||
endif
|
||||
|
||||
let l:status = 'failed'
|
||||
let l:Callback = function('s:HandleExit', [{
|
||||
\ 'linter': l:linter,
|
||||
\ 'executable': l:executable,
|
||||
\ 'next_chain_index': l:next_chain_index,
|
||||
\}])
|
||||
let l:result = ale#command#Run(l:buffer, l:command, l:Callback, {
|
||||
\ 'output_stream': l:output_stream,
|
||||
\ 'executable': l:executable,
|
||||
\ 'read_buffer': l:read_buffer,
|
||||
\ 'log_output': l:next_chain_index >= len(get(l:linter, 'command_chain', [])),
|
||||
\})
|
||||
|
||||
" Only proceed if the job is being run.
|
||||
if l:job_id
|
||||
" Add the job to the list of jobs, so we can track them.
|
||||
call add(l:info.job_list, l:job_id)
|
||||
|
||||
if index(l:info.active_linter_list, l:linter.name) < 0
|
||||
call add(l:info.active_linter_list, l:linter.name)
|
||||
endif
|
||||
|
||||
let l:status = 'started'
|
||||
" Store the ID for the job in the map to read back again.
|
||||
let s:job_info_map[l:job_id] = {
|
||||
\ 'linter': l:linter,
|
||||
\ 'buffer': l:buffer,
|
||||
\ 'executable': l:executable,
|
||||
\ 'output': [],
|
||||
\ 'next_chain_index': l:next_chain_index,
|
||||
\}
|
||||
|
||||
silent doautocmd <nomodeline> User ALEJobStarted
|
||||
if empty(l:result)
|
||||
return 0
|
||||
endif
|
||||
|
||||
if g:ale_history_enabled
|
||||
call ale#history#Add(l:buffer, l:status, l:job_id, l:command)
|
||||
endif
|
||||
call ale#engine#MarkLinterActive(l:info, l:linter)
|
||||
|
||||
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) is v:t_list
|
||||
\ ? join(l:command[0:1]) . ' ' . ale#Escape(l:command[2])
|
||||
\ : l:command
|
||||
\)
|
||||
silent doautocmd <nomodeline> User ALEJobStarted
|
||||
|
||||
call l:job_options.exit_cb(l:job_id, v:shell_error)
|
||||
endif
|
||||
|
||||
return l:job_id != 0
|
||||
return 1
|
||||
endfunction
|
||||
|
||||
" Determine which commands to run for a link in a command chain, or
|
||||
" just a regular command.
|
||||
function! ale#engine#ProcessChain(buffer, linter, chain_index, input) abort
|
||||
function! ale#engine#ProcessChain(buffer, executable, linter, chain_index, input) abort
|
||||
let l:output_stream = get(a:linter, 'output_stream', 'stdout')
|
||||
let l:read_buffer = a:linter.read_buffer
|
||||
let l:chain_index = a:chain_index
|
||||
let l:input = a:input
|
||||
|
||||
if has_key(a:linter, 'command_chain')
|
||||
while l:chain_index < len(a:linter.command_chain)
|
||||
" Run a chain of commands, one asynchronous command after the other,
|
||||
" so that many programs can be run in a sequence.
|
||||
let l:chain_item = a:linter.command_chain[l:chain_index]
|
||||
while l:chain_index < len(a:linter.command_chain)
|
||||
" Run a chain of commands, one asynchronous command after the other,
|
||||
" so that many programs can be run in a sequence.
|
||||
let l:chain_item = a:linter.command_chain[l:chain_index]
|
||||
|
||||
if l:chain_index == 0
|
||||
" The first callback in the chain takes only a buffer number.
|
||||
let l:command = ale#util#GetFunction(l:chain_item.callback)(
|
||||
\ a:buffer
|
||||
\)
|
||||
else
|
||||
" The second callback in the chain takes some input too.
|
||||
let l:command = ale#util#GetFunction(l:chain_item.callback)(
|
||||
\ a:buffer,
|
||||
\ l:input
|
||||
\)
|
||||
if l:chain_index == 0
|
||||
" The first callback in the chain takes only a buffer number.
|
||||
let l:command = ale#util#GetFunction(l:chain_item.callback)(
|
||||
\ a:buffer
|
||||
\)
|
||||
else
|
||||
" The second callback in the chain takes some input too.
|
||||
let l:command = ale#util#GetFunction(l:chain_item.callback)(
|
||||
\ a:buffer,
|
||||
\ l:input
|
||||
\)
|
||||
endif
|
||||
|
||||
" If we have a command to run, execute that.
|
||||
if !empty(l:command)
|
||||
" The chain item can override the output_stream option.
|
||||
if has_key(l:chain_item, 'output_stream')
|
||||
let l:output_stream = l:chain_item.output_stream
|
||||
endif
|
||||
|
||||
" If we have a command to run, execute that.
|
||||
if !empty(l:command)
|
||||
" The chain item can override the output_stream option.
|
||||
if has_key(l:chain_item, 'output_stream')
|
||||
let l:output_stream = l:chain_item.output_stream
|
||||
endif
|
||||
|
||||
" The chain item can override the read_buffer option.
|
||||
if has_key(l:chain_item, 'read_buffer')
|
||||
let l:read_buffer = l:chain_item.read_buffer
|
||||
elseif l:chain_index != len(a:linter.command_chain) - 1
|
||||
" Don't read the buffer for commands besides the last one
|
||||
" in the chain by default.
|
||||
let l:read_buffer = 0
|
||||
endif
|
||||
|
||||
break
|
||||
" The chain item can override the read_buffer option.
|
||||
if has_key(l:chain_item, 'read_buffer')
|
||||
let l:read_buffer = l:chain_item.read_buffer
|
||||
elseif l:chain_index != len(a:linter.command_chain) - 1
|
||||
" Don't read the buffer for commands besides the last one
|
||||
" in the chain by default.
|
||||
let l:read_buffer = 0
|
||||
endif
|
||||
|
||||
" Command chain items can return an empty string to indicate that
|
||||
" a command should be skipped, so we should try the next item
|
||||
" with no input.
|
||||
let l:input = []
|
||||
let l:chain_index += 1
|
||||
endwhile
|
||||
else
|
||||
let l:command = ale#linter#GetCommand(a:buffer, a:linter)
|
||||
endif
|
||||
break
|
||||
endif
|
||||
|
||||
return {
|
||||
\ 'command': l:command,
|
||||
" Command chain items can return an empty string to indicate that
|
||||
" a command should be skipped, so we should try the next item
|
||||
" with no input.
|
||||
let l:input = []
|
||||
let l:chain_index += 1
|
||||
endwhile
|
||||
|
||||
return [l:command, {
|
||||
\ 'executable': a:executable,
|
||||
\ 'buffer': a:buffer,
|
||||
\ 'linter': a:linter,
|
||||
\ 'output_stream': l:output_stream,
|
||||
\ 'next_chain_index': l:chain_index + 1,
|
||||
\ 'read_buffer': l:read_buffer,
|
||||
\}
|
||||
\}]
|
||||
endfunction
|
||||
|
||||
function! s:InvokeChain(buffer, executable, linter, chain_index, input) abort
|
||||
let l:options = ale#engine#ProcessChain(a:buffer, a:linter, a:chain_index, a:input)
|
||||
let l:options.executable = a:executable
|
||||
|
||||
return s:RunJob(l:options)
|
||||
endfunction
|
||||
|
||||
function! s:StopCurrentJobs(buffer, include_lint_file_jobs) abort
|
||||
function! s:StopCurrentJobs(buffer, clear_lint_file_jobs) abort
|
||||
let l:info = get(g:ale_buffer_info, a:buffer, {})
|
||||
let l:new_job_list = []
|
||||
let l:new_active_linter_list = []
|
||||
call ale#command#StopJobs(a:buffer, 'linter')
|
||||
|
||||
for l:job_id in get(l:info, 'job_list', [])
|
||||
let l:job_info = get(s:job_info_map, l:job_id, {})
|
||||
|
||||
if !empty(l:job_info)
|
||||
if a:include_lint_file_jobs || !l:job_info.linter.lint_file
|
||||
call ale#job#Stop(l:job_id)
|
||||
call remove(s:job_info_map, l:job_id)
|
||||
else
|
||||
call add(l:new_job_list, l:job_id)
|
||||
" Linters with jobs still running are still active.
|
||||
call add(l:new_active_linter_list, l:job_info.linter.name)
|
||||
endif
|
||||
endif
|
||||
endfor
|
||||
|
||||
" Remove duplicates from the active linter list.
|
||||
call uniq(sort(l:new_active_linter_list))
|
||||
|
||||
" Update the List, so it includes only the jobs we still need.
|
||||
let l:info.job_list = l:new_job_list
|
||||
" Update the active linter list, clearing out anything not running.
|
||||
let l:info.active_linter_list = l:new_active_linter_list
|
||||
if a:clear_lint_file_jobs
|
||||
call ale#command#StopJobs(a:buffer, 'file_linter')
|
||||
let l:info.active_linter_list = []
|
||||
else
|
||||
" Keep jobs for linting files when we're only linting buffers.
|
||||
call filter(l:info.active_linter_list, 'get(v:val, ''lint_file'')')
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:RemoveProblemsForDisabledLinters(buffer, linters) abort
|
||||
" Figure out which linters are still enabled, and remove
|
||||
" problems for linters which are no longer enabled.
|
||||
@ -757,6 +596,48 @@ function! s:AddProblemsFromOtherBuffers(buffer, linters) abort
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:RunIfExecutable(buffer, linter, executable) abort
|
||||
if ale#command#IsDeferred(a:executable)
|
||||
let a:executable.result_callback = {
|
||||
\ executable -> s:RunIfExecutable(a:buffer, a:linter, executable)
|
||||
\}
|
||||
|
||||
return 1
|
||||
endif
|
||||
|
||||
if ale#engine#IsExecutable(a:buffer, a:executable)
|
||||
" Use different job types for file or linter jobs.
|
||||
let l:job_type = a:linter.lint_file ? 'file_linter' : 'linter'
|
||||
call setbufvar(a:buffer, 'ale_job_type', l:job_type)
|
||||
|
||||
if has_key(a:linter, 'command_chain')
|
||||
let [l:command, l:options] = ale#engine#ProcessChain(
|
||||
\ a:buffer,
|
||||
\ a:executable,
|
||||
\ a:linter,
|
||||
\ 0,
|
||||
\ []
|
||||
\)
|
||||
|
||||
return s:RunJob(l:command, l:options)
|
||||
endif
|
||||
|
||||
let l:command = ale#linter#GetCommand(a:buffer, a:linter)
|
||||
let l:options = {
|
||||
\ 'executable': a:executable,
|
||||
\ 'buffer': a:buffer,
|
||||
\ 'linter': a:linter,
|
||||
\ 'output_stream': get(a:linter, 'output_stream', 'stdout'),
|
||||
\ 'next_chain_index': 1,
|
||||
\ 'read_buffer': a:linter.read_buffer,
|
||||
\}
|
||||
|
||||
return s:RunJob(l:command, l:options)
|
||||
endif
|
||||
|
||||
return 0
|
||||
endfunction
|
||||
|
||||
" Run a linter for a buffer.
|
||||
"
|
||||
" Returns 1 if the linter was successfully run.
|
||||
@ -766,9 +647,7 @@ function! s:RunLinter(buffer, linter) abort
|
||||
else
|
||||
let l:executable = ale#linter#GetExecutable(a:buffer, a:linter)
|
||||
|
||||
if ale#engine#IsExecutable(a:buffer, l:executable)
|
||||
return s:InvokeChain(a:buffer, l:executable, a:linter, 0, [])
|
||||
endif
|
||||
return s:RunIfExecutable(a:buffer, a:linter, l:executable)
|
||||
endif
|
||||
|
||||
return 0
|
||||
@ -836,90 +715,3 @@ function! ale#engine#GetLoclist(buffer) abort
|
||||
|
||||
return g:ale_buffer_info[a:buffer].loclist
|
||||
endfunction
|
||||
|
||||
" This function can be called with a timeout to wait for all jobs to finish.
|
||||
" If the jobs to not finish in the given number of milliseconds,
|
||||
" an exception will be thrown.
|
||||
"
|
||||
" The time taken will be a very rough approximation, and more time may be
|
||||
" permitted than is specified.
|
||||
function! ale#engine#WaitForJobs(deadline) abort
|
||||
let l:start_time = ale#events#ClockMilliseconds()
|
||||
|
||||
if l:start_time == 0
|
||||
throw 'Failed to read milliseconds from the clock!'
|
||||
endif
|
||||
|
||||
let l:job_list = []
|
||||
|
||||
" Gather all of the jobs from every buffer.
|
||||
for l:info in values(g:ale_buffer_info)
|
||||
call extend(l:job_list, get(l:info, 'job_list', []))
|
||||
endfor
|
||||
|
||||
" NeoVim has a built-in API for this, so use that.
|
||||
if has('nvim')
|
||||
let l:nvim_code_list = jobwait(l:job_list, a:deadline)
|
||||
|
||||
if index(l:nvim_code_list, -1) >= 0
|
||||
throw 'Jobs did not complete on time!'
|
||||
endif
|
||||
|
||||
return
|
||||
endif
|
||||
|
||||
let l:should_wait_more = 1
|
||||
|
||||
while l:should_wait_more
|
||||
let l:should_wait_more = 0
|
||||
|
||||
for l:job_id in l:job_list
|
||||
if ale#job#IsRunning(l:job_id)
|
||||
let l:now = ale#events#ClockMilliseconds()
|
||||
|
||||
if l:now - l:start_time > a:deadline
|
||||
" Stop waiting after a timeout, so we don't wait forever.
|
||||
throw 'Jobs did not complete on time!'
|
||||
endif
|
||||
|
||||
" Wait another 10 milliseconds
|
||||
let l:should_wait_more = 1
|
||||
sleep 10ms
|
||||
break
|
||||
endif
|
||||
endfor
|
||||
endwhile
|
||||
|
||||
" Sleep for a small amount of time after all jobs finish.
|
||||
" This seems to be enough to let handlers after jobs end run, and
|
||||
" prevents the occasional failure where this function exits after jobs
|
||||
" end, but before handlers are run.
|
||||
sleep 10ms
|
||||
|
||||
" We must check the buffer data again to see if new jobs started
|
||||
" for command_chain linters.
|
||||
let l:has_new_jobs = 0
|
||||
|
||||
" Check again to see if any jobs are running.
|
||||
for l:info in values(g:ale_buffer_info)
|
||||
for l:job_id in get(l:info, 'job_list', [])
|
||||
if ale#job#IsRunning(l:job_id)
|
||||
let l:has_new_jobs = 1
|
||||
break
|
||||
endif
|
||||
endfor
|
||||
endfor
|
||||
|
||||
if l:has_new_jobs
|
||||
" We have to wait more. Offset the timeout by the time taken so far.
|
||||
let l:now = ale#events#ClockMilliseconds()
|
||||
let l:new_deadline = a:deadline - (l:now - l:start_time)
|
||||
|
||||
if l:new_deadline <= 0
|
||||
" Enough time passed already, so stop immediately.
|
||||
throw 'Jobs did not complete on time!'
|
||||
endif
|
||||
|
||||
call ale#engine#WaitForJobs(l:new_deadline)
|
||||
endif
|
||||
endfunction
|
||||
|
@ -5,7 +5,7 @@ function! ale#filetypes#LoadExtensionMap() abort
|
||||
" Output includes:
|
||||
" '*.erl setf erlang'
|
||||
redir => l:output
|
||||
silent exec 'autocmd'
|
||||
silent exec 'autocmd'
|
||||
redir end
|
||||
|
||||
let l:map = {}
|
||||
|
@ -1,13 +1,3 @@
|
||||
if !has_key(s:, 'job_info_map')
|
||||
let s:job_info_map = {}
|
||||
endif
|
||||
|
||||
function! s:GatherOutput(job_id, line) abort
|
||||
if has_key(s:job_info_map, a:job_id)
|
||||
call add(s:job_info_map[a:job_id].output, a:line)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" Apply fixes queued up for buffers which may be hidden.
|
||||
" Vim doesn't let you modify hidden buffers.
|
||||
function! ale#fix#ApplyQueuedFixes() abort
|
||||
@ -66,11 +56,17 @@ function! ale#fix#ApplyQueuedFixes() abort
|
||||
endfunction
|
||||
|
||||
function! ale#fix#ApplyFixes(buffer, output) abort
|
||||
call ale#fix#RemoveManagedFiles(a:buffer)
|
||||
|
||||
let l:data = g:ale_fix_buffer_data[a:buffer]
|
||||
let l:data.output = a:output
|
||||
let l:data.changes_made = l:data.lines_before != l:data.output
|
||||
let l:data.done = 1
|
||||
|
||||
call ale#command#RemoveManagedFiles(a:buffer)
|
||||
|
||||
if !bufexists(a:buffer)
|
||||
" Remove the buffer data when it doesn't exist.
|
||||
call remove(g:ale_fix_buffer_data, a:buffer)
|
||||
endif
|
||||
|
||||
if l:data.changes_made && bufexists(a:buffer)
|
||||
let l:lines = getbufline(a:buffer, 1, '$')
|
||||
@ -83,279 +79,174 @@ function! ale#fix#ApplyFixes(buffer, output) abort
|
||||
endif
|
||||
endif
|
||||
|
||||
if !bufexists(a:buffer)
|
||||
" Remove the buffer data when it doesn't exist.
|
||||
call remove(g:ale_fix_buffer_data, a:buffer)
|
||||
endif
|
||||
|
||||
let l:data.done = 1
|
||||
|
||||
" We can only change the lines of a buffer which is currently open,
|
||||
" so try and apply the fixes to the current buffer.
|
||||
call ale#fix#ApplyQueuedFixes()
|
||||
endfunction
|
||||
|
||||
function! s:HandleExit(job_id, exit_code) abort
|
||||
if !has_key(s:job_info_map, a:job_id)
|
||||
function! s:HandleExit(job_info, buffer, job_output, data) abort
|
||||
let l:buffer_info = get(g:ale_fix_buffer_data, a:buffer, {})
|
||||
|
||||
if empty(l:buffer_info)
|
||||
return
|
||||
endif
|
||||
|
||||
let l:job_info = remove(s:job_info_map, a:job_id)
|
||||
let l:buffer = l:job_info.buffer
|
||||
|
||||
if g:ale_history_enabled
|
||||
call ale#history#SetExitCode(l:buffer, a:job_id, a:exit_code)
|
||||
if a:job_info.read_temporary_file
|
||||
let l:output = !empty(a:data.temporary_file)
|
||||
\ ? readfile(a:data.temporary_file)
|
||||
\ : []
|
||||
else
|
||||
let l:output = a:job_output
|
||||
endif
|
||||
|
||||
if has_key(l:job_info, 'file_to_read')
|
||||
let l:job_info.output = readfile(l:job_info.file_to_read)
|
||||
endif
|
||||
|
||||
let l:ChainCallback = get(l:job_info, 'chain_with', v:null)
|
||||
let l:ProcessWith = get(l:job_info, 'process_with', v:null)
|
||||
let l:ChainCallback = get(a:job_info, 'chain_with', v:null)
|
||||
let l:ProcessWith = get(a:job_info, 'process_with', v:null)
|
||||
|
||||
" Post-process the output with a function if we have one.
|
||||
if l:ProcessWith isnot v:null
|
||||
let l:job_info.output = call(
|
||||
\ ale#util#GetFunction(l:ProcessWith),
|
||||
\ [l:buffer, l:job_info.output]
|
||||
\)
|
||||
let l:output = call(l:ProcessWith, [a:buffer, l:output])
|
||||
endif
|
||||
|
||||
" Use the output of the job for changing the file if it isn't empty,
|
||||
" otherwise skip this job and use the input from before.
|
||||
"
|
||||
" We'll use the input from before for chained commands.
|
||||
if l:ChainCallback is v:null && !empty(split(join(l:job_info.output)))
|
||||
let l:input = l:job_info.output
|
||||
if l:ChainCallback is v:null && !empty(split(join(l:output)))
|
||||
let l:input = l:output
|
||||
else
|
||||
let l:input = l:job_info.input
|
||||
let l:input = a:job_info.input
|
||||
endif
|
||||
|
||||
let l:next_index = l:ChainCallback is v:null
|
||||
\ ? l:job_info.callback_index + 1
|
||||
\ : l:job_info.callback_index
|
||||
\ ? a:job_info.callback_index + 1
|
||||
\ : a:job_info.callback_index
|
||||
|
||||
call s:RunFixer({
|
||||
\ 'buffer': l:buffer,
|
||||
\ 'buffer': a:buffer,
|
||||
\ 'input': l:input,
|
||||
\ 'output': l:job_info.output,
|
||||
\ 'callback_list': l:job_info.callback_list,
|
||||
\ 'output': l:output,
|
||||
\ 'callback_list': a:job_info.callback_list,
|
||||
\ 'callback_index': l:next_index,
|
||||
\ 'chain_callback': l:ChainCallback,
|
||||
\})
|
||||
endfunction
|
||||
|
||||
function! ale#fix#ManageDirectory(buffer, directory) abort
|
||||
call add(g:ale_fix_buffer_data[a:buffer].temporary_directory_list, a:directory)
|
||||
endfunction
|
||||
function! s:RunJob(result, options) abort
|
||||
if ale#command#IsDeferred(a:result)
|
||||
let a:result.result_callback = {x -> s:RunJob(x, a:options)}
|
||||
|
||||
function! ale#fix#RemoveManagedFiles(buffer) abort
|
||||
if !has_key(g:ale_fix_buffer_data, a:buffer)
|
||||
return
|
||||
endif
|
||||
|
||||
" We can't delete anything in a sandbox, so wait until we escape from
|
||||
" it to delete temporary files and directories.
|
||||
if ale#util#InSandbox()
|
||||
return
|
||||
endif
|
||||
|
||||
" Delete directories like `rm -rf`.
|
||||
" Directories are handled differently from files, so paths that are
|
||||
" intended to be single files can be set up for automatic deletion without
|
||||
" accidentally deleting entire directories.
|
||||
for l:directory in g:ale_fix_buffer_data[a:buffer].temporary_directory_list
|
||||
call delete(l:directory, 'rf')
|
||||
endfor
|
||||
|
||||
let g:ale_fix_buffer_data[a:buffer].temporary_directory_list = []
|
||||
endfunction
|
||||
|
||||
function! s:CreateTemporaryFileForJob(buffer, temporary_file, input) abort
|
||||
if empty(a:temporary_file)
|
||||
" There is no file, so we didn't create anything.
|
||||
return 0
|
||||
endif
|
||||
|
||||
let l:temporary_directory = fnamemodify(a:temporary_file, ':h')
|
||||
" Create the temporary directory for the file, unreadable by 'other'
|
||||
" users.
|
||||
call mkdir(l:temporary_directory, '', 0750)
|
||||
" Automatically delete the directory later.
|
||||
call ale#fix#ManageDirectory(a:buffer, l:temporary_directory)
|
||||
" Write the buffer out to a file.
|
||||
call ale#util#Writefile(a:buffer, a:input, a:temporary_file)
|
||||
|
||||
return 1
|
||||
endfunction
|
||||
|
||||
function! s:RunJob(options) abort
|
||||
let l:buffer = a:options.buffer
|
||||
let l:command = a:options.command
|
||||
let l:input = a:options.input
|
||||
let l:output_stream = a:options.output_stream
|
||||
let l:read_temporary_file = a:options.read_temporary_file
|
||||
let l:ChainWith = a:options.chain_with
|
||||
let l:read_buffer = a:options.read_buffer
|
||||
|
||||
if empty(l:command)
|
||||
" If there's nothing further to chain the command with, stop here.
|
||||
if l:ChainWith is v:null
|
||||
return 0
|
||||
if a:result is 0 || type(a:result) is v:t_list
|
||||
if type(a:result) is v:t_list
|
||||
let l:input = a:result
|
||||
endif
|
||||
|
||||
" If there's another chained callback to run, then run that.
|
||||
call s:RunFixer({
|
||||
\ 'buffer': l:buffer,
|
||||
\ 'input': l:input,
|
||||
\ 'callback_index': a:options.callback_index,
|
||||
\ 'callback_index': a:options.callback_index + 1,
|
||||
\ 'callback_list': a:options.callback_list,
|
||||
\})
|
||||
|
||||
return
|
||||
endif
|
||||
|
||||
let l:command = get(a:result, 'command', '')
|
||||
let l:ChainWith = get(a:result, 'chain_with', v:null)
|
||||
|
||||
if empty(l:command)
|
||||
" If the command is empty, skip to the next item, or call the
|
||||
" chain_with function.
|
||||
call s:RunFixer({
|
||||
\ 'buffer': l:buffer,
|
||||
\ 'input': l:input,
|
||||
\ 'callback_index': a:options.callback_index + (l:ChainWith is v:null),
|
||||
\ 'callback_list': a:options.callback_list,
|
||||
\ 'chain_callback': l:ChainWith,
|
||||
\ 'output': [],
|
||||
\})
|
||||
|
||||
return 1
|
||||
return
|
||||
endif
|
||||
|
||||
let [l:temporary_file, l:command] = ale#command#FormatCommand(
|
||||
\ l:buffer,
|
||||
\ '',
|
||||
\ l:command,
|
||||
\ l:read_buffer,
|
||||
\)
|
||||
call s:CreateTemporaryFileForJob(l:buffer, l:temporary_file, l:input)
|
||||
let l:read_temporary_file = get(a:result, 'read_temporary_file', 0)
|
||||
" Default to piping the buffer for the last fixer in the chain.
|
||||
let l:read_buffer = get(a:result, 'read_buffer', l:ChainWith is v:null)
|
||||
let l:output_stream = get(a:result, 'output_stream', 'stdout')
|
||||
|
||||
let l:command = ale#job#PrepareCommand(l:buffer, l:command)
|
||||
let l:job_options = {
|
||||
\ 'mode': 'nl',
|
||||
\ 'exit_cb': function('s:HandleExit'),
|
||||
\}
|
||||
if l:read_temporary_file
|
||||
let l:output_stream = 'none'
|
||||
endif
|
||||
|
||||
let l:job_info = {
|
||||
\ 'buffer': l:buffer,
|
||||
let l:Callback = function('s:HandleExit', [{
|
||||
\ 'input': l:input,
|
||||
\ 'output': [],
|
||||
\ 'chain_with': l:ChainWith,
|
||||
\ 'callback_index': a:options.callback_index,
|
||||
\ 'callback_list': a:options.callback_list,
|
||||
\ 'process_with': a:options.process_with,
|
||||
\}
|
||||
\ 'process_with': get(a:result, 'process_with', v:null),
|
||||
\ 'read_temporary_file': l:read_temporary_file,
|
||||
\}])
|
||||
let l:run_result = ale#command#Run(l:buffer, l:command, l:Callback, {
|
||||
\ 'output_stream': l:output_stream,
|
||||
\ 'executable': '',
|
||||
\ 'read_buffer': l:read_buffer,
|
||||
\ 'input': l:input,
|
||||
\ 'log_output': 0,
|
||||
\})
|
||||
|
||||
if l:read_temporary_file
|
||||
" TODO: Check that a temporary file is set here.
|
||||
let l:job_info.file_to_read = l:temporary_file
|
||||
elseif l:output_stream is# 'stderr'
|
||||
let l:job_options.err_cb = function('s:GatherOutput')
|
||||
elseif l:output_stream is# 'both'
|
||||
let l:job_options.out_cb = function('s:GatherOutput')
|
||||
let l:job_options.err_cb = function('s:GatherOutput')
|
||||
else
|
||||
let l:job_options.out_cb = function('s:GatherOutput')
|
||||
if empty(l:run_result)
|
||||
call s:RunFixer({
|
||||
\ 'buffer': l:buffer,
|
||||
\ 'input': l:input,
|
||||
\ 'callback_index': a:options.callback_index + 1,
|
||||
\ 'callback_list': a:options.callback_list,
|
||||
\})
|
||||
endif
|
||||
|
||||
if get(g:, 'ale_emulate_job_failure') == 1
|
||||
let l:job_id = 0
|
||||
elseif get(g:, 'ale_run_synchronously') == 1
|
||||
" Find a unique Job value to use, which will be the same as the ID for
|
||||
" running commands synchronously. This is only for test code.
|
||||
let l:job_id = len(s:job_info_map) + 1
|
||||
|
||||
while has_key(s:job_info_map, l:job_id)
|
||||
let l:job_id += 1
|
||||
endwhile
|
||||
else
|
||||
let l:job_id = ale#job#Start(l:command, l:job_options)
|
||||
endif
|
||||
|
||||
let l:status = l:job_id ? 'started' : 'failed'
|
||||
|
||||
if g:ale_history_enabled
|
||||
call ale#history#Add(l:buffer, l:status, l:job_id, l:command)
|
||||
endif
|
||||
|
||||
if l:job_id == 0
|
||||
return 0
|
||||
endif
|
||||
|
||||
let s:job_info_map[l:job_id] = l:job_info
|
||||
|
||||
if get(g:, 'ale_run_synchronously') == 1
|
||||
" Run a command synchronously if this test option is set.
|
||||
let l:output = systemlist(
|
||||
\ type(l:command) is v:t_list
|
||||
\ ? join(l:command[0:1]) . ' ' . ale#Escape(l:command[2])
|
||||
\ : l:command
|
||||
\)
|
||||
|
||||
if !l:read_temporary_file
|
||||
let s:job_info_map[l:job_id].output = l:output
|
||||
endif
|
||||
|
||||
call l:job_options.exit_cb(l:job_id, v:shell_error)
|
||||
endif
|
||||
|
||||
return 1
|
||||
endfunction
|
||||
|
||||
function! s:RunFixer(options) abort
|
||||
let l:buffer = a:options.buffer
|
||||
let l:input = a:options.input
|
||||
let l:index = a:options.callback_index
|
||||
|
||||
if len(a:options.callback_list) <= l:index
|
||||
call ale#fix#ApplyFixes(l:buffer, l:input)
|
||||
|
||||
return
|
||||
endif
|
||||
|
||||
let l:ChainCallback = get(a:options, 'chain_callback', v:null)
|
||||
|
||||
while len(a:options.callback_list) > l:index
|
||||
let l:Function = l:ChainCallback isnot v:null
|
||||
\ ? ale#util#GetFunction(l:ChainCallback)
|
||||
\ : a:options.callback_list[l:index]
|
||||
let l:Function = l:ChainCallback isnot v:null
|
||||
\ ? ale#util#GetFunction(l:ChainCallback)
|
||||
\ : a:options.callback_list[l:index]
|
||||
|
||||
if l:ChainCallback isnot v:null
|
||||
" Chained commands accept (buffer, output, [input])
|
||||
let l:result = ale#util#FunctionArgCount(l:Function) == 2
|
||||
\ ? call(l:Function, [l:buffer, a:options.output])
|
||||
\ : call(l:Function, [l:buffer, a:options.output, copy(l:input)])
|
||||
else
|
||||
" Chained commands accept (buffer, [done, input])
|
||||
let l:result = ale#util#FunctionArgCount(l:Function) == 1
|
||||
\ ? call(l:Function, [l:buffer])
|
||||
\ : call(l:Function, [l:buffer, v:null, copy(l:input)])
|
||||
endif
|
||||
" Record new jobs started as fixer jobs.
|
||||
call setbufvar(l:buffer, 'ale_job_type', 'fixer')
|
||||
|
||||
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) is v:t_list
|
||||
let l:input = l:result
|
||||
let l:index += 1
|
||||
else
|
||||
let l:ChainWith = get(l:result, 'chain_with', v:null)
|
||||
" Default to piping the buffer for the last fixer in the chain.
|
||||
let l:read_buffer = get(l:result, 'read_buffer', l:ChainWith is v:null)
|
||||
if l:ChainCallback isnot v:null
|
||||
" Chained commands accept (buffer, output, [input])
|
||||
let l:result = ale#util#FunctionArgCount(l:Function) == 2
|
||||
\ ? call(l:Function, [l:buffer, a:options.output])
|
||||
\ : call(l:Function, [l:buffer, a:options.output, copy(l:input)])
|
||||
else
|
||||
" Regular fixer commands accept (buffer, [input])
|
||||
let l:result = ale#util#FunctionArgCount(l:Function) == 1
|
||||
\ ? call(l:Function, [l:buffer])
|
||||
\ : call(l:Function, [l:buffer, copy(l:input)])
|
||||
endif
|
||||
|
||||
let l:job_ran = s:RunJob({
|
||||
\ 'buffer': l:buffer,
|
||||
\ 'command': l:result.command,
|
||||
\ 'input': l:input,
|
||||
\ 'output_stream': get(l:result, 'output_stream', 'stdout'),
|
||||
\ 'read_temporary_file': get(l:result, 'read_temporary_file', 0),
|
||||
\ 'read_buffer': l:read_buffer,
|
||||
\ 'chain_with': l:ChainWith,
|
||||
\ 'callback_list': a:options.callback_list,
|
||||
\ 'callback_index': l:index,
|
||||
\ 'process_with': get(l:result, 'process_with', v:null),
|
||||
\})
|
||||
|
||||
if !l:job_ran
|
||||
" The job failed to run, so skip to the next item.
|
||||
let l:index += 1
|
||||
else
|
||||
" Stop here, we will handle exit later on.
|
||||
return
|
||||
endif
|
||||
endif
|
||||
endwhile
|
||||
|
||||
call ale#fix#ApplyFixes(l:buffer, l:input)
|
||||
call s:RunJob(l:result, {
|
||||
\ 'buffer': l:buffer,
|
||||
\ 'input': l:input,
|
||||
\ 'callback_list': a:options.callback_list,
|
||||
\ 'callback_index': l:index,
|
||||
\})
|
||||
endfunction
|
||||
|
||||
function! s:AddSubCallbacks(full_list, callbacks) abort
|
||||
@ -464,13 +355,9 @@ function! ale#fix#Fix(buffer, fixing_flag, ...) abort
|
||||
return 0
|
||||
endif
|
||||
|
||||
for l:job_id in keys(s:job_info_map)
|
||||
call remove(s:job_info_map, l:job_id)
|
||||
call ale#job#Stop(l:job_id)
|
||||
endfor
|
||||
|
||||
call ale#command#StopJobs(a:buffer, 'fixer')
|
||||
" Clean up any files we might have left behind from a previous run.
|
||||
call ale#fix#RemoveManagedFiles(a:buffer)
|
||||
call ale#command#RemoveManagedFiles(a:buffer)
|
||||
call ale#fix#InitBufferData(a:buffer, a:fixing_flag)
|
||||
|
||||
silent doautocmd <nomodeline> User ALEFixPre
|
||||
|
@ -122,7 +122,7 @@ let s:default_registry = {
|
||||
\ },
|
||||
\ 'stylelint': {
|
||||
\ 'function': 'ale#fixers#stylelint#Fix',
|
||||
\ 'suggested_filetypes': ['css', 'sass', 'scss', 'stylus'],
|
||||
\ 'suggested_filetypes': ['css', 'sass', 'scss', 'sugarss', 'stylus'],
|
||||
\ 'description': 'Fix stylesheet files using stylelint --fix.',
|
||||
\ },
|
||||
\ 'swiftformat': {
|
||||
@ -145,6 +145,11 @@ let s:default_registry = {
|
||||
\ 'suggested_filetypes': ['c', 'cpp'],
|
||||
\ 'description': 'Fix C/C++ files with clang-format.',
|
||||
\ },
|
||||
\ 'cmakeformat': {
|
||||
\ 'function': 'ale#fixers#cmakeformat#Fix',
|
||||
\ 'suggested_filetypes': ['cmake'],
|
||||
\ 'description': 'Fix CMake files with cmake-format.',
|
||||
\ },
|
||||
\ 'gofmt': {
|
||||
\ 'function': 'ale#fixers#gofmt#Fix',
|
||||
\ 'suggested_filetypes': ['go'],
|
||||
@ -170,6 +175,11 @@ let s:default_registry = {
|
||||
\ 'suggested_filetypes': ['rust'],
|
||||
\ 'description': 'Fix Rust files with Rustfmt.',
|
||||
\ },
|
||||
\ 'textlint': {
|
||||
\ 'function': 'ale#fixers#textlint#Fix',
|
||||
\ 'suggested_filetypes': ['text','markdown','asciidoc','tex'],
|
||||
\ 'description': 'Fix text files with textlint --fix',
|
||||
\ },
|
||||
\ 'hackfmt': {
|
||||
\ 'function': 'ale#fixers#hackfmt#Fix',
|
||||
\ 'suggested_filetypes': ['hack'],
|
||||
@ -265,6 +275,11 @@ let s:default_registry = {
|
||||
\ 'suggested_filetypes': ['hcl', 'terraform'],
|
||||
\ 'description': 'Fix tf and hcl files with terraform fmt.',
|
||||
\ },
|
||||
\ 'ktlint': {
|
||||
\ 'function': 'ale#fixers#ktlint#Fix',
|
||||
\ 'suggested_filetypes': ['kt'],
|
||||
\ 'description': 'Fix Kotlin files with ktlint.',
|
||||
\ },
|
||||
\}
|
||||
|
||||
" Reset the function registry to the default entries.
|
||||
|
@ -5,6 +5,7 @@ call ale#Set('python_black_executable', 'black')
|
||||
call ale#Set('python_black_use_global', get(g:, 'ale_use_global_executables', 0))
|
||||
call ale#Set('python_black_options', '')
|
||||
call ale#Set('python_black_auto_pipenv', 0)
|
||||
call ale#Set('python_black_change_directory', 1)
|
||||
|
||||
function! ale#fixers#black#GetExecutable(buffer) abort
|
||||
if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_black_auto_pipenv'))
|
||||
@ -16,6 +17,10 @@ function! ale#fixers#black#GetExecutable(buffer) abort
|
||||
endfunction
|
||||
|
||||
function! ale#fixers#black#Fix(buffer) abort
|
||||
let l:cd_string = ale#Var(a:buffer, 'python_black_change_directory')
|
||||
\ ? ale#path#BufferCdString(a:buffer)
|
||||
\ : ''
|
||||
|
||||
let l:executable = ale#fixers#black#GetExecutable(a:buffer)
|
||||
|
||||
let l:exec_args = l:executable =~? 'pipenv$'
|
||||
@ -25,7 +30,7 @@ function! ale#fixers#black#Fix(buffer) abort
|
||||
let l:options = ale#Var(a:buffer, 'python_black_options')
|
||||
|
||||
return {
|
||||
\ 'command': ale#Escape(l:executable) . l:exec_args
|
||||
\ 'command': l:cd_string . ale#Escape(l:executable) . l:exec_args
|
||||
\ . (!empty(l:options) ? ' ' . l:options : '')
|
||||
\ . ' -',
|
||||
\}
|
||||
|
@ -1,7 +1,7 @@
|
||||
" Author: w0rp <devw0rp@gmail.com>
|
||||
" Description: Generic functions for fixing files with.
|
||||
|
||||
function! ale#fixers#generic#RemoveTrailingBlankLines(buffer, done, lines) abort
|
||||
function! ale#fixers#generic#RemoveTrailingBlankLines(buffer, lines) abort
|
||||
let l:end_index = len(a:lines) - 1
|
||||
|
||||
while l:end_index > 0 && empty(a:lines[l:end_index])
|
||||
@ -12,7 +12,7 @@ function! ale#fixers#generic#RemoveTrailingBlankLines(buffer, done, lines) abort
|
||||
endfunction
|
||||
|
||||
" Remove all whitespaces at the end of lines
|
||||
function! ale#fixers#generic#TrimWhitespace(buffer, done, lines) abort
|
||||
function! ale#fixers#generic#TrimWhitespace(buffer, lines) abort
|
||||
let l:index = 0
|
||||
let l:lines_new = range(len(a:lines))
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
" Description: Generic fixer functions for Python.
|
||||
|
||||
" Add blank lines before control statements.
|
||||
function! ale#fixers#generic_python#AddLinesBeforeControlStatements(buffer, done, lines) abort
|
||||
function! ale#fixers#generic_python#AddLinesBeforeControlStatements(buffer, lines) abort
|
||||
let l:new_lines = []
|
||||
let l:last_indent_size = 0
|
||||
let l:last_line_is_blank = 0
|
||||
@ -41,7 +41,7 @@ endfunction
|
||||
|
||||
" This function breaks up long lines so that autopep8 or other tools can
|
||||
" fix the badly-indented code which is produced as a result.
|
||||
function! ale#fixers#generic_python#BreakUpLongLines(buffer, done, lines) abort
|
||||
function! ale#fixers#generic_python#BreakUpLongLines(buffer, lines) abort
|
||||
" Default to a maximum line length of 79
|
||||
let l:max_line_length = 79
|
||||
let l:conf = ale#path#FindNearestFile(a:buffer, 'setup.cfg')
|
||||
|
@ -1,7 +1,7 @@
|
||||
" Author: w0rp <devw0rp@gmail.com>
|
||||
" Description: Generic fixer functions for Vim help documents.
|
||||
|
||||
function! ale#fixers#help#AlignTags(buffer, done, lines) abort
|
||||
function! ale#fixers#help#AlignTags(buffer, lines) abort
|
||||
let l:new_lines = []
|
||||
|
||||
for l:line in a:lines
|
||||
|
@ -7,16 +7,16 @@ function! ale#fixers#jq#GetExecutable(buffer) abort
|
||||
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')
|
||||
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
|
||||
if empty(l:filters)
|
||||
return 0
|
||||
endif
|
||||
|
||||
return {
|
||||
\ 'command': ale#Escape(ale#fixers#jq#GetExecutable(a:buffer))
|
||||
\ . ' ' . l:filters . ' '
|
||||
\ . l:options,
|
||||
\}
|
||||
return {
|
||||
\ 'command': ale#Escape(ale#fixers#jq#GetExecutable(a:buffer))
|
||||
\ . ' ' . l:filters . ' '
|
||||
\ . l:options,
|
||||
\}
|
||||
endfunction
|
||||
|
@ -47,6 +47,15 @@ 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
|
||||
" Mimic Prettier's defaults. In cases without a file extension or
|
||||
" filetype (scratch buffer), Prettier needs `parser` set to know how
|
||||
" to process the buffer.
|
||||
if ale#semver#GTE(l:version, [1, 16, 0])
|
||||
let l:parser = 'babel'
|
||||
else
|
||||
let l:parser = 'babylon'
|
||||
endif
|
||||
|
||||
let l:prettier_parsers = {
|
||||
\ 'typescript': 'typescript',
|
||||
\ 'css': 'css',
|
||||
@ -60,7 +69,6 @@ function! ale#fixers#prettier#ApplyFixForVersion(buffer, version_output) abort
|
||||
\ 'yaml': 'yaml',
|
||||
\ 'html': 'html',
|
||||
\}
|
||||
let l:parser = ''
|
||||
|
||||
for l:filetype in split(getbufvar(a:buffer, '&filetype'), '\.')
|
||||
if has_key(l:prettier_parsers, l:filetype)
|
||||
|
@ -5,7 +5,7 @@ function! ale#fixers#qmlfmt#GetExecutable(buffer) abort
|
||||
endfunction
|
||||
|
||||
function! ale#fixers#qmlfmt#Fix(buffer) abort
|
||||
return {
|
||||
\ 'command': ale#Escape(ale#fixers#qmlfmt#GetExecutable(a:buffer)),
|
||||
\}
|
||||
return {
|
||||
\ 'command': ale#Escape(ale#fixers#qmlfmt#GetExecutable(a:buffer)),
|
||||
\}
|
||||
endfunction
|
||||
|
@ -5,13 +5,12 @@ call ale#Set('stylelint_executable', 'stylelint')
|
||||
call ale#Set('stylelint_use_global', get(g:, 'ale_use_global_executables', 0))
|
||||
|
||||
function! ale#fixers#stylelint#GetExecutable(buffer) abort
|
||||
return ale#node#FindExecutable(a:buffer, 'stylelint', [
|
||||
\ 'node_modules/stylelint/bin/stylelint.js',
|
||||
\ 'node_modules/.bin/stylelint',
|
||||
\])
|
||||
return ale#node#FindExecutable(a:buffer, 'stylelint', [
|
||||
\ 'node_modules/stylelint/bin/stylelint.js',
|
||||
\ 'node_modules/.bin/stylelint',
|
||||
\])
|
||||
endfunction
|
||||
|
||||
|
||||
function! ale#fixers#stylelint#Fix(buffer) abort
|
||||
let l:executable = ale#fixers#stylelint#GetExecutable(a:buffer)
|
||||
|
||||
|
@ -9,18 +9,18 @@ function! ale#go#FindProjectRoot(buffer) abort
|
||||
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)
|
||||
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
|
||||
" 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
|
||||
return l:default_go_path
|
||||
endif
|
||||
|
||||
return ''
|
||||
|
@ -1,10 +1,23 @@
|
||||
" Author: Johannes Wienke <languitar@semipol.de>
|
||||
" Description: Error handling for errors in alex output format
|
||||
|
||||
function! ale#handlers#alex#GetExecutable(buffer) abort
|
||||
return ale#node#FindExecutable(a:buffer, 'alex', [
|
||||
\ 'node_modules/.bin/alex',
|
||||
\ 'node_modules/alex/cli.js',
|
||||
\])
|
||||
endfunction
|
||||
|
||||
function! ale#handlers#alex#CreateCommandCallback(flags) abort
|
||||
return {b -> ale#node#Executable(b, ale#handlers#alex#GetExecutable(b))
|
||||
\ . ' %s '
|
||||
\ . a:flags}
|
||||
endfunction
|
||||
|
||||
function! ale#handlers#alex#Handle(buffer, lines) abort
|
||||
" Example output:
|
||||
" 6:256-6:262 warning Be careful with “killed”, it’s profane in some cases killed retext-profanities
|
||||
let l:pattern = '^ *\(\d\+\):\(\d\+\)-\(\d\+\):\(\d\+\) \+warning \+\(.\{-\}\) \+\(.\{-\}\) \+\(.\{-\}\)$'
|
||||
let l:pattern = '\v^ *(\d+):(\d+)-(\d+):(\d+) +warning +(.{-}) +(.{-}) +(.{-})$'
|
||||
let l:output = []
|
||||
|
||||
for l:match in ale#util#GetMatches(a:lines, l:pattern)
|
||||
@ -20,3 +33,21 @@ function! ale#handlers#alex#Handle(buffer, lines) abort
|
||||
|
||||
return l:output
|
||||
endfunction
|
||||
|
||||
" Define a linter for a specific filetype. Accept flags to adapt to the filetype.
|
||||
" no flags treat input as markdown
|
||||
" --html treat input as HTML
|
||||
" --text treat input as plaintext
|
||||
function! ale#handlers#alex#DefineLinter(filetype, flags) abort
|
||||
call ale#Set('alex_executable', 'alex')
|
||||
call ale#Set('alex_use_global', get(g:, 'ale_use_global_executables', 0))
|
||||
|
||||
call ale#linter#Define(a:filetype, {
|
||||
\ 'name': 'alex',
|
||||
\ 'executable_callback': 'ale#handlers#alex#GetExecutable',
|
||||
\ 'command_callback': ale#handlers#alex#CreateCommandCallback(a:flags),
|
||||
\ 'output_stream': 'stderr',
|
||||
\ 'callback': 'ale#handlers#alex#Handle',
|
||||
\ 'lint_file': 1,
|
||||
\})
|
||||
endfunction
|
||||
|
@ -121,7 +121,7 @@ function! ale#handlers#eslint#Handle(buffer, lines) abort
|
||||
let l:text = l:match[3]
|
||||
|
||||
if ale#Var(a:buffer, 'javascript_eslint_suppress_eslintignore')
|
||||
if l:text is# 'File ignored because of a matching ignore pattern. Use "--no-ignore" to override.'
|
||||
if l:text =~# '^File ignored'
|
||||
continue
|
||||
endif
|
||||
endif
|
||||
|
@ -1,3 +1,4 @@
|
||||
scriptencoding utf-8
|
||||
" Author: Christian Gibbons <cgibbons@gmu.edu>
|
||||
" Description: This file defines a handler function that should work for the
|
||||
" flawfinder format with the -CDQS flags.
|
||||
@ -30,7 +31,7 @@ function! ale#handlers#flawfinder#HandleFlawfinderFormat(buffer, lines) abort
|
||||
\ 'lnum': str2nr(l:match[2]),
|
||||
\ 'col': str2nr(l:match[3]),
|
||||
\ 'type': (l:severity < ale#Var(a:buffer, 'c_flawfinder_error_severity'))
|
||||
\ ? 'W' : 'E',
|
||||
\ ? 'W' : 'E',
|
||||
\ 'text': s:RemoveUnicodeQuotes(join(split(l:match[4])[1:]) . ': ' . l:match[5]),
|
||||
\}
|
||||
|
||||
|
@ -66,11 +66,11 @@ function! ale#handlers#haskell#HandleGHCFormat(buffer, lines) abort
|
||||
let l:errors = matchlist(l:match[4], '\v([wW]arning|[eE]rror): ?(.*)')
|
||||
|
||||
if len(l:errors) > 0
|
||||
let l:ghc_type = l:errors[1]
|
||||
let l:text = l:errors[2]
|
||||
let l:ghc_type = l:errors[1]
|
||||
let l:text = l:errors[2]
|
||||
else
|
||||
let l:ghc_type = ''
|
||||
let l:text = l:match[4][:0] is# ' ' ? l:match[4][1:] : l:match[4]
|
||||
let l:ghc_type = ''
|
||||
let l:text = l:match[4][:0] is# ' ' ? l:match[4][1:] : l:match[4]
|
||||
endif
|
||||
|
||||
if l:ghc_type is? 'Warning'
|
||||
|
@ -7,10 +7,10 @@ function! ale#handlers#markdownlint#Handle(buffer, lines) abort
|
||||
|
||||
for l:match in ale#util#GetMatches(a:lines, l:pattern)
|
||||
call add(l:output, {
|
||||
\ 'lnum': l:match[1] + 0,
|
||||
\ 'text': '(' . l:match[2] . l:match[3] . l:match[4] . ')' . l:match[5],
|
||||
\ 'type': 'W',
|
||||
\ })
|
||||
\ 'lnum': l:match[1] + 0,
|
||||
\ 'text': '(' . l:match[2] . l:match[3] . l:match[4] . ')' . l:match[5],
|
||||
\ 'type': 'W',
|
||||
\})
|
||||
endfor
|
||||
|
||||
return l:output
|
||||
|
@ -9,7 +9,7 @@ function! ale#handlers#sh#GetShellType(buffer) abort
|
||||
" Remove options like -e, etc.
|
||||
let l:command = substitute(l:bang_line, ' --\?[a-zA-Z0-9]\+', '', 'g')
|
||||
|
||||
for l:possible_shell in ['bash', 'dash', 'ash', 'tcsh', 'csh', 'zsh', 'sh']
|
||||
for l:possible_shell in ['bash', 'dash', 'ash', 'tcsh', 'csh', 'zsh', 'ksh', 'sh']
|
||||
if l:command =~# l:possible_shell . '\s*$'
|
||||
return l:possible_shell
|
||||
endif
|
||||
|
@ -64,26 +64,27 @@ function! ale#handlers#sml#Handle(buffer, lines) abort
|
||||
let l:match2 = matchlist(l:line, l:pattern2)
|
||||
|
||||
if len(l:match2) != 0
|
||||
call add(l:out, {
|
||||
\ 'bufnr': a:buffer,
|
||||
\ 'lnum': l:match2[1] + 0,
|
||||
\ 'col' : l:match2[2] - 1,
|
||||
\ 'text': l:match2[3],
|
||||
\ 'type': l:match2[3] =~# '^Warning' ? 'W' : 'E',
|
||||
\})
|
||||
continue
|
||||
call add(l:out, {
|
||||
\ 'bufnr': a:buffer,
|
||||
\ 'lnum': l:match2[1] + 0,
|
||||
\ 'col' : l:match2[2] - 1,
|
||||
\ 'text': l:match2[3],
|
||||
\ 'type': l:match2[3] =~# '^Warning' ? 'W' : 'E',
|
||||
\})
|
||||
|
||||
continue
|
||||
endif
|
||||
|
||||
let l:match = matchlist(l:line, l:pattern)
|
||||
|
||||
if len(l:match) != 0
|
||||
call add(l:out, {
|
||||
\ 'bufnr': a:buffer,
|
||||
\ 'lnum': l:match[1] + 0,
|
||||
\ 'text': l:match[2] . ': ' . l:match[3],
|
||||
\ 'type': l:match[2] is# 'error' ? 'E' : 'W',
|
||||
\})
|
||||
continue
|
||||
call add(l:out, {
|
||||
\ 'bufnr': a:buffer,
|
||||
\ 'lnum': l:match[1] + 0,
|
||||
\ 'text': l:match[2] . ': ' . l:match[3],
|
||||
\ 'type': l:match[2] is# 'error' ? 'E' : 'W',
|
||||
\})
|
||||
continue
|
||||
endif
|
||||
endfor
|
||||
|
||||
|
@ -57,7 +57,7 @@ function! ale#hover#HandleLSPResponse(conn_id, response) abort
|
||||
" If the call did __not__ come from balloonexpr...
|
||||
if !get(l:options, 'hover_from_balloonexpr', 0)
|
||||
let l:buffer = bufnr('')
|
||||
let [l:line, l:column] = getcurpos()[1:2]
|
||||
let [l:line, l:column] = getpos('.')[1:2]
|
||||
let l:end = len(getline(l:line))
|
||||
|
||||
if l:buffer isnot l:options.buffer
|
||||
@ -78,8 +78,8 @@ function! ale#hover#HandleLSPResponse(conn_id, response) abort
|
||||
let l:result = l:result.contents
|
||||
|
||||
if type(l:result) is v:t_string
|
||||
" The result can be just a string.
|
||||
let l:result = [l:result]
|
||||
" The result can be just a string.
|
||||
let l:result = [l:result]
|
||||
endif
|
||||
|
||||
if type(l:result) is v:t_dict
|
||||
@ -106,10 +106,15 @@ function! ale#hover#HandleLSPResponse(conn_id, response) abort
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:OnReady(linter, lsp_details, line, column, opt, ...) abort
|
||||
let l:buffer = a:lsp_details.buffer
|
||||
function! s:OnReady(line, column, opt, linter, lsp_details) abort
|
||||
let l:id = a:lsp_details.connection_id
|
||||
|
||||
if !ale#lsp#HasCapability(l:id, 'hover')
|
||||
return
|
||||
endif
|
||||
|
||||
let l:buffer = a:lsp_details.buffer
|
||||
|
||||
let l:Callback = a:linter.lsp is# 'tsserver'
|
||||
\ ? function('ale#hover#HandleTSServerResponse')
|
||||
\ : function('ale#hover#HandleLSPResponse')
|
||||
@ -144,20 +149,6 @@ function! s:OnReady(linter, lsp_details, line, column, opt, ...) abort
|
||||
\}
|
||||
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)
|
||||
return 0
|
||||
endif
|
||||
|
||||
let l:id = l:lsp_details.connection_id
|
||||
|
||||
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
|
||||
" Pass optional arguments in the dictionary opt.
|
||||
" Currently, only one key/value is useful:
|
||||
@ -169,12 +160,13 @@ endfunction
|
||||
" - as status message otherwise
|
||||
function! ale#hover#Show(buffer, line, col, opt) abort
|
||||
let l:show_documentation = get(a:opt, 'show_documentation', 0)
|
||||
let l:Callback = function('s:OnReady', [a:line, a:col, a:opt])
|
||||
|
||||
for l:linter in ale#linter#Get(getbufvar(a:buffer, '&filetype'))
|
||||
" Only tsserver supports documentation requests at the moment.
|
||||
if !empty(l:linter.lsp)
|
||||
\&& (!l:show_documentation || l:linter.lsp is# 'tsserver')
|
||||
call s:ShowDetails(l:linter, a:buffer, a:line, a:col, a:opt)
|
||||
call ale#lsp_linter#StartLSP(a:buffer, l:linter, l:Callback)
|
||||
endif
|
||||
endfor
|
||||
endfunction
|
||||
@ -182,7 +174,7 @@ endfunction
|
||||
" This function implements the :ALEHover command.
|
||||
function! ale#hover#ShowAtCursor() abort
|
||||
let l:buffer = bufnr('')
|
||||
let l:pos = getcurpos()
|
||||
let l:pos = getpos('.')
|
||||
|
||||
call ale#hover#Show(l:buffer, l:pos[1], l:pos[2], {})
|
||||
endfunction
|
||||
@ -190,7 +182,7 @@ endfunction
|
||||
" This function implements the :ALEDocumentation command.
|
||||
function! ale#hover#ShowDocumentationAtCursor() abort
|
||||
let l:buffer = bufnr('')
|
||||
let l:pos = getcurpos()
|
||||
let l:pos = getpos('.')
|
||||
let l:options = {'show_documentation': 1}
|
||||
|
||||
call ale#hover#Show(l:buffer, l:pos[1], l:pos[2], l:options)
|
||||
|
@ -99,7 +99,8 @@ function! s:VimCloseCallback(channel) abort
|
||||
if job_status(l:job) is# 'dead'
|
||||
try
|
||||
if !empty(l:info) && has_key(l:info, 'exit_cb')
|
||||
call ale#util#GetFunction(l:info.exit_cb)(l:job_id, get(l:info, 'exit_code', 1))
|
||||
" We have to remove the callback, so we don't call it twice.
|
||||
call ale#util#GetFunction(remove(l:info, 'exit_cb'))(l:job_id, get(l:info, 'exit_code', 1))
|
||||
endif
|
||||
finally
|
||||
" Automatically forget about the job after it's done.
|
||||
@ -124,7 +125,8 @@ function! s:VimExitCallback(job, exit_code) abort
|
||||
if ch_status(job_getchannel(a:job)) is# 'closed'
|
||||
try
|
||||
if !empty(l:info) && has_key(l:info, 'exit_cb')
|
||||
call ale#util#GetFunction(l:info.exit_cb)(l:job_id, a:exit_code)
|
||||
" We have to remove the callback, so we don't call it twice.
|
||||
call ale#util#GetFunction(remove(l:info, 'exit_cb'))(l:job_id, a:exit_code)
|
||||
endif
|
||||
finally
|
||||
" Automatically forget about the job after it's done.
|
||||
@ -274,12 +276,34 @@ function! ale#job#Start(command, options) abort
|
||||
return l:job_id
|
||||
endfunction
|
||||
|
||||
" Force running commands in a Windows CMD command line.
|
||||
" This means the same command syntax works everywhere.
|
||||
function! ale#job#StartWithCmd(command, options) abort
|
||||
let l:shell = &l:shell
|
||||
let l:shellcmdflag = &l:shellcmdflag
|
||||
let &l:shell = 'cmd'
|
||||
let &l:shellcmdflag = '/c'
|
||||
|
||||
try
|
||||
let l:job_id = ale#job#Start(a:command, a:options)
|
||||
finally
|
||||
let &l:shell = l:shell
|
||||
let &l:shellcmdflag = l:shellcmdflag
|
||||
endtry
|
||||
|
||||
return l:job_id
|
||||
endfunction
|
||||
|
||||
" Send raw data to the job.
|
||||
function! ale#job#SendRaw(job_id, string) abort
|
||||
if has('nvim')
|
||||
call jobsend(a:job_id, a:string)
|
||||
else
|
||||
call ch_sendraw(job_getchannel(s:job_map[a:job_id].job), a:string)
|
||||
let l:job = s:job_map[a:job_id].job
|
||||
|
||||
if ch_status(l:job) is# 'open'
|
||||
call ch_sendraw(job_getchannel(l:job), a:string)
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
@ -303,6 +327,20 @@ function! ale#job#IsRunning(job_id) abort
|
||||
return 0
|
||||
endfunction
|
||||
|
||||
function! ale#job#HasOpenChannel(job_id) abort
|
||||
if ale#job#IsRunning(a:job_id)
|
||||
if has('nvim')
|
||||
" TODO: Implement a check for NeoVim.
|
||||
return 1
|
||||
endif
|
||||
|
||||
" Check if the Job's channel can be written to.
|
||||
return ch_status(s:job_map[a:job_id].job) is# 'open'
|
||||
endif
|
||||
|
||||
return 0
|
||||
endfunction
|
||||
|
||||
" Given a Job ID, stop that job.
|
||||
" Invalid job IDs will be ignored.
|
||||
function! ale#job#Stop(job_id) abort
|
||||
|
@ -80,7 +80,6 @@ function! ale#linter#PreProcess(filetype, linter) abort
|
||||
endif
|
||||
|
||||
let l:obj = {
|
||||
\ 'add_newline': get(a:linter, 'add_newline', 0),
|
||||
\ 'name': get(a:linter, 'name'),
|
||||
\ 'lsp': get(a:linter, 'lsp', ''),
|
||||
\}
|
||||
@ -121,7 +120,8 @@ function! ale#linter#PreProcess(filetype, linter) abort
|
||||
let l:obj.executable = a:linter.executable
|
||||
|
||||
if type(l:obj.executable) isnot v:t_string
|
||||
throw '`executable` must be a string if defined'
|
||||
\&& type(l:obj.executable) isnot v:t_func
|
||||
throw '`executable` must be a String or Function if defined'
|
||||
endif
|
||||
else
|
||||
throw 'Either `executable` or `executable_callback` must be defined'
|
||||
@ -177,7 +177,8 @@ function! ale#linter#PreProcess(filetype, linter) abort
|
||||
let l:obj.command = a:linter.command
|
||||
|
||||
if type(l:obj.command) isnot v:t_string
|
||||
throw '`command` must be a string if defined'
|
||||
\&& type(l:obj.command) isnot v:t_func
|
||||
throw '`command` must be a String or Function if defined'
|
||||
endif
|
||||
else
|
||||
throw 'Either `command`, `executable_callback`, `command_chain` '
|
||||
@ -194,9 +195,16 @@ function! ale#linter#PreProcess(filetype, linter) abort
|
||||
endif
|
||||
|
||||
if !l:needs_address
|
||||
if has_key(a:linter, 'address_callback')
|
||||
throw '`address_callback` cannot be used when lsp != ''socket'''
|
||||
if has_key(a:linter, 'address') || has_key(a:linter, 'address_callback')
|
||||
throw '`address` or `address_callback` cannot be used when lsp != ''socket'''
|
||||
endif
|
||||
elseif has_key(a:linter, 'address')
|
||||
if type(a:linter.address) isnot v:t_string
|
||||
\&& type(a:linter.address) isnot v:t_func
|
||||
throw '`address` must be a String or Function if defined'
|
||||
endif
|
||||
|
||||
let l:obj.address = a:linter.address
|
||||
elseif has_key(a:linter, 'address_callback')
|
||||
let l:obj.address_callback = a:linter.address_callback
|
||||
|
||||
@ -204,7 +212,7 @@ function! ale#linter#PreProcess(filetype, linter) abort
|
||||
throw '`address_callback` must be a callback if defined'
|
||||
endif
|
||||
else
|
||||
throw '`address_callback` must be defined for getting the LSP address'
|
||||
throw '`address` or `address_callback` must be defined for getting the LSP address'
|
||||
endif
|
||||
|
||||
if l:needs_lsp_details
|
||||
@ -221,20 +229,34 @@ function! ale#linter#PreProcess(filetype, linter) abort
|
||||
endif
|
||||
else
|
||||
" Default to using the filetype as the language.
|
||||
let l:obj.language = get(a:linter, 'language', a:filetype)
|
||||
let l:Language = get(a:linter, 'language', a:filetype)
|
||||
|
||||
if type(l:obj.language) isnot v:t_string
|
||||
throw '`language` must be a string'
|
||||
if type(l:Language) is v:t_string
|
||||
" Make 'language_callback' return the 'language' value.
|
||||
let l:obj.language = l:Language
|
||||
let l:obj.language_callback = function('s:LanguageGetter')
|
||||
elseif type(l:Language) is v:t_func
|
||||
let l:obj.language_callback = l:Language
|
||||
else
|
||||
throw '`language` must be a String or Funcref'
|
||||
endif
|
||||
|
||||
" Make 'language_callback' return the 'language' value.
|
||||
let l:obj.language_callback = function('s:LanguageGetter')
|
||||
endif
|
||||
|
||||
let l:obj.project_root_callback = get(a:linter, 'project_root_callback')
|
||||
if has_key(a:linter, 'project_root')
|
||||
let l:obj.project_root = a:linter.project_root
|
||||
|
||||
if !s:IsCallback(l:obj.project_root_callback)
|
||||
throw '`project_root_callback` must be a callback for LSP linters'
|
||||
if type(l:obj.project_root) isnot v:t_string
|
||||
\&& type(l:obj.project_root) isnot v:t_func
|
||||
throw '`project_root` must be a String or Function if defined'
|
||||
endif
|
||||
elseif has_key(a:linter, 'project_root_callback')
|
||||
let l:obj.project_root_callback = a:linter.project_root_callback
|
||||
|
||||
if !s:IsCallback(l:obj.project_root_callback)
|
||||
throw '`project_root_callback` must be a callback if defined'
|
||||
endif
|
||||
else
|
||||
throw '`project_root` or `project_root_callback` must be defined for LSP linters'
|
||||
endif
|
||||
|
||||
if has_key(a:linter, 'completion_filter')
|
||||
@ -258,6 +280,11 @@ function! ale#linter#PreProcess(filetype, linter) abort
|
||||
endif
|
||||
elseif has_key(a:linter, 'initialization_options')
|
||||
let l:obj.initialization_options = a:linter.initialization_options
|
||||
|
||||
if type(l:obj.initialization_options) isnot v:t_dict
|
||||
\&& type(l:obj.initialization_options) isnot v:t_func
|
||||
throw '`initialization_options` must be a String or Function if defined'
|
||||
endif
|
||||
endif
|
||||
|
||||
if has_key(a:linter, 'lsp_config_callback')
|
||||
@ -272,7 +299,8 @@ function! ale#linter#PreProcess(filetype, linter) abort
|
||||
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'
|
||||
\&& type(a:linter.lsp_config) isnot v:t_func
|
||||
throw '`lsp_config` must be a Dictionary or Function if defined'
|
||||
endif
|
||||
|
||||
let l:obj.lsp_config = a:linter.lsp_config
|
||||
@ -312,6 +340,8 @@ function! ale#linter#PreProcess(filetype, linter) abort
|
||||
throw '`aliases` must be a List of String values'
|
||||
endif
|
||||
|
||||
" TODO: Emit deprecation warnings for deprecated options later.
|
||||
|
||||
return l:obj
|
||||
endfunction
|
||||
|
||||
@ -477,22 +507,34 @@ endfunction
|
||||
|
||||
" Given a buffer and linter, get the executable String for the linter.
|
||||
function! ale#linter#GetExecutable(buffer, linter) abort
|
||||
return has_key(a:linter, 'executable_callback')
|
||||
\ ? ale#util#GetFunction(a:linter.executable_callback)(a:buffer)
|
||||
let l:Executable = has_key(a:linter, 'executable_callback')
|
||||
\ ? function(a:linter.executable_callback)
|
||||
\ : a:linter.executable
|
||||
|
||||
return type(l:Executable) is v:t_func
|
||||
\ ? l:Executable(a:buffer)
|
||||
\ : l:Executable
|
||||
endfunction
|
||||
|
||||
" Given a buffer and linter, get the command String for the linter.
|
||||
" The command_chain key is not supported.
|
||||
function! ale#linter#GetCommand(buffer, linter) abort
|
||||
return has_key(a:linter, 'command_callback')
|
||||
\ ? ale#util#GetFunction(a:linter.command_callback)(a:buffer)
|
||||
let l:Command = has_key(a:linter, 'command_callback')
|
||||
\ ? function(a:linter.command_callback)
|
||||
\ : a:linter.command
|
||||
|
||||
return type(l:Command) is v:t_func
|
||||
\ ? l:Command(a:buffer)
|
||||
\ : l:Command
|
||||
endfunction
|
||||
|
||||
" Given a buffer and linter, get the address for connecting to the server.
|
||||
function! ale#linter#GetAddress(buffer, linter) abort
|
||||
return has_key(a:linter, 'address_callback')
|
||||
\ ? ale#util#GetFunction(a:linter.address_callback)(a:buffer)
|
||||
let l:Address = has_key(a:linter, 'address_callback')
|
||||
\ ? function(a:linter.address_callback)
|
||||
\ : a:linter.address
|
||||
|
||||
return type(l:Address) is v:t_func
|
||||
\ ? l:Address(a:buffer)
|
||||
\ : l:Address
|
||||
endfunction
|
||||
|
@ -9,14 +9,26 @@
|
||||
" If there are no items or we have hit the end with wrapping off, an empty
|
||||
" List will be returned, otherwise a pair of [line_number, column_number] will
|
||||
" be returned.
|
||||
function! ale#loclist_jumping#FindNearest(direction, wrap) abort
|
||||
function! ale#loclist_jumping#FindNearest(direction, wrap, ...) abort
|
||||
let l:buffer = bufnr('')
|
||||
let l:pos = getcurpos()
|
||||
let l:pos = getpos('.')
|
||||
let l:info = get(g:ale_buffer_info, bufnr('%'), {'loclist': []})
|
||||
" Copy the list and filter to only the items in this buffer.
|
||||
let l:loclist = filter(copy(l:info.loclist), 'v:val.bufnr == l:buffer')
|
||||
let l:search_item = {'bufnr': l:buffer, 'lnum': l:pos[1], 'col': l:pos[2]}
|
||||
|
||||
if a:0 > 0
|
||||
let l:filter = a:1
|
||||
else
|
||||
let l:filter = 'any'
|
||||
endif
|
||||
|
||||
if a:0 > 1
|
||||
let l:subtype_filter = a:2
|
||||
else
|
||||
let l:subtype_filter = 'any'
|
||||
endif
|
||||
|
||||
" When searching backwards, so we can find the next smallest match.
|
||||
if a:direction is# 'before'
|
||||
call reverse(l:loclist)
|
||||
@ -41,29 +53,61 @@ function! ale#loclist_jumping#FindNearest(direction, wrap) abort
|
||||
\ l:search_item
|
||||
\)
|
||||
|
||||
if a:direction is# 'before' && l:cmp_value < 0
|
||||
return [l:item.lnum, l:item.col]
|
||||
endif
|
||||
if (l:filter is# 'any' || l:filter is# l:item.type)
|
||||
\&& (
|
||||
\ l:subtype_filter is# 'any'
|
||||
\ || l:subtype_filter is# get(l:item, 'sub_type', '')
|
||||
\)
|
||||
|
||||
if a:direction is# 'after' && l:cmp_value > 0
|
||||
return [l:item.lnum, l:item.col]
|
||||
if a:direction is# 'before' && l:cmp_value < 0
|
||||
return [l:item.lnum, l:item.col]
|
||||
endif
|
||||
|
||||
if a:direction is# 'after' && l:cmp_value > 0
|
||||
return [l:item.lnum, l:item.col]
|
||||
endif
|
||||
endif
|
||||
endfor
|
||||
|
||||
" If we found nothing, and the wrap option is set to 1, then we should
|
||||
" wrap around the list of warnings/errors
|
||||
if a:wrap && !empty(l:loclist)
|
||||
let l:item = l:loclist[0]
|
||||
|
||||
return [l:item.lnum, l:item.col]
|
||||
if a:wrap
|
||||
for l:item in l:loclist
|
||||
if (l:filter is# 'any' || l:filter is# l:item.type)
|
||||
\&& (
|
||||
\ l:subtype_filter is# 'any'
|
||||
\ || l:subtype_filter is# get(l:item, 'sub_type', '')
|
||||
\)
|
||||
return [l:item.lnum, l:item.col]
|
||||
endif
|
||||
endfor
|
||||
endif
|
||||
|
||||
return []
|
||||
endfunction
|
||||
|
||||
" As before, find the nearest match, but position the cursor at it.
|
||||
function! ale#loclist_jumping#Jump(direction, wrap) abort
|
||||
let l:nearest = ale#loclist_jumping#FindNearest(a:direction, a:wrap)
|
||||
function! ale#loclist_jumping#Jump(direction, ...) abort
|
||||
if a:0 > 0
|
||||
let l:wrap = a:1
|
||||
else
|
||||
let l:wrap = 0
|
||||
endif
|
||||
|
||||
if a:0 > 1
|
||||
let l:filter = a:2
|
||||
else
|
||||
let l:filter = 'any'
|
||||
endif
|
||||
|
||||
if a:0 > 2
|
||||
let l:subtype_filter = a:3
|
||||
else
|
||||
let l:subtype_filter = 'any'
|
||||
endif
|
||||
|
||||
let l:nearest = ale#loclist_jumping#FindNearest(a:direction,
|
||||
\ l:wrap, l:filter, l:subtype_filter)
|
||||
|
||||
if !empty(l:nearest)
|
||||
normal! m`
|
||||
@ -71,6 +115,36 @@ function! ale#loclist_jumping#Jump(direction, wrap) abort
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! ale#loclist_jumping#WrapJump(direction, sargs) abort
|
||||
let [l:args, l:rest] = ale#args#Parse(['error', 'warning', 'info', 'wrap',
|
||||
\ 'style', 'nostyle'], a:sargs)
|
||||
|
||||
let l:wrap = 0
|
||||
let l:type_filter = 'any'
|
||||
let l:subtype_filter = 'any'
|
||||
|
||||
if get(l:args, 'wrap', 'nil') is# ''
|
||||
let l:wrap = 1
|
||||
endif
|
||||
|
||||
if get(l:args, 'error', 'nil') is# ''
|
||||
let l:type_filter = 'E'
|
||||
elseif get(l:args, 'warning', 'nil') is# ''
|
||||
let l:type_filter = 'W'
|
||||
elseif get(l:args, 'info', 'nil') is# ''
|
||||
let l:type_filter = 'I'
|
||||
endif
|
||||
|
||||
if get(l:args, 'nostyle', 'nil') is# ''
|
||||
let l:subtype_filter = 'style'
|
||||
elseif get(l:args, 'style', 'nil') is# ''
|
||||
let l:subtype_filter = ''
|
||||
endif
|
||||
|
||||
call ale#loclist_jumping#Jump(a:direction, l:wrap, l:type_filter,
|
||||
\ l:subtype_filter)
|
||||
endfunction
|
||||
|
||||
function! ale#loclist_jumping#JumpToIndex(index) abort
|
||||
let l:buffer = bufnr('')
|
||||
let l:info = get(g:ale_buffer_info, l:buffer, {'loclist': []})
|
||||
|
@ -21,7 +21,6 @@ function! ale#lsp#Register(executable_or_address, project, init_options) abort
|
||||
" 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] = {
|
||||
@ -35,14 +34,14 @@ function! ale#lsp#Register(executable_or_address, project, init_options) abort
|
||||
\ 'init_options': a:init_options,
|
||||
\ 'config': {},
|
||||
\ 'callback_list': [],
|
||||
\ 'message_queue': [],
|
||||
\ 'capabilities_queue': [],
|
||||
\ 'init_queue': [],
|
||||
\ 'capabilities': {
|
||||
\ 'hover': 0,
|
||||
\ 'references': 0,
|
||||
\ 'completion': 0,
|
||||
\ 'completion_trigger_characters': [],
|
||||
\ 'definition': 0,
|
||||
\ 'typeDefinition': 0,
|
||||
\ 'symbol_search': 0,
|
||||
\ },
|
||||
\}
|
||||
@ -58,6 +57,15 @@ function! ale#lsp#RemoveConnectionWithID(id) abort
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#ResetConnections() abort
|
||||
let s:connections = {}
|
||||
endfunction
|
||||
|
||||
" Used only in tests.
|
||||
function! ale#lsp#GetConnections() abort
|
||||
return s:connections
|
||||
endfunction
|
||||
|
||||
" This is only needed for tests
|
||||
function! ale#lsp#MarkDocumentAsOpen(id, buffer) abort
|
||||
let l:conn = get(s:connections, a:id, {})
|
||||
@ -207,6 +215,10 @@ function! s:UpdateCapabilities(conn, capabilities) abort
|
||||
let a:conn.capabilities.definition = 1
|
||||
endif
|
||||
|
||||
if get(a:capabilities, 'typeDefinitionProvider') is v:true
|
||||
let a:conn.capabilities.typeDefinition = 1
|
||||
endif
|
||||
|
||||
if get(a:capabilities, 'workspaceSymbolProvider') is v:true
|
||||
let a:conn.capabilities.symbol_search = 1
|
||||
endif
|
||||
@ -245,22 +257,15 @@ function! ale#lsp#HandleInitResponse(conn, response) abort
|
||||
return
|
||||
endif
|
||||
|
||||
" 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
|
||||
|
||||
" Remove the messages now.
|
||||
let a:conn.message_queue = []
|
||||
" The initialized message must be sent before everything else.
|
||||
call ale#lsp#Send(a:conn.id, ale#lsp#message#Initialized())
|
||||
|
||||
" 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
|
||||
for l:Callback in a:conn.init_queue
|
||||
call l:Callback()
|
||||
endfor
|
||||
|
||||
let a:conn.capabilities_queue = []
|
||||
let a:conn.init_queue = []
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#HandleMessage(conn_id, message) abort
|
||||
@ -314,19 +319,35 @@ function! ale#lsp#MarkConnectionAsTsserver(conn_id) abort
|
||||
let l:conn.capabilities.symbol_search = 1
|
||||
endfunction
|
||||
|
||||
function! s:SendInitMessage(conn) abort
|
||||
let [l:init_id, l:init_data] = ale#lsp#CreateMessageData(
|
||||
\ ale#lsp#message#Initialize(a:conn.root, a:conn.init_options),
|
||||
\)
|
||||
let a:conn.init_request_id = l:init_id
|
||||
call s:SendMessageData(a:conn, l:init_data)
|
||||
endfunction
|
||||
|
||||
" Start a program for LSP servers.
|
||||
"
|
||||
" 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]
|
||||
let l:started = 0
|
||||
|
||||
if !has_key(l:conn, 'job_id') || !ale#job#IsRunning(l:conn.job_id)
|
||||
if !has_key(l:conn, 'job_id') || !ale#job#HasOpenChannel(l:conn.job_id)
|
||||
let l:options = {
|
||||
\ 'mode': 'raw',
|
||||
\ 'out_cb': {_, message -> ale#lsp#HandleMessage(a:conn_id, message)},
|
||||
\}
|
||||
let l:job_id = ale#job#Start(a:command, l:options)
|
||||
|
||||
if has('win32')
|
||||
let l:job_id = ale#job#StartWithCmd(a:command, l:options)
|
||||
else
|
||||
let l:job_id = ale#job#Start(a:command, l:options)
|
||||
endif
|
||||
|
||||
let l:started = 1
|
||||
else
|
||||
let l:job_id = l:conn.job_id
|
||||
endif
|
||||
@ -335,6 +356,10 @@ function! ale#lsp#StartProgram(conn_id, executable, command) abort
|
||||
let l:conn.job_id = l:job_id
|
||||
endif
|
||||
|
||||
if l:started && !l:conn.is_tsserver
|
||||
call s:SendInitMessage(l:conn)
|
||||
endif
|
||||
|
||||
return l:job_id > 0
|
||||
endfunction
|
||||
|
||||
@ -344,11 +369,14 @@ endfunction
|
||||
" not be opened.
|
||||
function! ale#lsp#ConnectToAddress(conn_id, address) abort
|
||||
let l:conn = s:connections[a:conn_id]
|
||||
let l:started = 0
|
||||
|
||||
if !has_key(l:conn, 'channel_id') || !ale#socket#IsOpen(l:conn.channel_id)
|
||||
let l:channel_id = ale#socket#Open(a:address, {
|
||||
\ 'callback': {_, mess -> ale#lsp#HandleMessage(a:conn_id, mess)},
|
||||
\})
|
||||
|
||||
let l:started = 1
|
||||
else
|
||||
let l:channel_id = l:conn.channel_id
|
||||
endif
|
||||
@ -357,6 +385,10 @@ function! ale#lsp#ConnectToAddress(conn_id, address) abort
|
||||
let l:conn.channel_id = l:channel_id
|
||||
endif
|
||||
|
||||
if l:started
|
||||
call s:SendInitMessage(l:conn)
|
||||
endif
|
||||
|
||||
return l:channel_id >= 0
|
||||
endfunction
|
||||
|
||||
@ -421,26 +453,12 @@ function! ale#lsp#Send(conn_id, message) abort
|
||||
return 0
|
||||
endif
|
||||
|
||||
" If we haven't initialized the server yet, then send the message for it.
|
||||
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:conn.init_request_id = l:init_id
|
||||
|
||||
call s:SendMessageData(l:conn, l:init_data)
|
||||
if !l:conn.initialized
|
||||
throw 'LSP server not initialized yet!'
|
||||
endif
|
||||
|
||||
let [l:id, l:data] = ale#lsp#CreateMessageData(a:message)
|
||||
|
||||
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:conn.message_queue, l:data)
|
||||
endif
|
||||
call s:SendMessageData(l:conn, l:data)
|
||||
|
||||
return l:id == 0 ? -1 : l:id
|
||||
endfunction
|
||||
@ -491,26 +509,32 @@ function! ale#lsp#NotifyForChanges(conn_id, buffer) abort
|
||||
return l:notified
|
||||
endfunction
|
||||
|
||||
" Given some LSP details that must contain at least `connection_id` and
|
||||
" `project_root` keys,
|
||||
function! ale#lsp#WaitForCapability(conn_id, capability, callback) abort
|
||||
" Wait for an LSP server to be initialized.
|
||||
function! ale#lsp#OnInit(conn_id, Callback) abort
|
||||
let l:conn = get(s:connections, a:conn_id, {})
|
||||
|
||||
if empty(l:conn)
|
||||
return
|
||||
endif
|
||||
|
||||
if l:conn.initialized
|
||||
call a:Callback()
|
||||
else
|
||||
call add(l:conn.init_queue, a:Callback)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" Check if an LSP has a given capability.
|
||||
function! ale#lsp#HasCapability(conn_id, capability) abort
|
||||
let l:conn = get(s:connections, a:conn_id, {})
|
||||
|
||||
if empty(l:conn)
|
||||
return 0
|
||||
endif
|
||||
|
||||
if type(get(l:conn.capabilities, a:capability, v:null)) isnot v:t_number
|
||||
throw 'Invalid capability ' . a:capability
|
||||
endif
|
||||
|
||||
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])
|
||||
endif
|
||||
else
|
||||
" Call the callback later, once we have the information we need.
|
||||
call add(l:conn.capabilities_queue, [a:capability, a:callback])
|
||||
endif
|
||||
return l:conn.capabilities[a:capability]
|
||||
endfunction
|
||||
|
@ -3,6 +3,10 @@
|
||||
"
|
||||
" Messages in this movie will be returned in the format
|
||||
" [is_notification, method_name, params?]
|
||||
"
|
||||
" All functions which accept line and column arguments expect them to be 1-based
|
||||
" (the same format as being returned by getpos() and friends), those then
|
||||
" will be converted to 0-based as specified by LSP.
|
||||
let g:ale_lsp_next_version_id = 1
|
||||
|
||||
" The LSP protocols demands that we send every change to a document, including
|
||||
@ -37,7 +41,7 @@ function! ale#lsp#message#Initialize(root_path, initialization_options) abort
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#message#Initialized() abort
|
||||
return [1, 'initialized']
|
||||
return [1, 'initialized', {}]
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#message#Shutdown() abort
|
||||
@ -98,7 +102,7 @@ function! ale#lsp#message#Completion(buffer, line, column, trigger_character) ab
|
||||
\ 'textDocument': {
|
||||
\ 'uri': ale#path#ToURI(expand('#' . a:buffer . ':p')),
|
||||
\ },
|
||||
\ 'position': {'line': a:line - 1, 'character': a:column},
|
||||
\ 'position': {'line': a:line - 1, 'character': a:column - 1},
|
||||
\}]
|
||||
|
||||
if !empty(a:trigger_character)
|
||||
@ -116,7 +120,16 @@ function! ale#lsp#message#Definition(buffer, line, column) abort
|
||||
\ 'textDocument': {
|
||||
\ 'uri': ale#path#ToURI(expand('#' . a:buffer . ':p')),
|
||||
\ },
|
||||
\ 'position': {'line': a:line - 1, 'character': a:column},
|
||||
\ 'position': {'line': a:line - 1, 'character': a:column - 1},
|
||||
\}]
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#message#TypeDefinition(buffer, line, column) abort
|
||||
return [0, 'textDocument/typeDefinition', {
|
||||
\ 'textDocument': {
|
||||
\ 'uri': ale#path#ToURI(expand('#' . a:buffer . ':p')),
|
||||
\ },
|
||||
\ 'position': {'line': a:line - 1, 'character': a:column - 1},
|
||||
\}]
|
||||
endfunction
|
||||
|
||||
@ -125,7 +138,7 @@ function! ale#lsp#message#References(buffer, line, column) abort
|
||||
\ 'textDocument': {
|
||||
\ 'uri': ale#path#ToURI(expand('#' . a:buffer . ':p')),
|
||||
\ },
|
||||
\ 'position': {'line': a:line - 1, 'character': a:column},
|
||||
\ 'position': {'line': a:line - 1, 'character': a:column - 1},
|
||||
\ 'context': {'includeDeclaration': v:false},
|
||||
\}]
|
||||
endfunction
|
||||
@ -141,7 +154,7 @@ function! ale#lsp#message#Hover(buffer, line, column) abort
|
||||
\ 'textDocument': {
|
||||
\ 'uri': ale#path#ToURI(expand('#' . a:buffer . ':p')),
|
||||
\ },
|
||||
\ 'position': {'line': a:line - 1, 'character': a:column},
|
||||
\ 'position': {'line': a:line - 1, 'character': a:column - 1},
|
||||
\}]
|
||||
endfunction
|
||||
|
||||
|
@ -33,7 +33,7 @@ function! ale#lsp#response#ReadDiagnostics(response) abort
|
||||
\ 'lnum': l:diagnostic.range.start.line + 1,
|
||||
\ 'col': l:diagnostic.range.start.character + 1,
|
||||
\ 'end_lnum': l:diagnostic.range.end.line + 1,
|
||||
\ 'end_col': l:diagnostic.range.end.character + 1,
|
||||
\ 'end_col': l:diagnostic.range.end.character,
|
||||
\}
|
||||
|
||||
if l:severity == s:SEVERITY_WARNING
|
||||
@ -58,16 +58,20 @@ function! ale#lsp#response#ReadDiagnostics(response) abort
|
||||
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
|
||||
\ })
|
||||
\ 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')
|
||||
let l:loclist_item.detail = printf('[%s] %s', l:diagnostic.source, l:diagnostic.message)
|
||||
let l:loclist_item.detail = printf(
|
||||
\ '[%s] %s',
|
||||
\ l:diagnostic.source,
|
||||
\ l:diagnostic.message
|
||||
\)
|
||||
endif
|
||||
|
||||
call add(l:loclist, l:loclist_item)
|
||||
|
@ -129,113 +129,242 @@ function! ale#lsp_linter#HandleLSPResponse(conn_id, response) abort
|
||||
endfunction
|
||||
|
||||
function! ale#lsp_linter#GetOptions(buffer, linter) abort
|
||||
let l:initialization_options = {}
|
||||
|
||||
if has_key(a:linter, 'initialization_options_callback')
|
||||
let l:initialization_options = ale#util#GetFunction(a:linter.initialization_options_callback)(a:buffer)
|
||||
elseif has_key(a:linter, 'initialization_options')
|
||||
let l:initialization_options = a:linter.initialization_options
|
||||
return ale#util#GetFunction(a:linter.initialization_options_callback)(a:buffer)
|
||||
endif
|
||||
|
||||
return l:initialization_options
|
||||
if has_key(a:linter, 'initialization_options')
|
||||
let l:Options = a:linter.initialization_options
|
||||
|
||||
if type(l:Options) is v:t_func
|
||||
let l:Options = l:Options(a:buffer)
|
||||
endif
|
||||
|
||||
return l:Options
|
||||
endif
|
||||
|
||||
return {}
|
||||
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
|
||||
return ale#util#GetFunction(a:linter.lsp_config_callback)(a:buffer)
|
||||
endif
|
||||
|
||||
return l:config
|
||||
if has_key(a:linter, 'lsp_config')
|
||||
let l:Config = a:linter.lsp_config
|
||||
|
||||
if type(l:Config) is v:t_func
|
||||
let l:Config = l:Config(a:buffer)
|
||||
endif
|
||||
|
||||
return l:Config
|
||||
endif
|
||||
|
||||
return {}
|
||||
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
|
||||
let l:command = ''
|
||||
let l:address = ''
|
||||
let l:root = ale#util#GetFunction(a:linter.project_root_callback)(a:buffer)
|
||||
function! ale#lsp_linter#FindProjectRoot(buffer, linter) abort
|
||||
let l:buffer_ale_root = getbufvar(a:buffer, 'ale_lsp_root', {})
|
||||
|
||||
if empty(l:root) && a:linter.lsp isnot# 'tsserver'
|
||||
" If there's no project root, then we can't check files with LSP,
|
||||
" unless we are using tsserver, which doesn't use project roots.
|
||||
return {}
|
||||
if type(l:buffer_ale_root) is v:t_string
|
||||
return l:buffer_ale_root
|
||||
endif
|
||||
|
||||
let l:init_options = ale#lsp_linter#GetOptions(a:buffer, a:linter)
|
||||
" Try to get a buffer-local setting for the root
|
||||
if has_key(l:buffer_ale_root, a:linter.name)
|
||||
let l:Root = l:buffer_ale_root[a:linter.name]
|
||||
|
||||
if a:linter.lsp is# 'socket'
|
||||
let l:address = ale#linter#GetAddress(a:buffer, a:linter)
|
||||
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)
|
||||
|
||||
if empty(l:executable) || !executable(l:executable)
|
||||
return {}
|
||||
if type(l:Root) is v:t_func
|
||||
return l:Root(a:buffer)
|
||||
else
|
||||
return l:Root
|
||||
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:ready = ale#lsp#StartProgram(l:conn_id, l:executable, l:command)
|
||||
endif
|
||||
|
||||
if !l:ready
|
||||
if g:ale_history_enabled && !empty(l:command)
|
||||
call ale#history#Add(a:buffer, 'failed', l:conn_id, l:command)
|
||||
" Try to get a global setting for the root
|
||||
if has_key(g:ale_lsp_root, a:linter.name)
|
||||
let l:Root = g:ale_lsp_root[a:linter.name]
|
||||
|
||||
if type(l:Root) is v:t_func
|
||||
return l:Root(a:buffer)
|
||||
else
|
||||
return l:Root
|
||||
endif
|
||||
|
||||
return {}
|
||||
endif
|
||||
|
||||
" tsserver behaves differently, so tell the LSP API that it is tsserver.
|
||||
if a:linter.lsp is# 'tsserver'
|
||||
call ale#lsp#MarkConnectionAsTsserver(l:conn_id)
|
||||
" Fall back to the linter-specific configuration
|
||||
if has_key(a:linter, 'project_root')
|
||||
let l:Root = a:linter.project_root
|
||||
|
||||
return type(l:Root) is v:t_func ? l:Root(a:buffer) : l:Root
|
||||
endif
|
||||
|
||||
let l:config = ale#lsp_linter#GetConfig(a:buffer, a:linter)
|
||||
let l:language_id = ale#util#GetFunction(a:linter.language_callback)(a:buffer)
|
||||
return ale#util#GetFunction(a:linter.project_root_callback)(a:buffer)
|
||||
endfunction
|
||||
|
||||
let l:details = {
|
||||
\ 'buffer': a:buffer,
|
||||
\ 'connection_id': l:conn_id,
|
||||
\ 'command': l:command,
|
||||
\ 'project_root': l:root,
|
||||
\ 'language_id': l:language_id,
|
||||
\}
|
||||
" This function is accessible so tests can call it.
|
||||
function! ale#lsp_linter#OnInit(linter, details, Callback) abort
|
||||
let l:buffer = a:details.buffer
|
||||
let l:conn_id = a:details.connection_id
|
||||
let l:command = a:details.command
|
||||
|
||||
call ale#lsp#UpdateConfig(l:conn_id, a:buffer, l:config)
|
||||
let l:config = ale#lsp_linter#GetConfig(l:buffer, a:linter)
|
||||
let l:language_id = ale#util#GetFunction(a:linter.language_callback)(l:buffer)
|
||||
|
||||
if ale#lsp#OpenDocument(l:conn_id, a:buffer, l:language_id)
|
||||
call ale#lsp#UpdateConfig(l:conn_id, l:buffer, l:config)
|
||||
|
||||
if ale#lsp#OpenDocument(l:conn_id, l:buffer, l:language_id)
|
||||
if g:ale_history_enabled && !empty(l:command)
|
||||
call ale#history#Add(a:buffer, 'started', l:conn_id, l:command)
|
||||
call ale#history#Add(l:buffer, 'started', l:conn_id, l:command)
|
||||
endif
|
||||
endif
|
||||
|
||||
" 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, a:buffer)
|
||||
call ale#lsp#NotifyForChanges(l:conn_id, l:buffer)
|
||||
endif
|
||||
|
||||
return l:details
|
||||
call a:Callback(a:linter, a:details)
|
||||
endfunction
|
||||
|
||||
function! ale#lsp_linter#CheckWithLSP(buffer, linter) abort
|
||||
let l:info = g:ale_buffer_info[a:buffer]
|
||||
let l:lsp_details = ale#lsp_linter#StartLSP(a:buffer, a:linter)
|
||||
function! s:StartLSP(options, address, executable, command) abort
|
||||
let l:buffer = a:options.buffer
|
||||
let l:linter = a:options.linter
|
||||
let l:root = a:options.root
|
||||
let l:Callback = a:options.callback
|
||||
|
||||
let l:init_options = ale#lsp_linter#GetOptions(l:buffer, l:linter)
|
||||
|
||||
if l:linter.lsp is# 'socket'
|
||||
let l:conn_id = ale#lsp#Register(a:address, l:root, l:init_options)
|
||||
let l:ready = ale#lsp#ConnectToAddress(l:conn_id, a:address)
|
||||
let l:command = ''
|
||||
else
|
||||
let l:conn_id = ale#lsp#Register(a:executable, l:root, l:init_options)
|
||||
|
||||
" tsserver behaves differently, so tell the LSP API that it is tsserver.
|
||||
if l:linter.lsp is# 'tsserver'
|
||||
call ale#lsp#MarkConnectionAsTsserver(l:conn_id)
|
||||
endif
|
||||
|
||||
let l:command = ale#command#FormatCommand(l:buffer, a:executable, a:command, 0, v:false)[1]
|
||||
let l:command = ale#job#PrepareCommand(l:buffer, l:command)
|
||||
let l:ready = ale#lsp#StartProgram(l:conn_id, a:executable, l:command)
|
||||
endif
|
||||
|
||||
if !l:ready
|
||||
if g:ale_history_enabled && !empty(a:command)
|
||||
call ale#history#Add(l:buffer, 'failed', l:conn_id, a:command)
|
||||
endif
|
||||
|
||||
if empty(l:lsp_details)
|
||||
return 0
|
||||
endif
|
||||
|
||||
let l:id = l:lsp_details.connection_id
|
||||
let l:details = {
|
||||
\ 'buffer': l:buffer,
|
||||
\ 'connection_id': l:conn_id,
|
||||
\ 'command': l:command,
|
||||
\ 'project_root': l:root,
|
||||
\}
|
||||
|
||||
call ale#lsp#OnInit(l:conn_id, {->
|
||||
\ ale#lsp_linter#OnInit(l:linter, l:details, l:Callback)
|
||||
\})
|
||||
|
||||
return 1
|
||||
endfunction
|
||||
|
||||
function! s:StartWithAddress(options, address) abort
|
||||
if ale#command#IsDeferred(a:address)
|
||||
let a:address.result_callback = {
|
||||
\ address -> s:StartWithAddress(a:options, address)
|
||||
\}
|
||||
|
||||
return 1
|
||||
endif
|
||||
|
||||
if empty(a:address)
|
||||
return 0
|
||||
endif
|
||||
|
||||
return s:StartLSP(a:options, a:address, '', '')
|
||||
endfunction
|
||||
|
||||
function! s:StartWithCommand(options, executable, command) abort
|
||||
if ale#command#IsDeferred(a:command)
|
||||
let a:command.result_callback = {
|
||||
\ command -> s:StartWithCommand(a:options, a:executable, command)
|
||||
\}
|
||||
|
||||
return 1
|
||||
endif
|
||||
|
||||
if empty(a:command)
|
||||
return 0
|
||||
endif
|
||||
|
||||
return s:StartLSP(a:options, '', a:executable, a:command)
|
||||
endfunction
|
||||
|
||||
function! s:StartIfExecutable(options, executable) abort
|
||||
if ale#command#IsDeferred(a:executable)
|
||||
let a:executable.result_callback = {
|
||||
\ executable -> s:StartIfExecutable(a:options, executable)
|
||||
\}
|
||||
|
||||
return 1
|
||||
endif
|
||||
|
||||
if !ale#engine#IsExecutable(a:options.buffer, a:executable)
|
||||
return 0
|
||||
endif
|
||||
|
||||
let l:command = ale#linter#GetCommand(a:options.buffer, a:options.linter)
|
||||
|
||||
return s:StartWithCommand(a:options, a:executable, l:command)
|
||||
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, Callback) abort
|
||||
let l:command = ''
|
||||
let l:address = ''
|
||||
let l:root = ale#lsp_linter#FindProjectRoot(a:buffer, a:linter)
|
||||
|
||||
if empty(l:root) && a:linter.lsp isnot# 'tsserver'
|
||||
" If there's no project root, then we can't check files with LSP,
|
||||
" unless we are using tsserver, which doesn't use project roots.
|
||||
return 0
|
||||
endif
|
||||
|
||||
let l:options = {
|
||||
\ 'buffer': a:buffer,
|
||||
\ 'linter': a:linter,
|
||||
\ 'callback': a:Callback,
|
||||
\ 'root': l:root,
|
||||
\}
|
||||
|
||||
if a:linter.lsp is# 'socket'
|
||||
let l:address = ale#linter#GetAddress(a:buffer, a:linter)
|
||||
|
||||
return s:StartWithAddress(l:options, l:address)
|
||||
endif
|
||||
|
||||
let l:executable = ale#linter#GetExecutable(a:buffer, a:linter)
|
||||
|
||||
return s:StartIfExecutable(l:options, l:executable)
|
||||
endfunction
|
||||
|
||||
function! s:CheckWithLSP(linter, details) abort
|
||||
let l:buffer = a:details.buffer
|
||||
let l:info = get(g:ale_buffer_info, l:buffer)
|
||||
|
||||
if empty(l:info)
|
||||
return
|
||||
endif
|
||||
|
||||
let l:id = a:details.connection_id
|
||||
|
||||
" Register a callback now for handling errors now.
|
||||
let l:Callback = function('ale#lsp_linter#HandleLSPResponse')
|
||||
@ -245,26 +374,26 @@ function! ale#lsp_linter#CheckWithLSP(buffer, linter) abort
|
||||
let s:lsp_linter_map[l:id] = a:linter.name
|
||||
|
||||
if a:linter.lsp is# 'tsserver'
|
||||
let l:message = ale#lsp#tsserver_message#Geterr(a:buffer)
|
||||
let l:message = ale#lsp#tsserver_message#Geterr(l:buffer)
|
||||
let l:notified = ale#lsp#Send(l:id, l:message) != 0
|
||||
else
|
||||
let l:notified = ale#lsp#NotifyForChanges(l:id, a:buffer)
|
||||
let l:notified = ale#lsp#NotifyForChanges(l:id, l: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)
|
||||
\&& getbufvar(l:buffer, 'ale_save_event_fired', 0)
|
||||
let l:save_message = ale#lsp#message#DidSave(l:buffer)
|
||||
let l:notified = ale#lsp#Send(l:id, l:save_message) != 0
|
||||
endif
|
||||
|
||||
if l:notified
|
||||
if index(l:info.active_linter_list, a:linter.name) < 0
|
||||
call add(l:info.active_linter_list, a:linter.name)
|
||||
endif
|
||||
call ale#engine#MarkLinterActive(l:info, a:linter)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
return l:notified
|
||||
function! ale#lsp_linter#CheckWithLSP(buffer, linter) abort
|
||||
return ale#lsp_linter#StartLSP(a:buffer, a:linter, function('s:CheckWithLSP'))
|
||||
endfunction
|
||||
|
||||
" Clear LSP linter data for the linting engine.
|
||||
|
@ -23,11 +23,6 @@ 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.
|
||||
"
|
||||
|
@ -197,15 +197,18 @@ function! ale#path#ToURI(path) abort
|
||||
endfunction
|
||||
|
||||
function! ale#path#FromURI(uri) abort
|
||||
let l:i = len('file://')
|
||||
let l:encoded_path = a:uri[: l:i - 1] is# 'file://' ? a:uri[l:i :] : a:uri
|
||||
|
||||
let l:path = ale#uri#Decode(l:encoded_path)
|
||||
|
||||
" If the path is like /C:/foo/bar, it should be C:\foo\bar instead.
|
||||
if l:path =~# '^/[a-zA-Z]:'
|
||||
let l:path = substitute(l:path[1:], '/', '\\', 'g')
|
||||
if a:uri[:6] is? 'file://'
|
||||
let l:encoded_path = a:uri[7:]
|
||||
elseif a:uri[:4] is? 'file:'
|
||||
let l:encoded_path = a:uri[5:]
|
||||
else
|
||||
let l:encoded_path = a:uri
|
||||
endif
|
||||
|
||||
return l:path
|
||||
" If the path is like /C:/foo/bar, it should be C:\foo\bar instead.
|
||||
if l:encoded_path =~# '^/[a-zA-Z]:'
|
||||
let l:encoded_path = substitute(l:encoded_path[1:], '/', '\\', 'g')
|
||||
endif
|
||||
|
||||
return ale#uri#Decode(l:encoded_path)
|
||||
endfunction
|
||||
|
@ -41,16 +41,24 @@ endfunction
|
||||
|
||||
" Show a location selection preview window, given some items.
|
||||
" Each item should have 'filename', 'line', and 'column' keys.
|
||||
function! ale#preview#ShowSelection(item_list) abort
|
||||
function! ale#preview#ShowSelection(item_list, ...) abort
|
||||
let l:options = get(a:000, 0, {})
|
||||
let l:sep = has('win32') ? '\' : '/'
|
||||
let l:lines = []
|
||||
|
||||
" Create lines to display to users.
|
||||
for l:item in a:item_list
|
||||
let l:match = get(l:item, 'match', '')
|
||||
let l:filename = l:item.filename
|
||||
|
||||
if get(l:options, 'use_relative_paths')
|
||||
let l:cwd = getcwd() " no-custom-checks
|
||||
let l:filename = substitute(l:filename, '^' . l:cwd . l:sep, '', '')
|
||||
endif
|
||||
|
||||
call add(
|
||||
\ l:lines,
|
||||
\ l:item.filename
|
||||
\ l:filename
|
||||
\ . ':' . l:item.line
|
||||
\ . ':' . l:item.column
|
||||
\ . (!empty(l:match) ? ' ' . l:match : ''),
|
||||
@ -63,7 +71,7 @@ endfunction
|
||||
|
||||
function! s:Open(open_in_tab) abort
|
||||
let l:item_list = get(b:, 'ale_preview_item_list', [])
|
||||
let l:item = get(l:item_list, getcurpos()[1] - 1, {})
|
||||
let l:item = get(l:item_list, getpos('.')[1] - 1, {})
|
||||
|
||||
if empty(l:item)
|
||||
return
|
||||
|
@ -27,6 +27,9 @@ function! ale#python#FindProjectRootIni(buffer) abort
|
||||
\|| filereadable(l:path . '/pycodestyle.cfg')
|
||||
\|| filereadable(l:path . '/flake8.cfg')
|
||||
\|| filereadable(l:path . '/.flake8rc')
|
||||
\|| filereadable(l:path . '/pylama.ini')
|
||||
\|| filereadable(l:path . '/pylintrc')
|
||||
\|| filereadable(l:path . '/.pylintrc')
|
||||
\|| filereadable(l:path . '/Pipfile')
|
||||
\|| filereadable(l:path . '/Pipfile.lock')
|
||||
return l:path
|
||||
@ -48,7 +51,7 @@ function! ale#python#FindProjectRoot(buffer) abort
|
||||
let l:ini_root = ale#python#FindProjectRootIni(a:buffer)
|
||||
|
||||
if !empty(l:ini_root)
|
||||
return l:ini_root
|
||||
return l:ini_root
|
||||
endif
|
||||
|
||||
for l:path in ale#path#Upwards(expand('#' . a:buffer . ':p:h'))
|
||||
@ -110,6 +113,44 @@ function! ale#python#FindExecutable(buffer, base_var_name, path_list) abort
|
||||
return ale#Var(a:buffer, a:base_var_name . '_executable')
|
||||
endfunction
|
||||
|
||||
" Handle traceback.print_exception() output starting in the first a:limit lines.
|
||||
function! ale#python#HandleTraceback(lines, limit) abort
|
||||
let l:nlines = len(a:lines)
|
||||
let l:limit = a:limit > l:nlines ? l:nlines : a:limit
|
||||
let l:start = 0
|
||||
|
||||
while l:start < l:limit
|
||||
if a:lines[l:start] is# 'Traceback (most recent call last):'
|
||||
break
|
||||
endif
|
||||
|
||||
let l:start += 1
|
||||
endwhile
|
||||
|
||||
if l:start >= l:limit
|
||||
return []
|
||||
endif
|
||||
|
||||
let l:end = l:start + 1
|
||||
|
||||
" Traceback entries are always prefixed with 2 spaces.
|
||||
" SyntaxError marker (if present) is prefixed with at least 4 spaces.
|
||||
" Final exc line starts with exception class name (never a space).
|
||||
while l:end < l:nlines && a:lines[l:end][0] is# ' '
|
||||
let l:end += 1
|
||||
endwhile
|
||||
|
||||
let l:exc_line = l:end < l:nlines
|
||||
\ ? a:lines[l:end]
|
||||
\ : 'An exception was thrown.'
|
||||
|
||||
return [{
|
||||
\ 'lnum': 1,
|
||||
\ 'text': l:exc_line . ' (See :ALEDetail)',
|
||||
\ 'detail': join(a:lines[(l:start):(l:end)], "\n"),
|
||||
\}]
|
||||
endfunction
|
||||
|
||||
" Detects whether a pipenv environment is present.
|
||||
function! ale#python#PipenvPresent(buffer) abort
|
||||
return findfile('Pipfile.lock', expand('#' . a:buffer . ':p:h') . ';') isnot# ''
|
||||
|
@ -17,7 +17,7 @@ endfunction
|
||||
function! ale#references#HandleTSServerResponse(conn_id, response) abort
|
||||
if get(a:response, 'command', '') is# 'references'
|
||||
\&& has_key(s:references_map, a:response.request_seq)
|
||||
call remove(s:references_map, a:response.request_seq)
|
||||
let l:options = remove(s:references_map, a:response.request_seq)
|
||||
|
||||
if get(a:response, 'success', v:false) is v:true
|
||||
let l:item_list = []
|
||||
@ -34,7 +34,7 @@ function! ale#references#HandleTSServerResponse(conn_id, response) abort
|
||||
if empty(l:item_list)
|
||||
call ale#util#Execute('echom ''No references found.''')
|
||||
else
|
||||
call ale#preview#ShowSelection(l:item_list)
|
||||
call ale#preview#ShowSelection(l:item_list, l:options)
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
@ -43,7 +43,7 @@ endfunction
|
||||
function! ale#references#HandleLSPResponse(conn_id, response) abort
|
||||
if has_key(a:response, 'id')
|
||||
\&& has_key(s:references_map, a:response.id)
|
||||
call remove(s:references_map, a:response.id)
|
||||
let l:options = remove(s:references_map, a:response.id)
|
||||
|
||||
" The result can be a Dictionary item, a List of the same, or null.
|
||||
let l:result = get(a:response, 'result', [])
|
||||
@ -60,15 +60,20 @@ function! ale#references#HandleLSPResponse(conn_id, response) abort
|
||||
if empty(l:item_list)
|
||||
call ale#util#Execute('echom ''No references found.''')
|
||||
else
|
||||
call ale#preview#ShowSelection(l:item_list)
|
||||
call ale#preview#ShowSelection(l:item_list, l:options)
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:OnReady(linter, lsp_details, line, column, ...) abort
|
||||
let l:buffer = a:lsp_details.buffer
|
||||
function! s:OnReady(line, column, options, linter, lsp_details) abort
|
||||
let l:id = a:lsp_details.connection_id
|
||||
|
||||
if !ale#lsp#HasCapability(l:id, 'references')
|
||||
return
|
||||
endif
|
||||
|
||||
let l:buffer = a:lsp_details.buffer
|
||||
|
||||
let l:Callback = a:linter.lsp is# 'tsserver'
|
||||
\ ? function('ale#references#HandleTSServerResponse')
|
||||
\ : function('ale#references#HandleLSPResponse')
|
||||
@ -91,34 +96,30 @@ function! s:OnReady(linter, lsp_details, line, column, ...) abort
|
||||
|
||||
let l:request_id = ale#lsp#Send(l:id, l:message)
|
||||
|
||||
let s:references_map[l:request_id] = {}
|
||||
let s:references_map[l:request_id] = {
|
||||
\ 'use_relative_paths': has_key(a:options, 'use_relative_paths') ? a:options.use_relative_paths : 0
|
||||
\}
|
||||
endfunction
|
||||
|
||||
function! s:FindReferences(linter) abort
|
||||
function! ale#references#Find(...) abort
|
||||
let l:options = {}
|
||||
|
||||
if len(a:000) > 0
|
||||
for l:option in a:000
|
||||
if l:option is? '-relative'
|
||||
let l:options.use_relative_paths = 1
|
||||
endif
|
||||
endfor
|
||||
endif
|
||||
|
||||
let l:buffer = bufnr('')
|
||||
let [l:line, l:column] = getcurpos()[1:2]
|
||||
let [l:line, l:column] = getpos('.')[1:2]
|
||||
let l:column = min([l:column, len(getline(l:line))])
|
||||
let l:Callback = function('s:OnReady', [l:line, l:column, l:options])
|
||||
|
||||
if a:linter.lsp isnot# 'tsserver'
|
||||
let l:column = min([l:column, len(getline(l:line))])
|
||||
endif
|
||||
|
||||
let l:lsp_details = ale#lsp_linter#StartLSP(l:buffer, a:linter)
|
||||
|
||||
if empty(l:lsp_details)
|
||||
return 0
|
||||
endif
|
||||
|
||||
let l:id = l:lsp_details.connection_id
|
||||
|
||||
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
|
||||
for l:linter in ale#linter#Get(&filetype)
|
||||
if !empty(l:linter.lsp)
|
||||
call s:FindReferences(l:linter)
|
||||
call ale#lsp_linter#StartLSP(l:buffer, l:linter, l:Callback)
|
||||
endif
|
||||
endfor
|
||||
endfunction
|
||||
|
@ -26,7 +26,7 @@ function! ale#ruby#FindProjectRoot(buffer) abort
|
||||
let l:dir = ale#ruby#FindRailsRoot(a:buffer)
|
||||
|
||||
if isdirectory(l:dir)
|
||||
return l:dir
|
||||
return l:dir
|
||||
endif
|
||||
|
||||
for l:name in ['.solargraph.yml', 'Rakefile', 'Gemfile']
|
||||
|
@ -14,7 +14,7 @@ function! ale#semver#GetVersion(executable, version_lines) abort
|
||||
let l:version = get(s:version_cache, a:executable, [])
|
||||
|
||||
for l:line in a:version_lines
|
||||
let l:match = matchlist(l:line, '\v(\d+)\.(\d+)\.(\d+)')
|
||||
let l:match = matchlist(l:line, '\v(\d+)\.(\d+)\.?(\d?)')
|
||||
|
||||
if !empty(l:match)
|
||||
let l:version = [l:match[1] + 0, l:match[2] + 0, l:match[3] + 0]
|
||||
|
@ -116,7 +116,7 @@ endfunction
|
||||
" Read sign data for a buffer to a list of lines.
|
||||
function! ale#sign#ReadSigns(buffer) abort
|
||||
redir => l:output
|
||||
silent execute 'sign place buffer=' . a:buffer
|
||||
silent execute 'sign place buffer=' . a:buffer
|
||||
redir end
|
||||
|
||||
return split(l:output, "\n")
|
||||
|
@ -1,4 +1,5 @@
|
||||
" Author: KabbAmine <amine.kabb@gmail.com>
|
||||
" Additions by: petpetpetpet <chris@freelanceninjas.com>
|
||||
" Description: Statusline related function(s)
|
||||
|
||||
function! s:CreateCountDict() abort
|
||||
@ -26,19 +27,42 @@ function! ale#statusline#Update(buffer, loclist) abort
|
||||
let l:count = s:CreateCountDict()
|
||||
let l:count.total = len(l:loclist)
|
||||
|
||||
" Allows easy access to the first instance of each problem type.
|
||||
let l:first_problems = {}
|
||||
|
||||
for l:entry in l:loclist
|
||||
if l:entry.type is# 'W'
|
||||
if get(l:entry, 'sub_type', '') is# 'style'
|
||||
let l:count.style_warning += 1
|
||||
|
||||
if l:count.style_warning == 1
|
||||
let l:first_problems.style_warning = l:entry
|
||||
endif
|
||||
else
|
||||
let l:count.warning += 1
|
||||
|
||||
if l:count.warning == 1
|
||||
let l:first_problems.warning = l:entry
|
||||
endif
|
||||
endif
|
||||
elseif l:entry.type is# 'I'
|
||||
let l:count.info += 1
|
||||
|
||||
if l:count.info == 1
|
||||
let l:first_problems.info = l:entry
|
||||
endif
|
||||
elseif get(l:entry, 'sub_type', '') is# 'style'
|
||||
let l:count.style_error += 1
|
||||
|
||||
if l:count.style_error == 1
|
||||
let l:first_problems.style_error = l:entry
|
||||
endif
|
||||
else
|
||||
let l:count.error += 1
|
||||
|
||||
if l:count.error == 1
|
||||
let l:first_problems.error = l:entry
|
||||
endif
|
||||
endif
|
||||
endfor
|
||||
|
||||
@ -47,24 +71,65 @@ function! ale#statusline#Update(buffer, loclist) abort
|
||||
let l:count[1] = l:count.total - l:count[0]
|
||||
|
||||
let g:ale_buffer_info[a:buffer].count = l:count
|
||||
let g:ale_buffer_info[a:buffer].first_problems = l:first_problems
|
||||
endfunction
|
||||
|
||||
" Get the counts for the buffer, and update the counts if needed.
|
||||
function! s:UpdateCacheIfNecessary(buffer) abort
|
||||
" Cache is cold, so manually ask for an update.
|
||||
if !has_key(g:ale_buffer_info[a:buffer], 'count')
|
||||
call ale#statusline#Update(
|
||||
\ a:buffer,
|
||||
\ g:ale_buffer_info[a:buffer].loclist
|
||||
\)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:BufferCacheExists(buffer) abort
|
||||
if !exists('g:ale_buffer_info') || !has_key(g:ale_buffer_info, a:buffer)
|
||||
return 0
|
||||
endif
|
||||
|
||||
return 1
|
||||
endfunction
|
||||
|
||||
" Get the counts for the buffer, and update the counts if needed.
|
||||
function! s:GetCounts(buffer) abort
|
||||
if !exists('g:ale_buffer_info') || !has_key(g:ale_buffer_info, a:buffer)
|
||||
if !s:BufferCacheExists(a:buffer)
|
||||
return s:CreateCountDict()
|
||||
endif
|
||||
|
||||
" Cache is cold, so manually ask for an update.
|
||||
if !has_key(g:ale_buffer_info[a:buffer], 'count')
|
||||
call ale#statusline#Update(a:buffer, g:ale_buffer_info[a:buffer].loclist)
|
||||
endif
|
||||
call s:UpdateCacheIfNecessary(a:buffer)
|
||||
|
||||
return g:ale_buffer_info[a:buffer].count
|
||||
endfunction
|
||||
|
||||
" Get the dict of first_problems, update the buffer info cache if necessary.
|
||||
function! s:GetFirstProblems(buffer) abort
|
||||
if !s:BufferCacheExists(a:buffer)
|
||||
return {}
|
||||
endif
|
||||
|
||||
call s:UpdateCacheIfNecessary(a:buffer)
|
||||
|
||||
return g:ale_buffer_info[a:buffer].first_problems
|
||||
endfunction
|
||||
|
||||
" Returns a Dictionary with counts for use in third party integrations.
|
||||
function! ale#statusline#Count(buffer) abort
|
||||
" The Dictionary is copied here before exposing it to other plugins.
|
||||
return copy(s:GetCounts(a:buffer))
|
||||
endfunction
|
||||
|
||||
" Returns a copy of the *first* locline instance of the specified problem
|
||||
" type. (so this would allow an external integration to know all the info
|
||||
" about the first style warning in the file, for example.)
|
||||
function! ale#statusline#FirstProblem(buffer, type) abort
|
||||
let l:first_problems = s:GetFirstProblems(a:buffer)
|
||||
|
||||
if !empty(l:first_problems) && has_key(l:first_problems, a:type)
|
||||
return copy(l:first_problems[a:type])
|
||||
endif
|
||||
|
||||
return {}
|
||||
endfunction
|
||||
|
@ -52,12 +52,18 @@ function! ale#symbol#HandleLSPResponse(conn_id, response) abort
|
||||
if empty(l:item_list)
|
||||
call ale#util#Execute('echom ''No symbols found.''')
|
||||
else
|
||||
call ale#preview#ShowSelection(l:item_list)
|
||||
call ale#preview#ShowSelection(l:item_list, l:options)
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:OnReady(linter, lsp_details, query, ...) abort
|
||||
function! s:OnReady(query, options, linter, lsp_details) abort
|
||||
let l:id = a:lsp_details.connection_id
|
||||
|
||||
if !ale#lsp#HasCapability(l:id, 'symbol_search')
|
||||
return
|
||||
endif
|
||||
|
||||
let l:buffer = a:lsp_details.buffer
|
||||
|
||||
" If we already made a request, stop here.
|
||||
@ -65,8 +71,6 @@ function! s:OnReady(linter, lsp_details, query, ...) abort
|
||||
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)
|
||||
|
||||
@ -76,34 +80,31 @@ function! s:OnReady(linter, lsp_details, query, ...) abort
|
||||
call setbufvar(l:buffer, 'ale_symbol_request_made', 1)
|
||||
let s:symbol_map[l:request_id] = {
|
||||
\ 'buffer': l:buffer,
|
||||
\ 'use_relative_paths': has_key(a:options, 'use_relative_paths') ? a:options.use_relative_paths : 0
|
||||
\}
|
||||
endfunction
|
||||
|
||||
function! s:Search(linter, buffer, query) abort
|
||||
let l:lsp_details = ale#lsp_linter#StartLSP(a:buffer, a:linter)
|
||||
function! ale#symbol#Search(args) abort
|
||||
let [l:opts, l:query] = ale#args#Parse(['relative'], a:args)
|
||||
|
||||
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)
|
||||
if empty(l:query)
|
||||
throw 'A non-empty string must be provided!'
|
||||
endif
|
||||
|
||||
let l:buffer = bufnr('')
|
||||
let l:options = {}
|
||||
|
||||
if has_key(l:opts, 'relative')
|
||||
let l:options.use_relative_paths = 1
|
||||
endif
|
||||
|
||||
" Set a flag so we only make one request.
|
||||
call setbufvar(l:buffer, 'ale_symbol_request_made', 0)
|
||||
let l:Callback = function('s:OnReady', [l:query, l:options])
|
||||
|
||||
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)
|
||||
call ale#lsp_linter#StartLSP(l:buffer, l:linter, l:Callback)
|
||||
endif
|
||||
endfor
|
||||
endfunction
|
||||
|
@ -55,9 +55,9 @@ endfunction
|
||||
|
||||
function! s:RemoveModule(results) abort
|
||||
for l:item in a:results
|
||||
if has_key(l:item, 'module')
|
||||
call remove(l:item, 'module')
|
||||
endif
|
||||
if has_key(l:item, 'module')
|
||||
call remove(l:item, 'module')
|
||||
endif
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
@ -85,3 +85,103 @@ function! ale#test#GetPreviewWindowText() abort
|
||||
endif
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
" This function can be called with a timeout to wait for all jobs to finish.
|
||||
" If the jobs to not finish in the given number of milliseconds,
|
||||
" an exception will be thrown.
|
||||
"
|
||||
" The time taken will be a very rough approximation, and more time may be
|
||||
" permitted than is specified.
|
||||
function! ale#test#WaitForJobs(deadline) abort
|
||||
let l:start_time = ale#events#ClockMilliseconds()
|
||||
|
||||
if l:start_time == 0
|
||||
throw 'Failed to read milliseconds from the clock!'
|
||||
endif
|
||||
|
||||
let l:job_list = []
|
||||
|
||||
" Gather all of the jobs from every buffer.
|
||||
for [l:buffer, l:data] in items(ale#command#GetData())
|
||||
call extend(l:job_list, map(keys(l:data.jobs), 'str2nr(v:val)'))
|
||||
endfor
|
||||
|
||||
" NeoVim has a built-in API for this, so use that.
|
||||
if has('nvim')
|
||||
let l:nvim_code_list = jobwait(l:job_list, a:deadline)
|
||||
|
||||
if index(l:nvim_code_list, -1) >= 0
|
||||
throw 'Jobs did not complete on time!'
|
||||
endif
|
||||
|
||||
return
|
||||
endif
|
||||
|
||||
let l:should_wait_more = 1
|
||||
|
||||
while l:should_wait_more
|
||||
let l:should_wait_more = 0
|
||||
|
||||
for l:job_id in l:job_list
|
||||
if ale#job#IsRunning(l:job_id)
|
||||
let l:now = ale#events#ClockMilliseconds()
|
||||
|
||||
if l:now - l:start_time > a:deadline
|
||||
" Stop waiting after a timeout, so we don't wait forever.
|
||||
throw 'Jobs did not complete on time!'
|
||||
endif
|
||||
|
||||
" Wait another 10 milliseconds
|
||||
let l:should_wait_more = 1
|
||||
sleep 10ms
|
||||
break
|
||||
endif
|
||||
endfor
|
||||
endwhile
|
||||
|
||||
" Sleep for a small amount of time after all jobs finish.
|
||||
" This seems to be enough to let handlers after jobs end run, and
|
||||
" prevents the occasional failure where this function exits after jobs
|
||||
" end, but before handlers are run.
|
||||
sleep 10ms
|
||||
|
||||
" We must check the buffer data again to see if new jobs started
|
||||
" for command_chain linters.
|
||||
let l:has_new_jobs = 0
|
||||
|
||||
" Check again to see if any jobs are running.
|
||||
for l:info in values(g:ale_buffer_info)
|
||||
for [l:job_id, l:linter] in get(l:info, 'job_list', [])
|
||||
if ale#job#IsRunning(l:job_id)
|
||||
let l:has_new_jobs = 1
|
||||
break
|
||||
endif
|
||||
endfor
|
||||
endfor
|
||||
|
||||
if l:has_new_jobs
|
||||
" We have to wait more. Offset the timeout by the time taken so far.
|
||||
let l:now = ale#events#ClockMilliseconds()
|
||||
let l:new_deadline = a:deadline - (l:now - l:start_time)
|
||||
|
||||
if l:new_deadline <= 0
|
||||
" Enough time passed already, so stop immediately.
|
||||
throw 'Jobs did not complete on time!'
|
||||
endif
|
||||
|
||||
call ale#test#WaitForJobs(l:new_deadline)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! ale#test#FlushJobs() abort
|
||||
" The variable is checked for in a loop, as calling one series of
|
||||
" callbacks can trigger a further series of callbacks.
|
||||
while exists('g:ale_run_synchronously_callbacks')
|
||||
let l:callbacks = g:ale_run_synchronously_callbacks
|
||||
unlet g:ale_run_synchronously_callbacks
|
||||
|
||||
for l:Callback in l:callbacks
|
||||
call l:Callback()
|
||||
endfor
|
||||
endwhile
|
||||
endfunction
|
||||
|
@ -13,6 +13,10 @@ function! s:DisablePostamble() abort
|
||||
if g:ale_set_highlights
|
||||
call ale#highlight#UpdateHighlights()
|
||||
endif
|
||||
|
||||
if g:ale_virtualtext_cursor
|
||||
call ale#virtualtext#Clear()
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! ale#toggle#Toggle() abort
|
||||
|
@ -111,6 +111,7 @@ function! ale#util#Open(filename, line, column, options) abort
|
||||
endif
|
||||
|
||||
call cursor(a:line, a:column)
|
||||
normal! zz
|
||||
endfunction
|
||||
|
||||
let g:ale#util#error_priority = 5
|
||||
@ -469,7 +470,7 @@ 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:pos = getpos('.')
|
||||
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] : {}
|
||||
|
||||
|
@ -47,7 +47,6 @@ function! ale#virtualtext#ShowMessage(message, hl_group) abort
|
||||
return
|
||||
endif
|
||||
|
||||
let l:cursor_position = getcurpos()
|
||||
let l:line = line('.')
|
||||
let l:buffer = bufnr('')
|
||||
let l:prefix = get(g:, 'ale_virtualtext_prefix', '> ')
|
||||
@ -117,7 +116,7 @@ function! ale#virtualtext#ShowCursorWarningWithDelay() abort
|
||||
|
||||
call s:StopCursorTimer()
|
||||
|
||||
let l:pos = getcurpos()[0:2]
|
||||
let l:pos = getpos('.')[0:2]
|
||||
|
||||
" Check the current buffer, line, and column number against the last
|
||||
" recorded position. If the position has actually changed, *then*
|
||||
|
Reference in New Issue
Block a user