summaryrefslogtreecommitdiff
path: root/autoload
diff options
context:
space:
mode:
Diffstat (limited to 'autoload')
-rw-r--r--autoload/ale.vim9
-rw-r--r--autoload/ale/assert.vim14
-rw-r--r--autoload/ale/c.vim149
-rw-r--r--autoload/ale/code_action.vim163
-rw-r--r--autoload/ale/completion.vim102
-rw-r--r--autoload/ale/debugging.vim8
-rw-r--r--autoload/ale/fix.vim4
-rw-r--r--autoload/ale/fix/registry.vim32
-rw-r--r--autoload/ale/fixers/black.vim4
-rw-r--r--autoload/ale/fixers/dfmt.vim18
-rw-r--r--autoload/ale/fixers/gnatpp.vim17
-rw-r--r--autoload/ale/fixers/hindent.vim20
-rw-r--r--autoload/ale/fixers/html_beautify.vim21
-rw-r--r--autoload/ale/fixers/nixpkgsfmt.vim12
-rw-r--r--autoload/ale/fixers/rubocop.vim2
-rw-r--r--autoload/ale/fixers/sorbet.vim2
-rw-r--r--autoload/ale/fixers/sqlformat.vim16
-rw-r--r--autoload/ale/fixers/standardrb.vim2
-rw-r--r--autoload/ale/fixers/stylelint.vim9
-rw-r--r--autoload/ale/handlers/languagetool.vim5
-rw-r--r--autoload/ale/handlers/ruby.vim8
-rw-r--r--autoload/ale/highlight.vim71
-rw-r--r--autoload/ale/linter.vim3
-rw-r--r--autoload/ale/list.vim37
-rw-r--r--autoload/ale/lsp.vim6
-rw-r--r--autoload/ale/lsp/message.vim10
-rw-r--r--autoload/ale/lsp/tsserver_message.vim28
-rw-r--r--autoload/ale/lsp_linter.vim6
-rw-r--r--autoload/ale/lsp_window.vim58
-rw-r--r--autoload/ale/organize_imports.vim59
-rw-r--r--autoload/ale/path.vim4
-rw-r--r--autoload/ale/rename.vim225
-rw-r--r--autoload/ale/ruby.vim7
-rw-r--r--autoload/ale/sign.vim76
-rw-r--r--autoload/ale/util.vim3
-rw-r--r--autoload/asyncomplete/sources/ale.vim26
36 files changed, 1127 insertions, 109 deletions
diff --git a/autoload/ale.vim b/autoload/ale.vim
index 3a4e79c8..ee1a0d54 100644
--- a/autoload/ale.vim
+++ b/autoload/ale.vim
@@ -5,11 +5,18 @@
" Strings used for severity in the echoed message
let g:ale_echo_msg_error_str = get(g:, 'ale_echo_msg_error_str', 'Error')
let g:ale_echo_msg_info_str = get(g:, 'ale_echo_msg_info_str', 'Info')
+let g:ale_echo_msg_log_str = get(g:, 'ale_echo_msg_log_str', 'Log')
let g:ale_echo_msg_warning_str = get(g:, 'ale_echo_msg_warning_str', 'Warning')
" Ignoring linters, for disabling some, or ignoring LSP diagnostics.
let g:ale_linters_ignore = get(g:, 'ale_linters_ignore', {})
let g:ale_disable_lsp = get(g:, 'ale_disable_lsp', 0)
+" LSP window/showMessage format
+let g:ale_lsp_show_message_format = get(g:, 'ale_lsp_show_message_format', '%severity%:%linter%: %s')
+" Valid values mimic LSP definitions (error, warning and information; log is
+" never shown)
+let g:ale_lsp_show_message_severity = get(g:, 'ale_lsp_show_message_severity', 'error')
+
let s:lint_timer = -1
let s:getcmdwintype_exists = exists('*getcmdwintype')
@@ -156,7 +163,7 @@ function! ale#Queue(delay, ...) abort
endif
endfunction
-let s:current_ale_version = [2, 5, 0]
+let s:current_ale_version = [2, 6, 0]
" A function used to check for ALE features in files outside of the project.
function! ale#Has(feature) abort
diff --git a/autoload/ale/assert.vim b/autoload/ale/assert.vim
index dac5efb7..291edcee 100644
--- a/autoload/ale/assert.vim
+++ b/autoload/ale/assert.vim
@@ -267,14 +267,22 @@ function! ale#assert#TearDownLinterTest() abort
endif
endfunction
-function! ale#assert#SetUpFixerTest(filetype, name) abort
+function! ale#assert#SetUpFixerTest(filetype, name, ...) abort
+ " If the suffix of the option names format is different, an additional
+ " argument can be used for that instead.
+ if a:0 > 1
+ throw 'Too many arguments'
+ endif
+
" Set up a marker so ALE doesn't create real random temporary filenames.
let g:ale_create_dummy_temporary_file = 1
let l:function_name = ale#fix#registry#GetFunc(a:name)
let s:FixerFunction = function(l:function_name)
- let l:prefix = 'ale_' . a:filetype . '_' . a:name
+ let l:option_suffix = get(a:000, 0, a:name)
+ let l:prefix = 'ale_' . a:filetype . '_'
+ \ . substitute(l:option_suffix, '-', '_', 'g')
let b:filter_expr = 'v:val[: len(l:prefix) - 1] is# l:prefix'
for l:key in filter(keys(g:), b:filter_expr)
@@ -286,7 +294,7 @@ function! ale#assert#SetUpFixerTest(filetype, name) abort
unlet b:[l:key]
endfor
- execute 'runtime autoload/ale/fixers/' . a:name . '.vim'
+ execute 'runtime autoload/ale/fixers/' . substitute(a:name, '-', '_', 'g') . '.vim'
if !exists('g:dir')
call ale#test#SetDirectory('/testplugin/test/fixers')
diff --git a/autoload/ale/c.vim b/autoload/ale/c.vim
index 2d2083da..9b428700 100644
--- a/autoload/ale/c.vim
+++ b/autoload/ale/c.vim
@@ -28,76 +28,107 @@ function! ale#c#GetBuildDirectory(buffer) abort
return ale#path#Dirname(l:json_file)
endfunction
-function! ale#c#AreSpecialCharsBalanced(option) abort
- " Escape \"
- let l:option_escaped = substitute(a:option, '\\"', '', 'g')
-
- " Retain special chars only
- let l:special_chars = substitute(l:option_escaped, '[^"''()`]', '', 'g')
- let l:special_chars = split(l:special_chars, '\zs')
-
- " Check if they are balanced
+function! ale#c#ShellSplit(line) abort
let l:stack = []
+ let l:args = ['']
+ let l:prev = ''
- for l:char in l:special_chars
- if l:char is# ')'
- if len(l:stack) == 0 || get(l:stack, -1) isnot# '('
- return 0
+ for l:char in split(a:line, '\zs')
+ if l:char is# ''''
+ if len(l:stack) > 0 && get(l:stack, -1) is# ''''
+ call remove(l:stack, -1)
+ elseif (len(l:stack) == 0 || get(l:stack, -1) isnot# '"') && l:prev isnot# '\'
+ call add(l:stack, l:char)
endif
-
- call remove(l:stack, -1)
- elseif l:char is# '('
- call add(l:stack, l:char)
- else
+ elseif (l:char is# '"' || l:char is# '`') && l:prev isnot# '\'
if len(l:stack) > 0 && get(l:stack, -1) is# l:char
call remove(l:stack, -1)
- else
+ elseif len(l:stack) == 0 || get(l:stack, -1) isnot# ''''
+ call add(l:stack, l:char)
+ endif
+ elseif (l:char is# '(' || l:char is# '[' || l:char is# '{') && l:prev isnot# '\'
+ if len(l:stack) == 0 || get(l:stack, -1) isnot# ''''
call add(l:stack, l:char)
endif
+ elseif (l:char is# ')' || l:char is# ']' || l:char is# '}') && l:prev isnot# '\'
+ if len(l:stack) > 0 && get(l:stack, -1) is# {')': '(', ']': '[', '}': '{'}[l:char]
+ call remove(l:stack, -1)
+ endif
+ elseif l:char is# ' ' && len(l:stack) == 0
+ if len(get(l:args, -1)) > 0
+ call add(l:args, '')
+ endif
+
+ continue
endif
+
+ let l:args[-1] = get(l:args, -1) . l:char
endfor
- return len(l:stack) == 0
+ return l:args
endfunction
function! ale#c#ParseCFlags(path_prefix, cflag_line) abort
- let l:split_lines = split(a:cflag_line)
+ let l:cflags_list = []
+
+ let l:split_lines = ale#c#ShellSplit(a:cflag_line)
let l:option_index = 0
while l:option_index < len(l:split_lines)
- let l:next_option_index = l:option_index + 1
-
- " Join space-separated option
- while l:next_option_index < len(l:split_lines)
- \&& stridx(l:split_lines[l:next_option_index], '-') != 0
- let l:next_option_index += 1
- endwhile
-
- let l:option = join(l:split_lines[l:option_index : l:next_option_index-1], ' ')
- call remove(l:split_lines, l:option_index, l:next_option_index-1)
- call insert(l:split_lines, l:option, l:option_index)
-
- " Ignore invalid or conflicting options
- if stridx(l:option, '-') != 0
- \|| stridx(l:option, '-o') == 0
- \|| stridx(l:option, '-c') == 0
- call remove(l:split_lines, l:option_index)
- let l:option_index = l:option_index - 1
- " Fix relative path
- elseif stridx(l:option, '-I') == 0
- if !(stridx(l:option, ':') == 2+1 || stridx(l:option, '/') == 2+0)
- let l:option = '-I' . a:path_prefix . s:sep . l:option[2:]
- call remove(l:split_lines, l:option_index)
- call insert(l:split_lines, l:option, l:option_index)
+ let l:option = l:split_lines[l:option_index]
+ let l:option_index = l:option_index + 1
+
+ " Include options, that may need relative path fix
+ if stridx(l:option, '-I') == 0
+ \ || stridx(l:option, '-iquote') == 0
+ \ || stridx(l:option, '-isystem') == 0
+ \ || stridx(l:option, '-idirafter') == 0
+ if stridx(l:option, '-I') == 0 && l:option isnot# '-I'
+ let l:arg = join(split(l:option, '\zs')[2:], '')
+ let l:option = '-I'
+ else
+ let l:arg = l:split_lines[l:option_index]
+ let l:option_index = l:option_index + 1
endif
- endif
- let l:option_index = l:option_index + 1
- endwhile
+ " Fix relative paths if needed
+ if stridx(l:arg, s:sep) != 0 && stridx(l:arg, '/') != 0
+ let l:rel_path = substitute(l:arg, '"', '', 'g')
+ let l:rel_path = substitute(l:rel_path, '''', '', 'g')
+ let l:arg = ale#Escape(a:path_prefix . s:sep . l:rel_path)
+ endif
- call uniq(l:split_lines)
+ call add(l:cflags_list, l:option)
+ call add(l:cflags_list, l:arg)
+ " Options with arg that can be grouped with the option or separate
+ elseif stridx(l:option, '-D') == 0 || stridx(l:option, '-B') == 0
+ call add(l:cflags_list, l:option)
- return join(l:split_lines, ' ')
+ if l:option is# '-D' || l:option is# '-B'
+ call add(l:cflags_list, l:split_lines[l:option_index])
+ let l:option_index = l:option_index + 1
+ endif
+ " Options that have an argument (always separate)
+ elseif l:option is# '-iprefix' || stridx(l:option, '-iwithprefix') == 0
+ \ || l:option is# '-isysroot' || l:option is# '-imultilib'
+ call add(l:cflags_list, l:option)
+ call add(l:cflags_list, l:split_lines[l:option_index])
+ let l:option_index = l:option_index + 1
+ " Options without argument
+ elseif (stridx(l:option, '-W') == 0 && stridx(l:option, '-Wa,') != 0 && stridx(l:option, '-Wl,') != 0 && stridx(l:option, '-Wp,') != 0)
+ \ || l:option is# '-w' || stridx(l:option, '-pedantic') == 0
+ \ || l:option is# '-ansi' || stridx(l:option, '-std=') == 0
+ \ || (stridx(l:option, '-f') == 0 && stridx(l:option, '-fdump') != 0 && stridx(l:option, '-fdiagnostics') != 0 && stridx(l:option, '-fno-show-column') != 0)
+ \ || stridx(l:option, '-O') == 0
+ \ || l:option is# '-C' || l:option is# '-CC' || l:option is# '-trigraphs'
+ \ || stridx(l:option, '-nostdinc') == 0 || stridx(l:option, '-iplugindir=') == 0
+ \ || stridx(l:option, '--sysroot=') == 0 || l:option is# '--no-sysroot-suffix'
+ \ || stridx(l:option, '-m') == 0
+ call add(l:cflags_list, l:option)
+ endif
+ endwhile
+
+ return join(l:cflags_list, ' ')
endfunction
function! ale#c#ParseCFlagsFromMakeOutput(buffer, make_output) abort
@@ -234,6 +265,16 @@ function! s:GetLookupFromCompileCommandsFile(compile_commands_file) abort
return l:empty
endfunction
+function! ale#c#GetCompileCommand(json_item) abort
+ if has_key(a:json_item, 'command')
+ return a:json_item.command
+ elseif has_key(a:json_item, 'arguments')
+ return join(a:json_item.arguments, ' ')
+ endif
+
+ return ''
+endfunction
+
function! ale#c#ParseCompileCommandsFlags(buffer, file_lookup, dir_lookup) abort
" Search for an exact file match first.
let l:basename = tolower(expand('#' . a:buffer . ':t'))
@@ -256,15 +297,14 @@ function! ale#c#ParseCompileCommandsFlags(buffer, file_lookup, dir_lookup) abort
for l:item in l:file_list
" Load the flags for this file, or for a source file matching the
" header file.
- if has_key(l:item, 'command')
- \&& (
+ if (
\ bufnr(l:item.file) is a:buffer
\ || (
\ !empty(l:source_file)
\ && l:item.file[-len(l:source_file):] is? l:source_file
\ )
\)
- return ale#c#ParseCFlags(l:item.directory, l:item.command)
+ return ale#c#ParseCFlags(l:item.directory, ale#c#GetCompileCommand(l:item))
endif
endfor
@@ -276,8 +316,7 @@ function! ale#c#ParseCompileCommandsFlags(buffer, file_lookup, dir_lookup) abort
for l:item in l:dir_list
if ale#path#Simplify(fnamemodify(l:item.file, ':h')) is? l:dir
- \&& has_key(l:item, 'command')
- return ale#c#ParseCFlags(l:item.directory, l:item.command)
+ return ale#c#ParseCFlags(l:item.directory, ale#c#GetCompileCommand(l:item))
endif
endfor
diff --git a/autoload/ale/code_action.vim b/autoload/ale/code_action.vim
new file mode 100644
index 00000000..0af1bb70
--- /dev/null
+++ b/autoload/ale/code_action.vim
@@ -0,0 +1,163 @@
+" Author: Jerko Steiner <jerko.steiner@gmail.com>
+" Description: Code action support for LSP / tsserver
+
+function! ale#code_action#HandleCodeAction(code_action) abort
+ let l:current_buffer = bufnr('')
+ let l:changes = a:code_action.changes
+
+ for l:file_code_edit in l:changes
+ let l:buf = bufnr(l:file_code_edit.fileName)
+
+ if l:buf != -1 && l:buf != l:current_buffer && getbufvar(l:buf, '&mod')
+ call ale#util#Execute('echom ''Aborting action, file is unsaved''')
+
+ return
+ endif
+ endfor
+
+ for l:file_code_edit in l:changes
+ call ale#code_action#ApplyChanges(
+ \ l:file_code_edit.fileName, l:file_code_edit.textChanges)
+ endfor
+endfunction
+
+function! ale#code_action#ApplyChanges(filename, changes) abort
+ let l:current_buffer = bufnr('')
+ " The buffer is used to determine the fileformat, if available.
+ let l:buffer = bufnr(a:filename)
+ let l:is_current_buffer = l:buffer > 0 && l:buffer == l:current_buffer
+
+ if l:buffer > 0
+ let l:lines = getbufline(l:buffer, 1, '$')
+ else
+ let l:lines = readfile(a:filename, 'b')
+ endif
+
+ if l:is_current_buffer
+ let l:pos = getpos('.')[1:2]
+ else
+ let l:pos = [1, 1]
+ endif
+
+ " We have to keep track of how many lines we have added, and offset
+ " changes accordingly.
+ let l:line_offset = 0
+ let l:column_offset = 0
+ let l:last_end_line = 0
+
+ for l:code_edit in a:changes
+ if l:code_edit.start.line isnot l:last_end_line
+ let l:column_offset = 0
+ endif
+
+ let l:line = l:code_edit.start.line + l:line_offset
+ let l:column = l:code_edit.start.offset + l:column_offset
+ let l:end_line = l:code_edit.end.line + l:line_offset
+ let l:end_column = l:code_edit.end.offset + l:column_offset
+ let l:text = l:code_edit.newText
+
+ let l:cur_line = l:pos[0]
+ let l:cur_column = l:pos[1]
+
+ let l:last_end_line = l:end_line
+
+ " Adjust the ends according to previous edits.
+ if l:end_line > len(l:lines)
+ let l:end_line_len = 0
+ else
+ let l:end_line_len = len(l:lines[l:end_line - 1])
+ endif
+
+ let l:insertions = split(l:text, '\n', 1)
+
+ if l:line is 1
+ " Same logic as for column below. Vimscript's slice [:-1] will not
+ " be an empty list.
+ let l:start = []
+ else
+ let l:start = l:lines[: l:line - 2]
+ endif
+
+ if l:column is 1
+ " We need to handle column 1 specially, because we can't slice an
+ " empty string ending on index 0.
+ let l:middle = [l:insertions[0]]
+ else
+ let l:middle = [l:lines[l:line - 1][: l:column - 2] . l:insertions[0]]
+ endif
+
+ call extend(l:middle, l:insertions[1:])
+ let l:middle[-1] .= l:lines[l:end_line - 1][l:end_column - 1 :]
+
+ let l:lines_before_change = len(l:lines)
+ let l:lines = l:start + l:middle + l:lines[l:end_line :]
+
+ let l:current_line_offset = len(l:lines) - l:lines_before_change
+ let l:line_offset += l:current_line_offset
+ let l:column_offset = len(l:middle[-1]) - l:end_line_len
+
+ let l:pos = s:UpdateCursor(l:pos,
+ \ [l:line, l:column],
+ \ [l:end_line, l:end_column],
+ \ [l:current_line_offset, l:column_offset])
+ endfor
+
+ if l:lines[-1] is# ''
+ call remove(l:lines, -1)
+ endif
+
+ call ale#util#Writefile(l:buffer, l:lines, a:filename)
+
+ if l:is_current_buffer
+ call ale#util#Execute(':e!')
+ call setpos('.', [0, l:pos[0], l:pos[1], 0])
+ endif
+endfunction
+
+function! s:UpdateCursor(cursor, start, end, offset) abort
+ let l:cur_line = a:cursor[0]
+ let l:cur_column = a:cursor[1]
+ let l:line = a:start[0]
+ let l:column = a:start[1]
+ let l:end_line = a:end[0]
+ let l:end_column = a:end[1]
+ let l:line_offset = a:offset[0]
+ let l:column_offset = a:offset[1]
+
+ if l:end_line < l:cur_line
+ " both start and end lines are before the cursor. only line offset
+ " needs to be updated
+ let l:cur_line += l:line_offset
+ elseif l:end_line == l:cur_line
+ " end line is at the same location as cursor, which means
+ " l:line <= l:cur_line
+ if l:line < l:cur_line || l:column <= l:cur_column
+ " updates are happening either before or around the cursor
+ if l:end_column < l:cur_column
+ " updates are happening before the cursor, update the
+ " column offset for cursor
+ let l:cur_line += l:line_offset
+ let l:cur_column += l:column_offset
+ else
+ " updates are happening around the cursor, move the cursor
+ " to the end of the changes
+ let l:cur_line += l:line_offset
+ let l:cur_column = l:end_column + l:column_offset
+ endif
+ " else is not necessary, it means modifications are happening
+ " after the cursor so no cursor updates need to be done
+ endif
+ else
+ " end line is after the cursor
+ if l:line < l:cur_line || l:line == l:cur_line && l:column <= l:cur_column
+ " changes are happening around the cursor, move the cursor
+ " to the end of the changes
+ let l:cur_line = l:end_line + l:line_offset
+ let l:cur_column = l:end_column + l:column_offset
+ " else is not necesary, it means modifications are happening
+ " after the cursor so no cursor updates need to be done
+ endif
+ endif
+
+ return [l:cur_line, l:cur_column]
+endfunction
diff --git a/autoload/ale/completion.vim b/autoload/ale/completion.vim
index c0e8abd9..fb87f1bb 100644
--- a/autoload/ale/completion.vim
+++ b/autoload/ale/completion.vim
@@ -15,6 +15,7 @@ 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 s:timer_id = -1
let s:last_done_pos = []
@@ -267,6 +268,14 @@ function! ale#completion#Show(result) abort
\ {-> ale#util#FeedKeys("\<Plug>(ale_show_completion_menu)")}
\)
endif
+
+ if l:source is# 'ale-callback'
+ call b:CompleteCallback(b:ale_completion_result)
+ endif
+endfunction
+
+function! ale#completion#GetAllTriggers() abort
+ return deepcopy(s:trigger_character_map)
endfunction
function! s:CompletionStillValid(request_id) abort
@@ -280,6 +289,7 @@ function! s:CompletionStillValid(request_id) abort
\ b:ale_completion_info.column == l:column
\ || b:ale_completion_info.source is# 'deoplete'
\ || b:ale_completion_info.source is# 'ale-omnifunc'
+ \ || b:ale_completion_info.source is# 'ale-callback'
\)
endfunction
@@ -287,7 +297,10 @@ function! ale#completion#ParseTSServerCompletions(response) abort
let l:names = []
for l:suggestion in a:response.body
- call add(l:names, l:suggestion.name)
+ call add(l:names, {
+ \ 'word': l:suggestion.name,
+ \ 'source': get(l:suggestion, 'source', ''),
+ \})
endfor
return l:names
@@ -301,6 +314,10 @@ function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort
for l:suggestion in a:response.body
let l:displayParts = []
+ for l:action in get(l:suggestion, 'codeActions', [])
+ call add(l:displayParts, l:action.description . ' ')
+ endfor
+
for l:part in l:suggestion.displayParts
call add(l:displayParts, l:part.text)
endfor
@@ -321,13 +338,22 @@ function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort
endif
" See :help complete-items
- call add(l:results, {
+ let l:result = {
\ 'word': l:suggestion.name,
\ 'kind': l:kind,
\ 'icase': 1,
\ 'menu': join(l:displayParts, ''),
+ \ 'dup': g:ale_completion_tsserver_autoimport,
\ 'info': join(l:documentationParts, ''),
- \})
+ \}
+
+ if has_key(l:suggestion, 'codeActions')
+ let l:result.user_data = json_encode({
+ \ 'codeActions': l:suggestion.codeActions,
+ \ })
+ endif
+
+ call add(l:results, l:result)
endfor
let l:names = getbufvar(l:buffer, 'ale_tsserver_completion_names', [])
@@ -336,12 +362,12 @@ function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort
let l:names_with_details = map(copy(l:results), 'v:val.word')
let l:missing_names = filter(
\ copy(l:names),
- \ 'index(l:names_with_details, v:val) < 0',
+ \ 'index(l:names_with_details, v:val.word) < 0',
\)
for l:name in l:missing_names
call add(l:results, {
- \ 'word': l:name,
+ \ 'word': l:name.word,
\ 'kind': 'v',
\ 'icase': 1,
\ 'menu': '',
@@ -463,13 +489,29 @@ function! ale#completion#HandleTSServerResponse(conn_id, response) abort
call setbufvar(l:buffer, 'ale_tsserver_completion_names', l:names)
if !empty(l:names)
+ let l:identifiers = []
+
+ for l:name in l:names
+ let l:identifier = {
+ \ 'name': l:name.word,
+ \}
+ let l:source = get(l:name, 'source', '')
+
+ " Empty source results in no details for the completed item
+ if !empty(l:source)
+ call extend(l:identifier, { 'source': l:source })
+ endif
+
+ call add(l:identifiers, l:identifier)
+ endfor
+
let b:ale_completion_info.request_id = ale#lsp#Send(
\ b:ale_completion_info.conn_id,
\ ale#lsp#tsserver_message#CompletionEntryDetails(
\ l:buffer,
\ b:ale_completion_info.line,
\ b:ale_completion_info.column,
- \ l:names,
+ \ l:identifiers,
\ ),
\)
endif
@@ -516,6 +558,7 @@ function! s:OnReady(linter, lsp_details) abort
\ b:ale_completion_info.line,
\ b:ale_completion_info.column,
\ b:ale_completion_info.prefix,
+ \ g:ale_completion_tsserver_autoimport,
\)
else
" Send a message saying the buffer has changed first, otherwise
@@ -560,12 +603,25 @@ endfunction
" This function can be used to manually trigger autocomplete, even when
" g:ale_completion_enabled is set to false
-function! ale#completion#GetCompletions(source) abort
+function! ale#completion#GetCompletions(...) abort
+ let l:source = get(a:000, 0, '')
+ let l:options = get(a:000, 1, {})
+
+ if len(a:000) > 2
+ throw 'Too many arguments!'
+ endif
+
+ let l:CompleteCallback = get(l:options, 'callback', v:null)
+
+ if l:CompleteCallback isnot v:null
+ let b:CompleteCallback = l:CompleteCallback
+ endif
+
let [l:line, l:column] = getpos('.')[1:2]
let l:prefix = ale#completion#GetPrefix(&filetype, l:line, l:column)
- if a:source is# 'ale-automatic' && empty(l:prefix)
+ if l:source is# 'ale-automatic' && empty(l:prefix)
return 0
endif
@@ -578,7 +634,7 @@ function! ale#completion#GetCompletions(source) abort
\ 'prefix': l:prefix,
\ 'conn_id': 0,
\ 'request_id': 0,
- \ 'source': a:source,
+ \ 'source': l:source,
\}
unlet! b:ale_completion_result
@@ -670,6 +726,30 @@ function! ale#completion#Queue() abort
let s:timer_id = timer_start(g:ale_completion_delay, function('s:TimerHandler'))
endfunction
+function! ale#completion#HandleUserData(completed_item) abort
+ let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '')
+
+ if l:source isnot# 'ale-automatic' && l:source isnot# 'ale-manual' && l:source isnot# 'ale-callback'
+ return
+ endif
+
+ let l:user_data_json = get(a:completed_item, 'user_data', '')
+
+ if empty(l:user_data_json)
+ return
+ endif
+
+ let l:user_data = json_decode(l:user_data_json)
+
+ if type(l:user_data) isnot v:t_dict
+ return
+ endif
+
+ for l:code_action in get(l:user_data, 'codeActions', [])
+ call ale#code_action#HandleCodeAction(l:code_action)
+ endfor
+endfunction
+
function! ale#completion#Done() abort
silent! pclose
@@ -678,6 +758,10 @@ function! ale#completion#Done() abort
let s:last_done_pos = getpos('.')[1:2]
endfunction
+augroup ALECompletionActions
+ autocmd CompleteDone * call ale#completion#HandleUserData(v:completed_item)
+augroup END
+
function! s:Setup(enabled) abort
augroup ALECompletionGroup
autocmd!
diff --git a/autoload/ale/debugging.vim b/autoload/ale/debugging.vim
index 7cdbabaa..4e134f8c 100644
--- a/autoload/ale/debugging.vim
+++ b/autoload/ale/debugging.vim
@@ -50,6 +50,7 @@ let s:global_variable_list = [
\ 'ale_sign_style_error',
\ 'ale_sign_style_warning',
\ 'ale_sign_warning',
+\ 'ale_sign_highlight_linenrs',
\ 'ale_statusline_format',
\ 'ale_type_map',
\ 'ale_use_global_executables',
@@ -62,7 +63,7 @@ function! s:Echo(message) abort
execute 'echo a:message'
endfunction
-function! s:GetLinterVariables(filetype, linter_names) abort
+function! s:GetLinterVariables(filetype, exclude_linter_names) abort
let l:variable_list = []
let l:filetype_parts = split(a:filetype, '\.')
@@ -73,7 +74,7 @@ function! s:GetLinterVariables(filetype, linter_names) abort
" Include matching variables.
if !empty(l:match)
\&& index(l:filetype_parts, l:match[1]) >= 0
- \&& index(a:linter_names, l:match[2]) >= 0
+ \&& index(a:exclude_linter_names, l:match[2]) == -1
call add(l:variable_list, l:key)
endif
endfor
@@ -211,10 +212,11 @@ function! ale#debugging#Info() abort
let l:all_names = map(copy(l:all_linters), 'v:val[''name'']')
let l:enabled_names = map(copy(l:enabled_linters), 'v:val[''name'']')
+ let l:exclude_names = filter(copy(l:all_names), 'index(l:enabled_names, v:val) == -1')
" Load linter variables to display
" This must be done after linters are loaded.
- let l:variable_list = s:GetLinterVariables(l:filetype, l:enabled_names)
+ let l:variable_list = s:GetLinterVariables(l:filetype, l:exclude_names)
let l:fixers = ale#fix#registry#SuggestedFixers(l:filetype)
let l:fixers = uniq(sort(l:fixers[0] + l:fixers[1]))
diff --git a/autoload/ale/fix.vim b/autoload/ale/fix.vim
index 9987fbdd..dad9e2bc 100644
--- a/autoload/ale/fix.vim
+++ b/autoload/ale/fix.vim
@@ -47,7 +47,7 @@ function! ale#fix#ApplyQueuedFixes(buffer) abort
set nomodified
endif
else
- call writefile(l:new_lines, expand(a:buffer . ':p')) " no-custom-checks
+ call writefile(l:new_lines, expand('#' . a:buffer . ':p')) " no-custom-checks
call setbufvar(a:buffer, '&modified', 0)
endif
endif
@@ -74,7 +74,7 @@ endfunction
function! ale#fix#ApplyFixes(buffer, output) abort
let l:data = g:ale_fix_buffer_data[a:buffer]
let l:data.output = a:output
- let l:data.changes_made = l:data.lines_before != l:data.output
+ let l:data.changes_made = l:data.lines_before !=# l:data.output " no-custom-checks
let l:data.done = 1
call ale#command#RemoveManagedFiles(a:buffer)
diff --git a/autoload/ale/fix/registry.vim b/autoload/ale/fix/registry.vim
index f6826b02..5ec27a10 100644
--- a/autoload/ale/fix/registry.vim
+++ b/autoload/ale/fix/registry.vim
@@ -27,6 +27,11 @@ let s:default_registry = {
\ 'suggested_filetypes': ['python'],
\ 'description': 'Fix PEP8 issues with black.',
\ },
+\ 'dfmt': {
+\ 'function': 'ale#fixers#dfmt#Fix',
+\ 'suggested_filetypes': ['d'],
+\ 'description': 'Fix D files with dfmt.',
+\ },
\ 'fecs': {
\ 'function': 'ale#fixers#fecs#Fix',
\ 'suggested_filetypes': ['javascript', 'css', 'html'],
@@ -215,6 +220,11 @@ let s:default_registry = {
\ 'suggested_filetypes': ['haskell'],
\ 'description': 'Fix Haskell files with brittany.',
\ },
+\ 'hindent': {
+\ 'function': 'ale#fixers#hindent#Fix',
+\ 'suggested_filetypes': ['haskell'],
+\ 'description': 'Fix Haskell files with hindent.',
+\ },
\ 'hlint': {
\ 'function': 'ale#fixers#hlint#Fix',
\ 'suggested_filetypes': ['haskell'],
@@ -255,6 +265,11 @@ let s:default_registry = {
\ 'suggested_filetypes': ['sql'],
\ 'description': 'Fix SQL files with sqlfmt.',
\ },
+\ 'sqlformat': {
+\ 'function': 'ale#fixers#sqlformat#Fix',
+\ 'suggested_filetypes': ['sql'],
+\ 'description': 'Fix SQL files with sqlformat.',
+\ },
\ 'google_java_format': {
\ 'function': 'ale#fixers#google_java_format#Fix',
\ 'suggested_filetypes': ['java'],
@@ -312,7 +327,7 @@ let s:default_registry = {
\ },
\ 'styler': {
\ 'function': 'ale#fixers#styler#Fix',
-\ 'suggested_filetypes': ['r', 'rmarkdown'],
+\ 'suggested_filetypes': ['r', 'rmarkdown', 'rmd'],
\ 'description': 'Fix R files with styler.',
\ },
\ 'latexindent': {
@@ -330,6 +345,21 @@ let s:default_registry = {
\ 'suggested_filetypes': ['python'],
\ 'description': 'Sort Python imports with reorder-python-imports.',
\ },
+\ 'gnatpp': {
+\ 'function': 'ale#fixers#gnatpp#Fix',
+\ 'suggested_filetypes': ['ada'],
+\ 'description': 'Format Ada files with gnatpp.',
+\ },
+\ 'nixpkgs-fmt': {
+\ 'function': 'ale#fixers#nixpkgsfmt#Fix',
+\ 'suggested_filetypes': ['nix'],
+\ 'description': 'A formatter for Nix code',
+\ },
+\ 'html-beautify': {
+\ 'function': 'ale#fixers#html_beautify#Fix',
+\ 'suggested_filetypes': ['html', 'htmldjango'],
+\ 'description': 'Fix HTML files with html-beautify.',
+\ },
\}
" Reset the function registry to the default entries.
diff --git a/autoload/ale/fixers/black.vim b/autoload/ale/fixers/black.vim
index 367b8d52..fba6c3b4 100644
--- a/autoload/ale/fixers/black.vim
+++ b/autoload/ale/fixers/black.vim
@@ -29,6 +29,10 @@ function! ale#fixers#black#Fix(buffer) abort
let l:options = ale#Var(a:buffer, 'python_black_options')
+ if expand('#' . a:buffer . ':e') is? 'pyi'
+ let l:options .= '--pyi'
+ endif
+
return {
\ 'command': l:cd_string . ale#Escape(l:executable) . l:exec_args
\ . (!empty(l:options) ? ' ' . l:options : '')
diff --git a/autoload/ale/fixers/dfmt.vim b/autoload/ale/fixers/dfmt.vim
new file mode 100644
index 00000000..0072e045
--- /dev/null
+++ b/autoload/ale/fixers/dfmt.vim
@@ -0,0 +1,18 @@
+" Author: theoldmoon0602
+" Description: Integration of dfmt with ALE.
+
+call ale#Set('d_dfmt_executable', 'dfmt')
+call ale#Set('d_dfmt_options', '')
+
+function! ale#fixers#dfmt#Fix(buffer) abort
+ let l:executable = ale#Var(a:buffer, 'd_dfmt_executable')
+ let l:options = ale#Var(a:buffer, 'd_dfmt_options')
+
+ return {
+ \ 'command': ale#Escape(l:executable)
+ \ . ' -i'
+ \ . (empty(l:options) ? '' : ' ' . l:options)
+ \ . ' %t',
+ \ 'read_temporary_file': 1,
+ \}
+endfunction
diff --git a/autoload/ale/fixers/gnatpp.vim b/autoload/ale/fixers/gnatpp.vim
new file mode 100644
index 00000000..bf3d484e
--- /dev/null
+++ b/autoload/ale/fixers/gnatpp.vim
@@ -0,0 +1,17 @@
+" Author: tim <tim@inept.tech>
+" Description: Fix files with gnatpp.
+
+call ale#Set('ada_gnatpp_executable', 'gnatpp')
+call ale#Set('ada_gnatpp_options', '')
+
+function! ale#fixers#gnatpp#Fix(buffer) abort
+ let l:executable = ale#Var(a:buffer, 'ada_gnatpp_executable')
+ let l:options = ale#Var(a:buffer, 'ada_gnatpp_options')
+
+ return {
+ \ 'command': ale#Escape(l:executable)
+ \ . (!empty(l:options) ? ' ' . l:options : '')
+ \ . ' %t',
+ \ 'read_temporary_file': 1,
+ \}
+endfunction
diff --git a/autoload/ale/fixers/hindent.vim b/autoload/ale/fixers/hindent.vim
new file mode 100644
index 00000000..b6009a2c
--- /dev/null
+++ b/autoload/ale/fixers/hindent.vim
@@ -0,0 +1,20 @@
+" Author: AlexeiDrake <drake.alexei@gmail.com>
+" Description: Integration of hindent formatting with ALE.
+"
+call ale#Set('haskell_hindent_executable', 'hindent')
+
+function! ale#fixers#hindent#GetExecutable(buffer) abort
+ let l:executable = ale#Var(a:buffer, 'haskell_hindent_executable')
+
+ return ale#handlers#haskell_stack#EscapeExecutable(l:executable, 'hindent')
+endfunction
+
+function! ale#fixers#hindent#Fix(buffer) abort
+ let l:executable = ale#fixers#hindent#GetExecutable(a:buffer)
+
+ return {
+ \ 'command': l:executable
+ \ . ' %t',
+ \ 'read_temporary_file': 1,
+ \}
+endfunction
diff --git a/autoload/ale/fixers/html_beautify.vim b/autoload/ale/fixers/html_beautify.vim
new file mode 100644
index 00000000..236cb6ec
--- /dev/null
+++ b/autoload/ale/fixers/html_beautify.vim
@@ -0,0 +1,21 @@
+" Author: WhyNotHugo <hugo@barrera.io>
+" Description: Lint HTML files with html-beautify.
+"
+call ale#Set('html_beautify_executable', 'html-beautify')
+call ale#Set('html_beautify_use_global', get(g:, 'ale_use_global_executables', 0))
+call ale#Set('html_beautify_options', '')
+call ale#Set('html_beautify_change_directory', 1)
+
+function! ale#fixers#html_beautify#Fix(buffer) abort
+ let l:executable = ale#python#FindExecutable(
+ \ a:buffer,
+ \ 'html_beautify',
+ \ ['html-beautify']
+ \)
+
+ let l:options = ale#Var(a:buffer, 'html_beautify_options')
+
+ return {
+ \ 'command': ale#Escape(l:executable). ' ' . l:options . ' -',
+ \}
+endfunction
diff --git a/autoload/ale/fixers/nixpkgsfmt.vim b/autoload/ale/fixers/nixpkgsfmt.vim
new file mode 100644
index 00000000..403ce798
--- /dev/null
+++ b/autoload/ale/fixers/nixpkgsfmt.vim
@@ -0,0 +1,12 @@
+call ale#Set('nix_nixpkgsfmt_executable', 'nixpkgs-fmt')
+call ale#Set('nix_nixpkgsfmt_options', '')
+
+function! ale#fixers#nixpkgsfmt#Fix(buffer) abort
+ let l:executable = ale#Var(a:buffer, 'nix_nixpkgsfmt_executable')
+ let l:options = ale#Var(a:buffer, 'nix_nixpkgsfmt_options')
+
+ return {
+ \ 'command': ale#Escape(l:executable)
+ \ . (empty(l:options) ? '' : ' ' . l:options),
+ \}
+endfunction
diff --git a/autoload/ale/fixers/rubocop.vim b/autoload/ale/fixers/rubocop.vim
index 33ba6887..0c7441e4 100644
--- a/autoload/ale/fixers/rubocop.vim
+++ b/autoload/ale/fixers/rubocop.vim
@@ -6,7 +6,7 @@ function! ale#fixers#rubocop#GetCommand(buffer) abort
let l:config = ale#path#FindNearestFile(a:buffer, '.rubocop.yml')
let l:options = ale#Var(a:buffer, 'ruby_rubocop_options')
- return ale#handlers#ruby#EscapeExecutable(l:executable, 'rubocop')
+ 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'
diff --git a/autoload/ale/fixers/sorbet.vim b/autoload/ale/fixers/sorbet.vim
index 182f7300..7c12fa1e 100644
--- a/autoload/ale/fixers/sorbet.vim
+++ b/autoload/ale/fixers/sorbet.vim
@@ -5,7 +5,7 @@ function! ale#fixers#sorbet#GetCommand(buffer) abort
let l:executable = ale#Var(a:buffer, 'ruby_sorbet_executable')
let l:options = ale#Var(a:buffer, 'ruby_sorbet_options')
- return ale#handlers#ruby#EscapeExecutable(l:executable, 'srb')
+ return ale#ruby#EscapeExecutable(l:executable, 'srb')
\ . ' tc'
\ . (!empty(l:options) ? ' ' . l:options : '')
\ . ' --autocorrect --file %t'
diff --git a/autoload/ale/fixers/sqlformat.vim b/autoload/ale/fixers/sqlformat.vim
new file mode 100644
index 00000000..6319c1ac
--- /dev/null
+++ b/autoload/ale/fixers/sqlformat.vim
@@ -0,0 +1,16 @@
+" Author: Cluas <Cluas@live.cn>
+" Description: Fixing files with sqlformat.
+
+call ale#Set('sql_sqlformat_executable', 'sqlformat')
+call ale#Set('sql_sqlformat_options', '')
+
+function! ale#fixers#sqlformat#Fix(buffer) abort
+ let l:executable = ale#Var(a:buffer, 'sql_sqlformat_executable')
+ let l:options = ale#Var(a:buffer, 'sql_sqlformat_options')
+
+ return {
+ \ 'command': ale#Escape(l:executable)
+ \ . (!empty(l:options) ? ' ' . l:options : '')
+ \ . ' -'
+ \}
+endfunction
diff --git a/autoload/ale/fixers/standardrb.vim b/autoload/ale/fixers/standardrb.vim
index fab1e2bc..54330a37 100644
--- a/autoload/ale/fixers/standardrb.vim
+++ b/autoload/ale/fixers/standardrb.vim
@@ -9,7 +9,7 @@ function! ale#fixers#standardrb#GetCommand(buffer) abort
let l:config = ale#path#FindNearestFile(a:buffer, '.standard.yml')
let l:options = ale#Var(a:buffer, 'ruby_standardrb_options')
- return ale#handlers#ruby#EscapeExecutable(l:executable, 'standardrb')
+ return ale#ruby#EscapeExecutable(l:executable, 'standardrb')
\ . (!empty(l:config) ? ' --config ' . ale#Escape(l:config) : '')
\ . (!empty(l:options) ? ' ' . l:options : '')
\ . ' --fix --force-exclusion %t'
diff --git a/autoload/ale/fixers/stylelint.vim b/autoload/ale/fixers/stylelint.vim
index 6bfb2fde..6f4cf177 100644
--- a/autoload/ale/fixers/stylelint.vim
+++ b/autoload/ale/fixers/stylelint.vim
@@ -3,6 +3,7 @@
call ale#Set('stylelint_executable', 'stylelint')
call ale#Set('stylelint_use_global', get(g:, 'ale_use_global_executables', 0))
+call ale#Set('stylelint_options', '')
function! ale#fixers#stylelint#GetExecutable(buffer) abort
return ale#node#FindExecutable(a:buffer, 'stylelint', [
@@ -13,10 +14,14 @@ endfunction
function! ale#fixers#stylelint#Fix(buffer) abort
let l:executable = ale#fixers#stylelint#GetExecutable(a:buffer)
+ let l:options = ale#Var(a:buffer, 'stylelint_options')
return {
- \ 'command': ale#node#Executable(a:buffer, l:executable)
- \ . ' --fix %t',
+ \ 'command': ale#path#BufferCdString(a:buffer)
+ \ . ale#node#Executable(a:buffer, l:executable)
+ \ . ' %t'
+ \ . ale#Pad(l:options)
+ \ . ' --fix',
\ 'read_temporary_file': 1,
\}
endfunction
diff --git a/autoload/ale/handlers/languagetool.vim b/autoload/ale/handlers/languagetool.vim
index 10e049df..73974ceb 100644
--- a/autoload/ale/handlers/languagetool.vim
+++ b/autoload/ale/handlers/languagetool.vim
@@ -2,6 +2,7 @@
" Description: languagetool for markdown files
"
call ale#Set('languagetool_executable', 'languagetool')
+call ale#Set('languagetool_options', '--autoDetect')
function! ale#handlers#languagetool#GetExecutable(buffer) abort
return ale#Var(a:buffer, 'languagetool_executable')
@@ -9,8 +10,10 @@ endfunction
function! ale#handlers#languagetool#GetCommand(buffer) abort
let l:executable = ale#handlers#languagetool#GetExecutable(a:buffer)
+ let l:options = ale#Var(a:buffer, 'languagetool_options')
- return ale#Escape(l:executable) . ' --autoDetect %s'
+ return ale#Escape(l:executable)
+ \ . (empty(l:options) ? '' : ' ' . l:options) . ' %s'
endfunction
function! ale#handlers#languagetool#HandleOutput(buffer, lines) abort
diff --git a/autoload/ale/handlers/ruby.vim b/autoload/ale/handlers/ruby.vim
index c28b8b75..7a1c5765 100644
--- a/autoload/ale/handlers/ruby.vim
+++ b/autoload/ale/handlers/ruby.vim
@@ -36,11 +36,3 @@ endfunction
function! ale#handlers#ruby#HandleSyntaxErrors(buffer, lines) abort
return s:HandleSyntaxError(a:buffer, a:lines)
endfunction
-
-function! ale#handlers#ruby#EscapeExecutable(executable, bundle_exec) abort
- let l:exec_args = a:executable =~? 'bundle'
- \ ? ' exec ' . a:bundle_exec
- \ : ''
-
- return ale#Escape(a:executable) . l:exec_args
-endfunction
diff --git a/autoload/ale/highlight.vim b/autoload/ale/highlight.vim
index cb7911e1..82ad57e0 100644
--- a/autoload/ale/highlight.vim
+++ b/autoload/ale/highlight.vim
@@ -26,6 +26,25 @@ endif
let s:MAX_POS_VALUES = 8
let s:MAX_COL_SIZE = 1073741824 " pow(2, 30)
+let s:has_nvim_highlight = exists('*nvim_buf_add_highlight') && exists('*nvim_buf_clear_namespace')
+
+if s:has_nvim_highlight
+ let s:ns_id = nvim_create_namespace('ale_highlight')
+endif
+
+" Wrappers are necessary to test this functionality by faking the calls in tests.
+function! ale#highlight#nvim_buf_add_highlight(buffer, ns_id, hl_group, line, col_start, col_end) abort
+ " Ignore all errors for adding highlights.
+ try
+ call nvim_buf_add_highlight(a:buffer, a:ns_id, a:hl_group, a:line, a:col_start, a:col_end)
+ catch
+ endtry
+endfunction
+
+function! ale#highlight#nvim_buf_clear_namespace(buffer, ns_id, line_start, line_end) abort
+ call nvim_buf_clear_namespace(a:buffer, a:ns_id, a:line_start, a:line_end)
+endfunction
+
function! ale#highlight#CreatePositions(line, col, end_line, end_col) abort
if a:line >= a:end_line
" For single lines, just return the one position.
@@ -51,15 +70,53 @@ endfunction
" except these which have matching loclist item entries.
function! ale#highlight#RemoveHighlights() abort
- for l:match in getmatches()
- if l:match.group =~? '\v^ALE(Style)?(Error|Warning|Info)(Line)?$'
- call matchdelete(l:match.id)
- endif
- endfor
+ if s:has_nvim_highlight
+ call ale#highlight#nvim_buf_clear_namespace(bufnr(''), s:ns_id, 0, -1)
+ else
+ for l:match in getmatches()
+ if l:match.group =~? '\v^ALE(Style)?(Error|Warning|Info)(Line)?$'
+ call matchdelete(l:match.id)
+ endif
+ endfor
+ endif
+endfunction
+
+" Same semantics of matchaddpos but will use nvim_buf_add_highlight if
+" available. This involves iterating over the position list, switching from
+" 1-based indexing to 0-based indexing, and translating the multiple ways
+" that position can be specified for matchaddpos into line + col_start +
+" col_end.
+function! s:matchaddpos(group, pos_list) abort
+ if s:has_nvim_highlight
+ for l:pos in a:pos_list
+ let l:line = type(l:pos) == v:t_number
+ \ ? l:pos - 1
+ \ : l:pos[0] - 1
+
+ if type(l:pos) == v:t_number || len(l:pos) == 1
+ let l:col_start = 0
+ let l:col_end = s:MAX_COL_SIZE
+ else
+ let l:col_start = l:pos[1] - 1
+ let l:col_end = l:col_start + get(l:pos, 2, 1)
+ endif
+
+ call ale#highlight#nvim_buf_add_highlight(
+ \ bufnr(''),
+ \ s:ns_id,
+ \ a:group,
+ \ l:line,
+ \ l:col_start,
+ \ l:col_end,
+ \)
+ endfor
+ else
+ call matchaddpos(a:group, a:pos_list)
+ endif
endfunction
function! s:highlight_line(bufnr, lnum, group) abort
- call matchaddpos(a:group, [a:lnum])
+ call s:matchaddpos(a:group, [a:lnum])
endfunction
function! s:highlight_range(bufnr, range, group) abort
@@ -72,7 +129,7 @@ function! s:highlight_range(bufnr, range, group) abort
\ a:range.end_lnum,
\ a:range.end_col
\ ),
- \ 'matchaddpos(a:group, v:val)'
+ \ 's:matchaddpos(a:group, v:val)'
\)
endfunction
diff --git a/autoload/ale/linter.vim b/autoload/ale/linter.vim
index 78dcd3a2..a85f06e2 100644
--- a/autoload/ale/linter.vim
+++ b/autoload/ale/linter.vim
@@ -12,9 +12,12 @@ let s:linters = {}
let s:default_ale_linter_aliases = {
\ 'Dockerfile': 'dockerfile',
\ 'csh': 'sh',
+\ 'javascriptreact': ['javascript', 'jsx'],
\ 'plaintex': 'tex',
\ 'rmarkdown': 'r',
+\ 'rmd': 'r',
\ 'systemverilog': 'verilog',
+\ 'typescriptreact': ['typescript', 'tsx'],
\ 'verilog_systemverilog': ['verilog_systemverilog', 'verilog'],
\ 'vimwiki': 'markdown',
\ 'vue': ['vue', 'javascript'],
diff --git a/autoload/ale/list.vim b/autoload/ale/list.vim
index e9f3f4ec..4bfe2a7b 100644
--- a/autoload/ale/list.vim
+++ b/autoload/ale/list.vim
@@ -103,6 +103,9 @@ function! s:SetListsImpl(timer_id, buffer, loclist) abort
endfor
endif
+ " Save the current view before opening/closing any window
+ call setbufvar(a:buffer, 'ale_winview', winsaveview())
+
" Open a window to show the problems if we need to.
"
" We'll check if the current buffer's List is not empty here, so the
@@ -141,6 +144,8 @@ function! s:SetListsImpl(timer_id, buffer, loclist) abort
normal! "\<c-g>"
endif
endif
+
+ call s:RestoreViewIfNeeded(a:buffer)
endif
" If ALE isn't currently checking for more problems, close the window if
@@ -151,6 +156,30 @@ function! s:SetListsImpl(timer_id, buffer, loclist) abort
endif
endfunction
+" Try to restore the window view after closing any of the lists to avoid making
+" the it moving around, especially useful when on insert mode
+function! s:RestoreViewIfNeeded(buffer) abort
+ let l:saved_view = getbufvar(a:buffer, 'ale_winview', {})
+
+ " Saved view is empty, can't do anything
+ if empty(l:saved_view)
+ return
+ endif
+
+ " Check wether the cursor has moved since linting was actually requested. If
+ " the user has indeed moved lines, do nothing
+ let l:current_view = winsaveview()
+
+ if l:current_view['lnum'] != l:saved_view['lnum']
+ return
+ endif
+
+ " Anchor view by topline if the list is set to open horizontally
+ if ale#Var(a:buffer, 'list_vertical') == 0
+ call winrestview({'topline': l:saved_view['topline']})
+ endif
+endfunction
+
function! ale#list#SetLists(buffer, loclist) abort
if get(g:, 'ale_set_lists_synchronously') == 1
\|| getbufvar(a:buffer, 'ale_save_event_fired', 0)
@@ -174,12 +203,15 @@ function! s:CloseWindowIfNeeded(buffer) abort
return
endif
+ let l:did_close_any_list = 0
+
try
" Only close windows if the quickfix list or loclist is completely empty,
" including errors set through other means.
if g:ale_set_quickfix
if empty(getqflist())
cclose
+ let l:did_close_any_list = 1
endif
else
let l:win_ids = s:WinFindBuf(a:buffer)
@@ -187,10 +219,15 @@ function! s:CloseWindowIfNeeded(buffer) abort
for l:win_id in l:win_ids
if g:ale_set_loclist && empty(getloclist(l:win_id))
lclose
+ let l:did_close_any_list = 1
endif
endfor
endif
" Ignore 'Cannot close last window' errors.
catch /E444/
endtry
+
+ if l:did_close_any_list
+ call s:RestoreViewIfNeeded(a:buffer)
+ endif
endfunction
diff --git a/autoload/ale/lsp.vim b/autoload/ale/lsp.vim
index 017096cd..2509174e 100644
--- a/autoload/ale/lsp.vim
+++ b/autoload/ale/lsp.vim
@@ -37,6 +37,7 @@ function! ale#lsp#Register(executable_or_address, project, init_options) abort
\ 'init_queue': [],
\ 'capabilities': {
\ 'hover': 0,
+ \ 'rename': 0,
\ 'references': 0,
\ 'completion': 0,
\ 'completion_trigger_characters': [],
@@ -199,6 +200,10 @@ function! s:UpdateCapabilities(conn, capabilities) abort
let a:conn.capabilities.references = 1
endif
+ if get(a:capabilities, 'renameProvider') is v:true
+ let a:conn.capabilities.rename = 1
+ endif
+
if !empty(get(a:capabilities, 'completionProvider'))
let a:conn.capabilities.completion = 1
endif
@@ -317,6 +322,7 @@ function! ale#lsp#MarkConnectionAsTsserver(conn_id) abort
let l:conn.capabilities.completion_trigger_characters = ['.']
let l:conn.capabilities.definition = 1
let l:conn.capabilities.symbol_search = 1
+ let l:conn.capabilities.rename = 1
endfunction
function! s:SendInitMessage(conn) abort
diff --git a/autoload/ale/lsp/message.vim b/autoload/ale/lsp/message.vim
index b6b14a22..5b0cb8b7 100644
--- a/autoload/ale/lsp/message.vim
+++ b/autoload/ale/lsp/message.vim
@@ -162,3 +162,13 @@ function! ale#lsp#message#DidChangeConfiguration(buffer, config) abort
\ 'settings': a:config,
\}]
endfunction
+
+function! ale#lsp#message#Rename(buffer, line, column, new_name) abort
+ return [0, 'textDocument/rename', {
+ \ 'textDocument': {
+ \ 'uri': ale#path#ToURI(expand('#' . a:buffer . ':p')),
+ \ },
+ \ 'position': {'line': a:line - 1, 'character': a:column - 1},
+ \ 'newName': a:new_name,
+ \}]
+endfunction
diff --git a/autoload/ale/lsp/tsserver_message.vim b/autoload/ale/lsp/tsserver_message.vim
index d6919516..b9fafaa0 100644
--- a/autoload/ale/lsp/tsserver_message.vim
+++ b/autoload/ale/lsp/tsserver_message.vim
@@ -36,12 +36,14 @@ function! ale#lsp#tsserver_message#Geterr(buffer) abort
return [1, 'ts@geterr', {'files': [expand('#' . a:buffer . ':p')]}]
endfunction
-function! ale#lsp#tsserver_message#Completions(buffer, line, column, prefix) abort
+function! ale#lsp#tsserver_message#Completions(
+\ buffer, line, column, prefix, include_external) abort
return [0, 'ts@completions', {
\ 'line': a:line,
\ 'offset': a:column,
\ 'file': expand('#' . a:buffer . ':p'),
\ 'prefix': a:prefix,
+ \ 'includeExternalModuleExports': a:include_external,
\}]
endfunction
@@ -77,3 +79,27 @@ function! ale#lsp#tsserver_message#Quickinfo(buffer, line, column) abort
\ 'file': expand('#' . a:buffer . ':p'),
\}]
endfunction
+
+function! ale#lsp#tsserver_message#Rename(
+\ buffer, line, column, find_in_comments, find_in_strings) abort
+ return [0, 'ts@rename', {
+ \ 'line': a:line,
+ \ 'offset': a:column,
+ \ 'file': expand('#' . a:buffer . ':p'),
+ \ 'arguments': {
+ \ 'findInComments': a:find_in_comments,
+ \ 'findInStrings': a:find_in_strings,
+ \ }
+ \}]
+endfunction
+
+function! ale#lsp#tsserver_message#OrganizeImports(buffer) abort
+ return [0, 'ts@organizeImports', {
+ \ 'scope': {
+ \ 'type': 'file',
+ \ 'args': {
+ \ 'file': expand('#' . a:buffer . ':p'),
+ \ },
+ \ },
+ \}]
+endfunction
diff --git a/autoload/ale/lsp_linter.vim b/autoload/ale/lsp_linter.vim
index 190a16b4..e4148ceb 100644
--- a/autoload/ale/lsp_linter.vim
+++ b/autoload/ale/lsp_linter.vim
@@ -130,6 +130,12 @@ function! ale#lsp_linter#HandleLSPResponse(conn_id, response) abort
call s:HandleLSPErrorMessage(l:linter_name, a:response)
elseif l:method is# 'textDocument/publishDiagnostics'
call s:HandleLSPDiagnostics(a:conn_id, a:response)
+ elseif l:method is# 'window/showMessage'
+ call ale#lsp_window#HandleShowMessage(
+ \ s:lsp_linter_map[a:conn_id],
+ \ g:ale_lsp_show_message_format,
+ \ a:response.params
+ \)
elseif get(a:response, 'type', '') is# 'event'
\&& get(a:response, 'event', '') is# 'semanticDiag'
call s:HandleTSServerDiagnostics(a:response, 'semantic')
diff --git a/autoload/ale/lsp_window.vim b/autoload/ale/lsp_window.vim
new file mode 100644
index 00000000..9a27f2f1
--- /dev/null
+++ b/autoload/ale/lsp_window.vim
@@ -0,0 +1,58 @@
+" Author: suoto <andre820@gmail.com>
+" Description: Handling of window/* LSP methods, although right now only
+" handles window/showMessage
+
+" Constants for message type codes
+let s:LSP_MESSAGE_TYPE_DISABLED = 0
+let s:LSP_MESSAGE_TYPE_ERROR = 1
+let s:LSP_MESSAGE_TYPE_WARNING = 2
+let s:LSP_MESSAGE_TYPE_INFORMATION = 3
+let s:LSP_MESSAGE_TYPE_LOG = 4
+
+" Translate strings from the user config to a number so we can check
+" severities
+let s:CFG_TO_LSP_SEVERITY = {
+\ 'disabled': s:LSP_MESSAGE_TYPE_DISABLED,
+\ 'error': s:LSP_MESSAGE_TYPE_ERROR,
+\ 'warning': s:LSP_MESSAGE_TYPE_WARNING,
+\ 'information': s:LSP_MESSAGE_TYPE_INFORMATION,
+\ 'info': s:LSP_MESSAGE_TYPE_INFORMATION,
+\ 'log': s:LSP_MESSAGE_TYPE_LOG
+\}
+
+" Handle window/showMessage response.
+" - details: dict containing linter name and format (g:ale_lsp_show_message_format)
+" - params: dict with the params for the call in the form of {type: number, message: string}
+function! ale#lsp_window#HandleShowMessage(linter_name, format, params) abort
+ let l:message = a:params.message
+ let l:type = a:params.type
+
+ " Get the configured severity level threshold and check if the message
+ " should be displayed or not
+ let l:configured_severity = tolower(get(g:, 'ale_lsp_show_message_severity', 'error'))
+ " If the user has configured with a value we can't find on the conversion
+ " dict, fall back to warning
+ let l:cfg_severity_threshold = get(s:CFG_TO_LSP_SEVERITY, l:configured_severity, s:LSP_MESSAGE_TYPE_WARNING)
+
+ if l:type > l:cfg_severity_threshold
+ return
+ endif
+
+ " Severity will depend on the message type
+ if l:type is# s:LSP_MESSAGE_TYPE_ERROR
+ let l:severity = g:ale_echo_msg_error_str
+ elseif l:type is# s:LSP_MESSAGE_TYPE_INFORMATION
+ let l:severity = g:ale_echo_msg_info_str
+ elseif l:type is# s:LSP_MESSAGE_TYPE_LOG
+ let l:severity = g:ale_echo_msg_log_str
+ else
+ " Default to warning just in case
+ let l:severity = g:ale_echo_msg_warning_str
+ endif
+
+ let l:string = substitute(a:format, '\V%severity%', l:severity, 'g')
+ let l:string = substitute(l:string, '\V%linter%', a:linter_name, 'g')
+ let l:string = substitute(l:string, '\V%s\>', l:message, 'g')
+
+ call ale#util#ShowMessage(l:string)
+endfunction
diff --git a/autoload/ale/organize_imports.vim b/autoload/ale/organize_imports.vim
new file mode 100644
index 00000000..bc9b829e
--- /dev/null
+++ b/autoload/ale/organize_imports.vim
@@ -0,0 +1,59 @@
+" Author: Jerko Steiner <jerko.steiner@gmail.com>
+" Description: Organize imports support for tsserver
+"
+function! ale#organize_imports#HandleTSServerResponse(conn_id, response) abort
+ if get(a:response, 'command', '') isnot# 'organizeImports'
+ return
+ endif
+
+ if get(a:response, 'success', v:false) isnot v:true
+ return
+ endif
+
+ let l:file_code_edits = a:response.body
+
+ call ale#code_action#HandleCodeAction({
+ \ 'description': 'Organize Imports',
+ \ 'changes': l:file_code_edits,
+ \})
+endfunction
+
+function! s:OnReady(linter, lsp_details) abort
+ let l:id = a:lsp_details.connection_id
+
+ if a:linter.lsp isnot# 'tsserver'
+ call ale#util#Execute('echom ''OrganizeImports currently only works with tsserver''')
+
+ return
+ endif
+
+ let l:buffer = a:lsp_details.buffer
+
+ let l:Callback = function('ale#organize_imports#HandleTSServerResponse')
+
+ call ale#lsp#RegisterCallback(l:id, l:Callback)
+
+ let l:message = ale#lsp#tsserver_message#OrganizeImports(l:buffer)
+
+ let l:request_id = ale#lsp#Send(l:id, l:message)
+endfunction
+
+function! s:OrganizeImports(linter) abort
+ let l:buffer = bufnr('')
+ let [l:line, l:column] = getpos('.')[1:2]
+
+ if a:linter.lsp isnot# 'tsserver'
+ let l:column = min([l:column, len(getline(l:line))])
+ endif
+
+ let l:Callback = function('s:OnReady')
+ call ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback)
+endfunction
+
+function! ale#organize_imports#Execute() abort
+ for l:linter in ale#linter#Get(&filetype)
+ if !empty(l:linter.lsp)
+ call s:OrganizeImports(l:linter)
+ endif
+ endfor
+endfunction
diff --git a/autoload/ale/path.vim b/autoload/ale/path.vim
index 84c26d0a..30550503 100644
--- a/autoload/ale/path.vim
+++ b/autoload/ale/path.vim
@@ -54,14 +54,14 @@ function! ale#path#FindNearestDirectory(buffer, directory_name) abort
return ''
endfunction
-" Given a buffer, a string to search for, an a global fallback for when
+" Given a buffer, a string to search for, and a global fallback for when
" the search fails, look for a file in parent paths, and if that fails,
" use the global fallback path instead.
function! ale#path#ResolveLocalPath(buffer, search_string, global_fallback) abort
" Search for a locally installed file first.
let l:path = ale#path#FindNearestFile(a:buffer, a:search_string)
- " If the serach fails, try the global executable instead.
+ " If the search fails, try the global executable instead.
if empty(l:path)
let l:path = a:global_fallback
endif
diff --git a/autoload/ale/rename.vim b/autoload/ale/rename.vim
new file mode 100644
index 00000000..02b7b579
--- /dev/null
+++ b/autoload/ale/rename.vim
@@ -0,0 +1,225 @@
+" Author: Jerko Steiner <jerko.steiner@gmail.com>
+" Description: Rename symbol support for LSP / tsserver
+
+let s:rename_map = {}
+
+" Used to get the rename map in tests.
+function! ale#rename#GetMap() abort
+ return deepcopy(s:rename_map)
+endfunction
+
+" Used to set the rename map in tests.
+function! ale#rename#SetMap(map) abort
+ let s:rename_map = a:map
+endfunction
+
+function! ale#rename#ClearLSPData() abort
+ let s:rename_map = {}
+endfunction
+
+let g:ale_rename_tsserver_find_in_comments = get(g:, 'ale_rename_tsserver_find_in_comments')
+let g:ale_rename_tsserver_find_in_strings = get(g:, 'ale_rename_tsserver_find_in_strings')
+
+function! s:message(message) abort
+ call ale#util#Execute('echom ' . string(a:message))
+endfunction
+
+function! ale#rename#HandleTSServerResponse(conn_id, response) abort
+ if get(a:response, 'command', '') isnot# 'rename'
+ return
+ endif
+
+ if !has_key(s:rename_map, a:response.request_seq)
+ return
+ endif
+
+ let l:old_name = s:rename_map[a:response.request_seq].old_name
+ let l:new_name = s:rename_map[a:response.request_seq].new_name
+ call remove(s:rename_map, a:response.request_seq)
+
+ if get(a:response, 'success', v:false) is v:false
+ let l:message = get(a:response, 'message', 'unknown')
+ call s:message('Error renaming "' . l:old_name . '" to: "' . l:new_name
+ \ . '". Reason: ' . l:message)
+
+ return
+ endif
+
+ let l:changes = []
+
+ for l:response_item in a:response.body.locs
+ let l:filename = l:response_item.file
+ let l:text_changes = []
+
+ for l:loc in l:response_item.locs
+ call add(l:text_changes, {
+ \ 'start': {
+ \ 'line': l:loc.start.line,
+ \ 'offset': l:loc.start.offset,
+ \ },
+ \ 'end': {
+ \ 'line': l:loc.end.line,
+ \ 'offset': l:loc.end.offset,
+ \ },
+ \ 'newText': l:new_name,
+ \})
+ endfor
+
+ call add(l:changes, {
+ \ 'fileName': l:filename,
+ \ 'textChanges': l:text_changes,
+ \})
+ endfor
+
+ if empty(l:changes)
+ call s:message('Error renaming "' . l:old_name . '" to: "' . l:new_name . '"')
+
+ return
+ endif
+
+ call ale#code_action#HandleCodeAction({
+ \ 'description': 'rename',
+ \ 'changes': l:changes,
+ \})
+endfunction
+
+function! ale#rename#HandleLSPResponse(conn_id, response) abort
+ if has_key(a:response, 'id')
+ \&& has_key(s:rename_map, a:response.id)
+ call remove(s:rename_map, a:response.id)
+
+ if !has_key(a:response, 'result')
+ call s:message('No rename result received from server')
+
+ return
+ endif
+
+ let l:workspace_edit = a:response.result
+
+ if !has_key(l:workspace_edit, 'changes') || empty(l:workspace_edit.changes)
+ call s:message('No changes received from server')
+
+ return
+ endif
+
+ let l:changes = []
+
+ for l:file_name in keys(l:workspace_edit.changes)
+ let l:text_edits = l:workspace_edit.changes[l:file_name]
+ let l:text_changes = []
+
+ for l:edit in l:text_edits
+ let l:range = l:edit.range
+ let l:new_text = l:edit.newText
+
+ 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:new_text,
+ \})
+ endfor
+
+ call add(l:changes, {
+ \ 'fileName': ale#path#FromURI(l:file_name),
+ \ 'textChanges': l:text_changes,
+ \})
+ endfor
+
+ call ale#code_action#HandleCodeAction({
+ \ 'description': 'rename',
+ \ 'changes': l:changes,
+ \})
+ endif
+endfunction
+
+function! s:OnReady(line, column, old_name, new_name, linter, lsp_details) abort
+ let l:id = a:lsp_details.connection_id
+
+ if !ale#lsp#HasCapability(l:id, 'rename')
+ return
+ endif
+
+ let l:buffer = a:lsp_details.buffer
+
+ let l:Callback = a:linter.lsp is# 'tsserver'
+ \ ? function('ale#rename#HandleTSServerResponse')
+ \ : function('ale#rename#HandleLSPResponse')
+
+ call ale#lsp#RegisterCallback(l:id, l:Callback)
+
+ if a:linter.lsp is# 'tsserver'
+ let l:message = ale#lsp#tsserver_message#Rename(
+ \ l:buffer,
+ \ a:line,
+ \ a:column,
+ \ g:ale_rename_tsserver_find_in_comments,
+ \ g:ale_rename_tsserver_find_in_strings,
+ \)
+ else
+ " Send a message saying the buffer has changed first, or the
+ " rename position probably won't make sense.
+ call ale#lsp#NotifyForChanges(l:id, l:buffer)
+
+ let l:message = ale#lsp#message#Rename(
+ \ l:buffer,
+ \ a:line,
+ \ a:column,
+ \ a:new_name
+ \)
+ endif
+
+ let l:request_id = ale#lsp#Send(l:id, l:message)
+
+ let s:rename_map[l:request_id] = {
+ \ 'new_name': a:new_name,
+ \ 'old_name': a:old_name,
+ \}
+endfunction
+
+function! s:ExecuteRename(linter, old_name, new_name) abort
+ let l:buffer = bufnr('')
+ let [l:line, l:column] = getpos('.')[1:2]
+
+ if a:linter.lsp isnot# 'tsserver'
+ let l:column = min([l:column, len(getline(l:line))])
+ endif
+
+ let l:Callback = function(
+ \ 's:OnReady', [l:line, l:column, a:old_name, a:new_name])
+ call ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback)
+endfunction
+
+function! ale#rename#Execute() abort
+ let l:lsp_linters = []
+
+ for l:linter in ale#linter#Get(&filetype)
+ if !empty(l:linter.lsp)
+ call add(l:lsp_linters, l:linter)
+ endif
+ endfor
+
+ if empty(l:lsp_linters)
+ call s:message('No active LSPs')
+
+ return
+ endif
+
+ let l:old_name = expand('<cword>')
+ let l:new_name = ale#util#Input('New name: ', l:old_name)
+
+ if empty(l:new_name)
+ call s:message('New name cannot be empty!')
+
+ return
+ endif
+
+ for l:lsp_linter in l:lsp_linters
+ call s:ExecuteRename(l:lsp_linter, l:old_name, l:new_name)
+ endfor
+endfunction
diff --git a/autoload/ale/ruby.vim b/autoload/ale/ruby.vim
index 15e835c9..d941bb2c 100644
--- a/autoload/ale/ruby.vim
+++ b/autoload/ale/ruby.vim
@@ -74,3 +74,10 @@ function! ale#ruby#HandleRubocopOutput(buffer, lines) abort
return l:output
endfunction
+function! ale#ruby#EscapeExecutable(executable, bundle_exec) abort
+ let l:exec_args = a:executable =~? 'bundle'
+ \ ? ' exec ' . a:bundle_exec
+ \ : ''
+
+ return ale#Escape(a:executable) . l:exec_args
+endfunction
diff --git a/autoload/ale/sign.vim b/autoload/ale/sign.vim
index eb0dd1cd..db0e1ab6 100644
--- a/autoload/ale/sign.vim
+++ b/autoload/ale/sign.vim
@@ -14,12 +14,16 @@ let g:ale_sign_style_error = get(g:, 'ale_sign_style_error', g:ale_sign_error)
let g:ale_sign_warning = get(g:, 'ale_sign_warning', '--')
let g:ale_sign_style_warning = get(g:, 'ale_sign_style_warning', g:ale_sign_warning)
let g:ale_sign_info = get(g:, 'ale_sign_info', g:ale_sign_warning)
+let g:ale_sign_priority = get(g:, 'ale_sign_priority', 30)
" This variable sets an offset which can be set for sign IDs.
" This ID can be changed depending on what IDs are set for other plugins.
" The dummy sign will use the ID exactly equal to the offset.
let g:ale_sign_offset = get(g:, 'ale_sign_offset', 1000000)
" This flag can be set to 1 to keep sign gutter always open
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'))
if !hlexists('ALEErrorSign')
highlight link ALEErrorSign error
@@ -82,7 +86,7 @@ execute 'sign define ALEInfoSign text=' . s:EscapeSignText(g:ale_sign_info)
\ . ' texthl=ALEInfoSign linehl=ALEInfoLine'
sign define ALEDummySign
-if has('nvim-0.3.2')
+if g:ale_sign_highlight_linenrs && has('nvim-0.3.2')
if !hlexists('ALEErrorSignLineNr')
highlight link ALEErrorSignLineNr CursorLineNr
endif
@@ -146,24 +150,59 @@ function! ale#sign#GetSignName(sublist) abort
return 'ALEErrorSign'
endfunction
+function! s:PriorityCmd() abort
+ if s:supports_sign_groups
+ return ' priority=' . g:ale_sign_priority . ' '
+ else
+ return ''
+ endif
+endfunction
+
+function! s:GroupCmd() abort
+ if s:supports_sign_groups
+ return ' group=ale '
+ else
+ return ' '
+ endif
+endfunction
+
" Read sign data for a buffer to a list of lines.
function! ale#sign#ReadSigns(buffer) abort
redir => l:output
- silent execute 'sign place buffer=' . a:buffer
+ silent execute 'sign place ' . s:GroupCmd() . s:PriorityCmd()
+ \ . ' buffer=' . a:buffer
redir end
return split(l:output, "\n")
endfunction
+function! ale#sign#ParsePattern() abort
+ if s:supports_sign_groups
+ " Matches output like :
+ " line=4 id=1 group=ale name=ALEErrorSign
+ " строка=1 id=1000001 группа=ale имя=ALEErrorSign
+ " 行=1 識別子=1000001 グループ=ale 名前=ALEWarningSign
+ " línea=12 id=1000001 grupo=ale nombre=ALEWarningSign
+ " riga=1 id=1000001 gruppo=ale nome=ALEWarningSign
+ " Zeile=235 id=1000001 Gruppe=ale Name=ALEErrorSign
+ let l:pattern = '\v^.*\=(\d+).*\=(\d+).*\=ale>.*\=(ALE[a-zA-Z]+Sign)'
+ else
+ " Matches output like :
+ " line=4 id=1 name=ALEErrorSign
+ " строка=1 id=1000001 имя=ALEErrorSign
+ " 行=1 識別子=1000001 名前=ALEWarningSign
+ " línea=12 id=1000001 nombre=ALEWarningSign
+ " riga=1 id=1000001 nome=ALEWarningSign
+ " Zeile=235 id=1000001 Name=ALEErrorSign
+ let l:pattern = '\v^.*\=(\d+).*\=(\d+).*\=(ALE[a-zA-Z]+Sign)'
+ endif
+
+ return l:pattern
+endfunction
+
" Given a list of lines for sign output, return a List of [line, id, group]
function! ale#sign#ParseSigns(line_list) abort
- " Matches output like :
- " line=4 id=1 name=ALEErrorSign
- " строка=1 id=1000001 имя=ALEErrorSign
- " 行=1 識別子=1000001 名前=ALEWarningSign
- " línea=12 id=1000001 nombre=ALEWarningSign
- " riga=1 id=1000001, nome=ALEWarningSign
- let l:pattern = '\v^.*\=(\d+).*\=(\d+).*\=(ALE[a-zA-Z]+Sign)'
+ let l:pattern =ale#sign#ParsePattern()
let l:result = []
let l:is_dummy_sign_set = 0
@@ -318,8 +357,10 @@ function! ale#sign#GetSignCommands(buffer, was_sign_set, sign_map) abort
if !l:is_dummy_sign_set && (!empty(a:sign_map) || g:ale_sign_column_always)
call add(l:command_list, 'sign place '
\ . g:ale_sign_offset
- \ . ' line=1 name=ALEDummySign buffer='
- \ . a:buffer
+ \ . s:GroupCmd()
+ \ . s:PriorityCmd()
+ \ . ' line=1 name=ALEDummySign '
+ \ . ' buffer=' . a:buffer
\)
let l:is_dummy_sign_set = 1
endif
@@ -336,6 +377,8 @@ function! ale#sign#GetSignCommands(buffer, was_sign_set, sign_map) abort
if index(l:info.current_id_list, l:info.new_id) < 0
call add(l:command_list, 'sign place '
\ . (l:info.new_id)
+ \ . s:GroupCmd()
+ \ . s:PriorityCmd()
\ . ' line=' . l:line_str
\ . ' name=' . (l:info.new_name)
\ . ' buffer=' . a:buffer
@@ -350,6 +393,7 @@ function! ale#sign#GetSignCommands(buffer, was_sign_set, sign_map) abort
if l:current_id isnot l:info.new_id
call add(l:command_list, 'sign unplace '
\ . l:current_id
+ \ . s:GroupCmd()
\ . ' buffer=' . a:buffer
\)
endif
@@ -360,6 +404,7 @@ function! ale#sign#GetSignCommands(buffer, was_sign_set, sign_map) abort
if l:is_dummy_sign_set && !g:ale_sign_column_always
call add(l:command_list, 'sign unplace '
\ . g:ale_sign_offset
+ \ . s:GroupCmd()
\ . ' buffer=' . a:buffer
\)
endif
@@ -414,3 +459,12 @@ function! ale#sign#SetSigns(buffer, loclist) abort
highlight link SignColumn ALESignColumnWithoutErrors
endif
endfunction
+
+" Remove all signs.
+function! ale#sign#Clear() abort
+ if s:supports_sign_groups
+ sign unplace group=ale *
+ else
+ sign unplace *
+ endif
+endfunction
diff --git a/autoload/ale/util.vim b/autoload/ale/util.vim
index e7563608..99cd856a 100644
--- a/autoload/ale/util.vim
+++ b/autoload/ale/util.vim
@@ -477,3 +477,6 @@ function! ale#util#FindItemAtCursor(buffer) abort
return [l:info, l:loc]
endfunction
+function! ale#util#Input(message, value) abort
+ return input(a:message, a:value)
+endfunction
diff --git a/autoload/asyncomplete/sources/ale.vim b/autoload/asyncomplete/sources/ale.vim
new file mode 100644
index 00000000..ce793773
--- /dev/null
+++ b/autoload/asyncomplete/sources/ale.vim
@@ -0,0 +1,26 @@
+function! asyncomplete#sources#ale#get_source_options(...) abort
+ let l:default = extend({
+ \ 'name': 'ale',
+ \ 'completor': function('asyncomplete#sources#ale#completor'),
+ \ 'whitelist': ['*'],
+ \ 'triggers': asyncomplete#sources#ale#get_triggers(),
+ \ }, a:0 >= 1 ? a:1 : {})
+
+ return extend(l:default, {'refresh_pattern': '\k\+$'})
+endfunction
+
+function! asyncomplete#sources#ale#get_triggers() abort
+ let l:triggers = ale#completion#GetAllTriggers()
+ let l:triggers['*'] = l:triggers['<default>']
+
+ return l:triggers
+endfunction
+
+function! asyncomplete#sources#ale#completor(options, context) abort
+ let l:keyword = matchstr(a:context.typed, '\w\+$')
+ let l:startcol = a:context.col - len(l:keyword)
+
+ call ale#completion#GetCompletions('ale-callback', { 'callback': {completions ->
+ \ asyncomplete#complete(a:options.name, a:context, l:startcol, completions)
+ \ }})
+endfunction