summaryrefslogtreecommitdiff
path: root/autoload
diff options
context:
space:
mode:
Diffstat (limited to 'autoload')
-rw-r--r--autoload/ale.vim24
-rw-r--r--autoload/ale/assert.vim2
-rw-r--r--autoload/ale/c.vim266
-rw-r--r--autoload/ale/code_action.vim20
-rw-r--r--autoload/ale/command.vim42
-rw-r--r--autoload/ale/completion.vim103
-rw-r--r--autoload/ale/cursor.vim2
-rw-r--r--autoload/ale/definition.vim31
-rw-r--r--autoload/ale/engine.vim262
-rw-r--r--autoload/ale/events.vim10
-rw-r--r--autoload/ale/filename_mapping.vim22
-rw-r--r--autoload/ale/fix.vim126
-rw-r--r--autoload/ale/fix/registry.vim15
-rw-r--r--autoload/ale/fixers/astyle.vim59
-rw-r--r--autoload/ale/fixers/dhall.vim23
-rw-r--r--autoload/ale/fixers/eslint.vim9
-rw-r--r--autoload/ale/fixers/ktlint.vim3
-rw-r--r--autoload/ale/fixers/latexindent.vim4
-rw-r--r--autoload/ale/fixers/ocamlformat.vim3
-rw-r--r--autoload/ale/fixers/prettier.vim17
-rw-r--r--autoload/ale/fixers/prettier_standard.vim4
-rw-r--r--autoload/ale/fixers/remark_lint.vim24
-rw-r--r--autoload/ale/fixers/rubocop.vim24
-rw-r--r--autoload/ale/fixers/standard.vim2
-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/gcc.vim19
-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/sh.vim17
-rw-r--r--autoload/ale/handlers/shellcheck.vim107
-rw-r--r--autoload/ale/hover.vim208
-rw-r--r--autoload/ale/linter.vim200
-rw-r--r--autoload/ale/lsp.vim28
-rw-r--r--autoload/ale/lsp_linter.vim11
-rw-r--r--autoload/ale/node.vim14
-rw-r--r--autoload/ale/organize_imports.vim2
-rw-r--r--autoload/ale/path.vim20
-rw-r--r--autoload/ale/preview.vim44
-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/test.vim4
-rw-r--r--autoload/ale/uri.vim15
-rw-r--r--autoload/ale/util.vim69
49 files changed, 1471 insertions, 602 deletions
diff --git a/autoload/ale.vim b/autoload/ale.vim
index 6d0afb9e..5ec22f57 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')
" Windows may insert carriage return line endings (^M), strip these characters.
@@ -268,3 +268,23 @@ function! ale#GetLocItemMessage(item, format_string) abort
return l:msg
endfunction
+
+" Given a buffer and a linter or fixer name, return an Array of two-item
+" Arrays describing how to map filenames to and from the local to foreign file
+" systems.
+function! ale#GetFilenameMappings(buffer, name) abort
+ let l:linter_mappings = ale#Var(a:buffer, 'filename_mappings')
+
+ if type(l:linter_mappings) is v:t_list
+ return l:linter_mappings
+ endif
+
+ let l:name = a:name
+
+ if !has_key(l:linter_mappings, l:name)
+ " Use * as a default setting for all tools.
+ let l:name = '*'
+ endif
+
+ return get(l:linter_mappings, l:name, [])
+endfunction
diff --git a/autoload/ale/assert.vim b/autoload/ale/assert.vim
index 291edcee..934fcaa8 100644
--- a/autoload/ale/assert.vim
+++ b/autoload/ale/assert.vim
@@ -130,7 +130,7 @@ endfunction
function! ale#assert#LSPLanguage(expected_language) abort
let l:buffer = bufnr('')
let l:linter = s:GetLinter()
- let l:language = ale#util#GetFunction(l:linter.language_callback)(l:buffer)
+ let l:language = ale#linter#GetLanguage(l:buffer, l:linter)
AssertEqual a:expected_language, l:language
endfunction
diff --git a/autoload/ale/c.vim b/autoload/ale/c.vim
index 9b428700..cff53125 100644
--- a/autoload/ale/c.vim
+++ b/autoload/ale/c.vim
@@ -2,20 +2,28 @@
" 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_always_make', has('unix') && !has('macunix'))
+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 g:ale_c_build_dir_names = get(g:, 'ale_c_build_dir_names', [
+\ 'build',
+\ 'bin',
+\])
+
+function! s:CanParseMakefile(buffer) abort
+ " Something somewhere seems to delete this setting in tests, so ensure we
+ " always have a default value.
+ call ale#Set('c_parse_makefile', 0)
+ return ale#Var(a:buffer, 'c_parse_makefile')
+endfunction
+
+function! ale#c#GetBuildDirectory(buffer) abort
let l:build_dir = ale#Var(a:buffer, 'c_build_dir')
" c_build_dir has the priority if defined
@@ -68,14 +76,73 @@ function! ale#c#ShellSplit(line) abort
return l:args
endfunction
-function! ale#c#ParseCFlags(path_prefix, cflag_line) abort
- let l:cflags_list = []
+" Takes the path prefix and a list of cflags and expands @file arguments to
+" the contents of the file.
+"
+" @file arguments are command line arguments recognised by gcc and clang. For
+" instance, if @./path/to/file was given to gcc, it would load .path/to/file
+" and use the contents of that file as arguments.
+function! ale#c#ExpandAtArgs(path_prefix, raw_split_lines) abort
+ let l:out_lines = []
+
+ for l:option in a:raw_split_lines
+ if stridx(l:option, '@') == 0
+ " This is an argument specifying a location of a file containing other arguments
+ let l:path = join(split(l:option, '\zs')[1:], '')
+
+ " Make path absolute
+ if !ale#path#IsAbsolute(l:path)
+ let l:rel_path = substitute(l:path, '"', '', 'g')
+ let l:rel_path = substitute(l:rel_path, '''', '', 'g')
+ let l:path = ale#path#GetAbsPath(a:path_prefix, l:rel_path)
+ endif
+
+ " Read the file and add all the arguments
+ try
+ let l:additional_args = readfile(l:path)
+ catch
+ continue " All we can really do is skip this argument
+ endtry
+
+ let l:file_lines = []
+
+ for l:line in l:additional_args
+ let l:file_lines += ale#c#ShellSplit(l:line)
+ endfor
+
+ " @file arguments can include other @file arguments, so we must
+ " recurse.
+ let l:out_lines += ale#c#ExpandAtArgs(a:path_prefix, l:file_lines)
+ else
+ " This is not an @file argument, so don't touch it.
+ let l:out_lines += [l:option]
+ endif
+ endfor
- let l:split_lines = ale#c#ShellSplit(a:cflag_line)
+ return l:out_lines
+endfunction
+
+" Quote C/C++ a compiler argument, if needed.
+"
+" Quoting arguments might cause issues with some systems/compilers, so we only
+" quote them if we need to.
+function! ale#c#QuoteArg(arg) abort
+ if a:arg !~# '\v[#$&*()\\|[\]{};''"<>/?! ^%]'
+ return a:arg
+ endif
+
+ return ale#Escape(a:arg)
+endfunction
+
+function! ale#c#ParseCFlags(path_prefix, should_quote, raw_arguments) abort
+ " Expand @file arguments now before parsing
+ let l:arguments = ale#c#ExpandAtArgs(a:path_prefix, a:raw_arguments)
+ " A list of [already_quoted, argument]
+ let l:items = []
let l:option_index = 0
- while l:option_index < len(l:split_lines)
- let l:option = l:split_lines[l:option_index]
+ while l:option_index < len(l:arguments)
+ let l:option = l:arguments[l:option_index]
let l:option_index = l:option_index + 1
" Include options, that may need relative path fix
@@ -83,56 +150,67 @@ function! ale#c#ParseCFlags(path_prefix, cflag_line) abort
\ || stridx(l:option, '-iquote') == 0
\ || stridx(l:option, '-isystem') == 0
\ || stridx(l:option, '-idirafter') == 0
+ \ || stridx(l:option, '-iframework') == 0
+ \ || stridx(l:option, '-include') == 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:arg = l:arguments[l:option_index]
let l:option_index = l:option_index + 1
endif
" Fix relative paths if needed
- if stridx(l:arg, s:sep) != 0 && stridx(l:arg, '/') != 0
+ if !ale#path#IsAbsolute(l:arg)
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)
+ let l:arg = ale#path#GetAbsPath(a:path_prefix, l:rel_path)
endif
- call add(l:cflags_list, l:option)
- call add(l:cflags_list, l:arg)
+ call add(l:items, [1, l:option])
+ call add(l:items, [1, ale#Escape(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)
-
if l:option is# '-D' || l:option is# '-B'
- call add(l:cflags_list, l:split_lines[l:option_index])
+ call add(l:items, [1, l:option])
+ call add(l:items, [0, l:arguments[l:option_index]])
let l:option_index = l:option_index + 1
+ else
+ call add(l:items, [0, l:option])
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])
+ call add(l:items, [0, l:option])
+ call add(l:items, [0, l:arguments[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, '-f') == 0 && l:option !~# '\v^-f(dump|diagnostics|no-show-column|stack-usage)'
\ || 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)
+ call add(l:items, [0, l:option])
endif
endwhile
- return join(l:cflags_list, ' ')
+ if a:should_quote
+ " Quote C arguments that haven't already been quoted above.
+ " If and only if we've been asked to quote them.
+ call map(l:items, 'v:val[0] ? v:val[1] : ale#c#QuoteArg(v:val[1])')
+ else
+ call map(l:items, 'v:val[1]')
+ endif
+
+ return join(l:items, ' ')
endfunction
function! ale#c#ParseCFlagsFromMakeOutput(buffer, make_output) abort
- if !g:ale_c_parse_makefile
+ if !s:CanParseMakefile(a:buffer)
return v:null
endif
@@ -150,7 +228,7 @@ function! ale#c#ParseCFlagsFromMakeOutput(buffer, make_output) abort
let l:makefile_path = ale#path#FindNearestFile(a:buffer, 'Makefile')
let l:makefile_dir = fnamemodify(l:makefile_path, ':p:h')
- return ale#c#ParseCFlags(l:makefile_dir, l:cflag_line)
+ return ale#c#ParseCFlags(l:makefile_dir, 0, ale#c#ShellSplit(l:cflag_line))
endfunction
" Given a buffer number, find the project directory containing
@@ -218,6 +296,10 @@ if !exists('s:compile_commands_cache')
let s:compile_commands_cache = {}
endif
+function! ale#c#ResetCompileCommandsCache() abort
+ let s:compile_commands_cache = {}
+endfunction
+
function! s:GetLookupFromCompileCommandsFile(compile_commands_file) abort
let l:empty = [{}, {}]
@@ -248,9 +330,20 @@ function! s:GetLookupFromCompileCommandsFile(compile_commands_file) abort
let l:dir_lookup = {}
for l:entry in (type(l:raw_data) is v:t_list ? l:raw_data : [])
+ let l:filename = ale#path#GetAbsPath(l:entry.directory, l:entry.file)
+
+ " Store a key for lookups by the absolute path to the filename.
+ let l:file_lookup[l:filename] = get(l:file_lookup, l:filename, []) + [l:entry]
+
+ " Store a key for fuzzy lookups by the absolute path to the directory.
+ let l:dirname = fnamemodify(l:filename, ':h')
+ let l:dir_lookup[l:dirname] = get(l:dir_lookup, l:dirname, []) + [l:entry]
+
+ " Store a key for fuzzy lookups by just the basename of the file.
let l:basename = tolower(fnamemodify(l:entry.file, ':t'))
let l:file_lookup[l:basename] = get(l:file_lookup, l:basename, []) + [l:entry]
+ " Store a key for fuzzy lookups by just the basename of the directory.
let l:dirbasename = tolower(fnamemodify(l:entry.directory, ':p:h:t'))
let l:dir_lookup[l:dirbasename] = get(l:dir_lookup, l:dirbasename, []) + [l:entry]
endfor
@@ -265,28 +358,80 @@ 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, ' ')
+" Get [should_quote, arguments] from either 'command' or 'arguments'
+" 'arguments' should be quoted later, the split 'command' strings should not.
+function! s:GetArguments(json_item) abort
+ if has_key(a:json_item, 'arguments')
+ return [1, a:json_item.arguments]
+ elseif has_key(a:json_item, 'command')
+ return [0, ale#c#ShellSplit(a:json_item.command)]
endif
- return ''
+ return [0, []]
endfunction
function! ale#c#ParseCompileCommandsFlags(buffer, file_lookup, dir_lookup) abort
+ let l:buffer_filename = ale#path#Simplify(expand('#' . a:buffer . ':p'))
+ let l:basename = tolower(fnamemodify(l:buffer_filename, ':t'))
+ " Look for any file in the same directory if we can't find an exact match.
+ let l:dir = fnamemodify(l:buffer_filename, ':h')
+
" Search for an exact file match first.
- let l:basename = tolower(expand('#' . a:buffer . ':t'))
- let l:file_list = get(a:file_lookup, l:basename, [])
+ let l:file_list = get(a:file_lookup, l:buffer_filename, [])
+
+ " We may have to look for /foo/bar instead of C:\foo\bar
+ if empty(l:file_list) && has('win32')
+ let l:file_list = get(
+ \ a:file_lookup,
+ \ ale#path#RemoveDriveLetter(l:buffer_filename),
+ \ []
+ \)
+ endif
+
+ " Try the absolute path to the directory second.
+ let l:dir_list = get(a:dir_lookup, l:dir, [])
+
+ if empty(l:dir_list) && has('win32')
+ let l:dir_list = get(
+ \ a:dir_lookup,
+ \ ale#path#RemoveDriveLetter(l:dir),
+ \ []
+ \)
+ endif
+
+ if empty(l:file_list) && empty(l:dir_list)
+ " If we can't find matches with the path to the file, try a
+ " case-insensitive match for any similarly-named file.
+ let l:file_list = get(a:file_lookup, l:basename, [])
+
+ " If we can't find matches with the path to the directory, try a
+ " case-insensitive match for anything in similarly-named directory.
+ let l:dir_list = get(a:dir_lookup, tolower(fnamemodify(l:dir, ':t')), [])
+ endif
+
" A source file matching the header filename.
let l:source_file = ''
if empty(l:file_list) && l:basename =~? '\.h$\|\.hpp$'
for l:suffix in ['.c', '.cpp']
- let l:key = fnamemodify(l:basename, ':r') . l:suffix
+ " Try to find a source file by an absolute path first.
+ let l:key = fnamemodify(l:buffer_filename, ':r') . l:suffix
let l:file_list = get(a:file_lookup, l:key, [])
+ if empty(l:file_list) && has('win32')
+ let l:file_list = get(
+ \ a:file_lookup,
+ \ ale#path#RemoveDriveLetter(l:key),
+ \ []
+ \)
+ endif
+
+ if empty(l:file_list)
+ " Look fuzzy matches on the basename second.
+ let l:key = fnamemodify(l:basename, ':r') . l:suffix
+ let l:file_list = get(a:file_lookup, l:key, [])
+ endif
+
if !empty(l:file_list)
let l:source_file = l:key
break
@@ -295,28 +440,31 @@ function! ale#c#ParseCompileCommandsFlags(buffer, file_lookup, dir_lookup) abort
endif
for l:item in l:file_list
+ let l:filename = ale#path#GetAbsPath(l:item.directory, l:item.file)
+
" Load the flags for this file, or for a source file matching the
" header file.
if (
- \ bufnr(l:item.file) is a:buffer
+ \ bufnr(l:filename) is a:buffer
\ || (
\ !empty(l:source_file)
- \ && l:item.file[-len(l:source_file):] is? l:source_file
+ \ && l:filename[-len(l:source_file):] is? l:source_file
\ )
\)
- return ale#c#ParseCFlags(l:item.directory, ale#c#GetCompileCommand(l:item))
+ let [l:should_quote, l:args] = s:GetArguments(l:item)
+
+ return ale#c#ParseCFlags(l:item.directory, l:should_quote, l:args)
endif
endfor
- " Look for any file in the same directory if we can't find an exact match.
- let l:dir = ale#path#Simplify(expand('#' . a:buffer . ':p:h'))
+ for l:item in l:dir_list
+ let l:filename = ale#path#GetAbsPath(l:item.directory, l:item.file)
- let l:dirbasename = tolower(expand('#' . a:buffer . ':p:h:t'))
- let l:dir_list = get(a:dir_lookup, l:dirbasename, [])
+ if ale#path#RemoveDriveLetter(fnamemodify(l:filename, ':h'))
+ \ is? ale#path#RemoveDriveLetter(l:dir)
+ let [l:should_quote, l:args] = s:GetArguments(l:item)
- for l:item in l:dir_list
- if ale#path#Simplify(fnamemodify(l:item.file, ':h')) is? l:dir
- return ale#c#ParseCFlags(l:item.directory, ale#c#GetCompileCommand(l:item))
+ return ale#c#ParseCFlags(l:item.directory, l:should_quote, l:args)
endif
endfor
@@ -334,10 +482,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 +490,10 @@ function! ale#c#GetCFlags(buffer, output) abort
endif
endif
+ if s:CanParseMakefile(a:buffer) && !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
@@ -354,11 +502,14 @@ function! ale#c#GetCFlags(buffer, output) abort
endfunction
function! ale#c#GetMakeCommand(buffer) abort
- if ale#Var(a:buffer, 'c_parse_makefile')
- let l:makefile_path = ale#path#FindNearestFile(a:buffer, 'Makefile')
+ if s:CanParseMakefile(a:buffer)
+ let l:path = ale#path#FindNearestFile(a:buffer, 'Makefile')
- if !empty(l:makefile_path)
- return 'cd '. fnamemodify(l:makefile_path, ':p:h') . ' && make -n'
+ if !empty(l:path)
+ let l:always_make = ale#Var(a:buffer, 'c_always_make')
+
+ return ale#path#CdString(fnamemodify(l:path, ':h'))
+ \ . 'make -n' . (l:always_make ? ' --always-make' : '')
endif
endif
@@ -427,8 +578,3 @@ function! ale#c#IncludeOptions(include_paths) abort
return join(l:option_list)
endfunction
-
-let g:ale_c_build_dir_names = get(g:, 'ale_c_build_dir_names', [
-\ 'build',
-\ 'bin',
-\])
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/command.vim b/autoload/ale/command.vim
index 1bbc4f4c..8f497169 100644
--- a/autoload/ale/command.vim
+++ b/autoload/ale/command.vim
@@ -133,11 +133,36 @@ function! ale#command#EscapeCommandPart(command_part) abort
return substitute(a:command_part, '%', '%%', 'g')
endfunction
+" Format a filename, converting it with filename mappings, if non-empty,
+" and escaping it for putting into a command string.
+"
+" The filename can be modified.
+function! s:FormatFilename(filename, mappings, modifiers) abort
+ let l:filename = a:filename
+
+ if !empty(a:mappings)
+ let l:filename = ale#filename_mapping#Map(l:filename, a:mappings)
+ endif
+
+ if !empty(a:modifiers)
+ let l:filename = fnamemodify(l:filename, a:modifiers)
+ endif
+
+ return ale#Escape(l:filename)
+endfunction
+
" Given a command string, replace every...
" %s -> with the current filename
" %t -> with the name of an unused file in a temporary directory
" %% -> with a literal %
-function! ale#command#FormatCommand(buffer, executable, command, pipe_file_if_needed, input) abort
+function! ale#command#FormatCommand(
+\ buffer,
+\ executable,
+\ command,
+\ pipe_file_if_needed,
+\ input,
+\ mappings,
+\) abort
let l:temporary_file = ''
let l:command = a:command
@@ -154,14 +179,24 @@ function! ale#command#FormatCommand(buffer, executable, command, pipe_file_if_ne
" file.
if l:command =~# '%s'
let l:filename = fnamemodify(bufname(a:buffer), ':p')
- let l:command = substitute(l:command, '%s', '\=ale#Escape(l:filename)', 'g')
+ let l:command = substitute(
+ \ l:command,
+ \ '\v\%s(%(:h|:t|:r|:e)*)',
+ \ '\=s:FormatFilename(l:filename, a:mappings, submatch(1))',
+ \ 'g'
+ \)
endif
if a:input isnot v:false && l:command =~# '%t'
" Create a temporary filename, <temp_dir>/<original_basename>
" The file itself will not be created by this function.
let l:temporary_file = s:TemporaryFilename(a:buffer)
- let l:command = substitute(l:command, '%t', '\=ale#Escape(l:temporary_file)', 'g')
+ let l:command = substitute(
+ \ l:command,
+ \ '\v\%t(%(:h|:t|:r|:e)*)',
+ \ '\=s:FormatFilename(l:temporary_file, a:mappings, submatch(1))',
+ \ 'g'
+ \)
endif
" Finish formatting so %% becomes %.
@@ -265,6 +300,7 @@ function! ale#command#Run(buffer, command, Callback, ...) abort
\ a:command,
\ get(l:options, 'read_buffer', 0),
\ get(l:options, 'input', v:null),
+ \ get(l:options, 'filename_mappings', []),
\)
let l:command = ale#job#PrepareCommand(a:buffer, l:command)
let l:job_options = {
diff --git a/autoload/ale/completion.vim b/autoload/ale/completion.vim
index 80684a30..c2cfd74a 100644
--- a/autoload/ale/completion.vim
+++ b/autoload/ale/completion.vim
@@ -5,7 +5,7 @@ scriptencoding utf-8
" The omnicompletion menu is shown through a special Plug mapping which is
" only valid in Insert mode. This way, feedkeys() won't send these keys if you
" quit Insert mode quickly enough.
-inoremap <silent> <Plug>(ale_show_completion_menu) <C-x><C-o>
+inoremap <silent> <Plug>(ale_show_completion_menu) <C-x><C-o><C-p>
" If we hit the key sequence in normal mode, then we won't show the menu, so
" we should restore the old settings right away.
nnoremap <silent> <Plug>(ale_show_completion_menu) :call ale#completion#RestoreCompletionOptions()<CR>
@@ -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 = []
@@ -323,6 +324,12 @@ function! ale#completion#AutomaticOmniFunc(findstart, base) abort
endif
endfunction
+function! s:OpenCompletionMenu(...) abort
+ if !&l:paste
+ call ale#util#FeedKeys("\<Plug>(ale_show_completion_menu)")
+ endif
+endfunction
+
function! ale#completion#Show(result) abort
if ale#util#Mode() isnot# 'i'
return
@@ -343,10 +350,7 @@ function! ale#completion#Show(result) abort
let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '')
if l:source is# 'ale-automatic' || l:source is# 'ale-manual'
- call timer_start(
- \ 0,
- \ {-> ale#util#FeedKeys("\<Plug>(ale_show_completion_menu)")}
- \)
+ call timer_start(0, function('s:OpenCompletionMenu'))
endif
if l:source is# 'ale-callback'
@@ -397,10 +401,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
@@ -413,12 +421,22 @@ function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort
for l:suggestion in a:response.body
let l:displayParts = []
+ let l:local_name = v:null
for l:action in get(l:suggestion, 'codeActions', [])
call add(l:displayParts, l:action.description . ' ')
endfor
for l:part in l:suggestion.displayParts
+ " Stop on stop on line breaks for the menu.
+ if get(l:part, 'kind') is# 'lineBreak'
+ break
+ endif
+
+ if get(l:part, 'kind') is# 'localName'
+ let l:local_name = l:part.text
+ endif
+
call add(l:displayParts, l:part.text)
endfor
@@ -431,11 +449,17 @@ function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort
" See :help complete-items
let l:result = {
- \ 'word': l:suggestion.name,
+ \ 'word': (
+ \ l:suggestion.name is# 'default'
+ \ && l:suggestion.kind is# 'alias'
+ \ && !empty(l:local_name)
+ \ ? l:local_name
+ \ : l:suggestion.name
+ \ ),
\ '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 +541,60 @@ 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 !empty(get(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
+ call add(l:text_changes, {
+ \ 'start': {
+ \ 'line': l:edit.range.start.line + 1,
+ \ 'offset': l:edit.range.start.character + 1,
+ \ },
+ \ 'end': {
+ \ 'line': l:edit.range.end.line + 1,
+ \ 'offset': l:edit.range.end.character + 1,
+ \ },
+ \ 'newText': l:edit.newText,
+ \})
+ endfor
+
+ if !empty(l:text_changes)
+ let l:result.user_data = json_encode({
+ \ 'codeActions': [{
+ \ 'description': 'completion',
+ \ 'changes': [
+ \ {
+ \ 'fileName': expand('#' . l:buffer . ':p'),
+ \ 'textChanges': l:text_changes,
+ \ }
+ \ ],
+ \ }],
+ \})
+ endif
+ endif
+
+ call add(l:results, l:result)
endfor
if has_key(l:info, 'prefix')
@@ -628,12 +693,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 +892,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
@@ -836,6 +905,8 @@ function! ale#completion#Done() abort
endfunction
augroup ALECompletionActions
+ autocmd!
+
autocmd CompleteDone * call ale#completion#HandleUserData(v:completed_item)
augroup END
diff --git a/autoload/ale/cursor.vim b/autoload/ale/cursor.vim
index 8c331c5c..9ca6fb15 100644
--- a/autoload/ale/cursor.vim
+++ b/autoload/ale/cursor.vim
@@ -39,6 +39,8 @@ function! ale#cursor#TruncatedEcho(original_message) abort
endif
exec 'echomsg l:message'
+ catch /E481/
+ " Do nothing if running from a visual selection.
endtry
" Reset the cursor position if we moved off the end of the line.
diff --git a/autoload/ale/definition.vim b/autoload/ale/definition.vim
index 3915cac1..0c1fb7cf 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
@@ -154,3 +155,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/engine.vim b/autoload/ale/engine.vim
index 491d3c2e..ae0354b8 100644
--- a/autoload/ale/engine.vim
+++ b/autoload/ale/engine.vim
@@ -4,6 +4,7 @@
" Remapping of linter problems.
let g:ale_type_map = get(g:, 'ale_type_map', {})
+let g:ale_filename_mappings = get(g:, 'ale_filename_mappings', {})
if !has_key(s:, 'executable_cache_map')
let s:executable_cache_map = {}
@@ -104,42 +105,6 @@ function! ale#engine#IsCheckingBuffer(buffer) abort
\ || !empty(get(l:info, 'active_other_sources_list', []))
endfunction
-" Register a temporary file to be managed with the ALE engine for
-" a current job run.
-function! ale#engine#ManageFile(buffer, filename) abort
- if !get(g:, 'ale_ignore_2_4_warnings')
- execute 'echom ''ale#engine#ManageFile is deprecated. Use `let g:ale_ignore_2_4_warnings = 1` to disable this message.'''
- endif
-
- call ale#command#ManageFile(a:buffer, a:filename)
-endfunction
-
-" Same as the above, but manage an entire directory.
-function! ale#engine#ManageDirectory(buffer, directory) abort
- if !get(g:, 'ale_ignore_2_4_warnings')
- execute 'echom ''ale#engine#ManageDirectory is deprecated. Use `let g:ale_ignore_2_4_warnings = 1` to disable this message.'''
- endif
-
- call ale#command#ManageDirectory(a:buffer, a:directory)
-endfunction
-
-function! ale#engine#CreateFile(buffer) abort
- if !get(g:, 'ale_ignore_2_4_warnings')
- execute 'echom ''ale#engine#CreateFile is deprecated. Use `let g:ale_ignore_2_4_warnings = 1` to disable this message.'''
- endif
-
- return ale#command#CreateFile(a:buffer)
-endfunction
-
-" Create a new temporary directory and manage it in one go.
-function! ale#engine#CreateDirectory(buffer) abort
- if !get(g:, 'ale_ignore_2_4_warnings')
- execute 'echom ''ale#engine#CreateDirectory is deprecated. Use `let g:ale_ignore_2_4_warnings = 1` to disable this message.'''
- endif
-
- return ale#command#CreateDirectory(a:buffer)
-endfunction
-
function! ale#engine#HandleLoclist(linter_name, buffer, loclist, from_other_source) abort
let l:info = get(g:ale_buffer_info, a:buffer, {})
@@ -192,7 +157,6 @@ function! s:HandleExit(job_info, buffer, output, data) abort
let l:linter = a:job_info.linter
let l:executable = a:job_info.executable
- let l:next_chain_index = a:job_info.next_chain_index
" Remove this job from the list.
call ale#engine#MarkLinterInactive(l:buffer_info, l:linter.name)
@@ -207,20 +171,6 @@ function! s:HandleExit(job_info, buffer, output, data) abort
call remove(a:output, -1)
endif
- if l:next_chain_index < len(get(l:linter, 'command_chain', []))
- let [l:command, l:options] = ale#engine#ProcessChain(
- \ a:buffer,
- \ l:executable,
- \ l:linter,
- \ l:next_chain_index,
- \ a:output,
- \)
-
- call s:RunJob(l:command, l:options)
-
- return
- endif
-
try
let l:loclist = ale#util#GetFunction(l:linter.callback)(a:buffer, a:output)
" Handle the function being unknown, or being deleted.
@@ -307,6 +257,13 @@ function! s:RemapItemTypes(type_map, loclist) abort
endfunction
function! ale#engine#FixLocList(buffer, linter_name, from_other_source, loclist) abort
+ let l:mappings = ale#GetFilenameMappings(a:buffer, a:linter_name)
+
+ if !empty(l:mappings)
+ " We need to apply reverse filename mapping here.
+ let l:mappings = ale#filename_mapping#Invert(l:mappings)
+ endif
+
let l:bufnr_map = {}
let l:new_loclist = []
@@ -347,13 +304,19 @@ function! ale#engine#FixLocList(buffer, linter_name, from_other_source, loclist)
let l:item.code = l:old_item.code
endif
- if has_key(l:old_item, 'filename')
- \&& !ale#path#IsTempName(l:old_item.filename)
+ let l:old_name = get(l:old_item, 'filename', '')
+
+ " Map parsed from output to local filesystem files.
+ if !empty(l:old_name) && !empty(l:mappings)
+ let l:old_name = ale#filename_mapping#Map(l:old_name, l:mappings)
+ endif
+
+ if !empty(l:old_name) && !ale#path#IsTempName(l:old_name)
" Use the filename given.
" Temporary files are assumed to be for this buffer,
" and the filename is not included then, because it looks bad
" in the loclist window.
- let l:filename = l:old_item.filename
+ let l:filename = l:old_name
let l:item.filename = l:filename
if has_key(l:old_item, 'bufnr')
@@ -454,20 +417,19 @@ function! s:RunJob(command, options) abort
let l:buffer = a:options.buffer
let l:linter = a:options.linter
let l:output_stream = a:options.output_stream
- let l:next_chain_index = a:options.next_chain_index
- let l:read_buffer = a:options.read_buffer
+ let l:read_buffer = a:options.read_buffer && !a:options.lint_file
let l:info = g:ale_buffer_info[l:buffer]
let l:Callback = function('s:HandleExit', [{
\ 'linter': l:linter,
\ 'executable': l:executable,
- \ 'next_chain_index': l:next_chain_index,
\}])
let l:result = ale#command#Run(l:buffer, l:command, l:Callback, {
\ 'output_stream': l:output_stream,
\ 'executable': l:executable,
\ 'read_buffer': l:read_buffer,
- \ 'log_output': l:next_chain_index >= len(get(l:linter, 'command_chain', [])),
+ \ 'log_output': 1,
+ \ 'filename_mappings': ale#GetFilenameMappings(l:buffer, l:linter.name),
\})
" Only proceed if the job is being run.
@@ -482,68 +444,6 @@ function! s:RunJob(command, options) abort
return 1
endfunction
-" Determine which commands to run for a link in a command chain, or
-" just a regular command.
-function! ale#engine#ProcessChain(buffer, executable, linter, chain_index, input) abort
- let l:output_stream = get(a:linter, 'output_stream', 'stdout')
- let l:read_buffer = a:linter.read_buffer
- let l:chain_index = a:chain_index
- let l:input = a:input
-
- while l:chain_index < len(a:linter.command_chain)
- " Run a chain of commands, one asynchronous command after the other,
- " so that many programs can be run in a sequence.
- let l:chain_item = a:linter.command_chain[l:chain_index]
-
- if l:chain_index == 0
- " The first callback in the chain takes only a buffer number.
- let l:command = ale#util#GetFunction(l:chain_item.callback)(
- \ a:buffer
- \)
- else
- " The second callback in the chain takes some input too.
- let l:command = ale#util#GetFunction(l:chain_item.callback)(
- \ a:buffer,
- \ l:input
- \)
- endif
-
- " If we have a command to run, execute that.
- if !empty(l:command)
- " The chain item can override the output_stream option.
- if has_key(l:chain_item, 'output_stream')
- let l:output_stream = l:chain_item.output_stream
- endif
-
- " The chain item can override the read_buffer option.
- if has_key(l:chain_item, 'read_buffer')
- let l:read_buffer = l:chain_item.read_buffer
- elseif l:chain_index != len(a:linter.command_chain) - 1
- " Don't read the buffer for commands besides the last one
- " in the chain by default.
- let l:read_buffer = 0
- endif
-
- break
- endif
-
- " Command chain items can return an empty string to indicate that
- " a command should be skipped, so we should try the next item
- " with no input.
- let l:input = []
- let l:chain_index += 1
- endwhile
-
- return [l:command, {
- \ 'executable': a:executable,
- \ 'buffer': a:buffer,
- \ 'linter': a:linter,
- \ 'output_stream': l:output_stream,
- \ 'next_chain_index': l:chain_index + 1,
- \ 'read_buffer': l:read_buffer,
- \}]
-endfunction
-
function! s:StopCurrentJobs(buffer, clear_lint_file_jobs) abort
let l:info = get(g:ale_buffer_info, a:buffer, {})
call ale#command#StopJobs(a:buffer, 'linter')
@@ -608,10 +508,15 @@ function! s:AddProblemsFromOtherBuffers(buffer, linters) abort
endif
endfunction
-function! s:RunIfExecutable(buffer, linter, executable) abort
+function! s:RunIfExecutable(buffer, linter, lint_file, executable) abort
if ale#command#IsDeferred(a:executable)
let a:executable.result_callback = {
- \ executable -> s:RunIfExecutable(a:buffer, a:linter, executable)
+ \ executable -> s:RunIfExecutable(
+ \ a:buffer,
+ \ a:linter,
+ \ a:lint_file,
+ \ executable
+ \ )
\}
return 1
@@ -619,29 +524,17 @@ function! s:RunIfExecutable(buffer, linter, executable) abort
if ale#engine#IsExecutable(a:buffer, a:executable)
" Use different job types for file or linter jobs.
- let l:job_type = a:linter.lint_file ? 'file_linter' : 'linter'
+ let l:job_type = a:lint_file ? 'file_linter' : 'linter'
call setbufvar(a:buffer, 'ale_job_type', l:job_type)
- if has_key(a:linter, 'command_chain')
- let [l:command, l:options] = ale#engine#ProcessChain(
- \ a:buffer,
- \ a:executable,
- \ a:linter,
- \ 0,
- \ []
- \)
-
- return s:RunJob(l:command, l:options)
- endif
-
let l:command = ale#linter#GetCommand(a:buffer, a:linter)
let l:options = {
\ 'executable': a:executable,
\ 'buffer': a:buffer,
\ 'linter': a:linter,
\ 'output_stream': get(a:linter, 'output_stream', 'stdout'),
- \ 'next_chain_index': 1,
\ 'read_buffer': a:linter.read_buffer,
+ \ 'lint_file': a:lint_file,
\}
return s:RunJob(l:command, l:options)
@@ -653,33 +546,62 @@ endfunction
" Run a linter for a buffer.
"
" Returns 1 if the linter was successfully run.
-function! s:RunLinter(buffer, linter) abort
+function! s:RunLinter(buffer, linter, lint_file) abort
if !empty(a:linter.lsp)
return ale#lsp_linter#CheckWithLSP(a:buffer, a:linter)
else
let l:executable = ale#linter#GetExecutable(a:buffer, a:linter)
- return s:RunIfExecutable(a:buffer, a:linter, l:executable)
+ return s:RunIfExecutable(a:buffer, a:linter, a:lint_file, l:executable)
endif
return 0
endfunction
-function! ale#engine#RunLinters(buffer, linters, should_lint_file) abort
- " Initialise the buffer information if needed.
- let l:new_buffer = ale#engine#InitBufferInfo(a:buffer)
- call s:StopCurrentJobs(a:buffer, a:should_lint_file)
- call s:RemoveProblemsForDisabledLinters(a:buffer, a:linters)
+function! s:GetLintFileValues(slots, Callback) abort
+ let l:deferred_list = []
+ let l:new_slots = []
- " We can only clear the results if we aren't checking the buffer.
- let l:can_clear_results = !ale#engine#IsCheckingBuffer(a:buffer)
+ for [l:lint_file, l:linter] in a:slots
+ while ale#command#IsDeferred(l:lint_file) && has_key(l:lint_file, 'value')
+ " If we've already computed the return value, use it.
+ let l:lint_file = l:lint_file.value
+ endwhile
- silent doautocmd <nomodeline> User ALELintPre
+ if ale#command#IsDeferred(l:lint_file)
+ " If we are going to return the result later, wait for it.
+ call add(l:deferred_list, l:lint_file)
+ else
+ " If we have the value now, coerce it to 0 or 1.
+ let l:lint_file = l:lint_file is 1
+ endif
- for l:linter in a:linters
+ call add(l:new_slots, [l:lint_file, l:linter])
+ endfor
+
+ if !empty(l:deferred_list)
+ for l:deferred in l:deferred_list
+ let l:deferred.result_callback =
+ \ {-> s:GetLintFileValues(l:new_slots, a:Callback)}
+ endfor
+ else
+ call a:Callback(l:new_slots)
+ endif
+endfunction
+
+function! s:RunLinters(
+\ buffer,
+\ slots,
+\ should_lint_file,
+\ new_buffer,
+\ can_clear_results
+\) abort
+ let l:can_clear_results = a:can_clear_results
+
+ for [l:lint_file, l:linter] in a:slots
" Only run lint_file linters if we should.
- if !l:linter.lint_file || a:should_lint_file
- if s:RunLinter(a:buffer, l:linter)
+ if !l:lint_file || a:should_lint_file
+ if s:RunLinter(a:buffer, l:linter, l:lint_file)
" If a single linter ran, we shouldn't clear everything.
let l:can_clear_results = 0
endif
@@ -694,11 +616,49 @@ function! ale#engine#RunLinters(buffer, linters, should_lint_file) abort
" disabled, or ALE itself is disabled.
if l:can_clear_results
call ale#engine#SetResults(a:buffer, [])
- elseif l:new_buffer
- call s:AddProblemsFromOtherBuffers(a:buffer, a:linters)
+ elseif a:new_buffer
+ call s:AddProblemsFromOtherBuffers(
+ \ a:buffer,
+ \ map(copy(a:slots), 'v:val[1]')
+ \)
endif
endfunction
+function! ale#engine#RunLinters(buffer, linters, should_lint_file) abort
+ " Initialise the buffer information if needed.
+ let l:new_buffer = ale#engine#InitBufferInfo(a:buffer)
+ call s:StopCurrentJobs(a:buffer, a:should_lint_file)
+ call s:RemoveProblemsForDisabledLinters(a:buffer, a:linters)
+
+ " We can only clear the results if we aren't checking the buffer.
+ let l:can_clear_results = !ale#engine#IsCheckingBuffer(a:buffer)
+
+ silent doautocmd <nomodeline> User ALELintPre
+
+ " Handle `lint_file` callbacks first.
+ let l:linter_slots = []
+
+ for l:linter in a:linters
+ let l:LintFile = l:linter.lint_file
+
+ if type(l:LintFile) is v:t_func
+ let l:LintFile = l:LintFile(a:buffer)
+ endif
+
+ call add(l:linter_slots, [l:LintFile, l:linter])
+ endfor
+
+ call s:GetLintFileValues(l:linter_slots, {
+ \ new_slots -> s:RunLinters(
+ \ a:buffer,
+ \ new_slots,
+ \ a:should_lint_file,
+ \ l:new_buffer,
+ \ l:can_clear_results,
+ \ )
+ \})
+endfunction
+
" Clean up a buffer.
"
" This function will stop all current jobs for the buffer,
diff --git a/autoload/ale/events.vim b/autoload/ale/events.vim
index da554ef9..3568c117 100644
--- a/autoload/ale/events.vim
+++ b/autoload/ale/events.vim
@@ -105,11 +105,11 @@ function! ale#events#Init() abort
if g:ale_enabled
if l:text_changed is? 'always' || l:text_changed is# '1'
- autocmd TextChanged,TextChangedI * call ale#Queue(g:ale_lint_delay)
+ autocmd TextChanged,TextChangedI * call ale#Queue(ale#Var(str2nr(expand('<abuf>')), 'lint_delay'))
elseif l:text_changed is? 'normal'
- autocmd TextChanged * call ale#Queue(g:ale_lint_delay)
+ autocmd TextChanged * call ale#Queue(ale#Var(str2nr(expand('<abuf>')), 'lint_delay'))
elseif l:text_changed is? 'insert'
- autocmd TextChangedI * call ale#Queue(g:ale_lint_delay)
+ autocmd TextChangedI * call ale#Queue(ale#Var(str2nr(expand('<abuf>')), 'lint_delay'))
endif
if g:ale_lint_on_enter
@@ -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/filename_mapping.vim b/autoload/ale/filename_mapping.vim
new file mode 100644
index 00000000..76d47acc
--- /dev/null
+++ b/autoload/ale/filename_mapping.vim
@@ -0,0 +1,22 @@
+" Author: w0rp <devw0rp@gmail.com>
+" Description: Logic for handling mappings between files
+
+" Invert filesystem mappings so they can be mapped in reverse.
+function! ale#filename_mapping#Invert(filename_mappings) abort
+ return map(copy(a:filename_mappings), '[v:val[1], v:val[0]]')
+endfunction
+
+" Given a filename and some filename_mappings, map a filename.
+function! ale#filename_mapping#Map(filename, filename_mappings) abort
+ let l:simplified_filename = ale#path#Simplify(a:filename)
+
+ for [l:mapping_from, l:mapping_to] in a:filename_mappings
+ let l:mapping_from = ale#path#Simplify(l:mapping_from)
+
+ if l:simplified_filename[:len(l:mapping_from) - 1] is# l:mapping_from
+ return l:mapping_to . l:simplified_filename[len(l:mapping_from):]
+ endif
+ endfor
+
+ return a:filename
+endfunction
diff --git a/autoload/ale/fix.vim b/autoload/ale/fix.vim
index dad9e2bc..8b841b13 100644
--- a/autoload/ale/fix.vim
+++ b/autoload/ale/fix.vim
@@ -1,57 +1,43 @@
-call ale#Set('fix_on_save_ignore', {})
+" Author: w0rp <devw0rp@gmail.com>
+" Description: Functions for fixing code with programs, or other means.
+
+let g:ale_fix_on_save_ignore = get(g:, 'ale_fix_on_save_ignore', {})
+let g:ale_filename_mappings = get(g:, 'ale_filename_mappings', {})
" Apply fixes queued up for buffers which may be hidden.
" 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
-
- if l:data.should_save
- if a:buffer is bufnr('')
- if empty(&buftype)
- noautocmd :w!
+ try
+ if l:data.changes_made
+ let l:new_lines = ale#util#SetBufferContents(a:buffer, l:data.output)
+
+ if l:data.should_save
+ if a:buffer is bufnr('')
+ if empty(&buftype)
+ noautocmd :w!
+ else
+ set nomodified
+ endif
else
- set nomodified
+ call writefile(l:new_lines, expand('#' . a:buffer . ':p')) " no-custom-checks
+ call setbufvar(a:buffer, '&modified', 0)
endif
- else
- call writefile(l:new_lines, expand('#' . a:buffer . ':p')) " no-custom-checks
- call setbufvar(a:buffer, '&modified', 0)
endif
endif
- endif
+ catch /E21/
+ " If we cannot modify the buffer now, try again later.
+ let g:ale_fix_buffer_data[a:buffer] = l:data
+
+ return
+ endtry
if l:data.should_save
let l:should_lint = ale#Var(a:buffer, 'fix_on_save')
@@ -115,7 +101,6 @@ function! s:HandleExit(job_info, buffer, job_output, data) abort
let l:output = a:job_output
endif
- let l:ChainCallback = get(a:job_info, 'chain_with', v:null)
let l:ProcessWith = get(a:job_info, 'process_with', v:null)
" Post-process the output with a function if we have one.
@@ -127,27 +112,17 @@ function! s:HandleExit(job_info, buffer, job_output, data) abort
" otherwise skip this job and use the input from before.
"
" We'll use the input from before for chained commands.
- if l:ChainCallback is v:null && !empty(split(join(l:output)))
+ if !empty(split(join(l:output)))
let l:input = l:output
else
let l:input = a:job_info.input
endif
- if l:ChainCallback isnot v:null && !get(g:, 'ale_ignore_2_4_warnings')
- execute 'echom ''chain_with is deprecated. Use `let g:ale_ignore_2_4_warnings = 1` to disable this message.'''
- endif
-
- let l:next_index = l:ChainCallback is v:null
- \ ? a:job_info.callback_index + 1
- \ : a:job_info.callback_index
-
call s:RunFixer({
\ 'buffer': a:buffer,
\ 'input': l:input,
- \ 'output': l:output,
\ 'callback_list': a:job_info.callback_list,
- \ 'callback_index': l:next_index,
- \ 'chain_callback': l:ChainCallback,
+ \ 'callback_index': a:job_info.callback_index + 1,
\})
endfunction
@@ -160,6 +135,7 @@ function! s:RunJob(result, options) abort
let l:buffer = a:options.buffer
let l:input = a:options.input
+ let l:fixer_name = a:options.fixer_name
if a:result is 0 || type(a:result) is v:t_list
if type(a:result) is v:t_list
@@ -177,26 +153,21 @@ function! s:RunJob(result, options) abort
endif
let l:command = get(a:result, 'command', '')
- let l:ChainWith = get(a:result, 'chain_with', v:null)
if empty(l:command)
- " If the command is empty, skip to the next item, or call the
- " chain_with function.
+ " If the command is empty, skip to the next item.
call s:RunFixer({
\ 'buffer': l:buffer,
\ 'input': l:input,
- \ 'callback_index': a:options.callback_index + (l:ChainWith is v:null),
+ \ 'callback_index': a:options.callback_index,
\ 'callback_list': a:options.callback_list,
- \ 'chain_callback': l:ChainWith,
- \ 'output': [],
\})
return
endif
let l:read_temporary_file = get(a:result, 'read_temporary_file', 0)
- " Default to piping the buffer for the last fixer in the chain.
- let l:read_buffer = get(a:result, 'read_buffer', l:ChainWith is v:null)
+ let l:read_buffer = get(a:result, 'read_buffer', 1)
let l:output_stream = get(a:result, 'output_stream', 'stdout')
if l:read_temporary_file
@@ -205,7 +176,6 @@ function! s:RunJob(result, options) abort
let l:Callback = function('s:HandleExit', [{
\ 'input': l:input,
- \ 'chain_with': l:ChainWith,
\ 'callback_index': a:options.callback_index,
\ 'callback_list': a:options.callback_list,
\ 'process_with': get(a:result, 'process_with', v:null),
@@ -217,6 +187,7 @@ function! s:RunJob(result, options) abort
\ 'read_buffer': l:read_buffer,
\ 'input': l:input,
\ 'log_output': 0,
+ \ 'filename_mappings': ale#GetFilenameMappings(l:buffer, l:fixer_name),
\})
if empty(l:run_result)
@@ -240,32 +211,22 @@ function! s:RunFixer(options) abort
return
endif
- let l:ChainCallback = get(a:options, 'chain_callback', v:null)
-
- let l:Function = l:ChainCallback isnot v:null
- \ ? ale#util#GetFunction(l:ChainCallback)
- \ : a:options.callback_list[l:index]
+ let [l:fixer_name, l:Function] = a:options.callback_list[l:index]
" Record new jobs started as fixer jobs.
call setbufvar(l:buffer, 'ale_job_type', 'fixer')
- if l:ChainCallback isnot v:null
- " Chained commands accept (buffer, output, [input])
- let l:result = ale#util#FunctionArgCount(l:Function) == 2
- \ ? call(l:Function, [l:buffer, a:options.output])
- \ : call(l:Function, [l:buffer, a:options.output, copy(l:input)])
- else
- " Regular fixer commands accept (buffer, [input])
- let l:result = ale#util#FunctionArgCount(l:Function) == 1
- \ ? call(l:Function, [l:buffer])
- \ : call(l:Function, [l:buffer, copy(l:input)])
- endif
+ " Regular fixer commands accept (buffer, [input])
+ let l:result = ale#util#FunctionArgCount(l:Function) == 1
+ \ ? call(l:Function, [l:buffer])
+ \ : call(l:Function, [l:buffer, copy(l:input)])
call s:RunJob(l:result, {
\ 'buffer': l:buffer,
\ 'input': l:input,
\ 'callback_list': a:options.callback_list,
\ 'callback_index': l:index,
+ \ 'fixer_name': l:fixer_name,
\})
endfunction
@@ -333,16 +294,24 @@ function! s:GetCallbacks(buffer, fixing_flag, fixers) abort
" Variables with capital characters are needed, or Vim will complain about
" funcref variables.
for l:Item in l:callback_list
+ " Try to capture the names of registered fixer names, so we can use
+ " them for filename mapping or other purposes later.
+ let l:fixer_name = v:null
+
if type(l:Item) is v:t_string
let l:Func = ale#fix#registry#GetFunc(l:Item)
if !empty(l:Func)
+ let l:fixer_name = l:Item
let l:Item = l:Func
endif
endif
try
- call add(l:corrected_list, ale#util#GetFunction(l:Item))
+ call add(l:corrected_list, [
+ \ l:fixer_name,
+ \ ale#util#GetFunction(l:Item)
+ \])
catch /E475/
" Rethrow exceptions for failing to get a function so we can print
" a friendly message about it.
@@ -414,3 +383,4 @@ endfunction
augroup ALEBufferFixGroup
autocmd!
autocmd BufEnter * call ale#fix#ApplyQueuedFixes(str2nr(expand('<abuf>')))
+augroup END
diff --git a/autoload/ale/fix/registry.vim b/autoload/ale/fix/registry.vim
index 1b3ca1a8..d71668f2 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'],
@@ -360,11 +365,21 @@ let s:default_registry = {
\ 'suggested_filetypes': ['nix'],
\ 'description': 'A formatter for Nix code',
\ },
+\ 'remark-lint': {
+\ 'function': 'ale#fixers#remark_lint#Fix',
+\ 'suggested_filetypes': ['markdown'],
+\ 'description': 'Fix markdown files with remark-lint',
+\ },
\ 'html-beautify': {
\ 'function': 'ale#fixers#html_beautify#Fix',
\ 'suggested_filetypes': ['html', 'htmldjango'],
\ 'description': 'Fix HTML files with html-beautify.',
\ },
+\ 'dhall': {
+\ 'function': 'ale#fixers#dhall#Fix',
+\ 'suggested_filetypes': ['dhall'],
+\ 'description': 'Fix Dhall files with dhall-format.',
+\ },
\}
" Reset the function registry to the default entries.
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/dhall.vim b/autoload/ale/fixers/dhall.vim
new file mode 100644
index 00000000..18f6006c
--- /dev/null
+++ b/autoload/ale/fixers/dhall.vim
@@ -0,0 +1,23 @@
+" Author: Pat Brisbin <pbrisbin@gmail.com>
+" Description: Integration of dhall-format with ALE.
+
+call ale#Set('dhall_format_executable', 'dhall')
+
+function! ale#fixers#dhall#GetExecutable(buffer) abort
+ let l:executable = ale#Var(a:buffer, 'dhall_format_executable')
+
+ " Dhall is written in Haskell and commonly installed with Stack
+ return ale#handlers#haskell_stack#EscapeExecutable(l:executable, 'dhall')
+endfunction
+
+function! ale#fixers#dhall#Fix(buffer) abort
+ let l:executable = ale#fixers#dhall#GetExecutable(a:buffer)
+
+ return {
+ \ 'command': l:executable
+ \ . ' format'
+ \ . ' --inplace'
+ \ . ' %t',
+ \ 'read_temporary_file': 1,
+ \}
+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/latexindent.vim b/autoload/ale/fixers/latexindent.vim
index b0a0884a..54f1231e 100644
--- a/autoload/ale/fixers/latexindent.vim
+++ b/autoload/ale/fixers/latexindent.vim
@@ -10,9 +10,7 @@ function! ale#fixers#latexindent#Fix(buffer) abort
return {
\ 'command': ale#Escape(l:executable)
- \ . ' -l -w'
+ \ . ' -l'
\ . (empty(l:options) ? '' : ' ' . l:options)
- \ . ' %t',
- \ 'read_temporary_file': 1,
\}
endfunction
diff --git a/autoload/ale/fixers/ocamlformat.vim b/autoload/ale/fixers/ocamlformat.vim
index 9b7c3e12..b12d2eb9 100644
--- a/autoload/ale/fixers/ocamlformat.vim
+++ b/autoload/ale/fixers/ocamlformat.vim
@@ -5,14 +5,13 @@ call ale#Set('ocaml_ocamlformat_executable', 'ocamlformat')
call ale#Set('ocaml_ocamlformat_options', '')
function! ale#fixers#ocamlformat#Fix(buffer) abort
- let l:filename = expand('#' . a:buffer . ':p')
let l:executable = ale#Var(a:buffer, 'ocaml_ocamlformat_executable')
let l:options = ale#Var(a:buffer, 'ocaml_ocamlformat_options')
return {
\ 'command': ale#Escape(l:executable)
\ . (empty(l:options) ? '' : ' ' . l:options)
- \ . ' --name=' . ale#Escape(l:filename)
+ \ . ' --name=%s'
\ . ' -'
\}
endfunction
diff --git a/autoload/ale/fixers/prettier.vim b/autoload/ale/fixers/prettier.vim
index 23120777..e0f4972e 100644
--- a/autoload/ale/fixers/prettier.vim
+++ b/autoload/ale/fixers/prettier.vim
@@ -34,6 +34,21 @@ function! ale#fixers#prettier#ProcessPrettierDOutput(buffer, output) abort
return a:output
endfunction
+function! ale#fixers#prettier#GetProjectRoot(buffer) abort
+ let l:config = ale#path#FindNearestFile(a:buffer, '.prettierignore')
+
+ if !empty(l:config)
+ return fnamemodify(l:config, ':h')
+ endif
+
+ " Fall back to the directory of the buffer
+ return fnamemodify(bufname(a:buffer), ':p:h')
+endfunction
+
+function! ale#fixers#prettier#CdProjectRoot(buffer) abort
+ return ale#path#CdString(ale#fixers#prettier#GetProjectRoot(a:buffer))
+endfunction
+
function! ale#fixers#prettier#ApplyFixForVersion(buffer, version) abort
let l:executable = ale#fixers#prettier#GetExecutable(a:buffer)
let l:options = ale#Var(a:buffer, 'javascript_prettier_options')
@@ -97,7 +112,7 @@ function! ale#fixers#prettier#ApplyFixForVersion(buffer, version) abort
" 1.4.0 is the first version with --stdin-filepath
if ale#semver#GTE(a:version, [1, 4, 0])
return {
- \ 'command': ale#path#BufferCdString(a:buffer)
+ \ 'command': ale#fixers#prettier#CdProjectRoot(a:buffer)
\ . ale#Escape(l:executable)
\ . (!empty(l:options) ? ' ' . l:options : '')
\ . ' --stdin-filepath %s --stdin',
diff --git a/autoload/ale/fixers/prettier_standard.vim b/autoload/ale/fixers/prettier_standard.vim
index b6e0a6f9..9d982ff6 100644
--- a/autoload/ale/fixers/prettier_standard.vim
+++ b/autoload/ale/fixers/prettier_standard.vim
@@ -17,8 +17,8 @@ function! ale#fixers#prettier_standard#Fix(buffer) abort
return {
\ 'command': ale#Escape(ale#fixers#prettier_standard#GetExecutable(a:buffer))
- \ . ' %t'
+ \ . ' --stdin'
+ \ . ' --stdin-filepath=%s'
\ . ' ' . l:options,
- \ 'read_temporary_file': 1,
\}
endfunction
diff --git a/autoload/ale/fixers/remark_lint.vim b/autoload/ale/fixers/remark_lint.vim
new file mode 100644
index 00000000..3ce442f3
--- /dev/null
+++ b/autoload/ale/fixers/remark_lint.vim
@@ -0,0 +1,24 @@
+" Author: blyoa <blyoa110@gmail.com>
+" Description: Fixing files with remark-lint.
+
+call ale#Set('markdown_remark_lint_executable', 'remark')
+call ale#Set('markdown_remark_lint_use_global', get(g:, 'ale_use_global_executables', 0))
+call ale#Set('markdown_remark_lint_options', '')
+
+function! ale#fixers#remark_lint#GetExecutable(buffer) abort
+ return ale#node#FindExecutable(a:buffer, 'markdown_remark_lint', [
+ \ 'node_modules/remark-cli/cli.js',
+ \ 'node_modules/.bin/remark',
+ \])
+endfunction
+
+function! ale#fixers#remark_lint#Fix(buffer) abort
+ let l:executable = ale#fixers#remark_lint#GetExecutable(a:buffer)
+ let l:options = ale#Var(a:buffer, 'markdown_remark_lint_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 0c7441e4..cdfb014a 100644
--- a/autoload/ale/fixers/rubocop.vim
+++ b/autoload/ale/fixers/rubocop.vim
@@ -1,20 +1,40 @@
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 %s'
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/standard.vim b/autoload/ale/fixers/standard.vim
index cffa9f9d..46decebf 100644
--- a/autoload/ale/fixers/standard.vim
+++ b/autoload/ale/fixers/standard.vim
@@ -27,7 +27,7 @@ function! ale#fixers#standard#Fix(buffer) abort
return {
\ 'command': ale#node#Executable(a:buffer, l:executable)
\ . (!empty(l:options) ? ' ' . l:options : '')
- \ . ' --fix %t',
+ \ . ' --fix --stdin < %s > %t',
\ 'read_temporary_file': 1,
\}
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/gcc.vim b/autoload/ale/handlers/gcc.vim
index ec16b977..0b37c98a 100644
--- a/autoload/ale/handlers/gcc.vim
+++ b/autoload/ale/handlers/gcc.vim
@@ -10,7 +10,7 @@ let s:pragma_error = '#pragma once in main file'
" <stdin>:8:5: warning: conversion lacks type at end of format [-Wformat=]
" <stdin>:10:27: error: invalid operands to binary - (have ‘int’ and ‘char *’)
" -:189:7: note: $/${} is unnecessary on arithmetic variables. [SC2004]
-let s:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):(\d+)?:? ([^:]+): (.+)$'
+let s:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+)?:?(\d+)?:? ([^:]+): (.+)$'
let s:inline_pattern = '\v inlined from .* at \<stdin\>:(\d+):(\d+):$'
function! s:IsHeaderFile(filename) abort
@@ -117,6 +117,23 @@ function! ale#handlers#gcc#HandleGCCFormat(buffer, lines) abort
if !empty(l:output)
if !has_key(l:output[-1], 'detail')
let l:output[-1].detail = l:output[-1].text
+
+ " handle macro expansion errors/notes
+ if l:match[5] =~? '^in expansion of macro ‘\w*\w’$'
+ " if the macro expansion is in the file we're in, add
+ " the lnum and col keys to the previous error
+ if l:match[1] is# '<stdin>'
+ \ && !has_key(l:output[-1], 'col')
+ let l:output[-1].lnum = str2nr(l:match[2])
+ let l:output[-1].col = str2nr(l:match[3])
+ else
+ " the error is not in the current file, and since
+ " macro expansion errors don't show the full path to
+ " the error from the current file, we have to just
+ " give out a generic error message
+ let l:output[-1].text = 'Error found in macro expansion. See :ALEDetail'
+ endif
+ endif
endif
let l:output[-1].detail = l:output[-1].detail . "\n"
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/sh.vim b/autoload/ale/handlers/sh.vim
index 75eaf71f..1e50cb89 100644
--- a/autoload/ale/handlers/sh.vim
+++ b/autoload/ale/handlers/sh.vim
@@ -4,17 +4,24 @@
function! ale#handlers#sh#GetShellType(buffer) abort
let l:bang_line = get(getbufline(a:buffer, 1), 0, '')
+ let l:command = ''
+
" Take the shell executable from the hashbang, if we can.
if l:bang_line[:1] is# '#!'
" Remove options like -e, etc.
let l:command = substitute(l:bang_line, ' --\?[a-zA-Z0-9]\+', '', 'g')
+ endif
- for l:possible_shell in ['bash', 'dash', 'ash', 'tcsh', 'csh', 'zsh', 'ksh', 'sh']
- if l:command =~# l:possible_shell . '\s*$'
- return l:possible_shell
- endif
- endfor
+ " If we couldn't find a hashbang, try the filetype
+ if l:command is# ''
+ let l:command = &filetype
endif
+ for l:possible_shell in ['bash', 'dash', 'ash', 'tcsh', 'csh', 'zsh', 'ksh', 'sh']
+ if l:command =~# l:possible_shell . '\s*$'
+ return l:possible_shell
+ endif
+ endfor
+
return ''
endfunction
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/hover.vim b/autoload/ale/hover.vim
index 8fdd288c..38b4b866 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
@@ -143,7 +264,10 @@ function! s:OnReady(line, column, opt, linter, lsp_details) abort
" hover position probably won't make sense.
call ale#lsp#NotifyForChanges(l:id, l:buffer)
- let l:column = min([a:column, len(getbufline(l:buffer, a:line)[0])])
+ let l:column = max([
+ \ min([a:column, len(getbufline(l:buffer, a:line)[0])]),
+ \ 1,
+ \])
let l:message = ale#lsp#message#Hover(l:buffer, a:line, l:column)
endif
@@ -156,6 +280,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 +306,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 +316,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 a85f06e2..b483fc19 100644
--- a/autoload/ale/linter.vim
+++ b/autoload/ale/linter.vim
@@ -14,6 +14,7 @@ let s:default_ale_linter_aliases = {
\ 'csh': 'sh',
\ 'javascriptreact': ['javascript', 'jsx'],
\ 'plaintex': 'tex',
+\ 'ps1': 'powershell',
\ 'rmarkdown': 'r',
\ 'rmd': 'r',
\ 'systemverilog': 'verilog',
@@ -31,7 +32,7 @@ let s:default_ale_linter_aliases = {
"
" No linters are used for plaintext files by default.
"
-" Only cargo is enabled for Rust by default.
+" Only cargo and rls are enabled for Rust by default.
" rpmlint is disabled by default because it can result in code execution.
" hhast is disabled by default because it executes code in the project root.
"
@@ -44,8 +45,8 @@ let s:default_ale_linters = {
\ 'help': [],
\ 'perl': ['perlcritic'],
\ 'perl6': [],
-\ 'python': ['flake8', 'mypy', 'pylint'],
-\ 'rust': ['cargo'],
+\ 'python': ['flake8', 'mypy', 'pylint', 'pyright'],
+\ 'rust': ['cargo', 'rls'],
\ 'spec': [],
\ 'text': [],
\ 'vue': ['eslint', 'vls'],
@@ -76,10 +77,6 @@ function! s:IsBoolean(value) abort
return type(a:value) is v:t_number && (a:value == 0 || a:value == 1)
endfunction
-function! s:LanguageGetter(buffer) dict abort
- return l:self.language
-endfunction
-
function! ale#linter#PreProcess(filetype, linter) abort
if type(a:linter) isnot v:t_dict
throw 'The linter object must be a Dictionary'
@@ -113,14 +110,7 @@ function! ale#linter#PreProcess(filetype, linter) abort
if !l:needs_executable
if has_key(a:linter, 'executable')
- \|| has_key(a:linter, 'executable_callback')
- throw '`executable` and `executable_callback` cannot be used when lsp == ''socket'''
- endif
- elseif has_key(a:linter, 'executable_callback')
- let l:obj.executable_callback = a:linter.executable_callback
-
- if !s:IsCallback(l:obj.executable_callback)
- throw '`executable_callback` must be a callback if defined'
+ throw '`executable` cannot be used when lsp == ''socket'''
endif
elseif has_key(a:linter, 'executable')
let l:obj.executable = a:linter.executable
@@ -130,54 +120,12 @@ function! ale#linter#PreProcess(filetype, linter) abort
throw '`executable` must be a String or Function if defined'
endif
else
- throw 'Either `executable` or `executable_callback` must be defined'
+ throw '`executable` must be defined'
endif
if !l:needs_command
if has_key(a:linter, 'command')
- \|| has_key(a:linter, 'command_callback')
- \|| has_key(a:linter, 'command_chain')
- throw '`command` and `command_callback` and `command_chain` cannot be used when lsp == ''socket'''
- endif
- elseif has_key(a:linter, 'command_chain')
- let l:obj.command_chain = a:linter.command_chain
-
- if type(l:obj.command_chain) isnot v:t_list
- throw '`command_chain` must be a List'
- endif
-
- if empty(l:obj.command_chain)
- throw '`command_chain` must contain at least one item'
- endif
-
- let l:link_index = 0
-
- for l:link in l:obj.command_chain
- let l:err_prefix = 'The `command_chain` item ' . l:link_index . ' '
-
- if !s:IsCallback(get(l:link, 'callback'))
- throw l:err_prefix . 'must define a `callback` function'
- endif
-
- if has_key(l:link, 'output_stream')
- if type(l:link.output_stream) isnot v:t_string
- \|| index(['stdout', 'stderr', 'both'], l:link.output_stream) < 0
- throw l:err_prefix . '`output_stream` flag must be '
- \ . "'stdout', 'stderr', or 'both'"
- endif
- endif
-
- if has_key(l:link, 'read_buffer') && !s:IsBoolean(l:link.read_buffer)
- throw l:err_prefix . 'value for `read_buffer` must be `0` or `1`'
- endif
-
- let l:link_index += 1
- endfor
- elseif has_key(a:linter, 'command_callback')
- let l:obj.command_callback = a:linter.command_callback
-
- if !s:IsCallback(l:obj.command_callback)
- throw '`command_callback` must be a callback if defined'
+ throw '`command` cannot be used when lsp == ''socket'''
endif
elseif has_key(a:linter, 'command')
let l:obj.command = a:linter.command
@@ -187,22 +135,12 @@ function! ale#linter#PreProcess(filetype, linter) abort
throw '`command` must be a String or Function if defined'
endif
else
- throw 'Either `command`, `executable_callback`, `command_chain` '
- \ . 'must be defined'
- endif
-
- if (
- \ has_key(a:linter, 'command')
- \ + has_key(a:linter, 'command_chain')
- \ + has_key(a:linter, 'command_callback')
- \) > 1
- throw 'Only one of `command`, `command_callback`, or `command_chain` '
- \ . 'should be set'
+ throw '`command` must be defined'
endif
if !l:needs_address
- if has_key(a:linter, 'address') || has_key(a:linter, 'address_callback')
- throw '`address` or `address_callback` cannot be used when lsp != ''socket'''
+ if has_key(a:linter, 'address')
+ throw '`address` cannot be used when lsp != ''socket'''
endif
elseif has_key(a:linter, 'address')
if type(a:linter.address) isnot v:t_string
@@ -211,41 +149,17 @@ function! ale#linter#PreProcess(filetype, linter) abort
endif
let l:obj.address = a:linter.address
- elseif has_key(a:linter, 'address_callback')
- let l:obj.address_callback = a:linter.address_callback
-
- if !s:IsCallback(l:obj.address_callback)
- throw '`address_callback` must be a callback if defined'
- endif
else
- throw '`address` or `address_callback` must be defined for getting the LSP address'
+ throw '`address` must be defined for getting the LSP address'
endif
if l:needs_lsp_details
- if has_key(a:linter, 'language_callback')
- if has_key(a:linter, 'language')
- throw 'Only one of `language` or `language_callback` '
- \ . 'should be set'
- endif
+ " Default to using the filetype as the language.
+ let l:obj.language = get(a:linter, 'language', a:filetype)
- let l:obj.language_callback = get(a:linter, 'language_callback')
-
- if !s:IsCallback(l:obj.language_callback)
- throw '`language_callback` must be a callback for LSP linters'
- endif
- else
- " Default to using the filetype as the language.
- let l:Language = get(a:linter, 'language', a:filetype)
-
- if type(l:Language) is v:t_string
- " Make 'language_callback' return the 'language' value.
- let l:obj.language = l:Language
- let l:obj.language_callback = function('s:LanguageGetter')
- elseif type(l:Language) is v:t_func
- let l:obj.language_callback = l:Language
- else
- throw '`language` must be a String or Funcref'
- endif
+ if type(l:obj.language) isnot v:t_string
+ \&& type(l:obj.language) isnot v:t_func
+ throw '`language` must be a String or Funcref if defined'
endif
if has_key(a:linter, 'project_root')
@@ -253,16 +167,10 @@ function! ale#linter#PreProcess(filetype, linter) abort
if type(l:obj.project_root) isnot v:t_string
\&& type(l:obj.project_root) isnot v:t_func
- throw '`project_root` must be a String or Function if defined'
- endif
- elseif has_key(a:linter, 'project_root_callback')
- let l:obj.project_root_callback = a:linter.project_root_callback
-
- if !s:IsCallback(l:obj.project_root_callback)
- throw '`project_root_callback` must be a callback if defined'
+ throw '`project_root` must be a String or Function'
endif
else
- throw '`project_root` or `project_root_callback` must be defined for LSP linters'
+ throw '`project_root` must be defined for LSP linters'
endif
if has_key(a:linter, 'completion_filter')
@@ -273,37 +181,16 @@ function! ale#linter#PreProcess(filetype, linter) abort
endif
endif
- if has_key(a:linter, 'initialization_options_callback')
- if has_key(a:linter, 'initialization_options')
- throw 'Only one of `initialization_options` or '
- \ . '`initialization_options_callback` should be set'
- endif
-
- let l:obj.initialization_options_callback = a:linter.initialization_options_callback
-
- if !s:IsCallback(l:obj.initialization_options_callback)
- throw '`initialization_options_callback` must be a callback if defined'
- endif
- elseif has_key(a:linter, 'initialization_options')
+ if has_key(a:linter, 'initialization_options')
let l:obj.initialization_options = a:linter.initialization_options
if type(l:obj.initialization_options) isnot v:t_dict
\&& type(l:obj.initialization_options) isnot v:t_func
- throw '`initialization_options` must be a String or Function if defined'
+ throw '`initialization_options` must be a Dictionary or Function if defined'
endif
endif
- if has_key(a:linter, 'lsp_config_callback')
- if has_key(a:linter, 'lsp_config')
- throw 'Only one of `lsp_config` or `lsp_config_callback` should be set'
- endif
-
- let l:obj.lsp_config_callback = a:linter.lsp_config_callback
-
- if !s:IsCallback(l:obj.lsp_config_callback)
- throw '`lsp_config_callback` must be a callback if defined'
- endif
- elseif has_key(a:linter, 'lsp_config')
+ if has_key(a:linter, 'lsp_config')
if type(a:linter.lsp_config) isnot v:t_dict
\&& type(a:linter.lsp_config) isnot v:t_func
throw '`lsp_config` must be a Dictionary or Function if defined'
@@ -324,21 +211,17 @@ function! ale#linter#PreProcess(filetype, linter) abort
" file on disk.
let l:obj.lint_file = get(a:linter, 'lint_file', 0)
- if !s:IsBoolean(l:obj.lint_file)
- throw '`lint_file` must be `0` or `1`'
+ if !s:IsBoolean(l:obj.lint_file) && type(l:obj.lint_file) isnot v:t_func
+ throw '`lint_file` must be `0`, `1`, or a Function'
endif
" An option indicating that the buffer should be read.
- let l:obj.read_buffer = get(a:linter, 'read_buffer', !l:obj.lint_file)
+ let l:obj.read_buffer = get(a:linter, 'read_buffer', 1)
if !s:IsBoolean(l:obj.read_buffer)
throw '`read_buffer` must be `0` or `1`'
endif
- if l:obj.lint_file && l:obj.read_buffer
- throw 'Only one of `lint_file` or `read_buffer` can be `1`'
- endif
-
let l:obj.aliases = get(a:linter, 'aliases', [])
if type(l:obj.aliases) isnot v:t_list
@@ -346,14 +229,6 @@ function! ale#linter#PreProcess(filetype, linter) abort
throw '`aliases` must be a List of String values'
endif
- for l:key in filter(keys(a:linter), 'v:val[-9:] is# ''_callback'' || v:val is# ''command_chain''')
- if !get(g:, 'ale_ignore_2_4_warnings')
- execute 'echom l:key . '' is deprecated. Use `let g:ale_ignore_2_4_warnings = 1` to disable this message.'''
- endif
-
- break
- endfor
-
return l:obj
endfunction
@@ -521,9 +396,7 @@ endfunction
" Given a buffer and linter, get the executable String for the linter.
function! ale#linter#GetExecutable(buffer, linter) abort
- let l:Executable = has_key(a:linter, 'executable_callback')
- \ ? function(a:linter.executable_callback)
- \ : a:linter.executable
+ let l:Executable = a:linter.executable
return type(l:Executable) is v:t_func
\ ? l:Executable(a:buffer)
@@ -531,24 +404,21 @@ function! ale#linter#GetExecutable(buffer, linter) abort
endfunction
" Given a buffer and linter, get the command String for the linter.
-" The command_chain key is not supported.
function! ale#linter#GetCommand(buffer, linter) abort
- let l:Command = has_key(a:linter, 'command_callback')
- \ ? function(a:linter.command_callback)
- \ : a:linter.command
+ let l:Command = a:linter.command
- return type(l:Command) is v:t_func
- \ ? l:Command(a:buffer)
- \ : l:Command
+ return type(l:Command) is v:t_func ? l:Command(a:buffer) : l:Command
endfunction
" Given a buffer and linter, get the address for connecting to the server.
function! ale#linter#GetAddress(buffer, linter) abort
- let l:Address = has_key(a:linter, 'address_callback')
- \ ? function(a:linter.address_callback)
- \ : a:linter.address
+ let l:Address = a:linter.address
+
+ return type(l:Address) is v:t_func ? l:Address(a:buffer) : l:Address
+endfunction
+
+function! ale#linter#GetLanguage(buffer, linter) abort
+ let l:Language = a:linter.language
- return type(l:Address) is v:t_func
- \ ? l:Address(a:buffer)
- \ : l:Address
+ return type(l:Language) is v:t_func ? l:Language(a:buffer) : l:Language
endfunction
diff --git a/autoload/ale/lsp.vim b/autoload/ale/lsp.vim
index 2509174e..7d99e9d2 100644
--- a/autoload/ale/lsp.vim
+++ b/autoload/ale/lsp.vim
@@ -64,6 +64,9 @@ endfunction
" Used only in tests.
function! ale#lsp#GetConnections() abort
+ " This command will throw from the sandbox.
+ let &l:equalprg=&l:equalprg
+
return s:connections
endfunction
@@ -196,14 +199,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 +235,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
@@ -425,6 +452,7 @@ function! ale#lsp#StartProgram(conn_id, executable, command) abort
endif
if l:started && !l:conn.is_tsserver
+ let l:conn.initialized = 0
call s:SendInitMessage(l:conn)
endif
diff --git a/autoload/ale/lsp_linter.vim b/autoload/ale/lsp_linter.vim
index e4148ceb..db640654 100644
--- a/autoload/ale/lsp_linter.vim
+++ b/autoload/ale/lsp_linter.vim
@@ -227,7 +227,7 @@ function! ale#lsp_linter#OnInit(linter, details, Callback) abort
let l:command = a:details.command
let l:config = ale#lsp_linter#GetConfig(l:buffer, a:linter)
- let l:language_id = ale#util#GetFunction(a:linter.language_callback)(l:buffer)
+ let l:language_id = ale#linter#GetLanguage(l:buffer, a:linter)
call ale#lsp#UpdateConfig(l:conn_id, l:buffer, l:config)
@@ -265,7 +265,14 @@ function! s:StartLSP(options, address, executable, command) abort
call ale#lsp#MarkConnectionAsTsserver(l:conn_id)
endif
- let l:command = ale#command#FormatCommand(l:buffer, a:executable, a:command, 0, v:false)[1]
+ let l:command = ale#command#FormatCommand(
+ \ l:buffer,
+ \ a:executable,
+ \ a:command,
+ \ 0,
+ \ v:false,
+ \ [],
+ \)[1]
let l:command = ale#job#PrepareCommand(l:buffer, l:command)
let l:ready = ale#lsp#StartProgram(l:conn_id, a:executable, l:command)
endif
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/path.vim b/autoload/ale/path.vim
index 30550503..fed95ccd 100644
--- a/autoload/ale/path.vim
+++ b/autoload/ale/path.vim
@@ -24,6 +24,14 @@ function! ale#path#Simplify(path) abort
return substitute(simplify(l:win_path), '^\\\+', '\', 'g') " no-custom-checks
endfunction
+" Simplify a path without a Windows drive letter.
+" This function can be used for checking if paths are equal.
+function! ale#path#RemoveDriveLetter(path) abort
+ return has('win32') && a:path[1:2] is# ':\'
+ \ ? ale#path#Simplify(a:path[2:])
+ \ : ale#path#Simplify(a:path)
+endfunction
+
" Given a buffer and a filename, find the nearest file by searching upwards
" through the paths relative to the given buffer.
function! ale#path#FindNearestFile(buffer, filename) abort
@@ -74,15 +82,19 @@ endfunction
function! ale#path#CdString(directory) abort
if has('win32')
return 'cd /d ' . ale#Escape(a:directory) . ' && '
- else
- return 'cd ' . ale#Escape(a:directory) . ' && '
endif
+
+ return 'cd ' . ale#Escape(a:directory) . ' && '
endfunction
" Output 'cd <buffer_filename_directory> && '
" This function can be used changing the directory for a linter command.
function! ale#path#BufferCdString(buffer) abort
- return ale#path#CdString(fnamemodify(bufname(a:buffer), ':p:h'))
+ if has('win32')
+ return 'cd /d %s:h && '
+ endif
+
+ return 'cd %s:h && '
endfunction
" Return 1 if a path is an absolute path.
@@ -95,7 +107,7 @@ function! ale#path#IsAbsolute(filename) abort
return a:filename[:0] is# '/' || a:filename[1:2] is# ':\'
endfunction
-let s:temp_dir = ale#path#Simplify(fnamemodify(ale#util#Tempname(), ':h'))
+let s:temp_dir = ale#path#Simplify(fnamemodify(ale#util#Tempname(), ':h:h'))
" Given a filename, return 1 if the file represents some temporary file
" created by Vim.
diff --git a/autoload/ale/preview.vim b/autoload/ale/preview.vim
index 6d58aca9..8b94aa7a 100644
--- a/autoload/ale/preview.vim
+++ b/autoload/ale/preview.vim
@@ -1,6 +1,22 @@
" Author: w0rp <devw0rp@gmail.com>
" Description: Preview windows for showing whatever information in.
+if !has_key(s:, 'last__list')
+ let s:last_list = []
+endif
+
+if !has_key(s:, 'last_options')
+ let s:last_options = {}
+endif
+
+function! ale#preview#SetLastSelection(item_list, options) abort
+ let s:last_list = a:item_list
+ let s:last_options = {
+ \ 'open_in': get(a:options, 'open_in', 'current-buffer'),
+ \ 'use_relative_paths': get(a:options, 'use_relative_paths', 0),
+ \}
+endfunction
+
" 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 +39,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 +87,19 @@ 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')
+
+ " Remember preview state, so we can repeat it later.
+ call ale#preview#SetLastSelection(a:item_list, l:options)
endfunction
-function! s:Open(open_in_tab) abort
+function! ale#preview#RepeatSelection() abort
+ if !empty(s:last_list)
+ call ale#preview#ShowSelection(s:last_list, s:last_options)
+ endif
+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 +107,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/test.vim b/autoload/ale/test.vim
index 082d91ff..6fcbf35e 100644
--- a/autoload/ale/test.vim
+++ b/autoload/ale/test.vim
@@ -145,8 +145,8 @@ function! ale#test#WaitForJobs(deadline) abort
" end, but before handlers are run.
sleep 10ms
- " We must check the buffer data again to see if new jobs started
- " for command_chain linters.
+ " We must check the buffer data again to see if new jobs started for
+ " linters with chained commands.
let l:has_new_jobs = 0
" Check again to see if any jobs are running.
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 8d166625..1f396377 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.
@@ -418,7 +423,10 @@ function! ale#util#Writefile(buffer, lines, filename) abort
\ ? map(copy(a:lines), 'substitute(v:val, ''\r*$'', ''\r'', '''')')
\ : a:lines
- call writefile(l:corrected_lines, a:filename, 'S') " no-custom-checks
+ " Set binary flag if buffer doesn't have eol and nofixeol to avoid appending newline
+ let l:flags = !getbufvar(a:buffer, '&eol') && exists('+fixeol') && !&fixeol ? 'bS' : 'S'
+
+ call writefile(l:corrected_lines, a:filename, l:flags) " no-custom-checks
endfunction
if !exists('s:patial_timers')
@@ -476,3 +484,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