summaryrefslogtreecommitdiff
path: root/autoload
diff options
context:
space:
mode:
Diffstat (limited to 'autoload')
-rw-r--r--autoload/ale.vim4
-rw-r--r--autoload/ale/c.vim19
-rw-r--r--autoload/ale/code_action.vim20
-rw-r--r--autoload/ale/completion.vim68
-rw-r--r--autoload/ale/definition.vim39
-rw-r--r--autoload/ale/events.vim4
-rw-r--r--autoload/ale/fix.vim29
-rw-r--r--autoload/ale/fix/registry.vim5
-rw-r--r--autoload/ale/fixers/astyle.vim59
-rw-r--r--autoload/ale/fixers/eslint.vim9
-rw-r--r--autoload/ale/fixers/ktlint.vim3
-rw-r--r--autoload/ale/fixers/rubocop.vim25
-rw-r--r--autoload/ale/fixers/tslint.vim2
-rw-r--r--autoload/ale/handlers/ccls.vim7
-rw-r--r--autoload/ale/handlers/cppcheck.vim15
-rw-r--r--autoload/ale/handlers/eslint.vim45
-rw-r--r--autoload/ale/handlers/go.vim5
-rw-r--r--autoload/ale/handlers/hdl_checker.vim71
-rw-r--r--autoload/ale/handlers/ktlint.vim2
-rw-r--r--autoload/ale/handlers/markdownlint.vim13
-rw-r--r--autoload/ale/handlers/shellcheck.vim107
-rw-r--r--autoload/ale/highlight.vim6
-rw-r--r--autoload/ale/hover.vim203
-rw-r--r--autoload/ale/linter.vim2
-rw-r--r--autoload/ale/lsp.vim24
-rw-r--r--autoload/ale/node.vim14
-rw-r--r--autoload/ale/organize_imports.vim2
-rw-r--r--autoload/ale/preview.vim41
-rw-r--r--autoload/ale/references.vim19
-rw-r--r--autoload/ale/rename.vim37
-rw-r--r--autoload/ale/sign.vim2
-rw-r--r--autoload/ale/uri.vim15
-rw-r--r--autoload/ale/util.vim76
33 files changed, 834 insertions, 158 deletions
diff --git a/autoload/ale.vim b/autoload/ale.vim
index ee1a0d54..6251b47b 100644
--- a/autoload/ale.vim
+++ b/autoload/ale.vim
@@ -163,7 +163,7 @@ function! ale#Queue(delay, ...) abort
endif
endfunction
-let s:current_ale_version = [2, 6, 0]
+let s:current_ale_version = [2, 7, 0]
" A function used to check for ALE features in files outside of the project.
function! ale#Has(feature) abort
@@ -258,9 +258,9 @@ function! ale#GetLocItemMessage(item, format_string) abort
" Replace special markers with certain information.
" \=l:variable is used to avoid escaping issues.
+ let l:msg = substitute(l:msg, '\v\%([^\%]*)code([^\%]*)\%', l:code_repl, 'g')
let l:msg = substitute(l:msg, '\V%severity%', '\=l:severity', 'g')
let l:msg = substitute(l:msg, '\V%linter%', '\=l:linter_name', 'g')
- let l:msg = substitute(l:msg, '\v\%([^\%]*)code([^\%]*)\%', l:code_repl, 'g')
" Replace %s with the text.
let l:msg = substitute(l:msg, '\V%s', '\=a:item.text', 'g')
diff --git a/autoload/ale/c.vim b/autoload/ale/c.vim
index 9b428700..39892d42 100644
--- a/autoload/ale/c.vim
+++ b/autoload/ale/c.vim
@@ -2,20 +2,13 @@
" Description: Functions for integrating with C-family linters.
call ale#Set('c_parse_makefile', 0)
-call ale#Set('c_parse_compile_commands', 0)
+call ale#Set('c_parse_compile_commands', 1)
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
@@ -334,10 +327,6 @@ endfunction
function! ale#c#GetCFlags(buffer, output) abort
let l:cflags = v:null
- 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:root, l:json_file] = ale#c#FindCompileCommands(a:buffer)
@@ -346,6 +335,12 @@ function! ale#c#GetCFlags(buffer, output) abort
endif
endif
+ if ale#Var(a:buffer, 'c_parse_makefile')
+ \&& !empty(a:output)
+ \&& !empty(l:cflags)
+ let l:cflags = ale#c#ParseCFlagsFromMakeOutput(a:buffer, a:output)
+ endif
+
if l:cflags is v:null
let l:cflags = ale#c#IncludeOptions(ale#c#FindLocalHeaderPaths(a:buffer))
endif
diff --git a/autoload/ale/code_action.vim b/autoload/ale/code_action.vim
index 0af1bb70..60c3bbef 100644
--- a/autoload/ale/code_action.vim
+++ b/autoload/ale/code_action.vim
@@ -1,7 +1,7 @@
" Author: Jerko Steiner <jerko.steiner@gmail.com>
" Description: Code action support for LSP / tsserver
-function! ale#code_action#HandleCodeAction(code_action) abort
+function! ale#code_action#HandleCodeAction(code_action, should_save) abort
let l:current_buffer = bufnr('')
let l:changes = a:code_action.changes
@@ -17,11 +17,14 @@ function! ale#code_action#HandleCodeAction(code_action) abort
for l:file_code_edit in l:changes
call ale#code_action#ApplyChanges(
- \ l:file_code_edit.fileName, l:file_code_edit.textChanges)
+ \ l:file_code_edit.fileName,
+ \ l:file_code_edit.textChanges,
+ \ a:should_save,
+ \ )
endfor
endfunction
-function! ale#code_action#ApplyChanges(filename, changes) abort
+function! ale#code_action#ApplyChanges(filename, changes, should_save) abort
let l:current_buffer = bufnr('')
" The buffer is used to determine the fileformat, if available.
let l:buffer = bufnr(a:filename)
@@ -106,10 +109,17 @@ function! ale#code_action#ApplyChanges(filename, changes) abort
call remove(l:lines, -1)
endif
- call ale#util#Writefile(l:buffer, l:lines, a:filename)
+ if a:should_save
+ call ale#util#Writefile(l:buffer, l:lines, a:filename)
+ else
+ call ale#util#SetBufferContents(l:buffer, l:lines)
+ endif
if l:is_current_buffer
- call ale#util#Execute(':e!')
+ if a:should_save
+ call ale#util#Execute(':e!')
+ endif
+
call setpos('.', [0, l:pos[0], l:pos[1], 0])
endif
endfunction
diff --git a/autoload/ale/completion.vim b/autoload/ale/completion.vim
index 80684a30..bffcdc8a 100644
--- a/autoload/ale/completion.vim
+++ b/autoload/ale/completion.vim
@@ -16,7 +16,8 @@ onoremap <silent> <Plug>(ale_show_completion_menu) <Nop>
let g:ale_completion_delay = get(g:, 'ale_completion_delay', 100)
let g:ale_completion_excluded_words = get(g:, 'ale_completion_excluded_words', [])
let g:ale_completion_max_suggestions = get(g:, 'ale_completion_max_suggestions', 50)
-let g:ale_completion_tsserver_autoimport = get(g:, 'ale_completion_tsserver_autoimport', 0)
+let g:ale_completion_autoimport = get(g:, 'ale_completion_autoimport', 0)
+let g:ale_completion_tsserver_remove_warnings = get(g:, 'ale_completion_tsserver_remove_warnings', 0)
let s:timer_id = -1
let s:last_done_pos = []
@@ -397,10 +398,14 @@ function! ale#completion#ParseTSServerCompletions(response) abort
let l:names = []
for l:suggestion in a:response.body
- call add(l:names, {
- \ 'word': l:suggestion.name,
- \ 'source': get(l:suggestion, 'source', ''),
- \})
+ let l:kind = get(l:suggestion, 'kind', '')
+
+ if g:ale_completion_tsserver_remove_warnings == 0 || l:kind isnot# 'warning'
+ call add(l:names, {
+ \ 'word': l:suggestion.name,
+ \ 'source': get(l:suggestion, 'source', ''),
+ \})
+ endif
endfor
return l:names
@@ -435,7 +440,7 @@ function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort
\ 'kind': ale#completion#GetCompletionSymbols(l:suggestion.kind),
\ 'icase': 1,
\ 'menu': join(l:displayParts, ''),
- \ 'dup': g:ale_completion_tsserver_autoimport,
+ \ 'dup': g:ale_completion_autoimport,
\ 'info': join(l:documentationParts, ''),
\}
@@ -517,19 +522,58 @@ function! ale#completion#ParseLSPCompletions(response) abort
continue
endif
+ " Don't use LSP items with additional text edits when autoimport for
+ " completions is turned off.
+ if has_key(l:item, 'additionalTextEdits') && !g:ale_completion_autoimport
+ continue
+ 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, {
+ let l:result = {
\ 'word': l:word,
\ 'kind': ale#completion#GetCompletionSymbols(get(l:item, 'kind', '')),
\ 'icase': 1,
\ 'menu': get(l:item, 'detail', ''),
\ 'info': (type(l:doc) is v:t_string ? l:doc : ''),
- \})
+ \}
+
+ if has_key(l:item, 'additionalTextEdits')
+ let l:text_changes = []
+
+ for l:edit in l:item.additionalTextEdits
+ let l:range = l:edit.range
+ call add(l:text_changes, {
+ \ 'start': {
+ \ 'line': l:range.start.line + 1,
+ \ 'offset': l:range.start.character + 1,
+ \ },
+ \ 'end': {
+ \ 'line': l:range.end.line + 1,
+ \ 'offset': l:range.end.character + 1,
+ \ },
+ \ 'newText': l:edit.newText,
+ \})
+ endfor
+
+ let l:changes = [{
+ \ 'fileName': expand('#' . l:buffer . ':p'),
+ \ 'textChanges': l:text_changes,
+ \}]
+ \
+ let l:result.user_data = json_encode({
+ \ 'codeActions': [{
+ \ 'description': 'completion',
+ \ 'changes': l:changes,
+ \ }],
+ \ })
+ endif
+
+ call add(l:results, l:result)
endfor
if has_key(l:info, 'prefix')
@@ -628,12 +672,16 @@ function! s:OnReady(linter, lsp_details) abort
call ale#lsp#RegisterCallback(l:id, l:Callback)
if a:linter.lsp is# 'tsserver'
+ if get(g:, 'ale_completion_tsserver_autoimport') is 1
+ execute 'echom `g:ale_completion_tsserver_autoimport` is deprecated. Use `g:ale_completion_autoimport` instead.'''
+ endif
+
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,
- \ g:ale_completion_tsserver_autoimport,
+ \ g:ale_completion_autoimport,
\)
else
" Send a message saying the buffer has changed first, otherwise
@@ -823,7 +871,7 @@ function! ale#completion#HandleUserData(completed_item) abort
endif
for l:code_action in get(l:user_data, 'codeActions', [])
- call ale#code_action#HandleCodeAction(l:code_action)
+ call ale#code_action#HandleCodeAction(l:code_action, v:false)
endfor
endfunction
diff --git a/autoload/ale/definition.vim b/autoload/ale/definition.vim
index 3915cac1..ffcd9d10 100644
--- a/autoload/ale/definition.vim
+++ b/autoload/ale/definition.vim
@@ -5,6 +5,7 @@ let s:go_to_definition_map = {}
" Enable automatic updates of the tagstack
let g:ale_update_tagstack = get(g:, 'ale_update_tagstack', 1)
+let g:ale_default_navigation = get(g:, 'ale_default_navigation', 'buffer')
" Used to get the definition map in tests.
function! ale#definition#GetMap() abort
@@ -134,6 +135,10 @@ function! s:GoToLSPDefinition(linter, options, capability) abort
endfunction
function! ale#definition#GoTo(options) abort
+ if !get(g:, 'ale_ignore_2_7_warnings') && has_key(a:options, 'deprecated_command')
+ execute 'echom '':' . a:options.deprecated_command . ' is deprecated. Use `let g:ale_ignore_2_7_warnings = 1` to disable this message.'''
+ endif
+
for l:linter in ale#linter#Get(&filetype)
if !empty(l:linter.lsp)
call s:GoToLSPDefinition(l:linter, a:options, 'definition')
@@ -142,6 +147,10 @@ function! ale#definition#GoTo(options) abort
endfunction
function! ale#definition#GoToType(options) abort
+ if !get(g:, 'ale_ignore_2_7_warnings') && has_key(a:options, 'deprecated_command')
+ execute 'echom '':' . a:options.deprecated_command . ' is deprecated. Use `let g:ale_ignore_2_7_warnings = 1` to disable this message.'''
+ endif
+
for l:linter in ale#linter#Get(&filetype)
if !empty(l:linter.lsp)
" TODO: handle typeDefinition for tsserver if supported by the
@@ -154,3 +163,33 @@ function! ale#definition#GoToType(options) abort
endif
endfor
endfunction
+
+function! ale#definition#GoToCommandHandler(command, ...) abort
+ let l:options = {}
+
+ if len(a:000) > 0
+ for l:option in a:000
+ if l:option is? '-tab'
+ let l:options.open_in = 'tab'
+ elseif l:option is? '-split'
+ let l:options.open_in = 'split'
+ elseif l:option is? '-vsplit'
+ let l:options.open_in = 'vsplit'
+ endif
+ endfor
+ endif
+
+ if !has_key(l:options, 'open_in')
+ let l:default_navigation = ale#Var(bufnr(''), 'default_navigation')
+
+ if index(['tab', 'split', 'vsplit'], l:default_navigation) >= 0
+ let l:options.open_in = l:default_navigation
+ endif
+ endif
+
+ if a:command is# 'type'
+ call ale#definition#GoToType(l:options)
+ else
+ call ale#definition#GoTo(l:options)
+ endif
+endfunction
diff --git a/autoload/ale/events.vim b/autoload/ale/events.vim
index da554ef9..731e36f2 100644
--- a/autoload/ale/events.vim
+++ b/autoload/ale/events.vim
@@ -147,6 +147,10 @@ function! ale#events#Init() abort
autocmd InsertLeave * if exists('*ale#engine#Cleanup') | call ale#virtualtext#ShowCursorWarning() | endif
endif
+ if g:ale_hover_cursor
+ autocmd CursorHold * if exists('*ale#lsp#Send') | call ale#hover#ShowTruncatedMessageAtCursor() | endif
+ endif
+
if g:ale_close_preview_on_insert
autocmd InsertEnter * if exists('*ale#preview#CloseIfTypeMatches') | call ale#preview#CloseIfTypeMatches('ale-preview') | endif
endif
diff --git a/autoload/ale/fix.vim b/autoload/ale/fix.vim
index dad9e2bc..69817b36 100644
--- a/autoload/ale/fix.vim
+++ b/autoload/ale/fix.vim
@@ -4,40 +4,15 @@ call ale#Set('fix_on_save_ignore', {})
" Vim doesn't let you modify hidden buffers.
function! ale#fix#ApplyQueuedFixes(buffer) abort
let l:data = get(g:ale_fix_buffer_data, a:buffer, {'done': 0})
- let l:has_bufline_api = exists('*deletebufline') && exists('*setbufline')
- if !l:data.done || (!l:has_bufline_api && a:buffer isnot bufnr(''))
+ if !l:data.done || (!ale#util#HasBuflineApi() && a:buffer isnot bufnr(''))
return
endif
call remove(g:ale_fix_buffer_data, a:buffer)
if l:data.changes_made
- " 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:new_lines = getbufvar(a:buffer, '&fileformat') is# 'dos'
- \ ? map(copy(l:data.output), 'substitute(v:val, ''\r\+$'', '''', '''')')
- \ : l:data.output
- let l:first_line_to_remove = len(l:new_lines) + 1
-
- " Use a Vim API for setting lines in other buffers, if available.
- if l:has_bufline_api
- call setbufline(a:buffer, 1, l:new_lines)
- call deletebufline(a:buffer, l:first_line_to_remove, '$')
- " Fall back on setting lines the old way, for the current buffer.
- else
- let l:old_line_length = len(l:data.lines_before)
-
- if l:old_line_length >= l:first_line_to_remove
- let l:save = winsaveview()
- silent execute
- \ l:first_line_to_remove . ',' . l:old_line_length . 'd_'
- call winrestview(l:save)
- endif
-
- call setline(1, l:new_lines)
- endif
+ let l:new_lines = ale#util#SetBufferContents(a:buffer, l:data.output)
if l:data.should_save
if a:buffer is bufnr('')
diff --git a/autoload/ale/fix/registry.vim b/autoload/ale/fix/registry.vim
index 1b3ca1a8..02b02699 100644
--- a/autoload/ale/fix/registry.vim
+++ b/autoload/ale/fix/registry.vim
@@ -160,6 +160,11 @@ let s:default_registry = {
\ 'suggested_filetypes': ['php'],
\ 'description': 'Fix PHP files with php-cs-fixer.',
\ },
+\ 'astyle': {
+\ 'function': 'ale#fixers#astyle#Fix',
+\ 'suggested_filetypes': ['c', 'cpp'],
+\ 'description': 'Fix C/C++ with astyle.',
+\ },
\ 'clangtidy': {
\ 'function': 'ale#fixers#clangtidy#Fix',
\ 'suggested_filetypes': ['c', 'cpp', 'objc'],
diff --git a/autoload/ale/fixers/astyle.vim b/autoload/ale/fixers/astyle.vim
new file mode 100644
index 00000000..3a5a70a1
--- /dev/null
+++ b/autoload/ale/fixers/astyle.vim
@@ -0,0 +1,59 @@
+" Author: James Kim <jhlink@users.noreply.github.com>
+" Description: Fix C/C++ files with astyle.
+
+function! s:set_variables() abort
+ for l:ft in ['c', 'cpp']
+ call ale#Set(l:ft . '_astyle_executable', 'astyle')
+ call ale#Set(l:ft . '_astyle_project_options', '')
+ endfor
+endfunction
+
+call s:set_variables()
+
+
+function! ale#fixers#astyle#Var(buffer, name) abort
+ let l:ft = getbufvar(str2nr(a:buffer), '&filetype')
+ let l:ft = l:ft =~# 'cpp' ? 'cpp' : 'c'
+
+ return ale#Var(a:buffer, l:ft . '_astyle_' . a:name)
+endfunction
+
+" Try to find a project options file.
+function! ale#fixers#astyle#FindProjectOptions(buffer) abort
+ let l:proj_options = ale#fixers#astyle#Var(a:buffer, 'project_options')
+
+ " If user has set project options variable then use it and skip any searching.
+ " This would allow users to use project files named differently than .astylerc.
+ if !empty(l:proj_options)
+ return l:proj_options
+ endif
+
+ " Try to find nearest .astylerc file.
+ let l:proj_options = fnamemodify(ale#path#FindNearestFile(a:buffer, '.astylerc'), ':t')
+
+ if !empty(l:proj_options)
+ return l:proj_options
+ endif
+
+ " Try to find nearest _astylerc file.
+ let l:proj_options = fnamemodify(ale#path#FindNearestFile(a:buffer, '_astylerc'), ':t')
+
+ if !empty(l:proj_options)
+ return l:proj_options
+ endif
+
+ " If no project options file is found return an empty string.
+ return ''
+endfunction
+
+function! ale#fixers#astyle#Fix(buffer) abort
+ let l:executable = ale#fixers#astyle#Var(a:buffer, 'executable')
+ let l:proj_options = ale#fixers#astyle#FindProjectOptions(a:buffer)
+ let l:command = ' --stdin=' . ale#Escape(expand('#' . a:buffer))
+
+ return {
+ \ 'command': ale#Escape(l:executable)
+ \ . (empty(l:proj_options) ? '' : ' --project=' . l:proj_options)
+ \ . l:command
+ \}
+endfunction
diff --git a/autoload/ale/fixers/eslint.vim b/autoload/ale/fixers/eslint.vim
index 62e692b1..f725875c 100644
--- a/autoload/ale/fixers/eslint.vim
+++ b/autoload/ale/fixers/eslint.vim
@@ -53,7 +53,8 @@ function! ale#fixers#eslint#ApplyFixForVersion(buffer, version) abort
" Use --fix-to-stdout with eslint_d
if l:executable =~# 'eslint_d$' && ale#semver#GTE(a:version, [3, 19, 0])
return {
- \ 'command': ale#node#Executable(a:buffer, l:executable)
+ \ 'command': ale#handlers#eslint#GetCdString(a:buffer)
+ \ . ale#node#Executable(a:buffer, l:executable)
\ . ale#Pad(l:options)
\ . ' --stdin-filename %s --stdin --fix-to-stdout',
\ 'process_with': 'ale#fixers#eslint#ProcessEslintDOutput',
@@ -63,7 +64,8 @@ function! ale#fixers#eslint#ApplyFixForVersion(buffer, version) abort
" 4.9.0 is the first version with --fix-dry-run
if ale#semver#GTE(a:version, [4, 9, 0])
return {
- \ 'command': ale#node#Executable(a:buffer, l:executable)
+ \ 'command': ale#handlers#eslint#GetCdString(a:buffer)
+ \ . ale#node#Executable(a:buffer, l:executable)
\ . ale#Pad(l:options)
\ . ' --stdin-filename %s --stdin --fix-dry-run --format=json',
\ 'process_with': 'ale#fixers#eslint#ProcessFixDryRunOutput',
@@ -71,7 +73,8 @@ function! ale#fixers#eslint#ApplyFixForVersion(buffer, version) abort
endif
return {
- \ 'command': ale#node#Executable(a:buffer, l:executable)
+ \ 'command': ale#handlers#eslint#GetCdString(a:buffer)
+ \ . ale#node#Executable(a:buffer, l:executable)
\ . ale#Pad(l:options)
\ . (!empty(l:config) ? ' -c ' . ale#Escape(l:config) : '')
\ . ' --fix %t',
diff --git a/autoload/ale/fixers/ktlint.vim b/autoload/ale/fixers/ktlint.vim
index cb975d6c..64d1340d 100644
--- a/autoload/ale/fixers/ktlint.vim
+++ b/autoload/ale/fixers/ktlint.vim
@@ -3,7 +3,6 @@
function! ale#fixers#ktlint#Fix(buffer) abort
return {
- \ 'command': ale#handlers#ktlint#GetCommand(a:buffer) . ' --format',
- \ 'read_temporary_file': 1,
+ \ 'command': ale#handlers#ktlint#GetCommand(a:buffer) . ' --format'
\}
endfunction
diff --git a/autoload/ale/fixers/rubocop.vim b/autoload/ale/fixers/rubocop.vim
index 0c7441e4..d9615256 100644
--- a/autoload/ale/fixers/rubocop.vim
+++ b/autoload/ale/fixers/rubocop.vim
@@ -1,20 +1,41 @@
call ale#Set('ruby_rubocop_options', '')
+call ale#Set('ruby_rubocop_auto_correct_all', 0)
call ale#Set('ruby_rubocop_executable', 'rubocop')
+" Rubocop fixer outputs diagnostics first and then the fixed
+" output. These are delimited by a "=======" string that we
+" look for to remove everything before it.
+function! ale#fixers#rubocop#PostProcess(buffer, output) abort
+ let l:line = 0
+
+ for l:output in a:output
+ let l:line = l:line + 1
+
+ if l:output =~# "^=\\+$"
+ break
+ endif
+ endfor
+
+ return a:output[l:line :]
+endfunction
+
function! ale#fixers#rubocop#GetCommand(buffer) abort
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')
+ let l:auto_correct_all = ale#Var(a:buffer, 'ruby_rubocop_auto_correct_all')
return ale#ruby#EscapeExecutable(l:executable, 'rubocop')
\ . (!empty(l:config) ? ' --config ' . ale#Escape(l:config) : '')
\ . (!empty(l:options) ? ' ' . l:options : '')
- \ . ' --auto-correct --force-exclusion %t'
+ \ . (l:auto_correct_all ? ' --auto-correct-all' : ' --auto-correct')
+ \ . ' --force-exclusion --stdin '
+ \ . ale#Escape(expand('#' . a:buffer . ':p'))
endfunction
function! ale#fixers#rubocop#Fix(buffer) abort
return {
\ 'command': ale#fixers#rubocop#GetCommand(a:buffer),
- \ 'read_temporary_file': 1,
+ \ 'process_with': 'ale#fixers#rubocop#PostProcess'
\}
endfunction
diff --git a/autoload/ale/fixers/tslint.vim b/autoload/ale/fixers/tslint.vim
index b352af3a..15768fd5 100644
--- a/autoload/ale/fixers/tslint.vim
+++ b/autoload/ale/fixers/tslint.vim
@@ -16,7 +16,7 @@ function! ale#fixers#tslint#Fix(buffer) abort
return {
\ 'command': ale#node#Executable(a:buffer, l:executable)
\ . l:tslint_config_option
- \ . ' --fix %t',
+ \ . ' --outputAbsolutePaths --fix %t',
\ 'read_temporary_file': 1,
\}
endfunction
diff --git a/autoload/ale/handlers/ccls.vim b/autoload/ale/handlers/ccls.vim
index 1e2aa318..290f5852 100644
--- a/autoload/ale/handlers/ccls.vim
+++ b/autoload/ale/handlers/ccls.vim
@@ -17,3 +17,10 @@ function! ale#handlers#ccls#GetProjectRoot(buffer) abort
" Fall back on default project root detection.
return ale#c#FindProjectRoot(a:buffer)
endfunction
+
+function! ale#handlers#ccls#GetInitOpts(buffer, init_options_var) abort
+ let l:build_dir = ale#c#GetBuildDirectory(a:buffer)
+ let l:init_options = empty(l:build_dir) ? {} : {'compilationDatabaseDirectory': l:build_dir}
+
+ return extend(l:init_options, ale#Var(a:buffer, a:init_options_var))
+endfunction
diff --git a/autoload/ale/handlers/cppcheck.vim b/autoload/ale/handlers/cppcheck.vim
index 6d8fa15d..7f68ba67 100644
--- a/autoload/ale/handlers/cppcheck.vim
+++ b/autoload/ale/handlers/cppcheck.vim
@@ -44,16 +44,21 @@ endfunction
function! ale#handlers#cppcheck#HandleCppCheckFormat(buffer, lines) abort
" Look for lines like the following.
"
- " [test.cpp:5]: (error) Array 'a[10]' accessed at index 10, which is out of bounds
- let l:pattern = '\v^\[(.+):(\d+)\]: \(([a-z]+)\) (.+)$'
+ "test.cpp:974:6: error: Array 'n[3]' accessed at index 3, which is out of bounds. [arrayIndexOutOfBounds]\
+ " n[3]=3;
+ " ^
+ let l:pattern = '\v^(\f+):(\d+):(\d+): (\w+): (.*) \[(\w+)\]\'
let l:output = []
for l:match in ale#util#GetMatches(a:lines, l:pattern)
if ale#path#IsBufferPath(a:buffer, l:match[1])
call add(l:output, {
- \ 'lnum': str2nr(l:match[2]),
- \ 'type': l:match[3] is# 'error' ? 'E' : 'W',
- \ 'text': l:match[4],
+ \ 'lnum': str2nr(l:match[2]),
+ \ 'col': str2nr(l:match[3]),
+ \ 'type': l:match[4] is# 'error' ? 'E' : 'W',
+ \ 'sub_type': l:match[4] is# 'style' ? 'style' : '',
+ \ 'text': l:match[5],
+ \ 'code': l:match[6]
\})
endif
endfor
diff --git a/autoload/ale/handlers/eslint.vim b/autoload/ale/handlers/eslint.vim
index 156b939f..e37d6902 100644
--- a/autoload/ale/handlers/eslint.vim
+++ b/autoload/ale/handlers/eslint.vim
@@ -1,6 +1,11 @@
" Author: w0rp <devw0rp@gmail.com>
" Description: Functions for working with eslint, for checking or fixing files.
+let s:executables = [
+\ 'node_modules/.bin/eslint_d',
+\ 'node_modules/eslint/bin/eslint.js',
+\ 'node_modules/.bin/eslint',
+\]
let s:sep = has('win32') ? '\' : '/'
call ale#Set('javascript_eslint_options', '')
@@ -30,29 +35,39 @@ function! ale#handlers#eslint#FindConfig(buffer) abort
endfunction
function! ale#handlers#eslint#GetExecutable(buffer) abort
- return ale#node#FindExecutable(a:buffer, 'javascript_eslint', [
- \ 'node_modules/.bin/eslint_d',
- \ 'node_modules/eslint/bin/eslint.js',
- \ 'node_modules/.bin/eslint',
- \])
+ return ale#node#FindExecutable(a:buffer, 'javascript_eslint', s:executables)
endfunction
-function! ale#handlers#eslint#GetCommand(buffer) abort
- let l:executable = ale#handlers#eslint#GetExecutable(a:buffer)
-
- let l:options = ale#Var(a:buffer, 'javascript_eslint_options')
-
+" Given a buffer, return a command prefix string which changes directory
+" as necessary for running ESLint.
+function! ale#handlers#eslint#GetCdString(buffer) abort
" ESLint 6 loads plugins/configs/parsers from the project root
" By default, the project root is simply the CWD of the running process.
" https://github.com/eslint/rfcs/blob/master/designs/2018-simplified-package-loading/README.md
" https://github.com/dense-analysis/ale/issues/2787
- " Identify project root from presence of node_modules dir.
+ "
+ " If eslint is installed in a directory which contains the buffer, assume
+ " it is the ESLint project root. Otherwise, use nearest node_modules.
" Note: If node_modules not present yet, can't load local deps anyway.
- let l:modules_dir = ale#path#FindNearestDirectory(a:buffer, 'node_modules')
- let l:project_dir = !empty(l:modules_dir) ? fnamemodify(l:modules_dir, ':h:h') : ''
- let l:cd_command = !empty(l:project_dir) ? ale#path#CdString(l:project_dir) : ''
+ let l:executable = ale#node#FindNearestExecutable(a:buffer, s:executables)
+
+ if !empty(l:executable)
+ let l:nmi = strridx(l:executable, 'node_modules')
+ let l:project_dir = l:executable[0:l:nmi - 2]
+ else
+ let l:modules_dir = ale#path#FindNearestDirectory(a:buffer, 'node_modules')
+ let l:project_dir = !empty(l:modules_dir) ? fnamemodify(l:modules_dir, ':h:h') : ''
+ endif
+
+ return !empty(l:project_dir) ? ale#path#CdString(l:project_dir) : ''
+endfunction
+
+function! ale#handlers#eslint#GetCommand(buffer) abort
+ let l:executable = ale#handlers#eslint#GetExecutable(a:buffer)
+
+ let l:options = ale#Var(a:buffer, 'javascript_eslint_options')
- return l:cd_command
+ return ale#handlers#eslint#GetCdString(a:buffer)
\ . ale#node#Executable(a:buffer, l:executable)
\ . (!empty(l:options) ? ' ' . l:options : '')
\ . ' -f json --stdin --stdin-filename %s'
diff --git a/autoload/ale/handlers/go.vim b/autoload/ale/handlers/go.vim
index f17cd862..c969669d 100644
--- a/autoload/ale/handlers/go.vim
+++ b/autoload/ale/handlers/go.vim
@@ -6,9 +6,12 @@
"
" Author: Ben Paxton <ben@gn32.uk>
" Description: moved to generic Golang file from govet
+"
+" Author: mostfunkyduck <mostfunkyduck@protonmail.com>
+" Description: updated to work with go 1.14
function! ale#handlers#go#Handler(buffer, lines) abort
- let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):?(\d+)?:? ?(.+)$'
+ let l:pattern = '\v^%(vet: )?([a-zA-Z]?:?[^:]+):(\d+):?(\d+)?:? ?(.+)$'
let l:output = []
let l:dir = expand('#' . a:buffer . ':p:h')
diff --git a/autoload/ale/handlers/hdl_checker.vim b/autoload/ale/handlers/hdl_checker.vim
new file mode 100644
index 00000000..36dbd259
--- /dev/null
+++ b/autoload/ale/handlers/hdl_checker.vim
@@ -0,0 +1,71 @@
+" Author: suoto <andre820@gmail.com>
+" Description: Adds support for HDL Code Checker, which wraps vcom/vlog, ghdl
+" or xvhdl. More info on https://github.com/suoto/hdl_checker
+
+call ale#Set('hdl_checker_executable', 'hdl_checker')
+call ale#Set('hdl_checker_config_file', has('unix') ? '.hdl_checker.config' : '_hdl_checker.config')
+call ale#Set('hdl_checker_options', '')
+
+" Use this as a function so we can mock it on testing. Need to do this because
+" test files are inside /testplugin (which refers to the ale repo), which will
+" always have a .git folder
+function! ale#handlers#hdl_checker#IsDotGit(path) abort
+ return ! empty(a:path) && isdirectory(a:path)
+endfunction
+
+" Sould return (in order of preference)
+" 1. Nearest config file
+" 2. Nearest .git directory
+" 3. The current path
+function! ale#handlers#hdl_checker#GetProjectRoot(buffer) abort
+ let l:project_root = ale#path#FindNearestFile(
+ \ a:buffer,
+ \ ale#Var(a:buffer, 'hdl_checker_config_file'))
+
+ if !empty(l:project_root)
+ return fnamemodify(l:project_root, ':h')
+ endif
+
+ " Search for .git to use as root
+ let l:project_root = ale#path#FindNearestDirectory(a:buffer, '.git')
+
+ if ale#handlers#hdl_checker#IsDotGit(l:project_root)
+ return fnamemodify(l:project_root, ':h:h')
+ endif
+endfunction
+
+function! ale#handlers#hdl_checker#GetExecutable(buffer) abort
+ return ale#Var(a:buffer, 'hdl_checker_executable')
+endfunction
+
+function! ale#handlers#hdl_checker#GetCommand(buffer) abort
+ let l:command = ale#Escape(ale#handlers#hdl_checker#GetExecutable(a:buffer)) . ' --lsp'
+
+ " Add extra parameters only if config has been set
+ let l:options = ale#Var(a:buffer, 'hdl_checker_options')
+
+ if ! empty(l:options)
+ let l:command = l:command . ' ' . l:options
+ endif
+
+ return l:command
+endfunction
+
+" To allow testing
+function! ale#handlers#hdl_checker#GetInitOptions(buffer) abort
+ return {'project_file': ale#Var(a:buffer, 'hdl_checker_config_file')}
+endfunction
+
+" Define the hdl_checker linter for a given filetype.
+function! ale#handlers#hdl_checker#DefineLinter(filetype) abort
+ call ale#linter#Define(a:filetype, {
+ \ 'name': 'hdl-checker',
+ \ 'lsp': 'stdio',
+ \ 'language': a:filetype,
+ \ 'executable': function('ale#handlers#hdl_checker#GetExecutable'),
+ \ 'command': function('ale#handlers#hdl_checker#GetCommand'),
+ \ 'project_root': function('ale#handlers#hdl_checker#GetProjectRoot'),
+ \ 'initialization_options': function('ale#handlers#hdl_checker#GetInitOptions'),
+ \ })
+endfunction
+
diff --git a/autoload/ale/handlers/ktlint.vim b/autoload/ale/handlers/ktlint.vim
index ad999485..77e7ab66 100644
--- a/autoload/ale/handlers/ktlint.vim
+++ b/autoload/ale/handlers/ktlint.vim
@@ -13,7 +13,7 @@ function! ale#handlers#ktlint#GetCommand(buffer) abort
return ale#Escape(l:executable)
\ . (empty(l:options) ? '' : ' ' . l:options)
\ . (empty(l:rulesets) ? '' : ' ' . l:rulesets)
- \ . ' %t'
+ \ . ' --stdin'
endfunction
function! ale#handlers#ktlint#GetRulesets(buffer) abort
diff --git a/autoload/ale/handlers/markdownlint.vim b/autoload/ale/handlers/markdownlint.vim
index daaa1d66..6c273bd0 100644
--- a/autoload/ale/handlers/markdownlint.vim
+++ b/autoload/ale/handlers/markdownlint.vim
@@ -2,15 +2,22 @@
" Description: Adds support for markdownlint
function! ale#handlers#markdownlint#Handle(buffer, lines) abort
- let l:pattern=': \(\d*\): \(MD\d\{3}\)\(\/\)\([A-Za-z0-9-]\+\)\(.*\)$'
+ let l:pattern=': \?\(\d\+\)\(:\(\d\+\)\?\)\? \(MD\d\{3}/[A-Za-z0-9-/]\+\) \(.*\)$'
let l:output=[]
for l:match in ale#util#GetMatches(a:lines, l:pattern)
- call add(l:output, {
+ let l:result = ({
\ 'lnum': l:match[1] + 0,
- \ 'text': '(' . l:match[2] . l:match[3] . l:match[4] . ')' . l:match[5],
+ \ 'code': l:match[4],
+ \ 'text': l:match[5],
\ 'type': 'W',
\})
+
+ if len(l:match[3]) > 0
+ let l:result.col = (l:match[3] + 0)
+ endif
+
+ call add(l:output, l:result)
endfor
return l:output
diff --git a/autoload/ale/handlers/shellcheck.vim b/autoload/ale/handlers/shellcheck.vim
new file mode 100644
index 00000000..b16280f0
--- /dev/null
+++ b/autoload/ale/handlers/shellcheck.vim
@@ -0,0 +1,107 @@
+" Author: w0rp <devw0rp@gmail.com>
+" Description: This file adds support for using the shellcheck linter
+
+function! ale#handlers#shellcheck#GetDialectArgument(buffer) abort
+ let l:shell_type = ale#handlers#sh#GetShellType(a:buffer)
+
+ if !empty(l:shell_type)
+ " Use the dash dialect for /bin/ash, etc.
+ if l:shell_type is# 'ash'
+ return 'dash'
+ endif
+
+ return l:shell_type
+ endif
+
+ " If there's no hashbang, try using Vim's buffer variables.
+ if getbufvar(a:buffer, 'is_bash', 0)
+ return 'bash'
+ elseif getbufvar(a:buffer, 'is_sh', 0)
+ return 'sh'
+ elseif getbufvar(a:buffer, 'is_kornshell', 0)
+ return 'ksh'
+ endif
+
+ return ''
+endfunction
+
+function! ale#handlers#shellcheck#GetCommand(buffer, version) abort
+ let l:options = ale#Var(a:buffer, 'sh_shellcheck_options')
+ let l:exclude_option = ale#Var(a:buffer, 'sh_shellcheck_exclusions')
+ let l:dialect = ale#Var(a:buffer, 'sh_shellcheck_dialect')
+ let l:external_option = ale#semver#GTE(a:version, [0, 4, 0]) ? ' -x' : ''
+ let l:cd_string = ale#Var(a:buffer, 'sh_shellcheck_change_directory')
+ \ ? ale#path#BufferCdString(a:buffer)
+ \ : ''
+
+ if l:dialect is# 'auto'
+ let l:dialect = ale#handlers#shellcheck#GetDialectArgument(a:buffer)
+ endif
+
+ return l:cd_string
+ \ . '%e'
+ \ . (!empty(l:dialect) ? ' -s ' . l:dialect : '')
+ \ . (!empty(l:options) ? ' ' . l:options : '')
+ \ . (!empty(l:exclude_option) ? ' -e ' . l:exclude_option : '')
+ \ . l:external_option
+ \ . ' -f gcc -'
+endfunction
+
+function! ale#handlers#shellcheck#Handle(buffer, lines) abort
+ let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):(\d+)?:? ([^:]+): (.+) \[([^\]]+)\]$'
+ let l:output = []
+
+ for l:match in ale#util#GetMatches(a:lines, l:pattern)
+ if l:match[4] is# 'error'
+ let l:type = 'E'
+ elseif l:match[4] is# 'note'
+ let l:type = 'I'
+ else
+ let l:type = 'W'
+ endif
+
+ let l:item = {
+ \ 'lnum': str2nr(l:match[2]),
+ \ 'type': l:type,
+ \ 'text': l:match[5],
+ \ 'code': l:match[6],
+ \}
+
+ if !empty(l:match[3])
+ let l:item.col = str2nr(l:match[3])
+ endif
+
+ " If the filename is something like <stdin>, <nofile> or -, then
+ " this is an error for the file we checked.
+ if l:match[1] isnot# '-' && l:match[1][0] isnot# '<'
+ let l:item['filename'] = l:match[1]
+ endif
+
+ call add(l:output, l:item)
+ endfor
+
+ return l:output
+endfunction
+
+function! ale#handlers#shellcheck#DefineLinter(filetype) abort
+ " This global variable can be set with a string of comma-separated error
+ " codes to exclude from shellcheck. For example:
+ " let g:ale_sh_shellcheck_exclusions = 'SC2002,SC2004'
+ call ale#Set('sh_shellcheck_exclusions', get(g:, 'ale_linters_sh_shellcheck_exclusions', ''))
+ call ale#Set('sh_shellcheck_executable', 'shellcheck')
+ call ale#Set('sh_shellcheck_dialect', 'auto')
+ call ale#Set('sh_shellcheck_options', '')
+ call ale#Set('sh_shellcheck_change_directory', 1)
+
+ call ale#linter#Define(a:filetype, {
+ \ 'name': 'shellcheck',
+ \ 'executable': {buffer -> ale#Var(buffer, 'sh_shellcheck_executable')},
+ \ 'command': {buffer -> ale#semver#RunWithVersionCheck(
+ \ buffer,
+ \ ale#Var(buffer, 'sh_shellcheck_executable'),
+ \ '%e --version',
+ \ function('ale#handlers#shellcheck#GetCommand'),
+ \ )},
+ \ 'callback': 'ale#handlers#shellcheck#Handle',
+ \})
+endfunction
diff --git a/autoload/ale/highlight.vim b/autoload/ale/highlight.vim
index 82ad57e0..473ad354 100644
--- a/autoload/ale/highlight.vim
+++ b/autoload/ale/highlight.vim
@@ -210,6 +210,12 @@ function! ale#highlight#SetHighlights(buffer, loclist) abort
" Set the list in the buffer variable.
call setbufvar(str2nr(a:buffer), 'ale_highlight_items', l:new_list)
+ let l:exclude_list = ale#Var(a:buffer, 'exclude_highlights')
+
+ if !empty(l:exclude_list)
+ call filter(l:new_list, 'empty(ale#util#GetMatches(v:val.text, l:exclude_list))')
+ endif
+
" Update highlights for the current buffer, which may or may not
" be the buffer we just set highlights for.
call ale#highlight#UpdateHighlights()
diff --git a/autoload/ale/hover.vim b/autoload/ale/hover.vim
index 8fdd288c..168ff424 100644
--- a/autoload/ale/hover.vim
+++ b/autoload/ale/hover.vim
@@ -42,6 +42,8 @@ function! ale#hover#HandleTSServerResponse(conn_id, response) abort
\&& exists('*balloon_show')
\&& ale#Var(l:options.buffer, 'set_balloons')
call balloon_show(a:response.body.displayString)
+ elseif get(l:options, 'truncated_echo', 0)
+ call ale#cursor#TruncatedEcho(split(a:response.body.displayString, "\n")[0])
elseif g:ale_hover_to_preview
call ale#preview#Show(split(a:response.body.displayString, "\n"), {
\ 'filetype': 'ale-preview.message',
@@ -54,6 +56,137 @@ function! ale#hover#HandleTSServerResponse(conn_id, response) abort
endif
endfunction
+" Convert a language name to another one.
+" The language name could be an empty string or v:null
+function! s:ConvertLanguageName(language) abort
+ return a:language
+endfunction
+
+function! ale#hover#ParseLSPResult(contents) abort
+ let l:includes = {}
+ let l:highlights = []
+ let l:lines = []
+ let l:list = type(a:contents) is v:t_list ? a:contents : [a:contents]
+ let l:region_index = 0
+
+ for l:item in l:list
+ if !empty(l:lines)
+ call add(l:lines, '')
+ endif
+
+ if type(l:item) is v:t_dict && has_key(l:item, 'kind')
+ if l:item.kind is# 'markdown'
+ " Handle markdown values as we handle strings below.
+ let l:item = get(l:item, 'value', '')
+ elseif l:item.kind is# 'plaintext'
+ " We shouldn't try to parse plaintext as markdown.
+ " Pass the lines on and skip parsing them.
+ call extend(l:lines, split(get(l:item, 'value', ''), "\n"))
+
+ continue
+ endif
+ endif
+
+ let l:marked_list = []
+
+ " If the item is a string, then we should parse it as Markdown text.
+ if type(l:item) is v:t_string
+ let l:fence_language = v:null
+ let l:fence_lines = []
+
+ for l:line in split(l:item, "\n")
+ if l:fence_language is v:null
+ " Look for the start of a code fence. (```python, etc.)
+ let l:match = matchlist(l:line, '^```\(.*\)$')
+
+ if !empty(l:match)
+ let l:fence_language = l:match[1]
+
+ if !empty(l:marked_list)
+ call add(l:fence_lines, '')
+ endif
+ else
+ if !empty(l:marked_list)
+ \&& l:marked_list[-1][0] isnot v:null
+ call add(l:marked_list, [v:null, ['']])
+ endif
+
+ call add(l:marked_list, [v:null, [l:line]])
+ endif
+ elseif l:line =~# '^```$'
+ " When we hit the end of a code fence, pass the fenced
+ " lines on to the next steps below.
+ call add(l:marked_list, [l:fence_language, l:fence_lines])
+ let l:fence_language = v:null
+ let l:fence_lines = []
+ else
+ " Gather lines inside of a code fence.
+ call add(l:fence_lines, l:line)
+ endif
+ endfor
+ " If the result from the LSP server is a {language: ..., value: ...}
+ " Dictionary, then that should be interpreted as if it was:
+ "
+ " ```${language}
+ " ${value}
+ " ```
+ elseif type(l:item) is v:t_dict
+ \&& has_key(l:item, 'language')
+ \&& type(l:item.language) is v:t_string
+ \&& has_key(l:item, 'value')
+ \&& type(l:item.value) is v:t_string
+ call add(
+ \ l:marked_list,
+ \ [l:item.language, split(l:item.value, "\n")],
+ \)
+ endif
+
+ for [l:language, l:marked_lines] in l:marked_list
+ if l:language is v:null
+ " NOTE: We could handle other Markdown formatting here.
+ call map(
+ \ l:marked_lines,
+ \ 'substitute(v:val, ''\\_'', ''_'', ''g'')',
+ \)
+ else
+ let l:language = s:ConvertLanguageName(l:language)
+
+ if !empty(l:language)
+ let l:includes[l:language] = printf(
+ \ 'syntax/%s.vim',
+ \ l:language,
+ \)
+
+ let l:start = len(l:lines) + 1
+ let l:end = l:start + len(l:marked_lines)
+ let l:region_index += 1
+
+ call add(l:highlights, 'syntax region'
+ \ . ' ALE_hover_' . l:region_index
+ \ . ' start=/\%' . l:start . 'l/'
+ \ . ' end=/\%' . l:end . 'l/'
+ \ . ' contains=@ALE_hover_' . l:language
+ \)
+ endif
+ endif
+
+ call extend(l:lines, l:marked_lines)
+ endfor
+ endfor
+
+ let l:include_commands = []
+
+ for [l:language, l:lang_path] in sort(items(l:includes))
+ call add(l:include_commands, 'unlet! b:current_syntax')
+ call add(
+ \ l:include_commands,
+ \ printf('syntax include @ALE_hover_%s %s', l:language, l:lang_path),
+ \)
+ endfor
+
+ return [l:include_commands + l:highlights, l:lines]
+endfunction
+
function! ale#hover#HandleLSPResponse(conn_id, response) abort
if has_key(a:response, 'id')
\&& has_key(s:hover_map, a:response.id)
@@ -80,37 +213,25 @@ function! ale#hover#HandleLSPResponse(conn_id, response) abort
return
endif
- let l:result = l:result.contents
-
- if type(l:result) is v:t_string
- " The result can be just a string.
- let l:result = [l:result]
- endif
-
- 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
+ let [l:commands, l:lines] = ale#hover#ParseLSPResult(l:result.contents)
- if type(l:result) is v:t_list
- " Replace objects with text values.
- 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', '')
-
- if !empty(l:str)
- if get(l:options, 'hover_from_balloonexpr', 0)
- \&& exists('*balloon_show')
- \&& ale#Var(l:options.buffer, 'set_balloons')
- call balloon_show(l:str)
- elseif g:ale_hover_to_preview
- call ale#preview#Show(split(l:str, "\n"), {
- \ 'filetype': 'ale-preview.message',
- \ 'stay_here': 1,
- \})
- else
- call ale#util#ShowMessage(l:str)
- endif
+ if !empty(l:lines)
+ if get(l:options, 'hover_from_balloonexpr', 0)
+ \&& exists('*balloon_show')
+ \&& ale#Var(l:options.buffer, 'set_balloons')
+ call balloon_show(join(l:lines, "\n"))
+ elseif get(l:options, 'truncated_echo', 0)
+ call ale#cursor#TruncatedEcho(l:lines[0])
+ elseif g:ale_hover_to_preview
+ call ale#preview#Show(l:lines, {
+ \ 'filetype': 'ale-preview.message',
+ \ 'stay_here': 1,
+ \ 'commands': l:commands,
+ \})
+ else
+ call ale#util#ShowMessage(join(l:lines, "\n"), {
+ \ 'commands': l:commands,
+ \})
endif
endif
endif
@@ -156,6 +277,7 @@ function! s:OnReady(line, column, opt, linter, lsp_details) abort
\ 'column': l:column,
\ 'hover_from_balloonexpr': get(a:opt, 'called_from_balloonexpr', 0),
\ 'show_documentation': get(a:opt, 'show_documentation', 0),
+ \ 'truncated_echo': get(a:opt, 'truncated_echo', 0),
\}
endfunction
@@ -181,6 +303,8 @@ function! ale#hover#Show(buffer, line, col, opt) abort
endfor
endfunction
+let s:last_pos = [0, 0, 0]
+
" This function implements the :ALEHover command.
function! ale#hover#ShowAtCursor() abort
let l:buffer = bufnr('')
@@ -189,6 +313,25 @@ function! ale#hover#ShowAtCursor() abort
call ale#hover#Show(l:buffer, l:pos[1], l:pos[2], {})
endfunction
+function! ale#hover#ShowTruncatedMessageAtCursor() abort
+ let l:buffer = bufnr('')
+ let l:pos = getpos('.')[0:2]
+
+ if l:pos != s:last_pos
+ let s:last_pos = l:pos
+ let [l:info, l:loc] = ale#util#FindItemAtCursor(l:buffer)
+
+ if empty(l:loc)
+ call ale#hover#Show(
+ \ l:buffer,
+ \ l:pos[1],
+ \ l:pos[2],
+ \ {'truncated_echo': 1},
+ \)
+ endif
+ endif
+endfunction
+
" This function implements the :ALEDocumentation command.
function! ale#hover#ShowDocumentationAtCursor() abort
let l:buffer = bufnr('')
diff --git a/autoload/ale/linter.vim b/autoload/ale/linter.vim
index f266a105..0e935149 100644
--- a/autoload/ale/linter.vim
+++ b/autoload/ale/linter.vim
@@ -45,7 +45,7 @@ let s:default_ale_linters = {
\ 'help': [],
\ 'perl': ['perlcritic'],
\ 'perl6': [],
-\ 'python': ['flake8', 'mypy', 'pylint'],
+\ 'python': ['flake8', 'mypy', 'pylint', 'pyright'],
\ 'rust': ['cargo'],
\ 'spec': [],
\ 'text': [],
diff --git a/autoload/ale/lsp.vim b/autoload/ale/lsp.vim
index 2509174e..ae8fd51d 100644
--- a/autoload/ale/lsp.vim
+++ b/autoload/ale/lsp.vim
@@ -196,14 +196,26 @@ function! s:UpdateCapabilities(conn, capabilities) abort
let a:conn.capabilities.hover = 1
endif
+ if type(get(a:capabilities, 'hoverProvider')) is v:t_dict
+ let a:conn.capabilities.hover = 1
+ endif
+
if get(a:capabilities, 'referencesProvider') is v:true
let a:conn.capabilities.references = 1
endif
+ if type(get(a:capabilities, 'referencesProvider')) is v:t_dict
+ let a:conn.capabilities.references = 1
+ endif
+
if get(a:capabilities, 'renameProvider') is v:true
let a:conn.capabilities.rename = 1
endif
+ if type(get(a:capabilities, 'renameProvider')) is v:t_dict
+ let a:conn.capabilities.rename = 1
+ endif
+
if !empty(get(a:capabilities, 'completionProvider'))
let a:conn.capabilities.completion = 1
endif
@@ -220,13 +232,25 @@ function! s:UpdateCapabilities(conn, capabilities) abort
let a:conn.capabilities.definition = 1
endif
+ if type(get(a:capabilities, 'definitionProvider')) is v:t_dict
+ let a:conn.capabilities.definition = 1
+ endif
+
if get(a:capabilities, 'typeDefinitionProvider') is v:true
let a:conn.capabilities.typeDefinition = 1
endif
+ if type(get(a:capabilities, 'typeDefinitionProvider')) is v:t_dict
+ let a:conn.capabilities.typeDefinition = 1
+ endif
+
if get(a:capabilities, 'workspaceSymbolProvider') is v:true
let a:conn.capabilities.symbol_search = 1
endif
+
+ if type(get(a:capabilities, 'workspaceSymbolProvider')) is v:t_dict
+ let a:conn.capabilities.symbol_search = 1
+ endif
endfunction
" Update a connection's configuration dictionary and notify LSP servers
diff --git a/autoload/ale/node.vim b/autoload/ale/node.vim
index 69060122..9b9b335a 100644
--- a/autoload/ale/node.vim
+++ b/autoload/ale/node.vim
@@ -12,6 +12,18 @@ function! ale#node#FindExecutable(buffer, base_var_name, path_list) abort
return ale#Var(a:buffer, a:base_var_name . '_executable')
endif
+ let l:nearest = ale#node#FindNearestExecutable(a:buffer, a:path_list)
+
+ if !empty(l:nearest)
+ return l:nearest
+ endif
+
+ return ale#Var(a:buffer, a:base_var_name . '_executable')
+endfunction
+
+" Given a buffer number, a base variable name, and a list of paths to search
+" for in ancestor directories, detect the executable path for a Node program.
+function! ale#node#FindNearestExecutable(buffer, path_list) abort
for l:path in a:path_list
let l:executable = ale#path#FindNearestFile(a:buffer, l:path)
@@ -20,7 +32,7 @@ function! ale#node#FindExecutable(buffer, base_var_name, path_list) abort
endif
endfor
- return ale#Var(a:buffer, a:base_var_name . '_executable')
+ return ''
endfunction
" Create a executable string which executes a Node.js script command with a
diff --git a/autoload/ale/organize_imports.vim b/autoload/ale/organize_imports.vim
index bc9b829e..e89c832c 100644
--- a/autoload/ale/organize_imports.vim
+++ b/autoload/ale/organize_imports.vim
@@ -15,7 +15,7 @@ function! ale#organize_imports#HandleTSServerResponse(conn_id, response) abort
call ale#code_action#HandleCodeAction({
\ 'description': 'Organize Imports',
\ 'changes': l:file_code_edits,
- \})
+ \}, v:false)
endfunction
function! s:OnReady(linter, lsp_details) abort
diff --git a/autoload/ale/preview.vim b/autoload/ale/preview.vim
index 6d58aca9..faf45cb0 100644
--- a/autoload/ale/preview.vim
+++ b/autoload/ale/preview.vim
@@ -1,6 +1,14 @@
" Author: w0rp <devw0rp@gmail.com>
" Description: Preview windows for showing whatever information in.
+if !has_key(s:, 'last_selection_list')
+ let s:last_selection_list = []
+endif
+
+if !has_key(s:, 'last_selection_open_in')
+ let s:last_selection_open_in = 'current-buffer'
+endif
+
" Open a preview window and show some lines in it.
" A second argument can be passed as a Dictionary with options. They are...
"
@@ -23,6 +31,10 @@ function! ale#preview#Show(lines, ...) abort
setlocal readonly
let &l:filetype = get(l:options, 'filetype', 'ale-preview')
+ for l:command in get(l:options, 'commands', [])
+ call execute(l:command)
+ endfor
+
if get(l:options, 'stay_here')
wincmd p
endif
@@ -67,9 +79,24 @@ function! ale#preview#ShowSelection(item_list, ...) abort
call ale#preview#Show(l:lines, {'filetype': 'ale-preview-selection'})
let b:ale_preview_item_list = a:item_list
+ let b:ale_preview_item_open_in = get(l:options, 'open_in', 'current-buffer')
+
+ " Remove the last preview
+ let s:last_selection_list = b:ale_preview_item_list
+ let s:last_selection_open_in = b:ale_preview_item_open_in
endfunction
-function! s:Open(open_in_tab) abort
+function! ale#preview#RepeatSelection() abort
+ if empty(s:last_selection_list)
+ return
+ endif
+
+ call ale#preview#ShowSelection(s:last_selection_list, {
+ \ 'open_in': s:last_selection_open_in,
+ \})
+endfunction
+
+function! s:Open(open_in) abort
let l:item_list = get(b:, 'ale_preview_item_list', [])
let l:item = get(l:item_list, getpos('.')[1] - 1, {})
@@ -77,22 +104,20 @@ function! s:Open(open_in_tab) abort
return
endif
- if !a:open_in_tab
- :q!
- endif
+ :q!
call ale#util#Open(
\ l:item.filename,
\ l:item.line,
\ l:item.column,
- \ {'open_in_tab': a:open_in_tab},
+ \ {'open_in': a:open_in},
\)
endfunction
-function! ale#preview#OpenSelectionInBuffer() abort
- call s:Open(0)
+function! ale#preview#OpenSelection() abort
+ call s:Open(b:ale_preview_item_open_in)
endfunction
function! ale#preview#OpenSelectionInTab() abort
- call s:Open(1)
+ call s:Open('tab')
endfunction
diff --git a/autoload/ale/references.vim b/autoload/ale/references.vim
index b9725e1e..38ff0d3d 100644
--- a/autoload/ale/references.vim
+++ b/autoload/ale/references.vim
@@ -1,3 +1,5 @@
+let g:ale_default_navigation = get(g:, 'ale_default_navigation', 'buffer')
+
let s:references_map = {}
" Used to get the references map in tests.
@@ -99,7 +101,8 @@ function! s:OnReady(line, column, options, linter, lsp_details) abort
let l:request_id = ale#lsp#Send(l:id, l:message)
let s:references_map[l:request_id] = {
- \ 'use_relative_paths': has_key(a:options, 'use_relative_paths') ? a:options.use_relative_paths : 0
+ \ 'use_relative_paths': has_key(a:options, 'use_relative_paths') ? a:options.use_relative_paths : 0,
+ \ 'open_in': get(a:options, 'open_in', 'current-buffer'),
\}
endfunction
@@ -110,10 +113,24 @@ function! ale#references#Find(...) abort
for l:option in a:000
if l:option is? '-relative'
let l:options.use_relative_paths = 1
+ elseif l:option is? '-tab'
+ let l:options.open_in = 'tab'
+ elseif l:option is? '-split'
+ let l:options.open_in = 'split'
+ elseif l:option is? '-vsplit'
+ let l:options.open_in = 'vsplit'
endif
endfor
endif
+ if !has_key(l:options, 'open_in')
+ let l:default_navigation = ale#Var(bufnr(''), 'default_navigation')
+
+ if index(['tab', 'split', 'vsplit'], l:default_navigation) >= 0
+ let l:options.open_in = l:default_navigation
+ endif
+ endif
+
let l:buffer = bufnr('')
let [l:line, l:column] = getpos('.')[1:2]
let l:column = min([l:column, len(getline(l:line))])
diff --git a/autoload/ale/rename.vim b/autoload/ale/rename.vim
index 02b7b579..64952e63 100644
--- a/autoload/ale/rename.vim
+++ b/autoload/ale/rename.vim
@@ -80,7 +80,32 @@ function! ale#rename#HandleTSServerResponse(conn_id, response) abort
call ale#code_action#HandleCodeAction({
\ 'description': 'rename',
\ 'changes': l:changes,
- \})
+ \}, v:true)
+endfunction
+
+function! s:getChanges(workspace_edit) abort
+ let l:changes = {}
+
+ if has_key(a:workspace_edit, 'changes') && !empty(a:workspace_edit.changes)
+ return a:workspace_edit.changes
+ elseif has_key(a:workspace_edit, 'documentChanges')
+ let l:document_changes = []
+
+ if type(a:workspace_edit.documentChanges) is v:t_dict
+ \ && has_key(a:workspace_edit.documentChanges, 'edits')
+ call add(l:document_changes, a:workspace_edit.documentChanges)
+ elseif type(a:workspace_edit.documentChanges) is v:t_list
+ let l:document_changes = a:workspace_edit.documentChanges
+ endif
+
+ for l:text_document_edit in l:document_changes
+ let l:filename = l:text_document_edit.textDocument.uri
+ let l:edits = l:text_document_edit.edits
+ let l:changes[l:filename] = l:edits
+ endfor
+ endif
+
+ return l:changes
endfunction
function! ale#rename#HandleLSPResponse(conn_id, response) abort
@@ -94,9 +119,9 @@ function! ale#rename#HandleLSPResponse(conn_id, response) abort
return
endif
- let l:workspace_edit = a:response.result
+ let l:changes_map = s:getChanges(a:response.result)
- if !has_key(l:workspace_edit, 'changes') || empty(l:workspace_edit.changes)
+ if empty(l:changes_map)
call s:message('No changes received from server')
return
@@ -104,8 +129,8 @@ function! ale#rename#HandleLSPResponse(conn_id, response) abort
let l:changes = []
- for l:file_name in keys(l:workspace_edit.changes)
- let l:text_edits = l:workspace_edit.changes[l:file_name]
+ for l:file_name in keys(l:changes_map)
+ let l:text_edits = l:changes_map[l:file_name]
let l:text_changes = []
for l:edit in l:text_edits
@@ -134,7 +159,7 @@ function! ale#rename#HandleLSPResponse(conn_id, response) abort
call ale#code_action#HandleCodeAction({
\ 'description': 'rename',
\ 'changes': l:changes,
- \})
+ \}, v:true)
endif
endfunction
diff --git a/autoload/ale/sign.vim b/autoload/ale/sign.vim
index db0e1ab6..8109c60e 100644
--- a/autoload/ale/sign.vim
+++ b/autoload/ale/sign.vim
@@ -23,7 +23,7 @@ let g:ale_sign_offset = get(g:, 'ale_sign_offset', 1000000)
let g:ale_sign_column_always = get(g:, 'ale_sign_column_always', 0)
let g:ale_sign_highlight_linenrs = get(g:, 'ale_sign_highlight_linenrs', 0)
-let s:supports_sign_groups = has('nvim-0.4.2') || (v:version >= 801 && has('patch614'))
+let s:supports_sign_groups = has('nvim-0.4.2') || has('patch-8.1.614')
if !hlexists('ALEErrorSign')
highlight link ALEErrorSign error
diff --git a/autoload/ale/uri.vim b/autoload/ale/uri.vim
index 934637d9..e71c6340 100644
--- a/autoload/ale/uri.vim
+++ b/autoload/ale/uri.vim
@@ -1,9 +1,18 @@
-" This probably doesn't handle Unicode characters well.
+function! s:EncodeChar(char) abort
+ let l:result = ''
+
+ for l:index in range(strlen(a:char))
+ let l:result .= printf('%%%02x', char2nr(a:char[l:index]))
+ endfor
+
+ return l:result
+endfunction
+
function! ale#uri#Encode(value) abort
return substitute(
\ a:value,
\ '\([^a-zA-Z0-9\\/$\-_.!*''(),]\)',
- \ '\=printf(''%%%02x'', char2nr(submatch(1)))',
+ \ '\=s:EncodeChar(submatch(1))',
\ 'g'
\)
endfunction
@@ -12,7 +21,7 @@ function! ale#uri#Decode(value) abort
return substitute(
\ a:value,
\ '%\(\x\x\)',
- \ '\=nr2char(''0x'' . submatch(1))',
+ \ '\=printf("%c", str2nr(submatch(1), 16))',
\ 'g'
\)
endfunction
diff --git a/autoload/ale/util.vim b/autoload/ale/util.vim
index 99cd856a..bb9c1961 100644
--- a/autoload/ale/util.vim
+++ b/autoload/ale/util.vim
@@ -16,7 +16,9 @@ endfunction
" Vim 8 does not support echoing long messages from asynchronous callbacks,
" but NeoVim does. Small messages can be echoed in Vim 8, and larger messages
" have to be shown in preview windows.
-function! ale#util#ShowMessage(string) abort
+function! ale#util#ShowMessage(string, ...) abort
+ let l:options = get(a:000, 0, {})
+
if !has('nvim')
call ale#preview#CloseIfTypeMatches('ale-preview.message')
endif
@@ -25,10 +27,13 @@ function! ale#util#ShowMessage(string) abort
if has('nvim') || (a:string !~? "\n" && len(a:string) < &columns)
execute 'echo a:string'
else
- call ale#preview#Show(split(a:string, "\n"), {
- \ 'filetype': 'ale-preview.message',
- \ 'stay_here': 1,
- \})
+ call ale#preview#Show(split(a:string, "\n"), extend(
+ \ {
+ \ 'filetype': 'ale-preview.message',
+ \ 'stay_here': 1,
+ \ },
+ \ l:options,
+ \))
endif
endfunction
@@ -91,17 +96,17 @@ endfunction
" options['open_in'] can be:
" current-buffer (default)
" tab
-" vertical-split
-" horizontal-split
+" split
+" vsplit
function! ale#util#Open(filename, line, column, options) abort
let l:open_in = get(a:options, 'open_in', 'current-buffer')
let l:args_to_open = '+' . a:line . ' ' . fnameescape(a:filename)
if l:open_in is# 'tab'
call ale#util#Execute('tabedit ' . l:args_to_open)
- elseif l:open_in is# 'horizontal-split'
+ elseif l:open_in is# 'split'
call ale#util#Execute('split ' . l:args_to_open)
- elseif l:open_in is# 'vertical-split'
+ elseif l:open_in is# 'vsplit'
call ale#util#Execute('vsplit ' . l:args_to_open)
elseif bufnr(a:filename) isnot bufnr('')
" Open another file only if we need to.
@@ -336,15 +341,11 @@ function! ale#util#GetMatches(lines, patterns) abort
endfunction
function! s:LoadArgCount(function) abort
- let l:Function = a:function
-
- redir => l:output
- silent! function Function
- redir END
-
- if !exists('l:output')
+ try
+ let l:output = execute('function a:function')
+ catch /E123/
return 0
- endif
+ endtry
let l:match = matchstr(split(l:output, "\n")[0], '\v\([^)]+\)')[1:-2]
let l:arg_list = filter(split(l:match, ', '), 'v:val isnot# ''...''')
@@ -480,3 +481,44 @@ endfunction
function! ale#util#Input(message, value) abort
return input(a:message, a:value)
endfunction
+
+function! ale#util#HasBuflineApi() abort
+ return exists('*deletebufline') && exists('*setbufline')
+endfunction
+
+" Sets buffer contents to lines
+function! ale#util#SetBufferContents(buffer, lines) abort
+ let l:has_bufline_api = ale#util#HasBuflineApi()
+
+ if !l:has_bufline_api && a:buffer isnot bufnr('')
+ return
+ endif
+
+ " 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:new_lines = getbufvar(a:buffer, '&fileformat') is# 'dos'
+ \ ? map(copy(a:lines), 'substitute(v:val, ''\r\+$'', '''', '''')')
+ \ : a:lines
+ let l:first_line_to_remove = len(l:new_lines) + 1
+
+ " Use a Vim API for setting lines in other buffers, if available.
+ if l:has_bufline_api
+ call setbufline(a:buffer, 1, l:new_lines)
+ call deletebufline(a:buffer, l:first_line_to_remove, '$')
+ " Fall back on setting lines the old way, for the current buffer.
+ else
+ let l:old_line_length = line('$')
+
+ if l:old_line_length >= l:first_line_to_remove
+ let l:save = winsaveview()
+ silent execute
+ \ l:first_line_to_remove . ',' . l:old_line_length . 'd_'
+ call winrestview(l:save)
+ endif
+
+ call setline(1, l:new_lines)
+ endif
+
+ return l:new_lines
+endfunction