Merge branch 'master' into sridhars

This commit is contained in:
Bjorn Neergaard
2018-11-29 14:57:35 -07:00
496 changed files with 10014 additions and 2946 deletions

View File

@@ -10,8 +10,7 @@ let g:ale_echo_msg_warning_str = get(g:, 'ale_echo_msg_warning_str', 'Warning')
let g:ale_linters_ignore = get(g:, 'ale_linters_ignore', {})
let s:lint_timer = -1
let s:queued_buffer_number = -1
let s:should_lint_file_for_buffer = {}
let s:getcmdwintype_exists = exists('*getcmdwintype')
" Return 1 if a file is too large for ALE to handle.
function! ale#FileTooLarge(buffer) abort
@@ -20,14 +19,12 @@ function! ale#FileTooLarge(buffer) abort
return l:max > 0 ? (line2byte(line('$') + 1) > l:max) : 0
endfunction
let s:getcmdwintype_exists = exists('*getcmdwintype')
" A function for checking various conditions whereby ALE just shouldn't
" attempt to do anything, say if particular buffer types are open in Vim.
function! ale#ShouldDoNothing(buffer) abort
" The checks are split into separate if statements to make it possible to
" profile each check individually with Vim's profiling tools.
"
" Do nothing if ALE is disabled.
if !getbufvar(a:buffer, 'ale_enabled', get(g:, 'ale_enabled', 0))
return 1
@@ -62,6 +59,11 @@ function! ale#ShouldDoNothing(buffer) abort
return 1
endif
" Don't start linting and so on when an operator is pending.
if ale#util#Mode(1) is# 'no'
return 1
endif
" Do nothing if running in the sandbox.
if ale#util#InSandbox()
return 1
@@ -81,21 +83,47 @@ function! ale#ShouldDoNothing(buffer) abort
return 0
endfunction
function! s:Lint(buffer, should_lint_file, timer_id) abort
" Use the filetype from the buffer
let l:filetype = getbufvar(a:buffer, '&filetype')
let l:linters = ale#linter#Get(l:filetype)
" Apply ignore lists for linters only if needed.
let l:ignore_config = ale#Var(a:buffer, 'linters_ignore')
let l:linters = !empty(l:ignore_config)
\ ? ale#engine#ignore#Exclude(l:filetype, l:linters, l:ignore_config)
\ : l:linters
" Tell other sources that they can start checking the buffer now.
let g:ale_want_results_buffer = a:buffer
silent doautocmd <nomodeline> User ALEWantResults
unlet! g:ale_want_results_buffer
" Don't set up buffer data and so on if there are no linters to run.
if !has_key(g:ale_buffer_info, a:buffer) && empty(l:linters)
return
endif
" Clear lint_file linters, or only run them if the file exists.
let l:lint_file = empty(l:linters)
\ || (a:should_lint_file && filereadable(expand('#' . a:buffer . ':p')))
call ale#engine#RunLinters(a:buffer, l:linters, l:lint_file)
endfunction
" (delay, [linting_flag, buffer_number])
function! ale#Queue(delay, ...) abort
if a:0 > 2
throw 'too many arguments!'
endif
" Default linting_flag to ''
let l:linting_flag = get(a:000, 0, '')
let l:buffer = get(a:000, 1, bufnr(''))
let l:buffer = get(a:000, 1, v:null)
if l:linting_flag isnot# '' && l:linting_flag isnot# 'lint_file'
throw "linting_flag must be either '' or 'lint_file'"
if l:buffer is v:null
let l:buffer = bufnr('')
endif
if type(l:buffer) != type(0)
if type(l:buffer) isnot v:t_number
throw 'buffer_number must be a Number'
endif
@@ -103,80 +131,24 @@ function! ale#Queue(delay, ...) abort
return
endif
" Remember that we want to check files for this buffer.
" We will remember this until we finally run the linters, via any event.
if l:linting_flag is# 'lint_file'
let s:should_lint_file_for_buffer[l:buffer] = 1
endif
" Default linting_flag to ''
let l:should_lint_file = get(a:000, 0) is# 'lint_file'
if s:lint_timer != -1
call timer_stop(s:lint_timer)
let s:lint_timer = -1
endif
let l:linters = ale#linter#Get(getbufvar(l:buffer, '&filetype'))
" Don't set up buffer data and so on if there are no linters to run.
if empty(l:linters)
" If we have some previous buffer data, then stop any jobs currently
" running and clear everything.
if has_key(g:ale_buffer_info, l:buffer)
call ale#engine#RunLinters(l:buffer, [], 1)
endif
return
endif
if a:delay > 0
let s:queued_buffer_number = l:buffer
let s:lint_timer = timer_start(a:delay, function('ale#Lint'))
let s:lint_timer = timer_start(
\ a:delay,
\ function('s:Lint', [l:buffer, l:should_lint_file])
\)
else
call ale#Lint(-1, l:buffer)
call s:Lint(l:buffer, l:should_lint_file, 0)
endif
endfunction
function! ale#Lint(...) abort
if a:0 > 1
" Use the buffer number given as the optional second argument.
let l:buffer = a:2
elseif a:0 > 0 && a:1 == s:lint_timer
" Use the buffer number for the buffer linting was queued for.
let l:buffer = s:queued_buffer_number
else
" Use the current buffer number.
let l:buffer = bufnr('')
endif
if ale#ShouldDoNothing(l:buffer)
return
endif
" Use the filetype from the buffer
let l:filetype = getbufvar(l:buffer, '&filetype')
let l:linters = ale#linter#Get(l:filetype)
let l:should_lint_file = 0
" Check if we previously requested checking the file.
if has_key(s:should_lint_file_for_buffer, l:buffer)
unlet s:should_lint_file_for_buffer[l:buffer]
" Lint files if they exist.
let l:should_lint_file = filereadable(expand('#' . l:buffer . ':p'))
endif
" Apply ignore lists for linters only if needed.
let l:ignore_config = ale#Var(l:buffer, 'linters_ignore')
let l:linters = !empty(l:ignore_config)
\ ? ale#engine#ignore#Exclude(l:filetype, l:linters, l:ignore_config)
\ : l:linters
call ale#engine#RunLinters(l:buffer, l:linters, l:should_lint_file)
endfunction
" Reset flags indicating that files should be checked for all buffers.
function! ale#ResetLintFileMarkers() abort
let s:should_lint_file_for_buffer = {}
endfunction
let g:ale_has_override = get(g:, 'ale_has_override', {})
" Call has(), but check a global Dictionary so we can force flags on or off
@@ -197,6 +169,11 @@ function! ale#Var(buffer, variable_name) abort
return get(l:vars, l:full_name, g:[l:full_name])
endfunction
" As above, but curry the arguments so only the buffer number is required.
function! ale#VarFunc(variable_name) abort
return {buf -> ale#Var(buf, a:variable_name)}
endfunction
" Initialize a variable with a default value, if it isn't already set.
"
" Every variable name will be prefixed with 'ale_'.

View File

@@ -30,7 +30,7 @@ function! ale#assert#Linter(expected_executable, expected_command) abort
let l:callbacks = map(copy(l:linter.command_chain), 'v:val.callback')
" If the expected command is a string, just check the last one.
if type(a:expected_command) is type('')
if type(a:expected_command) is v:t_string
if len(l:callbacks) is 1
let l:command = call(l:callbacks[0], [l:buffer])
else
@@ -54,9 +54,14 @@ function! ale#assert#Linter(expected_executable, expected_command) abort
endif
else
let l:command = ale#linter#GetCommand(l:buffer, l:linter)
endif
if type(l:command) is v:t_string
" Replace %e with the escaped executable, so tests keep passing after
" linters are changed to use %e.
let l:command = substitute(l:command, '%e', '\=ale#Escape(l:executable)', 'g')
else
call map(l:command, 'substitute(v:val, ''%e'', ''\=ale#Escape(l:executable)'', ''g'')')
endif
AssertEqual
@@ -80,6 +85,14 @@ function! ale#assert#LSPOptions(expected_options) abort
AssertEqual a:expected_options, l:initialization_options
endfunction
function! ale#assert#LSPConfig(expected_config) abort
let l:buffer = bufnr('')
let l:linter = s:GetLinter()
let l:config = ale#lsp_linter#GetConfig(l:buffer, l:linter)
AssertEqual a:expected_config, l:config
endfunction
function! ale#assert#LSPLanguage(expected_language) abort
let l:buffer = bufnr('')
let l:linter = s:GetLinter()
@@ -96,6 +109,14 @@ function! ale#assert#LSPProject(expected_root) abort
AssertEqual a:expected_root, l:root
endfunction
function! ale#assert#LSPAddress(expected_address) abort
let l:buffer = bufnr('')
let l:linter = s:GetLinter()
let l:address = ale#util#GetFunction(l:linter.address_callback)(l:buffer)
AssertEqual a:expected_address, l:address
endfunction
" A dummy function for making sure this module is loaded.
function! ale#assert#SetUpLinterTest(filetype, name) abort
" Set up a marker so ALE doesn't create real random temporary filenames.
@@ -126,28 +147,59 @@ function! ale#assert#SetUpLinterTest(filetype, name) abort
execute 'runtime ale_linters/' . a:filetype . '/' . a:name . '.vim'
call ale#test#SetDirectory('/testplugin/test/command_callback')
if !exists('g:dir')
call ale#test#SetDirectory('/testplugin/test/command_callback')
endif
command! -nargs=+ WithChainResults :call ale#assert#WithChainResults(<args>)
command! -nargs=+ AssertLinter :call ale#assert#Linter(<args>)
command! -nargs=0 AssertLinterNotExecuted :call ale#assert#LinterNotExecuted()
command! -nargs=+ AssertLSPOptions :call ale#assert#LSPOptions(<args>)
command! -nargs=+ AssertLSPConfig :call ale#assert#LSPConfig(<args>)
command! -nargs=+ AssertLSPLanguage :call ale#assert#LSPLanguage(<args>)
command! -nargs=+ AssertLSPProject :call ale#assert#LSPProject(<args>)
command! -nargs=+ AssertLSPAddress :call ale#assert#LSPAddress(<args>)
endfunction
function! ale#assert#TearDownLinterTest() abort
unlet! g:ale_create_dummy_temporary_file
let s:chain_results = []
delcommand WithChainResults
delcommand AssertLinter
delcommand AssertLinterNotExecuted
delcommand AssertLSPOptions
delcommand AssertLSPLanguage
delcommand AssertLSPProject
if exists(':WithChainResults')
delcommand WithChainResults
endif
call ale#test#RestoreDirectory()
if exists(':AssertLinter')
delcommand AssertLinter
endif
if exists(':AssertLinterNotExecuted')
delcommand AssertLinterNotExecuted
endif
if exists(':AssertLSPOptions')
delcommand AssertLSPOptions
endif
if exists(':AssertLSPConfig')
delcommand AssertLSPConfig
endif
if exists(':AssertLSPLanguage')
delcommand AssertLSPLanguage
endif
if exists(':AssertLSPProject')
delcommand AssertLSPProject
endif
if exists(':AssertLSPAddress')
delcommand AssertLSPAddress
endif
if exists('g:dir')
call ale#test#RestoreDirectory()
endif
Restore

View File

@@ -2,11 +2,31 @@
" Description: Functions for integrating with C-family linters.
call ale#Set('c_parse_makefile', 0)
call ale#Set('c_parse_compile_commands', 0)
let s:sep = has('win32') ? '\' : '/'
" Set just so tests can override it.
let g:__ale_c_project_filenames = ['.git/HEAD', 'configure', 'Makefile', 'CMakeLists.txt']
function! ale#c#GetBuildDirectory(buffer) abort
" Don't include build directory for header files, as compile_commands.json
" files don't consider headers to be translation units, and provide no
" commands for compiling header files.
if expand('#' . a:buffer) =~# '\v\.(h|hpp)$'
return ''
endif
let l:build_dir = ale#Var(a:buffer, 'c_build_dir')
" c_build_dir has the priority if defined
if !empty(l:build_dir)
return l:build_dir
endif
return ale#path#Dirname(ale#c#FindCompileCommands(a:buffer))
endfunction
function! ale#c#FindProjectRoot(buffer) abort
for l:project_filename in g:__ale_c_project_filenames
let l:full_path = ale#path#FindNearestFile(a:buffer, l:project_filename)
@@ -26,15 +46,21 @@ function! ale#c#FindProjectRoot(buffer) abort
return ''
endfunction
function! ale#c#ParseCFlagsToList(path_prefix, cflags) abort
function! ale#c#ParseCFlags(path_prefix, cflag_line) abort
let l:cflags_list = []
let l:previous_options = []
for l:option in a:cflags
let l:split_lines = split(a:cflag_line, '-')
let l:option_index = 0
while l:option_index < len(l:split_lines)
let l:option = l:split_lines[l:option_index]
let l:option_index = l:option_index + 1
call add(l:previous_options, l:option)
" Check if cflag contained a '-' and should not have been splitted
let l:option_list = split(l:option, '\zs')
if l:option_list[-1] isnot# ' '
if len(l:option_list) > 0 && l:option_list[-1] isnot# ' ' && l:option_index < len(l:split_lines)
continue
endif
@@ -60,34 +86,134 @@ function! ale#c#ParseCFlagsToList(path_prefix, cflags) abort
call add(l:cflags_list, l:option)
endif
endif
endfor
endwhile
return l:cflags_list
return join(l:cflags_list, ' ')
endfunction
function! ale#c#ParseCFlags(buffer, stdout_make) abort
function! ale#c#ParseCFlagsFromMakeOutput(buffer, make_output) abort
if !g:ale_c_parse_makefile
return []
return ''
endif
let l:buffer_filename = expand('#' . a:buffer . ':t')
let l:cflags = []
for l:lines in split(a:stdout_make, '\\n')
if stridx(l:lines, l:buffer_filename) >= 0
let l:cflags = split(l:lines, '-')
let l:cflag_line = ''
" Find a line matching this buffer's filename in the make output.
for l:line in a:make_output
if stridx(l:line, l:buffer_filename) >= 0
let l:cflag_line = l:line
break
endif
endfor
let l:makefile_path = ale#path#FindNearestFile(a:buffer, 'Makefile')
return ale#c#ParseCFlagsToList(fnamemodify(l:makefile_path, ':p:h'), l:cflags)
let l:makefile_dir = fnamemodify(l:makefile_path, ':p:h')
return ale#c#ParseCFlags(l:makefile_dir, l:cflag_line)
endfunction
" Given a buffer number, find the build subdirectory with compile commands
" The subdirectory is returned without the trailing /
function! ale#c#FindCompileCommands(buffer) abort
" Look above the current source file to find compile_commands.json
let l:json_file = ale#path#FindNearestFile(a:buffer, 'compile_commands.json')
if !empty(l:json_file)
return l:json_file
endif
" Search in build directories if we can't find it in the project.
for l:path in ale#path#Upwards(expand('#' . a:buffer . ':p:h'))
for l:dirname in ale#Var(a:buffer, 'c_build_dir_names')
let l:c_build_dir = l:path . s:sep . l:dirname
let l:json_file = l:c_build_dir . s:sep . 'compile_commands.json'
if filereadable(l:json_file)
return l:json_file
endif
endfor
endfor
return ''
endfunction
" Cache compile_commands.json data in a Dictionary, so we don't need to read
" the same files over and over again. The key in the dictionary will include
" the last modified time of the file.
if !exists('s:compile_commands_cache')
let s:compile_commands_cache = {}
endif
function! s:GetListFromCompileCommandsFile(compile_commands_file) abort
if empty(a:compile_commands_file)
return []
endif
let l:time = getftime(a:compile_commands_file)
if l:time < 0
return []
endif
let l:key = a:compile_commands_file . ':' . l:time
if has_key(s:compile_commands_cache, l:key)
return s:compile_commands_cache[l:key]
endif
let l:data = []
silent! let l:data = json_decode(join(readfile(a:compile_commands_file), ''))
if !empty(l:data)
let s:compile_commands_cache[l:key] = l:data
return l:data
endif
return []
endfunction
function! ale#c#ParseCompileCommandsFlags(buffer, dir, json_list) abort
" Search for an exact file match first.
for l:item in a:json_list
if bufnr(l:item.file) is a:buffer
return ale#c#ParseCFlags(a:dir, l:item.command)
endif
endfor
" Look for any file in the same directory if we can't find an exact match.
let l:dir = ale#path#Simplify(expand('#' . a:buffer . ':p:h'))
for l:item in a:json_list
if ale#path#Simplify(fnamemodify(l:item.file, ':h')) is? l:dir
return ale#c#ParseCFlags(a:dir, l:item.command)
endif
endfor
return ''
endfunction
function! ale#c#FlagsFromCompileCommands(buffer, compile_commands_file) abort
let l:dir = ale#path#Dirname(a:compile_commands_file)
let l:json_list = s:GetListFromCompileCommandsFile(a:compile_commands_file)
return ale#c#ParseCompileCommandsFlags(a:buffer, l:dir, l:json_list)
endfunction
function! ale#c#GetCFlags(buffer, output) abort
let l:cflags = ' '
if g:ale_c_parse_makefile && !empty(a:output)
let l:cflags = join(ale#c#ParseCFlags(a:buffer, join(a:output, '\n')), ' ') . ' '
if ale#Var(a:buffer, 'c_parse_makefile') && !empty(a:output)
let l:cflags = ale#c#ParseCFlagsFromMakeOutput(a:buffer, a:output)
endif
if ale#Var(a:buffer, 'c_parse_compile_commands')
let l:json_file = ale#c#FindCompileCommands(a:buffer)
if !empty(l:json_file)
let l:cflags = ale#c#FlagsFromCompileCommands(a:buffer, l:json_file)
endif
endif
if l:cflags is# ' '
@@ -98,8 +224,9 @@ function! ale#c#GetCFlags(buffer, output) abort
endfunction
function! ale#c#GetMakeCommand(buffer) abort
if g:ale_c_parse_makefile
if ale#Var(a:buffer, 'c_parse_makefile')
let l:makefile_path = ale#path#FindNearestFile(a:buffer, 'Makefile')
if !empty(l:makefile_path)
return 'cd '. fnamemodify(l:makefile_path, ':p:h') . ' && make -n'
endif
@@ -154,26 +281,10 @@ function! ale#c#IncludeOptions(include_paths) abort
return ''
endif
return ' ' . join(l:option_list) . ' '
return join(l:option_list)
endfunction
let g:ale_c_build_dir_names = get(g:, 'ale_c_build_dir_names', [
\ 'build',
\ 'bin',
\])
" Given a buffer number, find the build subdirectory with compile commands
" The subdirectory is returned without the trailing /
function! ale#c#FindCompileCommands(buffer) abort
for l:path in ale#path#Upwards(expand('#' . a:buffer . ':p:h'))
for l:dirname in ale#Var(a:buffer, 'c_build_dir_names')
let l:c_build_dir = l:path . s:sep . l:dirname
if filereadable(l:c_build_dir . '/compile_commands.json')
return l:c_build_dir
endif
endfor
endfor
return ''
endfunction

View File

@@ -39,10 +39,14 @@ let s:LSP_COMPLETION_COLOR_KIND = 16
let s:LSP_COMPLETION_FILE_KIND = 17
let s:LSP_COMPLETION_REFERENCE_KIND = 18
let s:lisp_regex = '\v[a-zA-Z_\-][a-zA-Z_\-0-9]*$'
" Regular expressions for checking the characters in the line before where
" the insert cursor is. If one of these matches, we'll check for completions.
let s:should_complete_map = {
\ '<default>': '\v[a-zA-Z$_][a-zA-Z$_0-9]*$|\.$',
\ 'clojure': s:lisp_regex,
\ 'lisp': s:lisp_regex,
\ 'typescript': '\v[a-zA-Z$_][a-zA-Z$_0-9]*$|\.$|''$|"$',
\ 'rust': '\v[a-zA-Z$_][a-zA-Z$_0-9]*$|\.$|::$',
\}
@@ -75,6 +79,7 @@ endfunction
" Check if we should look for completions for a language.
function! ale#completion#GetPrefix(filetype, line, column) abort
let l:regex = s:GetFiletypeValue(s:should_complete_map, a:filetype)
" The column we're using completions for is where we are inserting text,
" like so:
" abc
@@ -93,14 +98,15 @@ function! ale#completion#GetTriggerCharacter(filetype, prefix) abort
return ''
endfunction
function! ale#completion#Filter(buffer, suggestions, prefix) abort
function! ale#completion#Filter(buffer, filetype, suggestions, prefix) abort
let l:excluded_words = ale#Var(a:buffer, 'completion_excluded_words')
let l:triggers = s:GetFiletypeValue(s:trigger_character_map, a:filetype)
" For completing...
" foo.
" ^
" We need to include all of the given suggestions.
if a:prefix is# '.'
if index(l:triggers, a:prefix) >= 0
let l:filtered_suggestions = a:suggestions
else
let l:filtered_suggestions = []
@@ -113,7 +119,7 @@ function! ale#completion#Filter(buffer, suggestions, prefix) abort
for l:item in a:suggestions
" A List of String values or a List of completion item Dictionaries
" is accepted here.
let l:word = type(l:item) == type('') ? l:item : l:item.word
let l:word = type(l:item) is v:t_string ? l:item : l:item.word
" Add suggestions if the suggestion starts with a case-insensitive
" match for the prefix.
@@ -133,7 +139,7 @@ function! ale#completion#Filter(buffer, suggestions, prefix) abort
" Remove suggestions with words in the exclusion List.
call filter(
\ l:filtered_suggestions,
\ 'index(l:excluded_words, type(v:val) is type('''') ? v:val : v:val.word) < 0',
\ 'index(l:excluded_words, type(v:val) is v:t_string ? v:val : v:val.word) < 0',
\)
endif
@@ -214,8 +220,10 @@ function! ale#completion#Show(response, completion_parser) abort
" function, and then start omni-completion.
let b:ale_completion_response = a:response
let b:ale_completion_parser = a:completion_parser
" Replace completion options shortly before opening the menu.
call s:ReplaceCompletionOptions()
call ale#util#FeedKeys("\<Plug>(ale_show_completion_menu)")
call timer_start(0, {-> ale#util#FeedKeys("\<Plug>(ale_show_completion_menu)")})
endfunction
function! s:CompletionStillValid(request_id) abort
@@ -257,7 +265,7 @@ function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort
call add(l:documentationParts, l:part.text)
endfor
if l:suggestion.kind is# 'clasName'
if l:suggestion.kind is# 'className'
let l:kind = 'f'
elseif l:suggestion.kind is# 'parameterName'
let l:kind = 'f'
@@ -315,10 +323,10 @@ function! ale#completion#ParseLSPCompletions(response) abort
let l:item_list = []
if type(get(a:response, 'result')) is type([])
if type(get(a:response, 'result')) is v:t_list
let l:item_list = a:response.result
elseif type(get(a:response, 'result')) is type({})
\&& type(get(a:response.result, 'items')) is type([])
elseif type(get(a:response, 'result')) is v:t_dict
\&& type(get(a:response.result, 'items')) is v:t_list
let l:item_list = a:response.result.items
endif
@@ -352,17 +360,23 @@ function! ale#completion#ParseLSPCompletions(response) abort
let l:kind = 'v'
endif
let l:doc = get(l:item, 'documentation', '')
if type(l:doc) is v:t_dict && has_key(l:doc, 'value')
let l:doc = l:doc.value
endif
call add(l:results, {
\ 'word': l:word,
\ 'kind': l:kind,
\ 'icase': 1,
\ 'menu': get(l:item, 'detail', ''),
\ 'info': get(l:item, 'documentation', ''),
\ 'info': (type(l:doc) is v:t_string ? l:doc : ''),
\})
endfor
if has_key(l:info, 'prefix')
return ale#completion#Filter(l:buffer, l:results, l:info.prefix)
return ale#completion#Filter(l:buffer, &filetype, l:results, l:info.prefix)
endif
return l:results
@@ -383,6 +397,7 @@ function! ale#completion#HandleTSServerResponse(conn_id, response) abort
if l:command is# 'completions'
let l:names = ale#completion#Filter(
\ l:buffer,
\ &filetype,
\ ale#completion#ParseTSServerCompletions(a:response),
\ b:ale_completion_info.prefix,
\)[: g:ale_completion_max_suggestions - 1]
@@ -422,6 +437,58 @@ function! ale#completion#HandleLSPResponse(conn_id, response) abort
\)
endfunction
function! s:OnReady(linter, lsp_details, ...) abort
let l:buffer = a:lsp_details.buffer
let l:id = a:lsp_details.connection_id
" If we have sent a completion request already, don't send another.
if b:ale_completion_info.request_id
return
endif
let l:Callback = a:linter.lsp is# 'tsserver'
\ ? function('ale#completion#HandleTSServerResponse')
\ : function('ale#completion#HandleLSPResponse')
call ale#lsp#RegisterCallback(l:id, l:Callback)
if a:linter.lsp is# 'tsserver'
let l:message = ale#lsp#tsserver_message#Completions(
\ l:buffer,
\ b:ale_completion_info.line,
\ b:ale_completion_info.column,
\ b:ale_completion_info.prefix,
\)
else
" Send a message saying the buffer has changed first, otherwise
" completions won't know what text is nearby.
call ale#lsp#NotifyForChanges(l:id, l:buffer)
" For LSP completions, we need to clamp the column to the length of
" the line. python-language-server and perhaps others do not implement
" this correctly.
let l:message = ale#lsp#message#Completion(
\ l:buffer,
\ b:ale_completion_info.line,
\ min([
\ b:ale_completion_info.line_length,
\ b:ale_completion_info.column,
\ ]),
\ ale#completion#GetTriggerCharacter(&filetype, b:ale_completion_info.prefix),
\)
endif
let l:request_id = ale#lsp#Send(l:id, l:message)
if l:request_id
let b:ale_completion_info.conn_id = l:id
let b:ale_completion_info.request_id = l:request_id
if has_key(a:linter, 'completion_filter')
let b:ale_completion_info.completion_filter = a:linter.completion_filter
endif
endif
endfunction
function! s:GetLSPCompletions(linter) abort
let l:buffer = bufnr('')
let l:lsp_details = ale#lsp_linter#StartLSP(l:buffer, a:linter)
@@ -431,58 +498,10 @@ function! s:GetLSPCompletions(linter) abort
endif
let l:id = l:lsp_details.connection_id
let l:root = l:lsp_details.project_root
function! OnReady(...) abort closure
" If we have sent a completion request already, don't send another.
if b:ale_completion_info.request_id
return
endif
let l:OnReady = function('s:OnReady', [a:linter, l:lsp_details])
let l:Callback = a:linter.lsp is# 'tsserver'
\ ? function('ale#completion#HandleTSServerResponse')
\ : function('ale#completion#HandleLSPResponse')
call ale#lsp#RegisterCallback(l:id, l:Callback)
if a:linter.lsp is# 'tsserver'
let l:message = ale#lsp#tsserver_message#Completions(
\ l:buffer,
\ b:ale_completion_info.line,
\ b:ale_completion_info.column,
\ b:ale_completion_info.prefix,
\)
else
" Send a message saying the buffer has changed first, otherwise
" completions won't know what text is nearby.
call ale#lsp#NotifyForChanges(l:id, l:root, l:buffer)
" For LSP completions, we need to clamp the column to the length of
" the line. python-language-server and perhaps others do not implement
" this correctly.
let l:message = ale#lsp#message#Completion(
\ l:buffer,
\ b:ale_completion_info.line,
\ min([
\ b:ale_completion_info.line_length,
\ b:ale_completion_info.column,
\ ]),
\ ale#completion#GetTriggerCharacter(&filetype, b:ale_completion_info.prefix),
\)
endif
let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root)
if l:request_id
let b:ale_completion_info.conn_id = l:id
let b:ale_completion_info.request_id = l:request_id
if has_key(a:linter, 'completion_filter')
let b:ale_completion_info.completion_filter = a:linter.completion_filter
endif
endif
endfunction
call ale#lsp#WaitForCapability(l:id, l:root, 'completion', function('OnReady'))
call ale#lsp#WaitForCapability(l:id, 'completion', l:OnReady)
endfunction
function! ale#completion#GetCompletions() abort

View File

@@ -1,4 +1,6 @@
scriptencoding utf-8
" Author: w0rp <devw0rp@gmail.com>
" Author: João Paulo S. de Souza <joao.paulo.silvasouza@hotmail.com>
" Description: Echoes lint message for the current line, if any
" Controls the milliseconds delay before echoing a message.
@@ -24,7 +26,20 @@ function! ale#cursor#TruncatedEcho(original_message) abort
" The message is truncated and saved to the history.
setlocal shortmess+=T
exec "norm! :echomsg l:message\n"
try
exec "norm! :echomsg l:message\n"
catch /^Vim\%((\a\+)\)\=:E523/
" Fallback into manual truncate (#1987)
let l:winwidth = winwidth(0)
if l:winwidth < strdisplaywidth(l:message)
" Truncate message longer than window width with trailing '...'
let l:message = l:message[:l:winwidth - 4] . '...'
endif
exec 'echomsg l:message'
endtry
" Reset the cursor position if we moved off the end of the line.
" Using :norm and :echomsg can move the cursor off the end of the
@@ -37,17 +52,6 @@ function! ale#cursor#TruncatedEcho(original_message) abort
endtry
endfunction
function! s:FindItemAtCursor() abort
let l:buf = bufnr('')
let l:info = get(g:ale_buffer_info, l:buf, {})
let l:loclist = get(l:info, 'loclist', [])
let l:pos = getcurpos()
let l:index = ale#util#BinarySearch(l:loclist, l:buf, l:pos[1], l:pos[2])
let l:loc = l:index >= 0 ? l:loclist[l:index] : {}
return [l:info, l:loc]
endfunction
function! s:StopCursorTimer() abort
if s:cursor_timer != -1
call timer_stop(s:cursor_timer)
@@ -56,42 +60,55 @@ function! s:StopCursorTimer() abort
endfunction
function! ale#cursor#EchoCursorWarning(...) abort
if !g:ale_echo_cursor
let l:buffer = bufnr('')
if !g:ale_echo_cursor && !g:ale_cursor_detail
return
endif
" Only echo the warnings in normal mode, otherwise we will get problems.
if mode() isnot# 'n'
if mode(1) isnot# 'n'
return
endif
if ale#ShouldDoNothing(bufnr(''))
if ale#ShouldDoNothing(l:buffer)
return
endif
let l:buffer = bufnr('')
let [l:info, l:loc] = s:FindItemAtCursor()
let [l:info, l:loc] = ale#util#FindItemAtCursor(l:buffer)
if !empty(l:loc)
let l:format = ale#Var(l:buffer, 'echo_msg_format')
let l:msg = ale#GetLocItemMessage(l:loc, l:format)
call ale#cursor#TruncatedEcho(l:msg)
let l:info.echoed = 1
elseif get(l:info, 'echoed')
" We'll only clear the echoed message when moving off errors once,
" so we don't continually clear the echo line.
execute 'echo'
let l:info.echoed = 0
if g:ale_echo_cursor
if !empty(l:loc)
let l:format = ale#Var(l:buffer, 'echo_msg_format')
let l:msg = ale#GetLocItemMessage(l:loc, l:format)
call ale#cursor#TruncatedEcho(l:msg)
let l:info.echoed = 1
elseif get(l:info, 'echoed')
" We'll only clear the echoed message when moving off errors once,
" so we don't continually clear the echo line.
execute 'echo'
let l:info.echoed = 0
endif
endif
if g:ale_cursor_detail
if !empty(l:loc)
call s:ShowCursorDetailForItem(l:loc, {'stay_here': 1})
else
call ale#preview#CloseIfTypeMatches('ale-preview')
endif
endif
endfunction
function! ale#cursor#EchoCursorWarningWithDelay() abort
if !g:ale_echo_cursor
let l:buffer = bufnr('')
if !g:ale_echo_cursor && !g:ale_cursor_detail
return
endif
" Only echo the warnings in normal mode, otherwise we will get problems.
if mode() isnot# 'n'
if mode(1) isnot# 'n'
return
endif
@@ -104,7 +121,7 @@ function! ale#cursor#EchoCursorWarningWithDelay() abort
" we should echo something. Otherwise we can end up doing processing
" the echo message far too frequently.
if l:pos != s:last_pos
let l:delay = ale#Var(bufnr(''), 'echo_delay')
let l:delay = ale#Var(l:buffer, 'echo_delay')
let s:last_pos = l:pos
let s:cursor_timer = timer_start(
@@ -114,24 +131,37 @@ function! ale#cursor#EchoCursorWarningWithDelay() abort
endif
endfunction
function! s:ShowCursorDetailForItem(loc, options) abort
let l:stay_here = get(a:options, 'stay_here', 0)
let s:last_detailed_line = line('.')
let l:message = get(a:loc, 'detail', a:loc.text)
let l:lines = split(l:message, "\n")
call ale#preview#Show(l:lines, {'stay_here': l:stay_here})
" Clear the echo message if we manually displayed details.
if !l:stay_here
execute 'echo'
endif
endfunction
function! ale#cursor#ShowCursorDetail() abort
let l:buffer = bufnr('')
" Only echo the warnings in normal mode, otherwise we will get problems.
if mode() isnot# 'n'
return
endif
if ale#ShouldDoNothing(bufnr(''))
if ale#ShouldDoNothing(l:buffer)
return
endif
call s:StopCursorTimer()
let [l:info, l:loc] = s:FindItemAtCursor()
let [l:info, l:loc] = ale#util#FindItemAtCursor(l:buffer)
if !empty(l:loc)
let l:message = get(l:loc, 'detail', l:loc.text)
call ale#preview#Show(split(l:message, "\n"))
execute 'echo'
call s:ShowCursorDetailForItem(l:loc, {'stay_here': 0})
endif
endfunction

16
autoload/ale/d.vim Normal file
View File

@@ -0,0 +1,16 @@
" Author: Auri <me@aurieh.me>
" Description: Functions for integrating with D linters.
function! ale#d#FindDUBConfig(buffer) abort
" Find a DUB configuration file in ancestor paths.
" The most DUB-specific names will be tried first.
for l:possible_filename in ['dub.sdl', 'dub.json', 'package.json']
let l:dub_file = ale#path#FindNearestFile(a:buffer, l:possible_filename)
if !empty(l:dub_file)
return l:dub_file
endif
endfor
return ''
endfunction

View File

@@ -22,14 +22,14 @@ let s:global_variable_list = [
\ 'ale_lint_delay',
\ 'ale_lint_on_enter',
\ 'ale_lint_on_filetype_changed',
\ 'ale_lint_on_insert_leave',
\ 'ale_lint_on_save',
\ 'ale_lint_on_text_changed',
\ 'ale_lint_on_insert_leave',
\ 'ale_linter_aliases',
\ 'ale_linters',
\ 'ale_linters_explicit',
\ 'ale_list_window_size',
\ 'ale_list_vertical',
\ 'ale_list_window_size',
\ 'ale_loclist_msg_format',
\ 'ale_max_buffer_history_size',
\ 'ale_max_signs',
@@ -52,6 +52,7 @@ let s:global_variable_list = [
\ 'ale_statusline_format',
\ 'ale_type_map',
\ 'ale_use_global_executables',
\ 'ale_virtualtext_cursor',
\ 'ale_warn_about_trailing_blank_lines',
\ 'ale_warn_about_trailing_whitespace',
\]

View File

@@ -40,16 +40,16 @@ function! ale#definition#HandleLSPResponse(conn_id, response) abort
" The result can be a Dictionary item, a List of the same, or null.
let l:result = get(a:response, 'result', v:null)
if type(l:result) is type({})
if type(l:result) is v:t_dict
let l:result = [l:result]
elseif type(l:result) isnot type([])
elseif type(l:result) isnot v:t_list
let l:result = []
endif
for l:item in l:result
let l:filename = ale#path#FromURI(l:item.uri)
let l:line = l:item.range.start.line + 1
let l:column = l:item.range.start.character
let l:column = l:item.range.start.character + 1
call ale#util#Open(l:filename, l:line, l:column, l:options)
break
@@ -57,6 +57,39 @@ function! ale#definition#HandleLSPResponse(conn_id, response) abort
endif
endfunction
function! s:OnReady(linter, lsp_details, line, column, options, ...) abort
let l:buffer = a:lsp_details.buffer
let l:id = a:lsp_details.connection_id
let l:Callback = a:linter.lsp is# 'tsserver'
\ ? function('ale#definition#HandleTSServerResponse')
\ : function('ale#definition#HandleLSPResponse')
call ale#lsp#RegisterCallback(l:id, l:Callback)
if a:linter.lsp is# 'tsserver'
let l:message = ale#lsp#tsserver_message#Definition(
\ l:buffer,
\ a:line,
\ a:column
\)
else
" Send a message saying the buffer has changed first, or the
" definition position probably won't make sense.
call ale#lsp#NotifyForChanges(l:id, l:buffer)
" For LSP completions, we need to clamp the column to the length of
" the line. python-language-server and perhaps others do not implement
" this correctly.
let l:message = ale#lsp#message#Definition(l:buffer, a:line, a:column)
endif
let l:request_id = ale#lsp#Send(l:id, l:message)
let s:go_to_definition_map[l:request_id] = {
\ 'open_in_tab': get(a:options, 'open_in_tab', 0),
\}
endfunction
function! s:GoToLSPDefinition(linter, options) abort
let l:buffer = bufnr('')
let [l:line, l:column] = getcurpos()[1:2]
@@ -71,39 +104,10 @@ function! s:GoToLSPDefinition(linter, options) abort
endif
let l:id = l:lsp_details.connection_id
let l:root = l:lsp_details.project_root
function! OnReady(...) abort closure
let l:Callback = a:linter.lsp is# 'tsserver'
\ ? function('ale#definition#HandleTSServerResponse')
\ : function('ale#definition#HandleLSPResponse')
call ale#lsp#RegisterCallback(l:id, l:Callback)
if a:linter.lsp is# 'tsserver'
let l:message = ale#lsp#tsserver_message#Definition(
\ l:buffer,
\ l:line,
\ l:column
\)
else
" Send a message saying the buffer has changed first, or the
" definition position probably won't make sense.
call ale#lsp#NotifyForChanges(l:id, l:root, l:buffer)
" For LSP completions, we need to clamp the column to the length of
" the line. python-language-server and perhaps others do not implement
" this correctly.
let l:message = ale#lsp#message#Definition(l:buffer, l:line, l:column)
endif
let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root)
let s:go_to_definition_map[l:request_id] = {
\ 'open_in_tab': get(a:options, 'open_in_tab', 0),
\}
endfunction
call ale#lsp#WaitForCapability(l:id, l:root, 'definition', function('OnReady'))
call ale#lsp#WaitForCapability(l:id, 'definition', function('s:OnReady', [
\ a:linter, l:lsp_details, l:line, l:column, a:options
\]))
endfunction
function! ale#definition#GoTo(options) abort

View File

@@ -18,6 +18,22 @@ if !has_key(s:, 'executable_cache_map')
let s:executable_cache_map = {}
endif
function! ale#engine#CleanupEveryBuffer() abort
for l:key in keys(g:ale_buffer_info)
" The key could be a filename or a buffer number, so try and
" convert it to a number. We need a number for the other
" functions.
let l:buffer = str2nr(l:key)
if l:buffer > 0
" Stop all jobs and clear the results for everything, and delete
" all of the data we stored for the buffer.
call ale#engine#Cleanup(l:buffer)
endif
endfor
endfunction
function! ale#engine#ResetExecutableCache() abort
let s:executable_cache_map = {}
endfunction
@@ -63,6 +79,7 @@ function! ale#engine#InitBufferInfo(buffer) abort
let g:ale_buffer_info[a:buffer] = {
\ 'job_list': [],
\ 'active_linter_list': [],
\ 'active_other_sources_list': [],
\ 'loclist': [],
\ 'temporary_file_list': [],
\ 'temporary_directory_list': [],
@@ -81,6 +98,7 @@ function! ale#engine#IsCheckingBuffer(buffer) abort
let l:info = get(g:ale_buffer_info, a:buffer, {})
return !empty(get(l:info, 'active_linter_list', []))
\ || !empty(get(l:info, 'active_other_sources_list', []))
endfunction
" Register a temporary file to be managed with the ALE engine for
@@ -161,20 +179,27 @@ function! s:GatherOutput(job_id, line) abort
endif
endfunction
function! ale#engine#HandleLoclist(linter_name, buffer, loclist) abort
function! ale#engine#HandleLoclist(linter_name, buffer, loclist, from_other_source) abort
let l:info = get(g:ale_buffer_info, a:buffer, {})
if empty(l:info)
return
endif
" Remove this linter from the list of active linters.
" This may have already been done when the job exits.
call filter(l:info.active_linter_list, 'v:val isnot# a:linter_name')
if !a:from_other_source
" Remove this linter from the list of active linters.
" This may have already been done when the job exits.
call filter(l:info.active_linter_list, 'v:val isnot# a:linter_name')
endif
" Make some adjustments to the loclists to fix common problems, and also
" to set default values for loclist items.
let l:linter_loclist = ale#engine#FixLocList(a:buffer, a:linter_name, a:loclist)
let l:linter_loclist = ale#engine#FixLocList(
\ a:buffer,
\ a:linter_name,
\ a:from_other_source,
\ a:loclist,
\)
" Remove previous items for this linter.
call filter(l:info.loclist, 'v:val.linter_name isnot# a:linter_name')
@@ -231,6 +256,7 @@ function! s:HandleExit(job_id, exit_code) abort
if l:next_chain_index < len(get(l:linter, 'command_chain', []))
call s:InvokeChain(l:buffer, l:executable, l:linter, l:next_chain_index, l:output)
return
endif
@@ -246,7 +272,7 @@ function! s:HandleExit(job_id, exit_code) abort
let l:loclist = []
endtry
call ale#engine#HandleLoclist(l:linter.name, l:buffer, l:loclist)
call ale#engine#HandleLoclist(l:linter.name, l:buffer, l:loclist, 0)
endfunction
function! ale#engine#SetResults(buffer, loclist) abort
@@ -278,6 +304,12 @@ function! ale#engine#SetResults(buffer, loclist) abort
call ale#cursor#EchoCursorWarning()
endif
if g:ale_virtualtext_cursor
" Try and show the warning now.
" This will only do something meaningful if we're in normal mode.
call ale#virtualtext#ShowCursorWarning()
endif
" Reset the save event marker, used for opening windows, etc.
call setbufvar(a:buffer, 'ale_save_event_fired', 0)
" Set a marker showing how many times a buffer has been checked.
@@ -318,7 +350,7 @@ function! s:RemapItemTypes(type_map, loclist) abort
endfor
endfunction
function! ale#engine#FixLocList(buffer, linter_name, loclist) abort
function! ale#engine#FixLocList(buffer, linter_name, from_other_source, loclist) abort
let l:bufnr_map = {}
let l:new_loclist = []
@@ -351,6 +383,10 @@ function! ale#engine#FixLocList(buffer, linter_name, loclist) abort
\ 'linter_name': a:linter_name,
\}
if a:from_other_source
let l:item.from_other_source = 1
endif
if has_key(l:old_item, 'code')
let l:item.code = l:old_item.code
endif
@@ -557,7 +593,7 @@ function! s:RunJob(options) abort
if get(g:, 'ale_run_synchronously') == 1
" Run a command synchronously if this test option is set.
let s:job_info_map[l:job_id].output = systemlist(
\ type(l:command) == type([])
\ type(l:command) is v:t_list
\ ? join(l:command[0:1]) . ' ' . ale#Escape(l:command[2])
\ : l:command
\)
@@ -595,9 +631,8 @@ function! ale#engine#ProcessChain(buffer, linter, chain_index, input) abort
\)
endif
" If we have a command to run, execute that.
if !empty(l:command)
" We hit a command to run, so we'll execute that
" The chain item can override the output_stream option.
if has_key(l:chain_item, 'output_stream')
let l:output_stream = l:chain_item.output_stream
@@ -675,6 +710,7 @@ endfunction
function! s:RemoveProblemsForDisabledLinters(buffer, linters) abort
" Figure out which linters are still enabled, and remove
" problems for linters which are no longer enabled.
" Problems from other sources will be kept.
let l:name_map = {}
for l:linter in a:linters
@@ -683,7 +719,7 @@ function! s:RemoveProblemsForDisabledLinters(buffer, linters) abort
call filter(
\ get(g:ale_buffer_info[a:buffer], 'loclist', []),
\ 'get(l:name_map, get(v:val, ''linter_name''))',
\ 'get(v:val, ''from_other_source'') || get(l:name_map, get(v:val, ''linter_name''))',
\)
endfunction

View File

@@ -4,11 +4,11 @@
" Given a filetype and a configuration for ignoring linters, return a List of
" Strings for linter names to ignore.
function! ale#engine#ignore#GetList(filetype, config) abort
if type(a:config) is type([])
if type(a:config) is v:t_list
return a:config
endif
if type(a:config) is type({})
if type(a:config) is v:t_dict
let l:names_to_remove = []
for l:part in split(a:filetype , '\.')

View File

@@ -29,7 +29,7 @@ function! ale#events#SaveEvent(buffer) abort
call setbufvar(a:buffer, 'ale_save_event_fired', 1)
endif
if ale#Var(a:buffer, 'fix_on_save')
if ale#Var(a:buffer, 'fix_on_save') && !ale#events#QuitRecently(a:buffer)
let l:will_fix = ale#fix#Fix(a:buffer, 'save_file')
let l:should_lint = l:should_lint && !l:will_fix
endif
@@ -131,13 +131,25 @@ function! ale#events#Init() abort
autocmd InsertLeave * call ale#Queue(0)
endif
if g:ale_echo_cursor
if g:ale_echo_cursor || g:ale_cursor_detail
autocmd CursorMoved,CursorHold * if exists('*ale#engine#Cleanup') | call ale#cursor#EchoCursorWarningWithDelay() | endif
" Look for a warning to echo as soon as we leave Insert mode.
" The script's position variable used when moving the cursor will
" not be changed here.
autocmd InsertLeave * if exists('*ale#engine#Cleanup') | call ale#cursor#EchoCursorWarning() | endif
endif
if g:ale_virtualtext_cursor
autocmd CursorMoved,CursorHold * if exists('*ale#engine#Cleanup') | call ale#virtualtext#ShowCursorWarningWithDelay() | endif
" Look for a warning to echo as soon as we leave Insert mode.
" The script's position variable used when moving the cursor will
" not be changed here.
autocmd InsertLeave * if exists('*ale#engine#Cleanup') | call ale#virtualtext#ShowCursorWarning() | endif
endif
if g:ale_close_preview_on_insert
autocmd InsertEnter * if exists('*ale#preview#CloseIfTypeMatches') | call ale#preview#CloseIfTypeMatches('ale-preview') | endif
endif
endif
augroup END
endfunction

View File

@@ -30,7 +30,14 @@ function! ale#fix#ApplyQueuedFixes() abort
call winrestview(l:save)
endif
call setline(1, l:data.output)
" If the file is in DOS mode, we have to remove carriage returns from
" the ends of lines before calling setline(), or we will see them
" twice.
let l:lines_to_set = getbufvar(l:buffer, '&fileformat') is# 'dos'
\ ? map(copy(l:data.output), 'substitute(v:val, ''\r\+$'', '''', '''')')
\ : l:data.output
call setline(1, l:lines_to_set)
if l:data.should_save
if empty(&buftype)
@@ -71,6 +78,7 @@ function! ale#fix#ApplyFixes(buffer, output) abort
if l:data.lines_before != l:lines
call remove(g:ale_fix_buffer_data, a:buffer)
execute 'echoerr ''The file was changed before fixing finished'''
return
endif
endif
@@ -275,7 +283,7 @@ function! s:RunJob(options) abort
if get(g:, 'ale_run_synchronously') == 1
" Run a command synchronously if this test option is set.
let l:output = systemlist(
\ type(l:command) == type([])
\ type(l:command) is v:t_list
\ ? join(l:command[0:1]) . ' ' . ale#Escape(l:command[2])
\ : l:command
\)
@@ -313,10 +321,10 @@ function! s:RunFixer(options) abort
\ : call(l:Function, [l:buffer, copy(l:input)])
endif
if type(l:result) == type(0) && l:result == 0
if type(l:result) is v:t_number && l:result == 0
" When `0` is returned, skip this item.
let l:index += 1
elseif type(l:result) == type([])
elseif type(l:result) is v:t_list
let l:input = l:result
let l:index += 1
else
@@ -351,9 +359,9 @@ function! s:RunFixer(options) abort
endfunction
function! s:AddSubCallbacks(full_list, callbacks) abort
if type(a:callbacks) == type('')
if type(a:callbacks) is v:t_string
call add(a:full_list, a:callbacks)
elseif type(a:callbacks) == type([])
elseif type(a:callbacks) is v:t_list
call extend(a:full_list, a:callbacks)
else
return 0
@@ -365,7 +373,7 @@ endfunction
function! s:GetCallbacks(buffer, fixers) abort
if len(a:fixers)
let l:callback_list = a:fixers
elseif type(get(b:, 'ale_fixers')) is type([])
elseif type(get(b:, 'ale_fixers')) is v:t_list
" Lists can be used for buffer-local variables only
let l:callback_list = b:ale_fixers
else
@@ -396,7 +404,7 @@ function! s:GetCallbacks(buffer, fixers) abort
" Variables with capital characters are needed, or Vim will complain about
" funcref variables.
for l:Item in l:callback_list
if type(l:Item) == type('')
if type(l:Item) is v:t_string
let l:Func = ale#fix#registry#GetFunc(l:Item)
if !empty(l:Func)

View File

@@ -56,7 +56,7 @@ let s:default_registry = {
\ },
\ 'prettier': {
\ 'function': 'ale#fixers#prettier#Fix',
\ 'suggested_filetypes': ['javascript', 'typescript', 'css', 'less', 'scss', 'json', 'json5', 'graphql', 'markdown', 'vue'],
\ 'suggested_filetypes': ['javascript', 'typescript', 'css', 'less', 'scss', 'json', 'json5', 'graphql', 'markdown', 'vue', 'html', 'yaml'],
\ 'description': 'Apply prettier to a file.',
\ },
\ 'prettier_eslint': {
@@ -145,6 +145,11 @@ let s:default_registry = {
\ 'suggested_filetypes': ['go'],
\ 'description': 'Fix Go files imports with goimports.',
\ },
\ 'gomod': {
\ 'function': 'ale#fixers#gomod#Fix',
\ 'suggested_filetypes': ['gomod'],
\ 'description': 'Fix Go module files with go mod edit -fmt.',
\ },
\ 'tslint': {
\ 'function': 'ale#fixers#tslint#Fix',
\ 'suggested_filetypes': ['typescript'],
@@ -157,7 +162,7 @@ let s:default_registry = {
\ },
\ 'hackfmt': {
\ 'function': 'ale#fixers#hackfmt#Fix',
\ 'suggested_filetypes': ['php'],
\ 'suggested_filetypes': ['hack'],
\ 'description': 'Fix Hack files with hackfmt.',
\ },
\ 'hfmt': {
@@ -170,6 +175,21 @@ let s:default_registry = {
\ 'suggested_filetypes': ['haskell'],
\ 'description': 'Fix Haskell files with brittany.',
\ },
\ 'hlint': {
\ 'function': 'ale#fixers#hlint#Fix',
\ 'suggested_filetypes': ['haskell'],
\ 'description': 'Refactor Haskell files with hlint.',
\ },
\ 'stylish-haskell': {
\ 'function': 'ale#fixers#stylish_haskell#Fix',
\ 'suggested_filetypes': ['haskell'],
\ 'description': 'Refactor Haskell files with stylish-haskell.',
\ },
\ 'ocamlformat': {
\ 'function': 'ale#fixers#ocamlformat#Fix',
\ 'suggested_filetypes': ['ocaml'],
\ 'description': 'Fix OCaml files with ocamlformat.',
\ },
\ 'refmt': {
\ 'function': 'ale#fixers#refmt#Fix',
\ 'suggested_filetypes': ['reason'],
@@ -180,6 +200,11 @@ let s:default_registry = {
\ 'suggested_filetypes': ['sh'],
\ 'description': 'Fix sh files with shfmt.',
\ },
\ 'sqlfmt': {
\ 'function': 'ale#fixers#sqlfmt#Fix',
\ 'suggested_filetypes': ['sql'],
\ 'description': 'Fix SQL files with sqlfmt.',
\ },
\ 'google_java_format': {
\ 'function': 'ale#fixers#google_java_format#Fix',
\ 'suggested_filetypes': ['java'],
@@ -215,6 +240,21 @@ let s:default_registry = {
\ 'suggested_filetypes': ['dart'],
\ 'description': 'Fix Dart files with dartfmt.',
\ },
\ 'xmllint': {
\ 'function': 'ale#fixers#xmllint#Fix',
\ 'suggested_filetypes': ['xml'],
\ 'description': 'Fix XML files with xmllint.',
\ },
\ 'uncrustify': {
\ 'function': 'ale#fixers#uncrustify#Fix',
\ 'suggested_filetypes': ['c', 'cpp', 'cs', 'objc', 'objcpp', 'd', 'java', 'p', 'vala' ],
\ 'description': 'Fix C, C++, C#, ObjectiveC, ObjectiveC++, D, Java, Pawn, and VALA files with uncrustify.',
\ },
\ 'terraform': {
\ 'function': 'ale#fixers#terraform#Fix',
\ 'suggested_filetypes': ['hcl', 'terraform'],
\ 'description': 'Fix tf and hcl files with terraform fmt.',
\ },
\}
" Reset the function registry to the default entries.
@@ -243,34 +283,34 @@ endfunction
" (name, func, filetypes, desc, aliases)
function! ale#fix#registry#Add(name, func, filetypes, desc, ...) abort
" This command will throw from the sandbox.
let &equalprg=&equalprg
let &l:equalprg=&l:equalprg
if type(a:name) != type('')
if type(a:name) isnot v:t_string
throw '''name'' must be a String'
endif
if type(a:func) != type('')
if type(a:func) isnot v:t_string
throw '''func'' must be a String'
endif
if type(a:filetypes) != type([])
if type(a:filetypes) isnot v:t_list
throw '''filetypes'' must be a List'
endif
for l:type in a:filetypes
if type(l:type) != type('')
if type(l:type) isnot v:t_string
throw 'Each entry of ''filetypes'' must be a String'
endif
endfor
if type(a:desc) != type('')
if type(a:desc) isnot v:t_string
throw '''desc'' must be a String'
endif
let l:aliases = get(a:000, 0, [])
if type(l:aliases) != type([])
\|| !empty(filter(copy(l:aliases), 'type(v:val) != type('''')'))
if type(l:aliases) isnot v:t_list
\|| !empty(filter(copy(l:aliases), 'type(v:val) isnot v:t_string'))
throw '''aliases'' must be a List of String values'
endif

View File

@@ -3,11 +3,17 @@
call ale#Set('haskell_brittany_executable', 'brittany')
function! ale#fixers#brittany#Fix(buffer) abort
function! ale#fixers#brittany#GetExecutable(buffer) abort
let l:executable = ale#Var(a:buffer, 'haskell_brittany_executable')
return ale#handlers#haskell_stack#EscapeExecutable(l:executable, 'brittany')
endfunction
function! ale#fixers#brittany#Fix(buffer) abort
let l:executable = ale#fixers#brittany#GetExecutable(a:buffer)
return {
\ 'command': ale#Escape(l:executable)
\ 'command': l:executable
\ . ' --write-mode inplace'
\ . ' %t',
\ 'read_temporary_file': 1,

View File

@@ -25,7 +25,7 @@ endfunction
function! ale#fixers#eslint#ProcessEslintDOutput(buffer, output) abort
" If the output is an error message, don't use it.
for l:line in a:output[:10]
if l:line =~# '^Error:'
if l:line =~# '\v^Error:|^Could not connect'
return []
endif
endfor

View File

@@ -17,6 +17,7 @@ function! ale#fixers#fixjson#Fix(buffer) abort
let l:command = l:executable . ' --stdin-filename ' . l:filename
let l:options = ale#Var(a:buffer, 'json_fixjson_options')
if l:options isnot# ''
let l:command .= ' ' . l:options
endif

View File

@@ -6,13 +6,28 @@ function! ale#fixers#generic_python#AddLinesBeforeControlStatements(buffer, line
let l:new_lines = []
let l:last_indent_size = 0
let l:last_line_is_blank = 0
let l:in_docstring = 0
for l:line in a:lines
let l:indent_size = len(matchstr(l:line, '^ *'))
if !l:in_docstring
" Make sure it is not just a single line docstring and then verify
" it's starting a new docstring
if match(l:line, '\v^ *("""|'''''').*("""|'''''')') == -1
\&& match(l:line, '\v^ *("""|'''''')') >= 0
let l:in_docstring = 1
endif
else
if match(l:line, '\v^ *.*("""|'''''')') >= 0
let l:in_docstring = 0
endif
endif
if !l:last_line_is_blank
\&& !l:in_docstring
\&& l:indent_size <= l:last_indent_size
\&& match(l:line, '\v^ *(return|if|for|while|break|continue)') >= 0
\&& match(l:line, '\v^ *(return|if|for|while|break|continue)(\(| |$)') >= 0
call add(l:new_lines, '')
endif

View File

@@ -0,0 +1,10 @@
call ale#Set('go_go_executable', 'go')
function! ale#fixers#gomod#Fix(buffer) abort
let l:executable = ale#Var(a:buffer, 'go_go_executable')
return {
\ 'command': ale#Escape(l:executable) . ' mod edit -fmt %t',
\ 'read_temporary_file': 1,
\}
endfunction

View File

@@ -1,13 +1,13 @@
" Author: butlerx <butlerx@notthe,cloud>
" Description: Integration of Google-java-format with ALE.
call ale#Set('google_java_format_executable', 'google-java-format')
call ale#Set('google_java_format_use_global', get(g:, 'ale_use_global_executables', 0))
call ale#Set('google_java_format_options', '')
call ale#Set('java_google_java_format_executable', 'google-java-format')
call ale#Set('java_google_java_format_use_global', get(g:, 'ale_use_global_executables', 0))
call ale#Set('java_google_java_format_options', '')
function! ale#fixers#google_java_format#Fix(buffer) abort
let l:options = ale#Var(a:buffer, 'google_java_format_options')
let l:executable = ale#Var(a:buffer, 'google_java_format_executable')
let l:options = ale#Var(a:buffer, 'java_google_java_format_options')
let l:executable = ale#Var(a:buffer, 'java_google_java_format_executable')
if !executable(l:executable)
return 0

View File

@@ -1,12 +1,12 @@
" Author: Sam Howie <samhowie@gmail.com>
" Description: Integration of hackfmt with ALE.
call ale#Set('php_hackfmt_executable', 'hackfmt')
call ale#Set('php_hackfmt_options', '')
call ale#Set('hack_hackfmt_executable', 'hackfmt')
call ale#Set('hack_hackfmt_options', '')
function! ale#fixers#hackfmt#Fix(buffer) abort
let l:executable = ale#Var(a:buffer, 'php_hackfmt_executable')
let l:options = ale#Var(a:buffer, 'php_hackfmt_options')
let l:executable = ale#Var(a:buffer, 'hack_hackfmt_executable')
let l:options = ale#Var(a:buffer, 'hack_hackfmt_options')
return {
\ 'command': ale#Escape(l:executable)

View File

@@ -7,7 +7,7 @@ function! ale#fixers#hfmt#Fix(buffer) abort
let l:executable = ale#Var(a:buffer, 'haskell_hfmt_executable')
return {
\ 'command': ale#Escape(l:executable)
\ 'command': ale#handlers#haskell_stack#EscapeExecutable(l:executable, 'hfmt')
\ . ' -w'
\ . ' %t',
\ 'read_temporary_file': 1,

View File

@@ -0,0 +1,13 @@
" Author: eborden <evan@evan-borden.com>
" Description: Integration of hlint refactor with ALE.
"
function! ale#fixers#hlint#Fix(buffer) abort
return {
\ 'command': ale#handlers#hlint#GetExecutable(a:buffer)
\ . ' --refactor'
\ . ' --refactor-options="--inplace"'
\ . ' %t',
\ 'read_temporary_file': 1,
\}
endfunction

View File

@@ -1,15 +1,16 @@
" Author: Jeff Willette <jrwillette88@gmail.com>
" Description: Integration of importjs with ALE.
call ale#Set('js_importjs_executable', 'importjs')
call ale#Set('javascript_importjs_executable', 'importjs')
function! ale#fixers#importjs#ProcessOutput(buffer, output) abort
let l:result = ale#util#FuzzyJSONDecode(a:output, [])
return split(get(l:result, 'fileContent', ''), "\n")
endfunction
function! ale#fixers#importjs#Fix(buffer) abort
let l:executable = ale#Var(a:buffer, 'js_importjs_executable')
let l:executable = ale#Var(a:buffer, 'javascript_importjs_executable')
if !executable(l:executable)
return 0

View File

@@ -1,5 +1,6 @@
call ale#Set('json_jq_executable', 'jq')
call ale#Set('json_jq_options', '')
call ale#Set('json_jq_filters', '.')
function! ale#fixers#jq#GetExecutable(buffer) abort
return ale#Var(a:buffer, 'json_jq_executable')
@@ -7,9 +8,15 @@ endfunction
function! ale#fixers#jq#Fix(buffer) abort
let l:options = ale#Var(a:buffer, 'json_jq_options')
let l:filters = ale#Var(a:buffer, 'json_jq_filters')
if empty(l:filters)
return 0
endif
return {
\ 'command': ale#Escape(ale#fixers#jq#GetExecutable(a:buffer))
\ . ' . ' . l:options,
\ . ' ' . l:filters . ' '
\ . l:options,
\}
endfunction

View File

@@ -0,0 +1,18 @@
" Author: Stephen Lumenta <@sbl>
" Description: Integration of ocamlformat with ALE.
call ale#Set('ocaml_ocamlformat_executable', 'ocamlformat')
call ale#Set('ocaml_ocamlformat_options', '')
function! ale#fixers#ocamlformat#Fix(buffer) abort
let l:filename = expand('#' . a:buffer . ':p')
let l:executable = ale#Var(a:buffer, 'ocaml_ocamlformat_executable')
let l:options = ale#Var(a:buffer, 'ocaml_ocamlformat_options')
return {
\ 'command': ale#Escape(l:executable)
\ . (empty(l:options) ? '' : ' ' . l:options)
\ . ' --name=' . ale#Escape(l:filename)
\ . ' -'
\}
endfunction

View File

@@ -14,6 +14,7 @@ endfunction
function! ale#fixers#php_cs_fixer#Fix(buffer) abort
let l:executable = ale#fixers#php_cs_fixer#GetExecutable(a:buffer)
return {
\ 'command': ale#Escape(l:executable)
\ . ' ' . ale#Var(a:buffer, 'php_cs_fixer_options')

View File

@@ -18,6 +18,7 @@ function! ale#fixers#phpcbf#Fix(buffer) abort
let l:standard_option = !empty(l:standard)
\ ? '--standard=' . l:standard
\ : ''
return {
\ 'command': ale#Escape(l:executable) . ' --stdin-path=%s ' . l:standard_option . ' -'
\}

View File

@@ -27,6 +27,17 @@ function! ale#fixers#prettier#Fix(buffer) abort
\}
endfunction
function! ale#fixers#prettier#ProcessPrettierDOutput(buffer, output) abort
" If the output is an error message, don't use it.
for l:line in a:output[:10]
if l:line =~# '^\w*Error:'
return []
endif
endfor
return a:output
endfunction
function! ale#fixers#prettier#ApplyFixForVersion(buffer, version_output) abort
let l:executable = ale#fixers#prettier#GetExecutable(a:buffer)
let l:options = ale#Var(a:buffer, 'javascript_prettier_options')
@@ -36,12 +47,24 @@ function! ale#fixers#prettier#ApplyFixForVersion(buffer, version_output) abort
" Append the --parser flag depending on the current filetype (unless it's
" already set in g:javascript_prettier_options).
if empty(expand('#' . a:buffer . ':e')) && match(l:options, '--parser') == -1
let l:prettier_parsers = ['typescript', 'css', 'less', 'scss', 'json', 'json5', 'graphql', 'markdown', 'vue']
let l:parser = 'babylon'
let l:prettier_parsers = {
\ 'typescript': 'typescript',
\ 'css': 'css',
\ 'less': 'less',
\ 'scss': 'scss',
\ 'json': 'json',
\ 'json5': 'json5',
\ 'graphql': 'graphql',
\ 'markdown': 'markdown',
\ 'vue': 'vue',
\ 'yaml': 'yaml',
\ 'html': 'html',
\}
let l:parser = ''
for l:filetype in split(getbufvar(a:buffer, '&filetype'), '\.')
if index(l:prettier_parsers, l:filetype) > -1
let l:parser = l:filetype
if has_key(l:prettier_parsers, l:filetype)
let l:parser = l:prettier_parsers[l:filetype]
break
endif
endfor
@@ -51,6 +74,17 @@ function! ale#fixers#prettier#ApplyFixForVersion(buffer, version_output) abort
let l:options = (!empty(l:options) ? l:options . ' ' : '') . '--parser ' . l:parser
endif
" Special error handling needed for prettier_d
if l:executable =~# 'prettier_d$'
return {
\ 'command': ale#path#BufferCdString(a:buffer)
\ . ale#Escape(l:executable)
\ . (!empty(l:options) ? ' ' . l:options : '')
\ . ' --stdin-filepath %s --stdin',
\ 'process_with': 'ale#fixers#prettier#ProcessPrettierDOutput',
\}
endif
" 1.4.0 is the first version with --stdin-filepath
if ale#semver#GTE(l:version, [1, 4, 0])
return {

View File

@@ -4,6 +4,7 @@
if !exists('g:ale_puppet_puppetlint_executable')
let g:ale_puppet_puppetlint_executable = 'puppet-lint'
endif
if !exists('g:ale_puppet_puppetlint_options')
let g:ale_puppet_puppetlint_options = ''
endif

View File

@@ -1,16 +1,15 @@
call ale#Set('ruby_rubocop_options', '')
call ale#Set('ruby_rubocop_executable', 'rubocop')
function! ale#fixers#rubocop#GetCommand(buffer) abort
let l:executable = ale#handlers#rubocop#GetExecutable(a:buffer)
let l:exec_args = l:executable =~? 'bundle$'
\ ? ' exec rubocop'
\ : ''
let l:executable = ale#Var(a:buffer, 'ruby_rubocop_executable')
let l:config = ale#path#FindNearestFile(a:buffer, '.rubocop.yml')
let l:options = ale#Var(a:buffer, 'ruby_rubocop_options')
return ale#Escape(l:executable) . l:exec_args
return ale#handlers#ruby#EscapeExecutable(l:executable, 'rubocop')
\ . (!empty(l:config) ? ' --config ' . ale#Escape(l:config) : '')
\ . (!empty(l:options) ? ' ' . l:options : '')
\ . ' --auto-correct %t'
\ . ' --auto-correct --force-exclusion %t'
endfunction
function! ale#fixers#rubocop#Fix(buffer) abort

View File

@@ -15,7 +15,6 @@ function! ale#fixers#scalafmt#GetCommand(buffer) abort
return ale#Escape(l:executable) . l:exec_args
\ . (empty(l:options) ? '' : ' ' . l:options)
\ . ' %t'
endfunction
function! ale#fixers#scalafmt#Fix(buffer) abort

View File

@@ -5,13 +5,27 @@ scriptencoding utf-8
call ale#Set('sh_shfmt_executable', 'shfmt')
call ale#Set('sh_shfmt_options', '')
function! s:DefaultOption(buffer) abort
if getbufvar(a:buffer, '&expandtab') == 0
" Tab is used by default
return ''
endif
let l:tabsize = getbufvar(a:buffer, '&shiftwidth')
if l:tabsize == 0
let l:tabsize = getbufvar(a:buffer, '&tabstop')
endif
return ' -i ' . l:tabsize
endfunction
function! ale#fixers#shfmt#Fix(buffer) abort
let l:executable = ale#Var(a:buffer, 'sh_shfmt_executable')
let l:options = ale#Var(a:buffer, 'sh_shfmt_options')
return {
\ 'command': ale#Escape(l:executable)
\ . (empty(l:options) ? '' : ' ' . l:options)
\ . (empty(l:options) ? s:DefaultOption(a:buffer) : ' ' . l:options)
\}
endfunction

View File

@@ -0,0 +1,13 @@
call ale#Set('sql_sqlfmt_executable', 'sqlfmt')
call ale#Set('sql_sqlfmt_options', '')
function! ale#fixers#sqlfmt#Fix(buffer) abort
let l:executable = ale#Var(a:buffer, 'sql_sqlfmt_executable')
let l:options = ale#Var(a:buffer, 'sql_sqlfmt_options')
return {
\ 'command': ale#Escape(l:executable)
\ . ' -w'
\ . (empty(l:options) ? '' : ' ' . l:options),
\}
endfunction

View File

@@ -0,0 +1,21 @@
" Author: eborden <evan@evan-borden.com>
" Description: Integration of stylish-haskell formatting with ALE.
"
call ale#Set('haskell_stylish_haskell_executable', 'stylish-haskell')
function! ale#fixers#stylish_haskell#GetExecutable(buffer) abort
let l:executable = ale#Var(a:buffer, 'haskell_stylish_haskell_executable')
return ale#handlers#haskell_stack#EscapeExecutable(l:executable, 'stylish-haskell')
endfunction
function! ale#fixers#stylish_haskell#Fix(buffer) abort
let l:executable = ale#fixers#stylish_haskell#GetExecutable(a:buffer)
return {
\ 'command': l:executable
\ . ' --inplace'
\ . ' %t',
\ 'read_temporary_file': 1,
\}
endfunction

View File

@@ -0,0 +1,17 @@
" Author: dsifford <dereksifford@gmail.com>
" Description: Fixer for terraform and .hcl files
call ale#Set('terraform_fmt_executable', 'terraform')
call ale#Set('terraform_fmt_options', '')
function! ale#fixers#terraform#Fix(buffer) abort
let l:executable = ale#Var(a:buffer, 'terraform_fmt_executable')
let l:options = ale#Var(a:buffer, 'terraform_fmt_options')
return {
\ 'command': ale#Escape(l:executable)
\ . ' fmt'
\ . (empty(l:options) ? '' : ' ' . l:options)
\ . ' -'
\}
endfunction

View File

@@ -0,0 +1,16 @@
" Author: Derek P Sifford <dereksifford@gmail.com>
" Description: Fixer for C, C++, C#, ObjectiveC, D, Java, Pawn, and VALA.
call ale#Set('c_uncrustify_executable', 'uncrustify')
call ale#Set('c_uncrustify_options', '')
function! ale#fixers#uncrustify#Fix(buffer) abort
let l:executable = ale#Var(a:buffer, 'c_uncrustify_executable')
let l:options = ale#Var(a:buffer, 'c_uncrustify_options')
return {
\ 'command': ale#Escape(l:executable)
\ . ' --no-backup'
\ . (empty(l:options) ? '' : ' ' . l:options)
\}
endfunction

View File

@@ -0,0 +1,29 @@
" Author: Cyril Roelandt <tipecaml@gmail.com>
" Description: Integration of xmllint with ALE.
call ale#Set('xml_xmllint_executable', 'xmllint')
call ale#Set('xml_xmllint_options', '')
call ale#Set('xml_xmllint_indentsize', 2)
function! ale#fixers#xmllint#Fix(buffer) abort
let l:executable = ale#Escape(ale#Var(a:buffer, 'xml_xmllint_executable'))
let l:filename = ale#Escape(bufname(a:buffer))
let l:command = l:executable . ' --format ' . l:filename
let l:indent = ale#Var(a:buffer, 'xml_xmllint_indentsize')
if l:indent isnot# ''
let l:env = ale#Env('XMLLINT_INDENT', repeat(' ', l:indent))
let l:command = l:env . l:command
endif
let l:options = ale#Var(a:buffer, 'xml_xmllint_options')
if l:options isnot# ''
let l:command .= ' ' . l:options
endif
return {
\ 'command': l:command
\}
endfunction

27
autoload/ale/go.vim Normal file
View File

@@ -0,0 +1,27 @@
" Author: Horacio Sanson https://github.com/hsanson
" Description: Functions for integrating with Go tools
" Find the nearest dir listed in GOPATH and assume it the root of the go
" project.
function! ale#go#FindProjectRoot(buffer) abort
let l:sep = has('win32') ? ';' : ':'
let l:filename = ale#path#Simplify(expand('#' . a:buffer . ':p'))
for l:name in split($GOPATH, l:sep)
let l:path_dir = ale#path#Simplify(l:name)
" Use the directory from GOPATH if the current filename starts with it.
if l:filename[: len(l:path_dir) - 1] is? l:path_dir
return l:path_dir
endif
endfor
let l:default_go_path = ale#path#Simplify(expand('~/go'))
if isdirectory(l:default_go_path)
return l:default_go_path
endif
return ''
endfunction

View File

@@ -0,0 +1,17 @@
scriptencoding utf-8
" Author: Ye Jingchen <ye.jingchen@gmail.com>
" Description: Utilities for ccls
function! ale#handlers#ccls#GetProjectRoot(buffer) abort
let l:project_root = ale#path#FindNearestFile(a:buffer, '.ccls-root')
if empty(l:project_root)
let l:project_root = ale#path#FindNearestFile(a:buffer, 'compile_commands.json')
endif
if empty(l:project_root)
let l:project_root = ale#path#FindNearestFile(a:buffer, '.ccls')
endif
return !empty(l:project_root) ? fnamemodify(l:project_root, ':h') : ''
endfunction

View File

@@ -0,0 +1,28 @@
" Author: Matteo Centenaro (bugant) - https://github.com/bugant
" Author: Jon Parise <jon@indelible.org>
" Description: Functions for working with Elixir projects
" Find the root directory for an elixir project that uses mix.
function! ale#handlers#elixir#FindMixProjectRoot(buffer) abort
let l:mix_file = ale#path#FindNearestFile(a:buffer, 'mix.exs')
if !empty(l:mix_file)
return fnamemodify(l:mix_file, ':p:h')
endif
return '.'
endfunction
" Similar to ale#handlers#elixir#FindMixProjectRoot but also continue the
" search upward for a potential umbrella project root. If an umbrella root
" does not exist, the initial project root will be returned.
function! ale#handlers#elixir#FindMixUmbrellaRoot(buffer) abort
let l:app_root = ale#handlers#elixir#FindMixProjectRoot(a:buffer)
let l:umbrella_root = fnamemodify(l:app_root, ':h:h')
if filereadable(l:umbrella_root . '/mix.exs')
return l:umbrella_root
endif
return l:app_root
endfunction

View File

@@ -99,6 +99,13 @@ function! ale#handlers#eslint#Handle(buffer, lines) abort
\}]
endif
if a:lines == ['Could not connect']
return [{
\ 'lnum': 1,
\ 'text': 'Could not connect to eslint_d. Try updating eslint_d or killing it.',
\}]
endif
" Matches patterns line the following:
"
" /path/to/some-filename.js:47:14: Missing trailing comma. [Warning/comma-dangle]

View File

@@ -9,9 +9,11 @@ function! ale#handlers#gawk#HandleGawkFormat(buffer, lines) abort
for l:match in ale#util#GetMatches(a:lines, l:pattern)
let l:ecode = 'E'
if l:match[2] is? 'warning:'
let l:ecode = 'W'
endif
call add(l:output, {
\ 'lnum': l:match[1] + 0,
\ 'col': 0,

View File

@@ -5,6 +5,13 @@ scriptencoding utf-8
let s:pragma_error = '#pragma once in main file'
" Look for lines like the following.
"
" <stdin>:8:5: warning: conversion lacks type at end of format [-Wformat=]
" <stdin>:10:27: error: invalid operands to binary - (have int and char *)
" -:189:7: note: $/${} is unnecessary on arithmetic variables. [SC2004]
let s:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):(\d+)?:? ([^:]+): (.+)$'
function! s:IsHeaderFile(filename) abort
return a:filename =~? '\v\.(h|hpp)$'
endfunction
@@ -18,16 +25,63 @@ function! s:RemoveUnicodeQuotes(text) abort
return l:text
endfunction
" Report problems inside of header files just for gcc and clang
function! s:ParseProblemsInHeaders(buffer, lines) abort
let l:output = []
let l:include_item = {}
for l:line in a:lines[: -2]
let l:include_match = matchlist(l:line, '\v^In file included from')
if !empty(l:include_item)
let l:pattern_match = matchlist(l:line, s:pattern)
if !empty(l:pattern_match) && l:pattern_match[1] is# '<stdin>'
if has_key(l:include_item, 'lnum')
call add(l:output, l:include_item)
endif
let l:include_item = {}
continue
endif
let l:include_item.detail .= "\n" . l:line
endif
if !empty(l:include_match)
if empty(l:include_item)
let l:include_item = {
\ 'text': 'Error found in header. See :ALEDetail',
\ 'detail': l:line,
\}
endif
endif
if !empty(l:include_item)
let l:stdin_match = matchlist(l:line, '\vfrom \<stdin\>:(\d+):(\d*):?$')
if !empty(l:stdin_match)
let l:include_item.lnum = str2nr(l:stdin_match[1])
if str2nr(l:stdin_match[2])
let l:include_item.col = str2nr(l:stdin_match[2])
endif
endif
endif
endfor
if !empty(l:include_item) && has_key(l:include_item, 'lnum')
call add(l:output, l:include_item)
endif
return l:output
endfunction
function! ale#handlers#gcc#HandleGCCFormat(buffer, lines) abort
" Look for lines like the following.
"
" <stdin>:8:5: warning: conversion lacks type at end of format [-Wformat=]
" <stdin>:10:27: error: invalid operands to binary - (have int and char *)
" -:189:7: note: $/${} is unnecessary on arithmetic variables. [SC2004]
let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):(\d+)?:? ([^:]+): (.+)$'
let l:output = []
for l:match in ale#util#GetMatches(a:lines, l:pattern)
for l:match in ale#util#GetMatches(a:lines, s:pattern)
" Filter out the pragma errors
if s:IsHeaderFile(bufname(bufnr('')))
\&& l:match[5][:len(s:pragma_error) - 1] is# s:pragma_error
@@ -38,9 +92,12 @@ function! ale#handlers#gcc#HandleGCCFormat(buffer, lines) abort
" the previous error parsed in output
if l:match[4] is# 'note'
if !empty(l:output)
let l:output[-1]['detail'] =
\ get(l:output[-1], 'detail', '')
\ . s:RemoveUnicodeQuotes(l:match[0]) . "\n"
if !has_key(l:output[-1], 'detail')
let l:output[-1].detail = l:output[-1].text
endif
let l:output[-1].detail = l:output[-1].detail . "\n"
\ . s:RemoveUnicodeQuotes(l:match[0])
endif
continue
@@ -67,3 +124,12 @@ function! ale#handlers#gcc#HandleGCCFormat(buffer, lines) abort
return l:output
endfunction
" Handle problems with the GCC format, but report problems inside of headers.
function! ale#handlers#gcc#HandleGCCFormatWithIncludes(buffer, lines) abort
let l:output = ale#handlers#gcc#HandleGCCFormat(a:buffer, a:lines)
call extend(l:output, s:ParseProblemsInHeaders(a:buffer, a:lines))
return l:output
endfunction

View File

@@ -21,5 +21,6 @@ function! ale#handlers#go#Handler(buffer, lines) abort
\ 'type': 'E',
\})
endfor
return l:output
endfunction

View File

@@ -1,5 +1,15 @@
" Author: w0rp <devw0rp@gmail.com>
" Description: Error handling for the format GHC outputs.
"
function! ale#handlers#haskell#GetStackExecutable(bufnr) abort
if ale#path#FindNearestFile(a:bufnr, 'stack.yaml') isnot# ''
return 'stack'
endif
" if there is no stack.yaml file, we don't use stack even if it exists,
" so we return '', because executable('') apparently always fails
return ''
endfunction
" Remember the directory used for temporary files for Vim.
let s:temp_dir = fnamemodify(ale#util#Tempname(), ':h')

View File

@@ -0,0 +1,7 @@
function! ale#handlers#haskell_stack#EscapeExecutable(executable, stack_exec) abort
let l:exec_args = a:executable =~? 'stack$'
\ ? ' exec ' . ale#Escape(a:stack_exec) . ' --'
\ : ''
return ale#Escape(a:executable) . l:exec_args
endfunction

View File

@@ -0,0 +1,8 @@
call ale#Set('haskell_hlint_executable', 'hlint')
call ale#Set('haskell_hlint_options', get(g:, 'hlint_options', ''))
function! ale#handlers#hlint#GetExecutable(buffer) abort
let l:executable = ale#Var(a:buffer, 'haskell_hlint_executable')
return ale#handlers#haskell_stack#EscapeExecutable(l:executable, 'hlint')
endfunction

View File

@@ -3,6 +3,7 @@
function! ale#handlers#ols#GetExecutable(buffer) abort
let l:ols_setting = ale#handlers#ols#GetLanguage(a:buffer) . '_ols'
return ale#node#FindExecutable(a:buffer, l:ols_setting, [
\ 'node_modules/.bin/ocaml-language-server',
\])

View File

@@ -14,7 +14,6 @@ endfunction
function! ale#handlers#pony#HandlePonycFormat(buffer, lines) abort
" Look for lines like the following.
" /home/code/pony/classes/Wombat.pony:22:30: can't lookup private fields from outside the type
let l:pattern = '\v^([^:]+):(\d+):(\d+)?:? (.+)$'
let l:output = []

View File

@@ -6,15 +6,18 @@ function! ale#handlers#redpen#HandleRedpenOutput(buffer, lines) abort
" element.
let l:res = json_decode(join(a:lines))[0]
let l:output = []
for l:err in l:res.errors
let l:item = {
\ 'text': l:err.message,
\ 'type': 'W',
\ 'code': l:err.validator,
\}
if has_key(l:err, 'startPosition')
let l:item.lnum = l:err.startPosition.lineNum
let l:item.col = l:err.startPosition.offset + 1
if has_key(l:err, 'endPosition')
let l:item.end_lnum = l:err.endPosition.lineNum
let l:item.end_col = l:err.endPosition.offset
@@ -28,29 +31,35 @@ function! ale#handlers#redpen#HandleRedpenOutput(buffer, lines) abort
" Adjust column number for multibyte string
let l:line = getline(l:item.lnum)
if l:line is# ''
let l:line = l:err.sentence
endif
let l:line = split(l:line, '\zs')
if l:item.col >= 2
let l:col = 0
for l:strlen in map(l:line[0:(l:item.col - 2)], 'strlen(v:val)')
let l:col = l:col + l:strlen
endfor
let l:item.col = l:col + 1
endif
if has_key(l:item, 'end_col')
let l:col = 0
for l:strlen in map(l:line[0:(l:item.end_col - 1)], 'strlen(v:val)')
let l:col = l:col + l:strlen
endfor
let l:item.end_col = l:col
endif
call add(l:output, l:item)
endfor
return l:output
endfunction

View File

@@ -1,6 +0,0 @@
call ale#Set('ruby_rubocop_options', '')
call ale#Set('ruby_rubocop_executable', 'rubocop')
function! ale#handlers#rubocop#GetExecutable(buffer) abort
return ale#Var(a:buffer, 'ruby_rubocop_executable')
endfunction

View File

@@ -13,8 +13,10 @@ function! s:HandleSyntaxError(buffer, lines) abort
for l:line in a:lines
let l:match = matchlist(l:line, l:pattern)
if len(l:match) == 0
let l:match = matchlist(l:line, l:column)
if len(l:match) != 0
let l:output[len(l:output) - 1]['col'] = len(l:match[1])
endif
@@ -35,3 +37,10 @@ function! ale#handlers#ruby#HandleSyntaxErrors(buffer, lines) abort
return s:HandleSyntaxError(a:buffer, a:lines)
endfunction
function! ale#handlers#ruby#EscapeExecutable(executable, bundle_exec) abort
let l:exec_args = a:executable =~? 'bundle'
\ ? ' exec ' . a:bundle_exec
\ : ''
return ale#Escape(a:executable) . l:exec_args
endfunction

View File

@@ -7,6 +7,10 @@ if !exists('g:ale_rust_ignore_error_codes')
let g:ale_rust_ignore_error_codes = []
endif
if !exists('g:ale_rust_ignore_secondary_spans')
let g:ale_rust_ignore_secondary_spans = 0
endif
function! s:FindSpan(buffer, span) abort
if ale#path#IsBufferPath(a:buffer, a:span.file_name) || a:span.file_name is# '<anon>'
return a:span
@@ -32,7 +36,7 @@ function! ale#handlers#rust#HandleRustErrors(buffer, lines) abort
let l:error = json_decode(l:errorline)
if has_key(l:error, 'message') && type(l:error.message) == type({})
if has_key(l:error, 'message') && type(l:error.message) is v:t_dict
let l:error = l:error.message
endif
@@ -47,6 +51,10 @@ function! ale#handlers#rust#HandleRustErrors(buffer, lines) abort
for l:root_span in l:error.spans
let l:span = s:FindSpan(a:buffer, l:root_span)
if ale#Var(a:buffer, 'rust_ignore_secondary_spans') && !get(l:span, 'is_primary', 1)
continue
endif
if !empty(l:span)
call add(l:output, {
\ 'lnum': l:span.line_start,

View File

@@ -11,8 +11,10 @@ function! ale#handlers#sml#GetCmFile(buffer) abort
let l:as_list = 1
let l:cmfile = ''
for l:path in ale#path#Upwards(expand('#' . a:buffer . ':p:h'))
let l:results = glob(l:path . '/' . l:pattern, 0, l:as_list)
if len(l:results) > 0
" If there is more than one CM file, we take the first one
" See :help ale-sml-smlnj for how to configure this.
@@ -46,6 +48,7 @@ endfunction
function! ale#handlers#sml#GetExecutableSmlnjCm(buffer) abort
return s:GetExecutable(a:buffer, 'smlnj-cm')
endfunction
function! ale#handlers#sml#GetExecutableSmlnjFile(buffer) abort
return s:GetExecutable(a:buffer, 'smlnj-file')
endfunction
@@ -53,7 +56,6 @@ endfunction
function! ale#handlers#sml#Handle(buffer, lines) abort
" Try to match basic sml errors
" TODO(jez) We can get better errorfmt strings from Syntastic
let l:out = []
let l:pattern = '^.*\:\([0-9\.]\+\)\ \(\w\+\)\:\ \(.*\)'
let l:pattern2 = '^.*\:\([0-9]\+\)\.\?\([0-9]\+\).* \(\(Warning\|Error\): .*\)'
@@ -83,7 +85,6 @@ function! ale#handlers#sml#Handle(buffer, lines) abort
\})
continue
endif
endfor
return l:out

View File

@@ -23,6 +23,7 @@ function! ale#handlers#vale#Handle(buffer, lines) abort
endif
let l:output = []
for l:error in l:errors[keys(l:errors)[0]]
call add(l:output, {
\ 'lnum': l:error['Line'],

View File

@@ -63,19 +63,19 @@ function! ale#hover#HandleLSPResponse(conn_id, response) abort
let l:result = l:result.contents
if type(l:result) is type('')
if type(l:result) is v:t_string
" The result can be just a string.
let l:result = [l:result]
endif
if type(l:result) is type({})
if type(l:result) is v:t_dict
" If the result is an object, then it's markup content.
let l:result = [l:result.value]
endif
if type(l:result) is type([])
if type(l:result) is v:t_list
" Replace objects with text values.
call map(l:result, 'type(v:val) is type('''') ? v:val : v:val.value')
call map(l:result, 'type(v:val) is v:t_string ? v:val : v:val.value')
let l:str = join(l:result, "\n")
let l:str = substitute(l:str, '^\s*\(.\{-}\)\s*$', '\1', '')
@@ -92,7 +92,44 @@ function! ale#hover#HandleLSPResponse(conn_id, response) abort
endif
endfunction
function! s:ShowDetails(linter, buffer, line, column, opt) abort
function! s:OnReady(linter, lsp_details, line, column, opt, ...) abort
let l:buffer = a:lsp_details.buffer
let l:id = a:lsp_details.connection_id
let l:Callback = a:linter.lsp is# 'tsserver'
\ ? function('ale#hover#HandleTSServerResponse')
\ : function('ale#hover#HandleLSPResponse')
call ale#lsp#RegisterCallback(l:id, l:Callback)
if a:linter.lsp is# 'tsserver'
let l:column = a:column
let l:message = ale#lsp#tsserver_message#Quickinfo(
\ l:buffer,
\ a:line,
\ l:column
\)
else
" Send a message saying the buffer has changed first, or the
" hover position probably won't make sense.
call ale#lsp#NotifyForChanges(l:id, l:buffer)
let l:column = min([a:column, len(getbufline(l:buffer, a:line)[0])])
let l:message = ale#lsp#message#Hover(l:buffer, a:line, l:column)
endif
let l:request_id = ale#lsp#Send(l:id, l:message)
let s:hover_map[l:request_id] = {
\ 'buffer': l:buffer,
\ 'line': a:line,
\ 'column': l:column,
\ 'hover_from_balloonexpr': get(a:opt, 'called_from_balloonexpr', 0),
\}
endfunction
function! s:ShowDetails(linter, buffer, line, column, opt, ...) abort
let l:lsp_details = ale#lsp_linter#StartLSP(a:buffer, a:linter)
if empty(l:lsp_details)
@@ -100,44 +137,10 @@ function! s:ShowDetails(linter, buffer, line, column, opt) abort
endif
let l:id = l:lsp_details.connection_id
let l:root = l:lsp_details.project_root
let l:language_id = l:lsp_details.language_id
function! OnReady(...) abort closure
let l:Callback = a:linter.lsp is# 'tsserver'
\ ? function('ale#hover#HandleTSServerResponse')
\ : function('ale#hover#HandleLSPResponse')
call ale#lsp#RegisterCallback(l:id, l:Callback)
if a:linter.lsp is# 'tsserver'
let l:column = a:column
let l:message = ale#lsp#tsserver_message#Quickinfo(
\ a:buffer,
\ a:line,
\ l:column
\)
else
" Send a message saying the buffer has changed first, or the
" hover position probably won't make sense.
call ale#lsp#NotifyForChanges(l:id, l:root, a:buffer)
let l:column = min([a:column, len(getbufline(a:buffer, a:line)[0])])
let l:message = ale#lsp#message#Hover(a:buffer, a:line, l:column)
endif
let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root)
let s:hover_map[l:request_id] = {
\ 'buffer': a:buffer,
\ 'line': a:line,
\ 'column': l:column,
\ 'hover_from_balloonexpr': get(a:opt, 'called_from_balloonexpr', 0),
\}
endfunction
call ale#lsp#WaitForCapability(l:id, l:root, 'hover', function('OnReady'))
call ale#lsp#WaitForCapability(l:id, 'hover', function('s:OnReady', [
\ a:linter, l:lsp_details, a:line, a:column, a:opt
\]))
endfunction
" Obtain Hover information for the specified position

20
autoload/ale/java.vim Normal file
View File

@@ -0,0 +1,20 @@
" Author: Horacio Sanson https://github.com/hsanson
" Description: Functions for integrating with Java tools
" Find the nearest dir contining a gradle or pom file and asume it
" the root of a java app.
function! ale#java#FindProjectRoot(buffer) abort
let l:gradle_root = ale#gradle#FindProjectRoot(a:buffer)
if !empty(l:gradle_root)
return l:gradle_root
endif
let l:maven_pom_file = ale#path#FindNearestFile(a:buffer, 'pom.xml')
if !empty(l:maven_pom_file)
return fnamemodify(l:maven_pom_file, ':h')
endif
return ''
endfunction

View File

@@ -249,6 +249,11 @@ function! ale#job#Start(command, options) abort
let l:job_options.exit_cb = function('s:VimExitCallback')
endif
" Use non-blocking writes for Vim versions that support the option.
if has('patch-8.1.350')
let l:job_options.noblock = 1
endif
" Vim 8 will read the stdin from the file's buffer.
let l:job_info.job = job_start(a:command, l:job_options)
let l:job_id = ale#job#ParseVim8ProcessID(string(l:job_info.job))
@@ -278,11 +283,13 @@ function! ale#job#IsRunning(job_id) abort
try
" In NeoVim, if the job isn't running, jobpid() will throw.
call jobpid(a:job_id)
return 1
catch
endtry
elseif has_key(s:job_map, a:job_id)
let l:job = s:job_map[a:job_id].job
return job_status(l:job) is# 'run'
endif

19
autoload/ale/julia.vim Normal file
View File

@@ -0,0 +1,19 @@
" Author: Bartolomeo Stellato bartolomeo.stellato@gmail.com
" Description: Functions for integrating with Julia tools
" Find the nearest dir containing a julia project
let s:__ale_julia_project_filenames = ['REQUIRE', 'Manifest.toml', 'Project.toml']
function! ale#julia#FindProjectRoot(buffer) abort
for l:project_filename in s:__ale_julia_project_filenames
let l:full_path = ale#path#FindNearestFile(a:buffer, l:project_filename)
if !empty(l:full_path)
let l:path = fnamemodify(l:full_path, ':p:h')
return l:path
endif
endfor
return ''
endfunction

View File

@@ -16,6 +16,7 @@ let s:default_ale_linter_aliases = {
\ 'systemverilog': 'verilog',
\ 'verilog_systemverilog': ['verilog_systemverilog', 'verilog'],
\ 'vimwiki': 'markdown',
\ 'vue': ['vue', 'javascript'],
\ 'zsh': 'sh',
\}
@@ -26,17 +27,22 @@ let s:default_ale_linter_aliases = {
"
" Only cargo is enabled for Rust by default.
" rpmlint is disabled by default because it can result in code execution.
" hhast is disabled by default because it executes code in the project root.
"
" NOTE: Update the g:ale_linters documentation when modifying this.
let s:default_ale_linters = {
\ 'csh': ['shell'],
\ 'elixir': ['credo', 'dialyxir', 'dogma', 'elixir-ls'],
\ 'go': ['gofmt', 'golint', 'go vet'],
\ 'hack': ['hack'],
\ 'help': [],
\ 'perl': ['perlcritic'],
\ 'perl6': [],
\ 'python': ['flake8', 'mypy', 'pylint'],
\ 'rust': ['cargo'],
\ 'spec': [],
\ 'text': [],
\ 'vue': ['eslint', 'vls'],
\ 'zsh': ['shell'],
\}
@@ -51,17 +57,17 @@ endfunction
" Do not call this function.
function! ale#linter#GetLintersLoaded() abort
" This command will throw from the sandbox.
let &equalprg=&equalprg
let &l:equalprg=&l:equalprg
return s:linters
endfunction
function! s:IsCallback(value) abort
return type(a:value) == type('') || type(a:value) == type(function('type'))
return type(a:value) is v:t_string || type(a:value) is v:t_func
endfunction
function! s:IsBoolean(value) abort
return type(a:value) == type(0) && (a:value == 0 || a:value == 1)
return type(a:value) is v:t_number && (a:value == 0 || a:value == 1)
endfunction
function! s:LanguageGetter(buffer) dict abort
@@ -69,7 +75,7 @@ function! s:LanguageGetter(buffer) dict abort
endfunction
function! ale#linter#PreProcess(filetype, linter) abort
if type(a:linter) != type({})
if type(a:linter) isnot v:t_dict
throw 'The linter object must be a Dictionary'
endif
@@ -79,7 +85,7 @@ function! ale#linter#PreProcess(filetype, linter) abort
\ 'lsp': get(a:linter, 'lsp', ''),
\}
if type(l:obj.name) != type('')
if type(l:obj.name) isnot v:t_string
throw '`name` must be defined to name the linter'
endif
@@ -97,7 +103,7 @@ function! ale#linter#PreProcess(filetype, linter) abort
endif
if index(['', 'socket', 'stdio', 'tsserver'], l:obj.lsp) < 0
throw '`lsp` must be either `''lsp''` or `''tsserver''` if defined'
throw '`lsp` must be either `''lsp''`, `''stdio''`, `''socket''` or `''tsserver''` if defined'
endif
if !l:needs_executable
@@ -114,7 +120,7 @@ function! ale#linter#PreProcess(filetype, linter) abort
elseif has_key(a:linter, 'executable')
let l:obj.executable = a:linter.executable
if type(l:obj.executable) != type('')
if type(l:obj.executable) isnot v:t_string
throw '`executable` must be a string if defined'
endif
else
@@ -130,7 +136,7 @@ function! ale#linter#PreProcess(filetype, linter) abort
elseif has_key(a:linter, 'command_chain')
let l:obj.command_chain = a:linter.command_chain
if type(l:obj.command_chain) != type([])
if type(l:obj.command_chain) isnot v:t_list
throw '`command_chain` must be a List'
endif
@@ -148,7 +154,7 @@ function! ale#linter#PreProcess(filetype, linter) abort
endif
if has_key(l:link, 'output_stream')
if type(l:link.output_stream) != type('')
if type(l:link.output_stream) isnot v:t_string
\|| index(['stdout', 'stderr', 'both'], l:link.output_stream) < 0
throw l:err_prefix . '`output_stream` flag must be '
\ . "'stdout', 'stderr', or 'both'"
@@ -170,7 +176,7 @@ function! ale#linter#PreProcess(filetype, linter) abort
elseif has_key(a:linter, 'command')
let l:obj.command = a:linter.command
if type(l:obj.command) != type('')
if type(l:obj.command) isnot v:t_string
throw '`command` must be a string if defined'
endif
else
@@ -217,7 +223,7 @@ function! ale#linter#PreProcess(filetype, linter) abort
" Default to using the filetype as the language.
let l:obj.language = get(a:linter, 'language', a:filetype)
if type(l:obj.language) != type('')
if type(l:obj.language) isnot v:t_string
throw '`language` must be a string'
endif
@@ -253,11 +259,29 @@ function! ale#linter#PreProcess(filetype, linter) abort
elseif has_key(a:linter, 'initialization_options')
let l:obj.initialization_options = a:linter.initialization_options
endif
if has_key(a:linter, 'lsp_config_callback')
if has_key(a:linter, 'lsp_config')
throw 'Only one of `lsp_config` or `lsp_config_callback` should be set'
endif
let l:obj.lsp_config_callback = a:linter.lsp_config_callback
if !s:IsCallback(l:obj.lsp_config_callback)
throw '`lsp_config_callback` must be a callback if defined'
endif
elseif has_key(a:linter, 'lsp_config')
if type(a:linter.lsp_config) isnot v:t_dict
throw '`lsp_config` must be a Dictionary'
endif
let l:obj.lsp_config = a:linter.lsp_config
endif
endif
let l:obj.output_stream = get(a:linter, 'output_stream', 'stdout')
if type(l:obj.output_stream) != type('')
if type(l:obj.output_stream) isnot v:t_string
\|| index(['stdout', 'stderr', 'both'], l:obj.output_stream) < 0
throw "`output_stream` must be 'stdout', 'stderr', or 'both'"
endif
@@ -283,8 +307,8 @@ function! ale#linter#PreProcess(filetype, linter) abort
let l:obj.aliases = get(a:linter, 'aliases', [])
if type(l:obj.aliases) != type([])
\|| len(filter(copy(l:obj.aliases), 'type(v:val) != type('''')')) > 0
if type(l:obj.aliases) isnot v:t_list
\|| len(filter(copy(l:obj.aliases), 'type(v:val) isnot v:t_string')) > 0
throw '`aliases` must be a List of String values'
endif
@@ -293,7 +317,7 @@ endfunction
function! ale#linter#Define(filetype, linter) abort
" This command will throw from the sandbox.
let &equalprg=&equalprg
let &l:equalprg=&l:equalprg
if !has_key(s:linters, a:filetype)
let s:linters[a:filetype] = []
@@ -335,8 +359,9 @@ endfunction
function! s:GetAliasedFiletype(original_filetype) abort
let l:buffer_aliases = get(b:, 'ale_linter_aliases', {})
" b:ale_linter_aliases can be set to a List.
if type(l:buffer_aliases) is type([])
" b:ale_linter_aliases can be set to a List or String.
if type(l:buffer_aliases) is v:t_list
\|| type(l:buffer_aliases) is v:t_string
return l:buffer_aliases
endif
@@ -360,7 +385,7 @@ endfunction
function! ale#linter#ResolveFiletype(original_filetype) abort
let l:filetype = s:GetAliasedFiletype(a:original_filetype)
if type(l:filetype) != type([])
if type(l:filetype) isnot v:t_list
return [l:filetype]
endif
@@ -376,7 +401,7 @@ function! s:GetLinterNames(original_filetype) abort
endif
" b:ale_linters can be set to a List.
if type(l:buffer_ale_linters) is type([])
if type(l:buffer_ale_linters) is v:t_list
return l:buffer_ale_linters
endif
@@ -414,9 +439,9 @@ function! ale#linter#Get(original_filetypes) abort
let l:all_linters = ale#linter#GetAll(l:filetype)
let l:filetype_linters = []
if type(l:linter_names) == type('') && l:linter_names is# 'all'
if type(l:linter_names) is v:t_string && l:linter_names is# 'all'
let l:filetype_linters = l:all_linters
elseif type(l:linter_names) == type([])
elseif type(l:linter_names) is v:t_list
" Select only the linters we or the user has specified.
for l:linter in l:all_linters
let l:name_list = [l:linter.name] + l:linter.aliases

View File

@@ -25,6 +25,7 @@ function! ale#list#IsQuickfixOpen() abort
return 1
endif
endfor
return 0
endfunction
@@ -112,9 +113,11 @@ function! s:SetListsImpl(timer_id, buffer, loclist) abort
" open windows vertically instead of default horizontally
let l:open_type = ''
if ale#Var(a:buffer, 'list_vertical') == 1
let l:open_type = 'vert '
endif
if g:ale_set_quickfix
if !ale#list#IsQuickfixOpen()
silent! execute l:open_type . 'copen ' . str2nr(ale#Var(a:buffer, 'list_window_size'))

View File

@@ -66,6 +66,7 @@ function! ale#loclist_jumping#Jump(direction, wrap) abort
let l:nearest = ale#loclist_jumping#FindNearest(a:direction, a:wrap)
if !empty(l:nearest)
normal! m`
call cursor(l:nearest)
endif
endfunction
@@ -82,6 +83,7 @@ function! ale#loclist_jumping#JumpToIndex(index) abort
let l:item = l:loclist[a:index]
if !empty(l:item)
normal! m`
call cursor([l:item.lnum, l:item.col])
endif
endfunction

View File

@@ -1,62 +1,70 @@
" Author: w0rp <devw0rp@gmail.com>
" Description: Language Server Protocol client code
" A List of connections, used for tracking servers which have been connected
" to, and programs which are run.
let s:connections = get(s:, 'connections', [])
" A Dictionary for tracking connections.
let s:connections = get(s:, 'connections', {})
let g:ale_lsp_next_message_id = 1
" Exposed only so tests can get at it.
" Do not call this function basically anywhere.
function! ale#lsp#NewConnection(initialization_options) abort
" id: The job ID as a Number, or the server address as a string.
" data: The message data received so far.
" executable: An executable only set for program connections.
" open_documents: A Dictionary mapping buffers to b:changedtick, keeping
" track of when documents were opened, and when we last changed them.
" callback_list: A list of callbacks for handling LSP responses.
" initialization_options: Options to send to the server.
" capabilities: Features the server supports.
let l:conn = {
\ 'is_tsserver': 0,
\ 'id': '',
\ 'data': '',
\ 'projects': {},
\ 'open_documents': {},
\ 'callback_list': [],
\ 'initialization_options': a:initialization_options,
\ 'capabilities': {
\ 'hover': 0,
\ 'references': 0,
\ 'completion': 0,
\ 'completion_trigger_characters': [],
\ 'definition': 0,
\ },
\}
" Given an id, which can be an executable or address, and a project path,
" create a new connection if needed. Return a unique ID for the connection.
function! ale#lsp#Register(executable_or_address, project, init_options) abort
let l:conn_id = a:executable_or_address . ':' . a:project
call add(s:connections, l:conn)
if !has_key(s:connections, l:conn_id)
" is_tsserver: 1 if the connection is for tsserver.
" data: The message data received so far.
" root: The project root.
" open_documents: A Dictionary mapping buffers to b:changedtick, keeping
" track of when documents were opened, and when we last changed them.
" initialized: 0 if the connection is ready, 1 otherwise.
" init_request_id: The ID for the init request.
" init_options: Options to send to the server.
" config: Configuration settings to send to the server.
" callback_list: A list of callbacks for handling LSP responses.
" message_queue: Messages queued for sending to callbacks.
" capabilities_queue: The list of callbacks to call with capabilities.
" capabilities: Features the server supports.
let s:connections[l:conn_id] = {
\ 'id': l:conn_id,
\ 'is_tsserver': 0,
\ 'data': '',
\ 'root': a:project,
\ 'open_documents': {},
\ 'initialized': 0,
\ 'init_request_id': 0,
\ 'init_options': a:init_options,
\ 'config': {},
\ 'callback_list': [],
\ 'message_queue': [],
\ 'capabilities_queue': [],
\ 'capabilities': {
\ 'hover': 0,
\ 'references': 0,
\ 'completion': 0,
\ 'completion_trigger_characters': [],
\ 'definition': 0,
\ 'symbol_search': 0,
\ },
\}
endif
return l:conn
return l:conn_id
endfunction
" Remove an LSP connection with a given ID. This is only for tests.
function! ale#lsp#RemoveConnectionWithID(id) abort
call filter(s:connections, 'v:val.id isnot a:id')
if has_key(s:connections, a:id)
call remove(s:connections, a:id)
endif
endfunction
function! s:FindConnection(key, value) abort
for l:conn in s:connections
if has_key(l:conn, a:key) && get(l:conn, a:key) is# a:value
return l:conn
endif
endfor
" This is only needed for tests
function! ale#lsp#MarkDocumentAsOpen(id, buffer) abort
let l:conn = get(s:connections, a:id, {})
return {}
endfunction
" Get the capabilities for a connection, or an empty Dictionary.
function! ale#lsp#GetConnectionCapabilities(id) abort
return get(s:FindConnection('id', a:id), 'capabilities', {})
if !empty(l:conn)
let l:conn.open_documents[a:buffer] = -1
endif
endfunction
function! ale#lsp#GetNextMessageID() abort
@@ -94,13 +102,14 @@ function! s:CreateTSServerMessageData(message) abort
endif
let l:data = json_encode(l:obj) . "\n"
return [l:is_notification ? 0 : l:obj.seq, l:data]
endfunction
" Given a List of one or two items, [method_name] or [method_name, params],
" return a List containing [message_id, message_data]
function! ale#lsp#CreateMessageData(message) abort
if a:message[1] =~# '^ts@'
if a:message[1][:2] is# 'ts@'
return s:CreateTSServerMessageData(a:message)
endif
@@ -167,53 +176,10 @@ function! ale#lsp#ReadMessageData(data) abort
return [l:remainder, l:response_list]
endfunction
function! s:FindProjectWithInitRequestID(conn, init_request_id) abort
for l:project_root in keys(a:conn.projects)
let l:project = a:conn.projects[l:project_root]
if l:project.init_request_id == a:init_request_id
return l:project
endif
endfor
return {}
endfunction
function! s:MarkProjectAsInitialized(conn, project) abort
let a:project.initialized = 1
" After the server starts, send messages we had queued previously.
for l:message_data in a:project.message_queue
call s:SendMessageData(a:conn, l:message_data)
endfor
" Remove the messages now.
let a:conn.message_queue = []
" Call capabilities callbacks queued for the project.
for [l:capability, l:Callback] in a:project.capabilities_queue
if a:conn.is_tsserver || a:conn.capabilities[l:capability]
call call(l:Callback, [a:conn.id, a:project.root])
endif
endfor
" Clear the queued callbacks now.
let a:project.capabilities_queue = []
endfunction
function! s:HandleInitializeResponse(conn, response) abort
let l:request_id = a:response.request_id
let l:project = s:FindProjectWithInitRequestID(a:conn, l:request_id)
if !empty(l:project)
call s:MarkProjectAsInitialized(a:conn, l:project)
endif
endfunction
" Update capabilities from the server, so we know which features the server
" supports.
function! s:UpdateCapabilities(conn, capabilities) abort
if type(a:capabilities) != type({})
if type(a:capabilities) isnot v:t_dict
return
endif
@@ -229,10 +195,10 @@ function! s:UpdateCapabilities(conn, capabilities) abort
let a:conn.capabilities.completion = 1
endif
if type(get(a:capabilities, 'completionProvider')) is type({})
if type(get(a:capabilities, 'completionProvider')) is v:t_dict
let l:chars = get(a:capabilities.completionProvider, 'triggerCharacters')
if type(l:chars) is type([])
if type(l:chars) is v:t_list
let a:conn.capabilities.completion_trigger_characters = l:chars
endif
endif
@@ -240,180 +206,164 @@ function! s:UpdateCapabilities(conn, capabilities) abort
if get(a:capabilities, 'definitionProvider') is v:true
let a:conn.capabilities.definition = 1
endif
if get(a:capabilities, 'workspaceSymbolProvider') is v:true
let a:conn.capabilities.symbol_search = 1
endif
endfunction
function! ale#lsp#HandleOtherInitializeResponses(conn, response) abort
let l:uninitialized_projects = []
" Update a connection's configuration dictionary and notify LSP servers
" of any changes since the last update. Returns 1 if a configuration
" update was sent; otherwise 0 will be returned.
function! ale#lsp#UpdateConfig(conn_id, buffer, config) abort
let l:conn = get(s:connections, a:conn_id, {})
for [l:key, l:value] in items(a:conn.projects)
if l:value.initialized == 0
call add(l:uninitialized_projects, [l:key, l:value])
endif
endfor
if empty(l:conn) || a:config ==# l:conn.config " no-custom-checks
return 0
endif
if empty(l:uninitialized_projects)
let l:conn.config = a:config
let l:message = ale#lsp#message#DidChangeConfiguration(a:buffer, a:config)
call ale#lsp#Send(a:conn_id, l:message)
return 1
endfunction
function! ale#lsp#HandleInitResponse(conn, response) abort
if get(a:response, 'method', '') is# 'initialize'
let a:conn.initialized = 1
elseif type(get(a:response, 'result')) is v:t_dict
\&& has_key(a:response.result, 'capabilities')
call s:UpdateCapabilities(a:conn, a:response.result.capabilities)
let a:conn.initialized = 1
endif
if !a:conn.initialized
return
endif
if get(a:response, 'method', '') is# ''
if has_key(get(a:response, 'result', {}), 'capabilities')
call s:UpdateCapabilities(a:conn, a:response.result.capabilities)
" After the server starts, send messages we had queued previously.
for l:message_data in a:conn.message_queue
call s:SendMessageData(a:conn, l:message_data)
endfor
for [l:dir, l:project] in l:uninitialized_projects
call s:MarkProjectAsInitialized(a:conn, l:project)
endfor
" Remove the messages now.
let a:conn.message_queue = []
" Call capabilities callbacks queued for the project.
for [l:capability, l:Callback] in a:conn.capabilities_queue
if a:conn.capabilities[l:capability]
call call(l:Callback, [a:conn.id])
endif
elseif get(a:response, 'method', '') is# 'textDocument/publishDiagnostics'
let l:filename = ale#path#FromURI(a:response.params.uri)
endfor
for [l:dir, l:project] in l:uninitialized_projects
if l:filename[:len(l:dir) - 1] is# l:dir
call s:MarkProjectAsInitialized(a:conn, l:project)
endif
endfor
endif
let a:conn.capabilities_queue = []
endfunction
function! ale#lsp#HandleMessage(conn, message) abort
if type(a:message) != type('')
function! ale#lsp#HandleMessage(conn_id, message) abort
let l:conn = get(s:connections, a:conn_id, {})
if empty(l:conn)
return
endif
if type(a:message) isnot v:t_string
" Ignore messages that aren't strings.
return
endif
let a:conn.data .= a:message
let l:conn.data .= a:message
" Parse the objects now if we can, and keep the remaining text.
let [a:conn.data, l:response_list] = ale#lsp#ReadMessageData(a:conn.data)
let [l:conn.data, l:response_list] = ale#lsp#ReadMessageData(l:conn.data)
" Call our callbacks.
for l:response in l:response_list
if get(l:response, 'method', '') is# 'initialize'
call s:HandleInitializeResponse(a:conn, l:response)
else
call ale#lsp#HandleOtherInitializeResponses(a:conn, l:response)
" Look for initialize responses first.
if !l:conn.initialized
for l:response in l:response_list
call ale#lsp#HandleInitResponse(l:conn, l:response)
endfor
endif
" If the connection is marked as initialized, call the callbacks with the
" responses.
if l:conn.initialized
for l:response in l:response_list
" Call all of the registered handlers with the response.
for l:Callback in a:conn.callback_list
call ale#util#GetFunction(l:Callback)(a:conn.id, l:response)
for l:Callback in l:conn.callback_list
call ale#util#GetFunction(l:Callback)(a:conn_id, l:response)
endfor
endif
endfor
endfunction
function! s:HandleChannelMessage(channel_id, message) abort
let l:address = ale#socket#GetAddress(a:channel_id)
let l:conn = s:FindConnection('id', l:address)
call ale#lsp#HandleMessage(l:conn, a:message)
endfunction
function! s:HandleCommandMessage(job_id, message) abort
let l:conn = s:FindConnection('id', a:job_id)
call ale#lsp#HandleMessage(l:conn, a:message)
endfor
endif
endfunction
" Given a connection ID, mark it as a tsserver connection, so it will be
" handled that way.
function! ale#lsp#MarkConnectionAsTsserver(conn_id) abort
let l:conn = s:FindConnection('id', a:conn_id)
if !empty(l:conn)
let l:conn.is_tsserver = 1
endif
let l:conn = s:connections[a:conn_id]
let l:conn.is_tsserver = 1
let l:conn.initialized = 1
" Set capabilities which are supported by tsserver.
let l:conn.capabilities.hover = 1
let l:conn.capabilities.references = 1
let l:conn.capabilities.completion = 1
let l:conn.capabilities.completion_trigger_characters = ['.']
let l:conn.capabilities.definition = 1
let l:conn.capabilities.symbol_search = 1
endfunction
" Register a project for an LSP connection.
" Start a program for LSP servers.
"
" This function will throw if the connection doesn't exist.
function! ale#lsp#RegisterProject(conn_id, project_root) abort
let l:conn = s:FindConnection('id', a:conn_id)
" 1 will be returned if the program is running, or 0 if the program could
" not be started.
function! ale#lsp#StartProgram(conn_id, executable, command) abort
let l:conn = s:connections[a:conn_id]
" Empty strings can't be used for Dictionary keys in NeoVim, due to E713.
" This appears to be a nonsensical bug in NeoVim.
let l:key = empty(a:project_root) ? '<<EMPTY>>' : a:project_root
if !has_key(l:conn.projects, l:key)
" Tools without project roots are ready right away, like tsserver.
let l:conn.projects[l:key] = {
\ 'root': a:project_root,
\ 'initialized': empty(a:project_root),
\ 'init_request_id': 0,
\ 'message_queue': [],
\ 'capabilities_queue': [],
\}
endif
endfunction
function! ale#lsp#GetProject(conn, project_root) abort
if empty(a:conn)
return {}
endif
let l:key = empty(a:project_root) ? '<<EMPTY>>' : a:project_root
return get(a:conn.projects, l:key, {})
endfunction
" Start a program for LSP servers which run with executables.
"
" The job ID will be returned for for the program if it ran, otherwise
" 0 will be returned.
function! ale#lsp#StartProgram(executable, command, init_options) abort
if !executable(a:executable)
return 0
endif
let l:conn = s:FindConnection('executable', a:executable)
" Get the current connection or a new one.
let l:conn = !empty(l:conn) ? l:conn : ale#lsp#NewConnection(a:init_options)
let l:conn.executable = a:executable
if !has_key(l:conn, 'id') || !ale#job#IsRunning(l:conn.id)
if !has_key(l:conn, 'job_id') || !ale#job#IsRunning(l:conn.job_id)
let l:options = {
\ 'mode': 'raw',
\ 'out_cb': function('s:HandleCommandMessage'),
\ 'out_cb': {_, message -> ale#lsp#HandleMessage(a:conn_id, message)},
\}
let l:job_id = ale#job#Start(a:command, l:options)
else
let l:job_id = l:conn.id
let l:job_id = l:conn.job_id
endif
if l:job_id <= 0
return 0
if l:job_id > 0
let l:conn.job_id = l:job_id
endif
let l:conn.id = l:job_id
return l:job_id
return l:job_id > 0
endfunction
" Connect to an address and set up a callback for handling responses.
function! ale#lsp#ConnectToAddress(address, init_options) abort
let l:conn = s:FindConnection('id', a:address)
" Get the current connection or a new one.
let l:conn = !empty(l:conn) ? l:conn : ale#lsp#NewConnection(a:init_options)
" Connect to an LSP server via TCP.
"
" 1 will be returned if the connection is running, or 0 if the connection could
" not be opened.
function! ale#lsp#ConnectToAddress(conn_id, address) abort
let l:conn = s:connections[a:conn_id]
if !has_key(l:conn, 'channel_id') || !ale#socket#IsOpen(l:conn.channel_id)
let l:conn.channel_id = ale#socket#Open(a:address, {
\ 'callback': function('s:HandleChannelMessage'),
let l:channel_id = ale#socket#Open(a:address, {
\ 'callback': {_, mess -> ale#lsp#HandleMessage(a:conn_id, mess)},
\})
else
let l:channel_id = l:conn.channel_id
endif
if l:conn.channel_id < 0
return ''
if l:channel_id >= 0
let l:conn.channel_id = l:channel_id
endif
let l:conn.id = a:address
return a:address
return l:channel_id >= 0
endfunction
" Given a connection ID and a callback, register that callback for handling
" messages if the connection exists.
function! ale#lsp#RegisterCallback(conn_id, callback) abort
let l:conn = s:FindConnection('id', a:conn_id)
let l:conn = get(s:connections, a:conn_id, {})
if !empty(l:conn)
" Add the callback to the List if it's not there already.
@@ -421,23 +371,33 @@ function! ale#lsp#RegisterCallback(conn_id, callback) abort
endif
endfunction
" Stop a single LSP connection.
function! ale#lsp#Stop(conn_id) abort
if has_key(s:connections, a:conn_id)
let l:conn = remove(s:connections, a:conn_id)
if has_key(l:conn, 'channel_id')
call ale#socket#Close(l:conn.channel_id)
elseif has_key(l:conn, 'job_id')
call ale#job#Stop(l:conn.job_id)
endif
endif
endfunction
function! ale#lsp#CloseDocument(conn_id) abort
endfunction
" Stop all LSP connections, closing all jobs and channels, and removing any
" queued messages.
function! ale#lsp#StopAll() abort
for l:conn in s:connections
if has_key(l:conn, 'channel_id')
call ale#socket#Close(l:conn.channel_id)
else
call ale#job#Stop(l:conn.id)
endif
for l:conn_id in keys(s:connections)
call ale#lsp#Stop(l:conn_id)
endfor
let s:connections = []
endfunction
function! s:SendMessageData(conn, data) abort
if has_key(a:conn, 'executable')
call ale#job#SendRaw(a:conn.id, a:data)
if has_key(a:conn, 'job_id')
call ale#job#SendRaw(a:conn.job_id, a:data)
elseif has_key(a:conn, 'channel_id') && ale#socket#IsOpen(a:conn.channel_id)
" Send the message to the server
call ale#socket#Send(a:conn.channel_id, a:data)
@@ -454,38 +414,32 @@ endfunction
" Returns -1 when a message is sent, but no response is expected
" 0 when the message is not sent and
" >= 1 with the message ID when a response is expected.
function! ale#lsp#Send(conn_id, message, ...) abort
let l:project_root = get(a:000, 0, '')
function! ale#lsp#Send(conn_id, message) abort
let l:conn = get(s:connections, a:conn_id, {})
let l:conn = s:FindConnection('id', a:conn_id)
let l:project = ale#lsp#GetProject(l:conn, l:project_root)
if empty(l:project)
if empty(l:conn)
return 0
endif
" If we haven't initialized the server yet, then send the message for it.
if !l:project.initialized
" Only send the init message once.
if !l:project.init_request_id
let [l:init_id, l:init_data] = ale#lsp#CreateMessageData(
\ ale#lsp#message#Initialize(l:project_root, l:conn.initialization_options),
\)
if !l:conn.initialized && !l:conn.init_request_id
let [l:init_id, l:init_data] = ale#lsp#CreateMessageData(
\ ale#lsp#message#Initialize(l:conn.root, l:conn.init_options),
\)
let l:project.init_request_id = l:init_id
let l:conn.init_request_id = l:init_id
call s:SendMessageData(l:conn, l:init_data)
endif
call s:SendMessageData(l:conn, l:init_data)
endif
let [l:id, l:data] = ale#lsp#CreateMessageData(a:message)
if l:project.initialized
if l:conn.initialized
" Send the message now.
call s:SendMessageData(l:conn, l:data)
else
" Add the message we wanted to send to a List to send later.
call add(l:project.message_queue, l:data)
call add(l:conn.message_queue, l:data)
endif
return l:id == 0 ? -1 : l:id
@@ -493,11 +447,10 @@ endfunction
" Notify LSP servers or tsserver if a document is opened, if needed.
" If a document is opened, 1 will be returned, otherwise 0 will be returned.
function! ale#lsp#OpenDocument(conn_id, project_root, buffer, language_id) abort
let l:conn = s:FindConnection('id', a:conn_id)
function! ale#lsp#OpenDocument(conn_id, buffer, language_id) abort
let l:conn = get(s:connections, a:conn_id, {})
let l:opened = 0
" FIXME: Return 1 if the document is already open?
if !empty(l:conn) && !has_key(l:conn.open_documents, a:buffer)
if l:conn.is_tsserver
let l:message = ale#lsp#tsserver_message#Open(a:buffer)
@@ -505,7 +458,7 @@ function! ale#lsp#OpenDocument(conn_id, project_root, buffer, language_id) abort
let l:message = ale#lsp#message#DidOpen(a:buffer, a:language_id)
endif
call ale#lsp#Send(a:conn_id, l:message, a:project_root)
call ale#lsp#Send(a:conn_id, l:message)
let l:conn.open_documents[a:buffer] = getbufvar(a:buffer, 'changedtick')
let l:opened = 1
endif
@@ -515,8 +468,8 @@ endfunction
" Notify LSP servers or tsserver that a document has changed, if needed.
" If a notification is sent, 1 will be returned, otherwise 0 will be returned.
function! ale#lsp#NotifyForChanges(conn_id, project_root, buffer) abort
let l:conn = s:FindConnection('id', a:conn_id)
function! ale#lsp#NotifyForChanges(conn_id, buffer) abort
let l:conn = get(s:connections, a:conn_id, {})
let l:notified = 0
if !empty(l:conn) && has_key(l:conn.open_documents, a:buffer)
@@ -529,7 +482,7 @@ function! ale#lsp#NotifyForChanges(conn_id, project_root, buffer) abort
let l:message = ale#lsp#message#DidChange(a:buffer)
endif
call ale#lsp#Send(a:conn_id, l:message, a:project_root)
call ale#lsp#Send(a:conn_id, l:message)
let l:conn.open_documents[a:buffer] = l:new_tick
let l:notified = 1
endif
@@ -540,25 +493,24 @@ endfunction
" Given some LSP details that must contain at least `connection_id` and
" `project_root` keys,
function! ale#lsp#WaitForCapability(conn_id, project_root, capability, callback) abort
let l:conn = s:FindConnection('id', a:conn_id)
let l:project = ale#lsp#GetProject(l:conn, a:project_root)
function! ale#lsp#WaitForCapability(conn_id, capability, callback) abort
let l:conn = get(s:connections, a:conn_id, {})
if empty(l:project)
return 0
if empty(l:conn)
return
endif
if type(get(l:conn.capabilities, a:capability, v:null)) isnot type(0)
if type(get(l:conn.capabilities, a:capability, v:null)) isnot v:t_number
throw 'Invalid capability ' . a:capability
endif
if l:project.initialized
if l:conn.is_tsserver || l:conn.capabilities[a:capability]
if l:conn.initialized
if l:conn.capabilities[a:capability]
" The project has been initialized, so call the callback now.
call call(a:callback, [a:conn_id, a:project_root])
call call(a:callback, [a:conn_id])
endif
else
" Call the callback later, once we have the information we need.
call add(l:project.capabilities_queue, [a:capability, a:callback])
call add(l:conn.capabilities_queue, [a:capability, a:callback])
endif
endfunction

View File

@@ -130,6 +130,12 @@ function! ale#lsp#message#References(buffer, line, column) abort
\}]
endfunction
function! ale#lsp#message#Symbol(query) abort
return [0, 'workspace/symbol', {
\ 'query': a:query,
\}]
endfunction
function! ale#lsp#message#Hover(buffer, line, column) abort
return [0, 'textDocument/hover', {
\ 'textDocument': {
@@ -138,3 +144,9 @@ function! ale#lsp#message#Hover(buffer, line, column) abort
\ 'position': {'line': a:line - 1, 'character': a:column},
\}]
endfunction
function! ale#lsp#message#DidChangeConfiguration(buffer, config) abort
return [0, 'workspace/didChangeConfiguration', {
\ 'settings': a:config,
\}]
endfunction

View File

@@ -17,7 +17,7 @@ function! ale#lsp#reset#StopAllLSPs() abort
for l:linter in ale#linter#Get(getbufvar(l:buffer, '&filetype'))
if !empty(l:linter.lsp)
call ale#engine#HandleLoclist(l:linter.name, l:buffer, [])
call ale#engine#HandleLoclist(l:linter.name, l:buffer, [], 0)
endif
endfor
endfor

View File

@@ -47,7 +47,23 @@ function! ale#lsp#response#ReadDiagnostics(response) abort
endif
if has_key(l:diagnostic, 'code')
let l:loclist_item.nr = l:diagnostic.code
if type(l:diagnostic.code) == v:t_string
let l:loclist_item.code = l:diagnostic.code
elseif type(l:diagnostic.code) == v:t_number && l:diagnostic.code != -1
let l:loclist_item.code = string(l:diagnostic.code)
let l:loclist_item.nr = l:diagnostic.code
endif
endif
if has_key(l:diagnostic, 'relatedInformation')
let l:related = deepcopy(l:diagnostic.relatedInformation)
call map(l:related, {key, val ->
\ ale#path#FromURI(val.location.uri) .
\ ':' . (val.location.range.start.line + 1) .
\ ':' . (val.location.range.start.character + 1) .
\ ":\n\t" . val.message
\ })
let l:loclist_item.detail = l:diagnostic.message . "\n" . join(l:related, "\n")
endif
if has_key(l:diagnostic, 'source')
@@ -74,7 +90,12 @@ function! ale#lsp#response#ReadTSServerDiagnostics(response) abort
\}
if has_key(l:diagnostic, 'code')
let l:loclist_item.nr = l:diagnostic.code
if type(l:diagnostic.code) == v:t_string
let l:loclist_item.code = l:diagnostic.code
elseif type(l:diagnostic.code) == v:t_number && l:diagnostic.code != -1
let l:loclist_item.code = string(l:diagnostic.code)
let l:loclist_item.nr = l:diagnostic.code
endif
endif
if get(l:diagnostic, 'category') is# 'warning'
@@ -92,7 +113,7 @@ function! ale#lsp#response#ReadTSServerDiagnostics(response) abort
endfunction
function! ale#lsp#response#GetErrorMessage(response) abort
if type(get(a:response, 'error', 0)) isnot type({})
if type(get(a:response, 'error', 0)) isnot v:t_dict
return ''
endif
@@ -112,12 +133,12 @@ function! ale#lsp#response#GetErrorMessage(response) abort
" Include the traceback or error data as details, if present.
let l:error_data = get(a:response.error, 'data', {})
if type(l:error_data) is type('')
if type(l:error_data) is v:t_string
let l:message .= "\n" . l:error_data
else
elseif type(l:error_data) is v:t_dict
let l:traceback = get(l:error_data, 'traceback', [])
if type(l:traceback) is type([]) && !empty(l:traceback)
if type(l:traceback) is v:t_list && !empty(l:traceback)
let l:message .= "\n" . join(l:traceback, "\n")
endif
endif

View File

@@ -38,7 +38,7 @@ function! s:HandleLSPDiagnostics(conn_id, response) abort
let l:loclist = ale#lsp#response#ReadDiagnostics(a:response)
call ale#engine#HandleLoclist(l:linter_name, l:buffer, l:loclist)
call ale#engine#HandleLoclist(l:linter_name, l:buffer, l:loclist, 0)
endfunction
function! s:HandleTSServerDiagnostics(response, error_type) abort
@@ -55,20 +55,33 @@ function! s:HandleTSServerDiagnostics(response, error_type) abort
endif
let l:thislist = ale#lsp#response#ReadTSServerDiagnostics(a:response)
let l:no_changes = 0
" tsserver sends syntax and semantic errors in separate messages, so we
" have to collect the messages separately for each buffer and join them
" back together again.
if a:error_type is# 'syntax'
if len(l:thislist) is 0 && len(get(l:info, 'syntax_loclist', [])) is 0
let l:no_changes = 1
endif
let l:info.syntax_loclist = l:thislist
else
if len(l:thislist) is 0 && len(get(l:info, 'semantic_loclist', [])) is 0
let l:no_changes = 1
endif
let l:info.semantic_loclist = l:thislist
endif
if l:no_changes
return
endif
let l:loclist = get(l:info, 'semantic_loclist', [])
\ + get(l:info, 'syntax_loclist', [])
call ale#engine#HandleLoclist(l:linter_name, l:buffer, l:loclist)
call ale#engine#HandleLoclist(l:linter_name, l:buffer, l:loclist, 0)
endfunction
function! s:HandleLSPErrorMessage(linter_name, response) abort
@@ -99,9 +112,10 @@ endfunction
function! ale#lsp_linter#HandleLSPResponse(conn_id, response) abort
let l:method = get(a:response, 'method', '')
let l:linter_name = get(s:lsp_linter_map, a:conn_id, '')
if get(a:response, 'jsonrpc', '') is# '2.0' && has_key(a:response, 'error')
let l:linter_name = get(s:lsp_linter_map, a:conn_id, '')
call s:HandleLSPErrorMessage(l:linter_name, a:response)
elseif l:method is# 'textDocument/publishDiagnostics'
call s:HandleLSPDiagnostics(a:conn_id, a:response)
@@ -126,6 +140,18 @@ function! ale#lsp_linter#GetOptions(buffer, linter) abort
return l:initialization_options
endfunction
function! ale#lsp_linter#GetConfig(buffer, linter) abort
let l:config = {}
if has_key(a:linter, 'lsp_config_callback')
let l:config = ale#util#GetFunction(a:linter.lsp_config_callback)(a:buffer)
elseif has_key(a:linter, 'lsp_config')
let l:config = a:linter.lsp_config
endif
return l:config
endfunction
" Given a buffer, an LSP linter, start up an LSP linter and get ready to
" receive messages for the document.
function! ale#lsp_linter#StartLSP(buffer, linter) abort
@@ -143,7 +169,8 @@ function! ale#lsp_linter#StartLSP(buffer, linter) abort
if a:linter.lsp is# 'socket'
let l:address = ale#linter#GetAddress(a:buffer, a:linter)
let l:conn_id = ale#lsp#ConnectToAddress(l:address, l:init_options)
let l:conn_id = ale#lsp#Register(l:address, l:root, l:init_options)
let l:ready = ale#lsp#ConnectToAddress(l:conn_id, l:address)
else
let l:executable = ale#linter#GetExecutable(a:buffer, a:linter)
@@ -151,18 +178,16 @@ function! ale#lsp_linter#StartLSP(buffer, linter) abort
return {}
endif
let l:conn_id = ale#lsp#Register(l:executable, l:root, l:init_options)
let l:command = ale#linter#GetCommand(a:buffer, a:linter)
" Format the command, so %e can be formatted into it.
let l:command = ale#command#FormatCommand(a:buffer, l:executable, l:command, 0)[1]
let l:command = ale#job#PrepareCommand(a:buffer, l:command)
let l:conn_id = ale#lsp#StartProgram(
\ l:executable,
\ l:command,
\ l:init_options,
\)
let l:ready = ale#lsp#StartProgram(l:conn_id, l:executable, l:command)
endif
if empty(l:conn_id)
if !l:ready
if g:ale_history_enabled && !empty(l:command)
call ale#history#Add(a:buffer, 'failed', l:conn_id, l:command)
endif
@@ -175,9 +200,7 @@ function! ale#lsp_linter#StartLSP(buffer, linter) abort
call ale#lsp#MarkConnectionAsTsserver(l:conn_id)
endif
" Register the project now the connection is ready.
call ale#lsp#RegisterProject(l:conn_id, l:root)
let l:config = ale#lsp_linter#GetConfig(a:buffer, a:linter)
let l:language_id = ale#util#GetFunction(a:linter.language_callback)(a:buffer)
let l:details = {
@@ -188,7 +211,9 @@ function! ale#lsp_linter#StartLSP(buffer, linter) abort
\ 'language_id': l:language_id,
\}
if ale#lsp#OpenDocument(l:conn_id, l:root, a:buffer, l:language_id)
call ale#lsp#UpdateConfig(l:conn_id, a:buffer, l:config)
if ale#lsp#OpenDocument(l:conn_id, a:buffer, l:language_id)
if g:ale_history_enabled && !empty(l:command)
call ale#history#Add(a:buffer, 'started', l:conn_id, l:command)
endif
@@ -196,7 +221,7 @@ function! ale#lsp_linter#StartLSP(buffer, linter) abort
" The change message needs to be sent for tsserver before doing anything.
if a:linter.lsp is# 'tsserver'
call ale#lsp#NotifyForChanges(l:conn_id, l:root, a:buffer)
call ale#lsp#NotifyForChanges(l:conn_id, a:buffer)
endif
return l:details
@@ -211,7 +236,6 @@ function! ale#lsp_linter#CheckWithLSP(buffer, linter) abort
endif
let l:id = l:lsp_details.connection_id
let l:root = l:lsp_details.project_root
" Register a callback now for handling errors now.
let l:Callback = function('ale#lsp_linter#HandleLSPResponse')
@@ -222,16 +246,16 @@ function! ale#lsp_linter#CheckWithLSP(buffer, linter) abort
if a:linter.lsp is# 'tsserver'
let l:message = ale#lsp#tsserver_message#Geterr(a:buffer)
let l:notified = ale#lsp#Send(l:id, l:message, l:root) != 0
let l:notified = ale#lsp#Send(l:id, l:message) != 0
else
let l:notified = ale#lsp#NotifyForChanges(l:id, l:root, a:buffer)
let l:notified = ale#lsp#NotifyForChanges(l:id, a:buffer)
endif
" If this was a file save event, also notify the server of that.
if a:linter.lsp isnot# 'tsserver'
\&& getbufvar(a:buffer, 'ale_save_event_fired', 0)
let l:save_message = ale#lsp#message#DidSave(a:buffer)
let l:notified = ale#lsp#Send(l:id, l:save_message, l:root) != 0
let l:notified = ale#lsp#Send(l:id, l:save_message) != 0
endif
if l:notified

View File

@@ -23,6 +23,11 @@ function! ale#node#FindExecutable(buffer, base_var_name, path_list) abort
return ale#Var(a:buffer, a:base_var_name . '_executable')
endfunction
" As above, but curry the arguments so only the buffer number is required.
function! ale#node#FindExecutableFunc(base_var_name, path_list) abort
return {buf -> ale#node#FindExecutable(buf, a:base_var_name, a:path_list)}
endfunction
" Create a executable string which executes a Node.js script command with a
" Node.js executable if needed.
"

View File

@@ -0,0 +1,21 @@
" Tell ALE that another source has started checking a buffer.
function! ale#other_source#StartChecking(buffer, linter_name) abort
call ale#engine#InitBufferInfo(a:buffer)
let l:list = g:ale_buffer_info[a:buffer].active_other_sources_list
call add(l:list, a:linter_name)
call uniq(sort(l:list))
endfunction
" Show some results, and stop checking a buffer.
" To clear results or cancel checking a buffer, an empty List can be given.
function! ale#other_source#ShowResults(buffer, linter_name, loclist) abort
call ale#engine#InitBufferInfo(a:buffer)
let l:info = g:ale_buffer_info[a:buffer]
" Remove this linter name from the active list.
let l:list = l:info.active_other_sources_list
call filter(l:list, 'v:val isnot# a:linter_name')
call ale#engine#HandleLoclist(a:linter_name, a:buffer, a:loclist, 1)
endfunction

View File

@@ -65,7 +65,11 @@ endfunction
" Output 'cd <directory> && '
" This function can be used changing the directory for a linter command.
function! ale#path#CdString(directory) abort
return 'cd ' . ale#Escape(a:directory) . ' && '
if has('win32')
return 'cd /d ' . ale#Escape(a:directory) . ' && '
else
return 'cd ' . ale#Escape(a:directory) . ' && '
endif
endfunction
" Output 'cd <buffer_filename_directory> && '
@@ -105,6 +109,21 @@ function! ale#path#GetAbsPath(base_directory, filename) abort
return ale#path#Simplify(a:base_directory . l:sep . a:filename)
endfunction
" Given a path, return the directory name for that path, with no trailing
" slashes. If the argument is empty(), return an empty string.
function! ale#path#Dirname(path) abort
if empty(a:path)
return ''
endif
" For /foo/bar/ we need :h:h to get /foo
if a:path[-1:] is# '/'
return fnamemodify(a:path, ':h:h')
endif
return fnamemodify(a:path, ':h')
endfunction
" Given a buffer number and a relative or absolute path, return 1 if the
" two paths represent the same file on disk.
function! ale#path#IsBufferPath(buffer, complex_filename) abort

View File

@@ -15,13 +15,13 @@ function! ale#preview#Show(lines, ...) abort
setlocal modifiable
setlocal noreadonly
setlocal nobuflisted
let &l:filetype = get(l:options, 'filetype', 'ale-preview')
setlocal buftype=nofile
setlocal bufhidden=wipe
:%d
call setline(1, a:lines)
setlocal nomodifiable
setlocal readonly
let &l:filetype = get(l:options, 'filetype', 'ale-preview')
if get(l:options, 'stay_here')
wincmd p
@@ -46,11 +46,14 @@ function! ale#preview#ShowSelection(item_list) abort
" Create lines to display to users.
for l:item in a:item_list
let l:match = get(l:item, 'match', '')
call add(
\ l:lines,
\ l:item.filename
\ . ':' . l:item.line
\ . ':' . l:item.column,
\ . ':' . l:item.column
\ . (!empty(l:match) ? ' ' . l:match : ''),
\)
endfor

View File

@@ -1,6 +1,8 @@
" Author: w0rp <devw0rp@gmail.com>
" Description: Functions for integrating with Python linters.
call ale#Set('python_auto_pipenv', '0')
let s:sep = has('win32') ? '\' : '/'
" bin is used for Unix virtualenv directories, and Scripts is for Windows.
let s:bin_dir = has('unix') ? 'bin' : 'Scripts'
@@ -24,6 +26,7 @@ function! ale#python#FindProjectRootIni(buffer) abort
\|| filereadable(l:path . '/mypy.ini')
\|| filereadable(l:path . '/pycodestyle.cfg')
\|| filereadable(l:path . '/flake8.cfg')
\|| filereadable(l:path . '/.flake8rc')
\|| filereadable(l:path . '/Pipfile')
\|| filereadable(l:path . '/Pipfile.lock')
return l:path
@@ -106,3 +109,8 @@ function! ale#python#FindExecutable(buffer, base_var_name, path_list) abort
return ale#Var(a:buffer, a:base_var_name . '_executable')
endfunction
" Detects whether a pipenv environment is present.
function! ale#python#PipenvPresent(buffer) abort
return findfile('Pipfile.lock', expand('#' . a:buffer . ':p:h') . ';') isnot# ''
endfunction

View File

@@ -64,6 +64,35 @@ function! ale#references#HandleLSPResponse(conn_id, response) abort
endif
endfunction
function! s:OnReady(linter, lsp_details, line, column, ...) abort
let l:buffer = a:lsp_details.buffer
let l:id = a:lsp_details.connection_id
let l:Callback = a:linter.lsp is# 'tsserver'
\ ? function('ale#references#HandleTSServerResponse')
\ : function('ale#references#HandleLSPResponse')
call ale#lsp#RegisterCallback(l:id, l:Callback)
if a:linter.lsp is# 'tsserver'
let l:message = ale#lsp#tsserver_message#References(
\ l:buffer,
\ a:line,
\ a:column
\)
else
" Send a message saying the buffer has changed first, or the
" references position probably won't make sense.
call ale#lsp#NotifyForChanges(l:id, l:buffer)
let l:message = ale#lsp#message#References(l:buffer, a:line, a:column)
endif
let l:request_id = ale#lsp#Send(l:id, l:message)
let s:references_map[l:request_id] = {}
endfunction
function! s:FindReferences(linter) abort
let l:buffer = bufnr('')
let [l:line, l:column] = getcurpos()[1:2]
@@ -79,35 +108,10 @@ function! s:FindReferences(linter) abort
endif
let l:id = l:lsp_details.connection_id
let l:root = l:lsp_details.project_root
function! OnReady(...) abort closure
let l:Callback = a:linter.lsp is# 'tsserver'
\ ? function('ale#references#HandleTSServerResponse')
\ : function('ale#references#HandleLSPResponse')
call ale#lsp#RegisterCallback(l:id, l:Callback)
if a:linter.lsp is# 'tsserver'
let l:message = ale#lsp#tsserver_message#References(
\ l:buffer,
\ l:line,
\ l:column
\)
else
" Send a message saying the buffer has changed first, or the
" references position probably won't make sense.
call ale#lsp#NotifyForChanges(l:id, l:root, l:buffer)
let l:message = ale#lsp#message#References(l:buffer, l:line, l:column)
endif
let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root)
let s:references_map[l:request_id] = {}
endfunction
call ale#lsp#WaitForCapability(l:id, l:root, 'references', function('OnReady'))
call ale#lsp#WaitForCapability(l:id, 'references', function('s:OnReady', [
\ a:linter, l:lsp_details, l:line, l:column
\]))
endfunction
function! ale#references#Find() abort

View File

@@ -20,3 +20,25 @@ function! ale#ruby#FindRailsRoot(buffer) abort
return ''
endfunction
" Find the nearest dir containing a potential ruby project.
function! ale#ruby#FindProjectRoot(buffer) abort
let l:dir = ale#ruby#FindRailsRoot(a:buffer)
if isdirectory(l:dir)
return l:dir
endif
for l:name in ['.solargraph.yml', 'Rakefile', 'Gemfile']
let l:dir = fnamemodify(
\ ale#path#FindNearestFile(a:buffer, l:name),
\ ':h'
\)
if l:dir isnot# '.' && isdirectory(l:dir)
return l:dir
endif
endfor
return ''
endfunction

View File

@@ -211,7 +211,7 @@ function! s:BuildSignMap(buffer, current_sign_list, grouped_items) abort
if l:max_signs is 0
let l:selected_grouped_items = []
elseif type(l:max_signs) is type(0) && l:max_signs > 0
elseif type(l:max_signs) is v:t_number && l:max_signs > 0
let l:selected_grouped_items = a:grouped_items[:l:max_signs - 1]
else
let l:selected_grouped_items = a:grouped_items

View File

@@ -55,11 +55,18 @@ function! ale#socket#Open(address, options) abort
if !has('nvim')
" Vim
let l:channel_info.channel = ch_open(a:address, {
let l:channel_options = {
\ 'mode': l:mode,
\ 'waittime': 0,
\ 'callback': function('s:VimOutputCallback'),
\})
\}
" Use non-blocking writes for Vim versions that support the option.
if has('patch-8.1.350')
let l:channel_options.noblock = 1
endif
let l:channel_info.channel = ch_open(a:address, l:channel_options)
let l:vim_info = ch_info(l:channel_info.channel)
let l:channel_id = !empty(l:vim_info) ? l:vim_info.id : -1
elseif exists('*chansend') && exists('*sockconnect')
@@ -104,6 +111,7 @@ function! ale#socket#IsOpen(channel_id) abort
endif
let l:channel = s:channel_map[a:channel_id].channel
return ch_status(l:channel) is# 'open'
endfunction

109
autoload/ale/symbol.vim Normal file
View File

@@ -0,0 +1,109 @@
let s:symbol_map = {}
" Used to get the symbol map in tests.
function! ale#symbol#GetMap() abort
return deepcopy(s:symbol_map)
endfunction
" Used to set the symbol map in tests.
function! ale#symbol#SetMap(map) abort
let s:symbol_map = a:map
endfunction
function! ale#symbol#ClearLSPData() abort
let s:symbol_map = {}
endfunction
function! ale#symbol#HandleLSPResponse(conn_id, response) abort
if has_key(a:response, 'id')
\&& has_key(s:symbol_map, a:response.id)
let l:options = remove(s:symbol_map, a:response.id)
let l:result = get(a:response, 'result', v:null)
let l:item_list = []
if type(l:result) is v:t_list
" Each item looks like this:
" {
" 'name': 'foo',
" 'kind': 123,
" 'deprecated': v:false,
" 'location': {
" 'uri': 'file://...',
" 'range': {
" 'start': {'line': 0, 'character': 0},
" 'end': {'line': 0, 'character': 0},
" },
" },
" 'containerName': 'SomeContainer',
" }
for l:response_item in l:result
let l:location = l:response_item.location
call add(l:item_list, {
\ 'filename': ale#path#FromURI(l:location.uri),
\ 'line': l:location.range.start.line + 1,
\ 'column': l:location.range.start.character + 1,
\ 'match': l:response_item.name,
\})
endfor
endif
if empty(l:item_list)
call ale#util#Execute('echom ''No symbols found.''')
else
call ale#preview#ShowSelection(l:item_list)
endif
endif
endfunction
function! s:OnReady(linter, lsp_details, query, ...) abort
let l:buffer = a:lsp_details.buffer
" If we already made a request, stop here.
if getbufvar(l:buffer, 'ale_symbol_request_made', 0)
return
endif
let l:id = a:lsp_details.connection_id
let l:Callback = function('ale#symbol#HandleLSPResponse')
call ale#lsp#RegisterCallback(l:id, l:Callback)
let l:message = ale#lsp#message#Symbol(a:query)
let l:request_id = ale#lsp#Send(l:id, l:message)
call setbufvar(l:buffer, 'ale_symbol_request_made', 1)
let s:symbol_map[l:request_id] = {
\ 'buffer': l:buffer,
\}
endfunction
function! s:Search(linter, buffer, query) abort
let l:lsp_details = ale#lsp_linter#StartLSP(a:buffer, a:linter)
if !empty(l:lsp_details)
call ale#lsp#WaitForCapability(
\ l:lsp_details.connection_id,
\ 'symbol_search',
\ function('s:OnReady', [a:linter, l:lsp_details, a:query]),
\)
endif
endfunction
function! ale#symbol#Search(query) abort
if type(a:query) isnot v:t_string || empty(a:query)
throw 'A non-empty string must be provided!'
endif
let l:buffer = bufnr('')
" Set a flag so we only make one request.
call setbufvar(l:buffer, 'ale_symbol_request_made', 0)
for l:linter in ale#linter#Get(getbufvar(l:buffer, '&filetype'))
if !empty(l:linter.lsp) && l:linter.lsp isnot# 'tsserver'
call s:Search(l:linter, l:buffer, a:query)
endif
endfor
endfunction

View File

@@ -15,21 +15,6 @@ function! s:DisablePostamble() abort
endif
endfunction
function! s:CleanupEveryBuffer() abort
for l:key in keys(g:ale_buffer_info)
" The key could be a filename or a buffer number, so try and
" convert it to a number. We need a number for the other
" functions.
let l:buffer = str2nr(l:key)
if l:buffer > 0
" Stop all jobs and clear the results for everything, and delete
" all of the data we stored for the buffer.
call ale#engine#Cleanup(l:buffer)
endif
endfor
endfunction
function! ale#toggle#Toggle() abort
let g:ale_enabled = !get(g:, 'ale_enabled')
@@ -40,7 +25,7 @@ function! ale#toggle#Toggle() abort
call ale#balloon#Enable()
endif
else
call s:CleanupEveryBuffer()
call ale#engine#CleanupEveryBuffer()
call s:DisablePostamble()
if exists('*ale#balloon#Disable')
@@ -64,7 +49,7 @@ function! ale#toggle#Disable() abort
endfunction
function! ale#toggle#Reset() abort
call s:CleanupEveryBuffer()
call ale#engine#CleanupEveryBuffer()
call ale#highlight#UpdateHighlights()
endfunction
@@ -76,6 +61,7 @@ function! ale#toggle#ToggleBuffer(buffer) abort
" linting locally when linting is disabled globally
if l:enabled && !g:ale_enabled
execute 'echom ''ALE cannot be enabled locally when disabled globally'''
return
endif

View File

@@ -54,6 +54,7 @@ endif
function! ale#util#JoinNeovimOutput(job, last_line, data, mode, callback) abort
if a:mode is# 'raw'
call a:callback(a:job, join(a:data, "\n"))
return ''
endif
@@ -79,7 +80,7 @@ function! ale#util#GetLineCount(buffer) abort
endfunction
function! ale#util#GetFunction(string_or_ref) abort
if type(a:string_or_ref) == type('')
if type(a:string_or_ref) is v:t_string
return function(a:string_or_ref)
endif
@@ -88,12 +89,12 @@ endfunction
function! ale#util#Open(filename, line, column, options) abort
if get(a:options, 'open_in_tab', 0)
call ale#util#Execute('tabedit ' . fnameescape(a:filename))
else
call ale#util#Execute('tabedit +' . a:line . ' ' . fnameescape(a:filename))
elseif bufnr(a:filename) isnot bufnr('')
" Open another file only if we need to.
if bufnr(a:filename) isnot bufnr('')
call ale#util#Execute('edit ' . fnameescape(a:filename))
endif
call ale#util#Execute('edit +' . a:line . ' ' . fnameescape(a:filename))
else
normal! m`
endif
call cursor(a:line, a:column)
@@ -268,7 +269,7 @@ endfunction
" See :help sandbox
function! ale#util#InSandbox() abort
try
let &equalprg=&equalprg
let &l:equalprg=&l:equalprg
catch /E48/
" E48 is the sandbox error.
return 1
@@ -303,8 +304,8 @@ endfunction
" Only the first pattern which matches a line will be returned.
function! ale#util#GetMatches(lines, patterns) abort
let l:matches = []
let l:lines = type(a:lines) == type([]) ? a:lines : [a:lines]
let l:patterns = type(a:patterns) == type([]) ? a:patterns : [a:patterns]
let l:lines = type(a:lines) is v:t_list ? a:lines : [a:lines]
let l:patterns = type(a:patterns) is v:t_list ? a:patterns : [a:patterns]
for l:line in l:lines
for l:pattern in l:patterns
@@ -382,7 +383,7 @@ function! ale#util#FuzzyJSONDecode(data, default) abort
return a:default
endif
let l:str = type(a:data) == type('') ? a:data : join(a:data, '')
let l:str = type(a:data) is v:t_string ? a:data : join(a:data, '')
try
let l:result = json_decode(l:str)
@@ -404,7 +405,7 @@ endfunction
" the buffer.
function! ale#util#Writefile(buffer, lines, filename) abort
let l:corrected_lines = getbufvar(a:buffer, '&fileformat') is# 'dos'
\ ? map(copy(a:lines), 'v:val . "\r"')
\ ? map(copy(a:lines), 'substitute(v:val, ''\r*$'', ''\r'', '''')')
\ : a:lines
call writefile(l:corrected_lines, a:filename) " no-custom-checks
@@ -451,3 +452,14 @@ function! ale#util#Col(str, chr) abort
return strlen(join(split(a:str, '\zs')[0:a:chr - 2], '')) + 1
endfunction
function! ale#util#FindItemAtCursor(buffer) abort
let l:info = get(g:ale_buffer_info, a:buffer, {})
let l:loclist = get(l:info, 'loclist', [])
let l:pos = getcurpos()
let l:index = ale#util#BinarySearch(l:loclist, a:buffer, l:pos[1], l:pos[2])
let l:loc = l:index >= 0 ? l:loclist[l:index] : {}
return [l:info, l:loc]
endfunction

View File

@@ -0,0 +1,136 @@
scriptencoding utf-8
" Author: w0rp <devw0rp@gmail.com>
" Author: Luan Santos <cfcluan@gmail.com>
" Description: Shows lint message for the current line as virtualtext, if any
" Controls the milliseconds delay before showing a message.
let g:ale_virtualtext_delay = get(g:, 'ale_virtualtext_delay', 10)
let s:cursor_timer = -1
let s:last_pos = [0, 0, 0]
if has('nvim-0.3.2')
let s:ns_id = nvim_create_namespace('ale')
endif
if !hlexists('ALEVirtualTextError')
highlight link ALEVirtualTextError ALEError
endif
if !hlexists('ALEVirtualTextStyleError')
highlight link ALEVirtualTextStyleError ALEVirtualTextError
endif
if !hlexists('ALEVirtualTextWarning')
highlight link ALEVirtualTextWarning ALEWarning
endif
if !hlexists('ALEVirtualTextStyleWarning')
highlight link ALEVirtualTextStyleWarning ALEVirtualTextWarning
endif
if !hlexists('ALEVirtualTextInfo')
highlight link ALEVirtualTextInfo ALEVirtualTextWarning
endif
function! ale#virtualtext#Clear() abort
if !has('nvim-0.3.2')
return
endif
let l:buffer = bufnr('')
call nvim_buf_clear_highlight(l:buffer, s:ns_id, 0, -1)
endfunction
function! ale#virtualtext#ShowMessage(message, hl_group) abort
if !has('nvim-0.3.2')
return
endif
let l:cursor_position = getcurpos()
let l:line = line('.')
let l:buffer = bufnr('')
let l:prefix = get(g:, 'ale_virtualtext_prefix', '> ')
call nvim_buf_set_virtual_text(l:buffer, s:ns_id, l:line-1, [[l:prefix.a:message, a:hl_group]], {})
endfunction
function! s:StopCursorTimer() abort
if s:cursor_timer != -1
call timer_stop(s:cursor_timer)
let s:cursor_timer = -1
endif
endfunction
function! ale#virtualtext#ShowCursorWarning(...) abort
if !g:ale_virtualtext_cursor
return
endif
let l:buffer = bufnr('')
if mode(1) isnot# 'n'
return
endif
if ale#ShouldDoNothing(l:buffer)
return
endif
let [l:info, l:loc] = ale#util#FindItemAtCursor(l:buffer)
call ale#virtualtext#Clear()
if !empty(l:loc)
let l:msg = get(l:loc, 'detail', l:loc.text)
let l:hl_group = 'ALEVirtualTextInfo'
let l:type = get(l:loc, 'type', 'E')
if l:type is# 'E'
if get(l:loc, 'sub_type', '') is# 'style'
let l:hl_group = 'ALEVirtualTextStyleError'
else
let l:hl_group = 'ALEVirtualTextError'
endif
elseif l:type is# 'W'
if get(l:loc, 'sub_type', '') is# 'style'
let l:hl_group = 'ALEVirtualTextStyleWarning'
else
let l:hl_group = 'ALEVirtualTextWarning'
endif
endif
call ale#virtualtext#ShowMessage(l:msg, l:hl_group)
endif
endfunction
function! ale#virtualtext#ShowCursorWarningWithDelay() abort
let l:buffer = bufnr('')
if !g:ale_virtualtext_cursor
return
endif
if mode(1) isnot# 'n'
return
endif
call s:StopCursorTimer()
let l:pos = getcurpos()[0:2]
" Check the current buffer, line, and column number against the last
" recorded position. If the position has actually changed, *then*
" we should show something. Otherwise we can end up doing processing
" the show message far too frequently.
if l:pos != s:last_pos
let l:delay = ale#Var(l:buffer, 'virtualtext_delay')
let s:last_pos = l:pos
let s:cursor_timer = timer_start(
\ l:delay,
\ function('ale#virtualtext#ShowCursorWarning')
\)
endif
endfunction