diff --git a/README.md b/README.md
index 0f1c613b..438af9b6 100644
--- a/README.md
+++ b/README.md
@@ -53,6 +53,7 @@ other content at [w0rp.com](https://w0rp.com).
5. [Find References](#usage-find-references)
6. [Hovering](#usage-hover)
7. [Symbol Search](#usage-symbol-search)
+ 8. [Refactoring: Rename, Actions](#usage-refactoring)
3. [Installation](#installation)
1. [Installation with Vim package management](#standard-installation)
2. [Installation with Pathogen](#installation-with-pathogen)
@@ -253,6 +254,18 @@ similar to a given query string.
See `:help ale-symbol-search` for more information.
+
+
+### 2.viii Refactoring: Rename, Actions
+
+ALE supports renaming symbols in symbols in code such as variables or class
+names with the `ALERename` command.
+
+`ALECodeAction` will execute actions on the cursor or applied to a visual
+range selection, such as automatically fixing errors.
+
+See `:help ale-refactor` for more information.
+
## 3. Installation
@@ -328,12 +341,14 @@ git clone https://github.com/dense-analysis/ale.git
### 3.iii. Installation with Vundle
You can install this plugin using [Vundle](https://github.com/VundleVim/Vundle.vim)
-by using the path on GitHub for this repository.
+by adding the GitHub path for this repository to your `~/.vimrc`:
```vim
Plugin 'dense-analysis/ale'
```
+Then run the command `:PluginInstall` in Vim.
+
See the Vundle documentation for more information.
@@ -341,13 +356,16 @@ See the Vundle documentation for more information.
### 3.iiii. Installation with Vim-Plug
You can install this plugin using [Vim-Plug](https://github.com/junegunn/vim-plug)
-by adding the GitHub path for this repository to your `~/.vimrc`
-and running `:PlugInstall`.
+by adding the GitHub path for this repository to your `~/.vimrc`:
```vim
Plug 'dense-analysis/ale'
```
+Then run the command `:PlugInstall` in Vim.
+
+See the Vim-Plug documentation for more information.
+
## 4. Contributing
diff --git a/ale_linters/erlang/elvis.vim b/ale_linters/erlang/elvis.vim
new file mode 100644
index 00000000..31dea3dd
--- /dev/null
+++ b/ale_linters/erlang/elvis.vim
@@ -0,0 +1,39 @@
+" Author: Dmitri Vereshchagin
+" Description: Elvis linter for Erlang files
+
+call ale#Set('erlang_elvis_executable', 'elvis')
+
+function! ale_linters#erlang#elvis#Handle(buffer, lines) abort
+ let l:pattern = '\v:(\d+):[^:]+:(.+)'
+ let l:loclist = []
+
+ for l:match in ale#util#GetMatches(a:lines, l:pattern)
+ call add(l:loclist, {
+ \ 'lnum': str2nr(l:match[1]),
+ \ 'text': s:AbbreviateMessage(l:match[2]),
+ \ 'type': 'W',
+ \})
+ endfor
+
+ return l:loclist
+endfunction
+
+function! s:AbbreviateMessage(text) abort
+ let l:pattern = '\v\c^(line \d+ is too long):.*$'
+
+ return substitute(a:text, l:pattern, '\1.', '')
+endfunction
+
+function! s:GetCommand(buffer) abort
+ let l:file = ale#Escape(expand('#' . a:buffer . ':.'))
+
+ return '%e rock --output-format=parsable ' . l:file
+endfunction
+
+call ale#linter#Define('erlang', {
+\ 'name': 'elvis',
+\ 'callback': 'ale_linters#erlang#elvis#Handle',
+\ 'executable': {b -> ale#Var(b, 'erlang_elvis_executable')},
+\ 'command': function('s:GetCommand'),
+\ 'lint_file': 1,
+\})
diff --git a/ale_linters/php/intelephense.vim b/ale_linters/php/intelephense.vim
new file mode 100644
index 00000000..e9e07d1f
--- /dev/null
+++ b/ale_linters/php/intelephense.vim
@@ -0,0 +1,32 @@
+" Author: Eric Stern ,
+" Arnold Chand
+" Description: Intelephense language server integration for ALE
+
+call ale#Set('php_intelephense_executable', 'intelephense')
+call ale#Set('php_intelephense_use_global', 1)
+call ale#Set('php_intelephense_config', {})
+
+function! ale_linters#php#intelephense#GetProjectRoot(buffer) abort
+ let l:composer_path = ale#path#FindNearestFile(a:buffer, 'composer.json')
+
+ if (!empty(l:composer_path))
+ return fnamemodify(l:composer_path, ':h')
+ endif
+
+ let l:git_path = ale#path#FindNearestDirectory(a:buffer, '.git')
+
+ return !empty(l:git_path) ? fnamemodify(l:git_path, ':h:h') : ''
+endfunction
+
+function! ale_linters#php#intelephense#GetInitializationOptions() abort
+ return ale#Get('php_intelephense_config')
+endfunction
+
+call ale#linter#Define('php', {
+\ 'name': 'intelephense',
+\ 'lsp': 'stdio',
+\ 'initialization_options': function('ale_linters#php#intelephense#GetInitializationOptions'),
+\ 'executable': {b -> ale#node#FindExecutable(b, 'php_intelephense', [])},
+\ 'command': '%e --stdio',
+\ 'project_root': function('ale_linters#php#intelephense#GetProjectRoot'),
+\})
diff --git a/ale_linters/php/phpcs.vim b/ale_linters/php/phpcs.vim
index 11b81e84..c5a3faa9 100644
--- a/ale_linters/php/phpcs.vim
+++ b/ale_linters/php/phpcs.vim
@@ -23,7 +23,7 @@ function! ale_linters#php#phpcs#Handle(buffer, lines) abort
" Matches against lines like the following:
"
" /path/to/some-filename.php:18:3: error - Line indented incorrectly; expected 4 spaces, found 2 (Generic.WhiteSpace.ScopeIndent.IncorrectExact)
- let l:pattern = '^.*:\(\d\+\):\(\d\+\): \(.\+\) - \(.\+\) (\(.\+\))$'
+ let l:pattern = '^.*:\(\d\+\):\(\d\+\): \(.\+\) - \(.\+\) (\(.\+\)).*$'
let l:output = []
for l:match in ale#util#GetMatches(a:lines, l:pattern)
diff --git a/ale_linters/php/tlint.vim b/ale_linters/php/tlint.vim
new file mode 100644
index 00000000..6bba8def
--- /dev/null
+++ b/ale_linters/php/tlint.vim
@@ -0,0 +1,80 @@
+" Author: Jose Soto
+"
+" Description: Tighten Opinionated PHP Linting
+" Website: https://github.com/tightenco/tlint
+
+call ale#Set('php_tlint_executable', 'tlint')
+call ale#Set('php_tlint_use_global', get(g:, 'ale_use_global_executables', 0))
+call ale#Set('php_tlint_options', '')
+
+function! ale_linters#php#tlint#GetProjectRoot(buffer) abort
+ let l:composer_path = ale#path#FindNearestFile(a:buffer, 'composer.json')
+
+ if !empty(l:composer_path)
+ return fnamemodify(l:composer_path, ':h')
+ endif
+
+ let l:git_path = ale#path#FindNearestDirectory(a:buffer, '.git')
+
+ return !empty(l:git_path) ? fnamemodify(l:git_path, ':h:h') : ''
+endfunction
+
+function! ale_linters#php#tlint#GetExecutable(buffer) abort
+ return ale#node#FindExecutable(a:buffer, 'php_tlint', [
+ \ 'vendor/bin/tlint',
+ \ 'tlint',
+ \])
+endfunction
+
+function! ale_linters#php#tlint#GetCommand(buffer) abort
+ let l:executable = ale_linters#php#tlint#GetExecutable(a:buffer)
+ let l:options = ale#Var(a:buffer, 'php_tlint_options')
+
+ return ale#node#Executable(a:buffer, l:executable)
+ \ . (!empty(l:options) ? ' ' . l:options : '')
+ \ . ' lint %s'
+endfunction
+
+function! ale_linters#php#tlint#Handle(buffer, lines) abort
+ " Matches against lines like the following:
+ "
+ " ! There should be 1 space around `.` concatenations, and additional lines should always start with a `.`
+ " 22 : ` $something = 'a'.'name';`
+ "
+ let l:loop_count = 0
+ let l:messages_pattern = '^\! \(.*\)'
+ let l:output = []
+ let l:pattern = '^\(\d\+\) \:'
+ let l:temp_messages = []
+
+ for l:message in ale#util#GetMatches(a:lines, l:messages_pattern)
+ call add(l:temp_messages, l:message)
+ endfor
+
+ let l:loop_count = 0
+
+ for l:match in ale#util#GetMatches(a:lines, l:pattern)
+ let l:num = l:match[1]
+ let l:text = l:temp_messages[l:loop_count]
+
+ call add(l:output, {
+ \ 'lnum': l:num,
+ \ 'col': 0,
+ \ 'text': l:text,
+ \ 'type': 'W',
+ \ 'sub_type': 'style',
+ \})
+
+ let l:loop_count += 1
+ endfor
+
+ return l:output
+endfunction
+
+call ale#linter#Define('php', {
+\ 'name': 'tlint',
+\ 'executable': function('ale_linters#php#tlint#GetExecutable'),
+\ 'command': function('ale_linters#php#tlint#GetCommand'),
+\ 'callback': 'ale_linters#php#tlint#Handle',
+\ 'project_root': function('ale_linters#php#tlint#GetProjectRoot'),
+\})
diff --git a/ale_linters/python/jedils.vim b/ale_linters/python/jedils.vim
new file mode 100644
index 00000000..eae5fb07
--- /dev/null
+++ b/ale_linters/python/jedils.vim
@@ -0,0 +1,34 @@
+" Author: Dalius Dobravolskas
+" Description: https://github.com/pappasam/jedi-language-server
+
+call ale#Set('python_jedils_executable', 'jedi-language-server')
+call ale#Set('python_jedils_use_global', get(g:, 'ale_use_global_executables', 0))
+call ale#Set('python_jedils_auto_pipenv', 0)
+
+function! ale_linters#python#jedils#GetExecutable(buffer) abort
+ if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_jedils_auto_pipenv'))
+ \ && ale#python#PipenvPresent(a:buffer)
+ return 'pipenv'
+ endif
+
+ return ale#python#FindExecutable(a:buffer, 'python_jedils', ['jedi-language-server'])
+endfunction
+
+function! ale_linters#python#jedils#GetCommand(buffer) abort
+ let l:executable = ale_linters#python#jedils#GetExecutable(a:buffer)
+
+ let l:exec_args = l:executable =~? 'pipenv$'
+ \ ? ' run jedi-language-server'
+ \ : ''
+
+ return ale#Escape(l:executable) . l:exec_args
+endfunction
+
+call ale#linter#Define('python', {
+\ 'name': 'jedils',
+\ 'lsp': 'stdio',
+\ 'executable': function('ale_linters#python#jedils#GetExecutable'),
+\ 'command': function('ale_linters#python#jedils#GetCommand'),
+\ 'project_root': function('ale#python#FindProjectRoot'),
+\ 'completion_filter': 'ale#completion#python#CompletionItemFilter',
+\})
diff --git a/ale_linters/r/languageserver.vim b/ale_linters/r/languageserver.vim
new file mode 100644
index 00000000..febe66bd
--- /dev/null
+++ b/ale_linters/r/languageserver.vim
@@ -0,0 +1,26 @@
+" Author: Eric Zhao <21zhaoe@protonmail.com>
+" Description: Implementation of the Language Server Protocol for R.
+
+call ale#Set('r_languageserver_cmd', 'languageserver::run()')
+call ale#Set('r_languageserver_config', {})
+
+function! ale_linters#r#languageserver#GetCommand(buffer) abort
+ let l:cmd_string = ale#Var(a:buffer, 'r_languageserver_cmd')
+
+ return 'Rscript --vanilla -e ' . ale#Escape(l:cmd_string)
+endfunction
+
+function! ale_linters#r#languageserver#GetProjectRoot(buffer) abort
+ let l:project_root = ale#path#FindNearestFile(a:buffer, '.Rprofile')
+
+ return !empty(l:project_root) ? fnamemodify(l:project_root, ':h') : fnamemodify(a:buffer, ':h')
+endfunction
+
+call ale#linter#Define('r', {
+\ 'name': 'languageserver',
+\ 'lsp': 'stdio',
+\ 'lsp_config': {b -> ale#Var(b, 'r_languageserver_config')},
+\ 'executable': 'Rscript',
+\ 'command': function('ale_linters#r#languageserver#GetCommand'),
+\ 'project_root': function('ale_linters#r#languageserver#GetProjectRoot')
+\})
diff --git a/ale_linters/typescript/tsserver.vim b/ale_linters/typescript/tsserver.vim
index 840889f3..4726e40d 100644
--- a/ale_linters/typescript/tsserver.vim
+++ b/ale_linters/typescript/tsserver.vim
@@ -9,6 +9,7 @@ call ale#linter#Define('typescript', {
\ 'name': 'tsserver',
\ 'lsp': 'tsserver',
\ 'executable': {b -> ale#node#FindExecutable(b, 'typescript_tsserver', [
+\ '.yarn/sdks/typescript/bin/tsserver',
\ 'node_modules/.bin/tsserver',
\ ])},
\ 'command': '%e',
diff --git a/autoload/ale/balloon.vim b/autoload/ale/balloon.vim
index 72f6b91c..8678376f 100644
--- a/autoload/ale/balloon.vim
+++ b/autoload/ale/balloon.vim
@@ -2,23 +2,39 @@
" Description: balloonexpr support for ALE.
function! ale#balloon#MessageForPos(bufnr, lnum, col) abort
+ let l:set_balloons = ale#Var(a:bufnr, 'set_balloons')
+ let l:show_problems = 0
+ let l:show_hover = 0
+
+ if l:set_balloons is 1
+ let l:show_problems = 1
+ let l:show_hover = 1
+ elseif l:set_balloons is# 'hover'
+ let l:show_hover = 1
+ endif
+
" Don't show balloons if they are disabled, or linting is disabled.
- if !ale#Var(a:bufnr, 'set_balloons')
+ if !(l:show_problems || l:show_hover)
\|| !g:ale_enabled
\|| !getbufvar(a:bufnr, 'ale_enabled', 1)
return ''
endif
- let l:loclist = get(g:ale_buffer_info, a:bufnr, {'loclist': []}).loclist
- let l:index = ale#util#BinarySearch(l:loclist, a:bufnr, a:lnum, a:col)
+ if l:show_problems
+ let l:loclist = get(g:ale_buffer_info, a:bufnr, {'loclist': []}).loclist
+ let l:index = ale#util#BinarySearch(l:loclist, a:bufnr, a:lnum, a:col)
+ endif
" Show the diagnostics message if found, 'Hover' output otherwise
- if l:index >= 0
+ if l:show_problems && l:index >= 0
return l:loclist[l:index].text
- elseif exists('*balloon_show') || getbufvar(
- \ a:bufnr,
- \ 'ale_set_balloons_legacy_echo',
- \ get(g:, 'ale_set_balloons_legacy_echo', 0)
+ elseif l:show_hover && (
+ \ exists('*balloon_show')
+ \ || getbufvar(
+ \ a:bufnr,
+ \ 'ale_set_balloons_legacy_echo',
+ \ get(g:, 'ale_set_balloons_legacy_echo', 0)
+ \ )
\)
" Request LSP/tsserver hover information, but only if this version of
" Vim supports the balloon_show function, or if we turned a legacy
diff --git a/autoload/ale/code_action.vim b/autoload/ale/code_action.vim
index 8c7263f3..69d40933 100644
--- a/autoload/ale/code_action.vim
+++ b/autoload/ale/code_action.vim
@@ -1,26 +1,29 @@
" Author: Jerko Steiner
" Description: Code action support for LSP / tsserver
-function! ale#code_action#HandleCodeAction(code_action, should_save) abort
+function! ale#code_action#ReloadBuffer() abort
+ let l:buffer = bufnr('')
+
+ execute 'augroup ALECodeActionReloadGroup' . l:buffer
+ autocmd!
+ augroup END
+
+ silent! execute 'augroup! ALECodeActionReloadGroup' . l:buffer
+
+ call ale#util#Execute(':e!')
+endfunction
+
+function! ale#code_action#HandleCodeAction(code_action, options) abort
let l:current_buffer = bufnr('')
let l:changes = a:code_action.changes
-
- for l:file_code_edit in l:changes
- let l:buf = bufnr(l:file_code_edit.fileName)
-
- if l:buf != -1 && l:buf != l:current_buffer && getbufvar(l:buf, '&mod')
- call ale#util#Execute('echom ''Aborting action, file is unsaved''')
-
- return
- endif
- endfor
+ let l:should_save = get(a:options, 'should_save')
for l:file_code_edit in l:changes
call ale#code_action#ApplyChanges(
- \ l:file_code_edit.fileName,
- \ l:file_code_edit.textChanges,
- \ a:should_save,
- \ )
+ \ l:file_code_edit.fileName,
+ \ l:file_code_edit.textChanges,
+ \ l:should_save,
+ \)
endfor
endfunction
@@ -78,29 +81,14 @@ function! ale#code_action#ApplyChanges(filename, changes, should_save) abort
let l:pos = [1, 1]
endif
- " We have to keep track of how many lines we have added, and offset
- " changes accordingly.
- let l:line_offset = 0
- let l:column_offset = 0
- let l:last_end_line = 0
-
- " Changes have to be sorted so we apply them from top-to-bottom.
- for l:code_edit in sort(copy(a:changes), function('s:ChangeCmp'))
- if l:code_edit.start.line isnot l:last_end_line
- let l:column_offset = 0
- endif
-
- let l:line = l:code_edit.start.line + l:line_offset
- let l:column = l:code_edit.start.offset + l:column_offset
- let l:end_line = l:code_edit.end.line + l:line_offset
- let l:end_column = l:code_edit.end.offset + l:column_offset
+ " Changes have to be sorted so we apply them from bottom-to-top
+ for l:code_edit in reverse(sort(copy(a:changes), function('s:ChangeCmp')))
+ let l:line = l:code_edit.start.line
+ let l:column = l:code_edit.start.offset
+ let l:end_line = l:code_edit.end.line
+ let l:end_column = l:code_edit.end.offset
let l:text = l:code_edit.newText
- let l:cur_line = l:pos[0]
- let l:cur_column = l:pos[1]
-
- let l:last_end_line = l:end_line
-
" Adjust the ends according to previous edits.
if l:end_line > len(l:lines)
let l:end_line_len = 0
@@ -118,6 +106,12 @@ function! ale#code_action#ApplyChanges(filename, changes, should_save) abort
let l:start = l:lines[: l:line - 2]
endif
+ " Special case when text must be added after new line
+ if l:column > len(l:lines[l:line - 1])
+ call extend(l:start, [l:lines[l:line - 1]])
+ let l:column = 1
+ endif
+
if l:column is 1
" We need to handle column 1 specially, because we can't slice an
" empty string ending on index 0.
@@ -127,13 +121,17 @@ function! ale#code_action#ApplyChanges(filename, changes, should_save) abort
endif
call extend(l:middle, l:insertions[1:])
- let l:middle[-1] .= l:lines[l:end_line - 1][l:end_column - 1 :]
+
+ if l:end_line <= len(l:lines)
+ " Only extend the last line if end_line is within the range of
+ " lines.
+ let l:middle[-1] .= l:lines[l:end_line - 1][l:end_column - 1 :]
+ endif
let l:lines_before_change = len(l:lines)
let l:lines = l:start + l:middle + l:lines[l:end_line :]
let l:current_line_offset = len(l:lines) - l:lines_before_change
- let l:line_offset += l:current_line_offset
let l:column_offset = len(l:middle[-1]) - l:end_line_len
let l:pos = s:UpdateCursor(l:pos,
@@ -159,6 +157,20 @@ function! ale#code_action#ApplyChanges(filename, changes, should_save) abort
call setpos('.', [0, l:pos[0], l:pos[1], 0])
endif
+
+ if a:should_save && l:buffer > 0 && !l:is_current_buffer
+ " Set up a one-time use event that will delete itself to reload the
+ " buffer next time it's entered to view the changes made to it.
+ execute 'augroup ALECodeActionReloadGroup' . l:buffer
+ autocmd!
+
+ execute printf(
+ \ 'autocmd BufEnter '
+ \ . ' call ale#code_action#ReloadBuffer()',
+ \ l:buffer
+ \)
+ augroup END
+ endif
endfunction
function! s:UpdateCursor(cursor, start, end, offset) abort
@@ -208,3 +220,163 @@ function! s:UpdateCursor(cursor, start, end, offset) abort
return [l:cur_line, l:cur_column]
endfunction
+
+function! ale#code_action#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#code_action#BuildChangesList(changes_map) abort
+ let l:changes = []
+
+ for l:file_name in keys(a:changes_map)
+ let l:text_edits = a:changes_map[l:file_name]
+ let l:text_changes = []
+
+ for l:edit in l:text_edits
+ let l:range = l:edit.range
+ let l:new_text = l:edit.newText
+
+ call add(l:text_changes, {
+ \ 'start': {
+ \ 'line': l:range.start.line + 1,
+ \ 'offset': l:range.start.character + 1,
+ \ },
+ \ 'end': {
+ \ 'line': l:range.end.line + 1,
+ \ 'offset': l:range.end.character + 1,
+ \ },
+ \ 'newText': l:new_text,
+ \})
+ endfor
+
+ call add(l:changes, {
+ \ 'fileName': ale#path#FromURI(l:file_name),
+ \ 'textChanges': l:text_changes,
+ \})
+ endfor
+
+ return l:changes
+endfunction
+
+function! s:EscapeMenuName(text) abort
+ return substitute(a:text, '\\\| \|\.\|&', '\\\0', 'g')
+endfunction
+
+function! s:UpdateMenu(data, menu_items) abort
+ silent! aunmenu PopUp.Refactor\.\.\.
+
+ if empty(a:data)
+ return
+ endif
+
+ for [l:type, l:item] in a:menu_items
+ let l:name = l:type is# 'tsserver' ? l:item.name : l:item.title
+ let l:func_name = l:type is# 'tsserver'
+ \ ? 'ale#codefix#ApplyTSServerCodeAction'
+ \ : 'ale#codefix#ApplyLSPCodeAction'
+
+ execute printf(
+ \ 'anoremenu PopUp.&Refactor\.\.\..%s'
+ \ . ' :call %s(%s, %s)',
+ \ s:EscapeMenuName(l:name),
+ \ l:func_name,
+ \ string(a:data),
+ \ string(l:item),
+ \)
+ endfor
+
+ if empty(a:menu_items)
+ silent! anoremenu PopUp.Refactor\.\.\..(None) :silent
+ endif
+endfunction
+
+function! s:GetCodeActions(linter, options) abort
+ let l:buffer = bufnr('')
+ let [l:line, l:column] = getpos('.')[1:2]
+ let l:column = min([l:column, len(getline(l:line))])
+
+ let l:location = {
+ \ 'buffer': l:buffer,
+ \ 'line': l:line,
+ \ 'column': l:column,
+ \ 'end_line': l:line,
+ \ 'end_column': l:column,
+ \}
+ let l:Callback = function('s:OnReady', [l:location, a:options])
+ call ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback)
+endfunction
+
+function! ale#code_action#GetCodeActions(options) abort
+ silent! aunmenu PopUp.Rename
+ silent! aunmenu PopUp.Refactor\.\.\.
+
+ " Only display the menu items if there's an LSP server.
+ let l:has_lsp = 0
+
+ for l:linter in ale#linter#Get(&filetype)
+ if !empty(l:linter.lsp)
+ let l:has_lsp = 1
+
+ break
+ endif
+ endfor
+
+ if l:has_lsp
+ if !empty(expand(''))
+ silent! anoremenu PopUp.Rename :ALERename
+ endif
+
+ silent! anoremenu PopUp.Refactor\.\.\..(None) :silent
+
+ call ale#codefix#Execute(
+ \ mode() is# 'v' || mode() is# "\",
+ \ function('s:UpdateMenu')
+ \)
+ endif
+endfunction
+
+function! s:Setup(enabled) abort
+ augroup ALECodeActionsGroup
+ autocmd!
+
+ if a:enabled
+ autocmd MenuPopup * :call ale#code_action#GetCodeActions({})
+ endif
+ augroup END
+
+ if !a:enabled
+ silent! augroup! ALECodeActionsGroup
+
+ silent! aunmenu PopUp.Rename
+ silent! aunmenu PopUp.Refactor\.\.\.
+ endif
+endfunction
+
+function! ale#code_action#EnablePopUpMenu() abort
+ call s:Setup(1)
+endfunction
+
+function! ale#code_action#DisablePopUpMenu() abort
+ call s:Setup(0)
+endfunction
diff --git a/autoload/ale/codefix.vim b/autoload/ale/codefix.vim
new file mode 100644
index 00000000..69bf36fa
--- /dev/null
+++ b/autoload/ale/codefix.vim
@@ -0,0 +1,484 @@
+" Author: Dalius Dobravolskas
+" Description: Code Fix support for tsserver and LSP servers
+
+let s:codefix_map = {}
+
+" Used to get the codefix map in tests.
+function! ale#codefix#GetMap() abort
+ return deepcopy(s:codefix_map)
+endfunction
+
+" Used to set the codefix map in tests.
+function! ale#codefix#SetMap(map) abort
+ let s:codefix_map = a:map
+endfunction
+
+function! ale#codefix#ClearLSPData() abort
+ let s:codefix_map = {}
+endfunction
+
+function! s:message(message) abort
+ call ale#util#Execute('echom ' . string(a:message))
+endfunction
+
+function! ale#codefix#ApplyTSServerCodeAction(data, item) abort
+ if has_key(a:item, 'changes')
+ let l:changes = a:item.changes
+
+ call ale#code_action#HandleCodeAction(
+ \ {
+ \ 'description': 'codefix',
+ \ 'changes': l:changes,
+ \ },
+ \ {},
+ \)
+ else
+ let l:message = ale#lsp#tsserver_message#GetEditsForRefactor(
+ \ a:data.buffer,
+ \ a:data.line,
+ \ a:data.column,
+ \ a:data.end_line,
+ \ a:data.end_column,
+ \ a:item.id[0],
+ \ a:item.id[1],
+ \)
+
+ let l:request_id = ale#lsp#Send(a:data.connection_id, l:message)
+
+ let s:codefix_map[l:request_id] = a:data
+ endif
+endfunction
+
+function! ale#codefix#HandleTSServerResponse(conn_id, response) abort
+ if !has_key(a:response, 'request_seq')
+ \ || !has_key(s:codefix_map, a:response.request_seq)
+ return
+ endif
+
+ let l:data = remove(s:codefix_map, a:response.request_seq)
+ let l:MenuCallback = get(l:data, 'menu_callback', v:null)
+
+ if get(a:response, 'command', '') is# 'getCodeFixes'
+ if get(a:response, 'success', v:false) is v:false
+ \&& l:MenuCallback is v:null
+ let l:message = get(a:response, 'message', 'unknown')
+ call s:message('Error while getting code fixes. Reason: ' . l:message)
+
+ return
+ endif
+
+ let l:result = get(a:response, 'body', [])
+ call filter(l:result, 'has_key(v:val, ''changes'')')
+
+ if l:MenuCallback isnot v:null
+ call l:MenuCallback(
+ \ l:data,
+ \ map(copy(l:result), '[''tsserver'', v:val]')
+ \)
+
+ return
+ endif
+
+ if len(l:result) == 0
+ call s:message('No code fixes available.')
+
+ return
+ endif
+
+ let l:code_fix_to_apply = 0
+
+ if len(l:result) == 1
+ let l:code_fix_to_apply = 1
+ else
+ let l:codefix_no = 1
+ let l:codefixstring = "Code Fixes:\n"
+
+ for l:codefix in l:result
+ let l:codefixstring .= l:codefix_no . ') '
+ \ . l:codefix.description . "\n"
+ let l:codefix_no += 1
+ endfor
+
+ let l:codefixstring .= 'Type number and (empty cancels): '
+
+ let l:code_fix_to_apply = ale#util#Input(l:codefixstring, '')
+ let l:code_fix_to_apply = str2nr(l:code_fix_to_apply)
+
+ if l:code_fix_to_apply == 0
+ return
+ endif
+ endif
+
+ call ale#codefix#ApplyTSServerCodeAction(
+ \ l:data,
+ \ l:result[l:code_fix_to_apply - 1],
+ \)
+ elseif get(a:response, 'command', '') is# 'getApplicableRefactors'
+ if get(a:response, 'success', v:false) is v:false
+ \&& l:MenuCallback is v:null
+ let l:message = get(a:response, 'message', 'unknown')
+ call s:message('Error while getting applicable refactors. Reason: ' . l:message)
+
+ return
+ endif
+
+ let l:result = get(a:response, 'body', [])
+
+ if len(l:result) == 0
+ call s:message('No applicable refactors available.')
+
+ return
+ endif
+
+ let l:refactors = []
+
+ for l:item in l:result
+ for l:action in l:item.actions
+ call add(l:refactors, {
+ \ 'name': l:action.description,
+ \ 'id': [l:item.name, l:action.name],
+ \})
+ endfor
+ endfor
+
+ if l:MenuCallback isnot v:null
+ call l:MenuCallback(
+ \ l:data,
+ \ map(copy(l:refactors), '[''tsserver'', v:val]')
+ \)
+
+ return
+ endif
+
+ let l:refactor_no = 1
+ let l:refactorstring = "Applicable refactors:\n"
+
+ for l:refactor in l:refactors
+ let l:refactorstring .= l:refactor_no . ') '
+ \ . l:refactor.name . "\n"
+ let l:refactor_no += 1
+ endfor
+
+ let l:refactorstring .= 'Type number and (empty cancels): '
+
+ let l:refactor_to_apply = ale#util#Input(l:refactorstring, '')
+ let l:refactor_to_apply = str2nr(l:refactor_to_apply)
+
+ if l:refactor_to_apply == 0
+ return
+ endif
+
+ let l:id = l:refactors[l:refactor_to_apply - 1].id
+
+ call ale#codefix#ApplyTSServerCodeAction(
+ \ l:data,
+ \ l:refactors[l:refactor_to_apply - 1],
+ \)
+ elseif get(a:response, 'command', '') is# 'getEditsForRefactor'
+ if get(a:response, 'success', v:false) is v:false
+ let l:message = get(a:response, 'message', 'unknown')
+ call s:message('Error while getting edits for refactor. Reason: ' . l:message)
+
+ return
+ endif
+
+ call ale#code_action#HandleCodeAction(
+ \ {
+ \ 'description': 'editsForRefactor',
+ \ 'changes': a:response.body.edits,
+ \ },
+ \ {},
+ \)
+ endif
+endfunction
+
+function! ale#codefix#ApplyLSPCodeAction(data, item) abort
+ if has_key(a:item, 'command')
+ \&& type(a:item.command) == v:t_dict
+ let l:command = a:item.command
+ let l:message = ale#lsp#message#ExecuteCommand(
+ \ l:command.command,
+ \ l:command.arguments,
+ \)
+
+ let l:request_id = ale#lsp#Send(a:data.connection_id, l:message)
+ elseif has_key(a:item, 'edit') || has_key(a:item, 'arguments')
+ if has_key(a:item, 'edit')
+ let l:topass = a:item.edit
+ else
+ let l:topass = a:item.arguments[0]
+ endif
+
+ let l:changes_map = ale#code_action#GetChanges(l:topass)
+
+ if empty(l:changes_map)
+ return
+ endif
+
+ let l:changes = ale#code_action#BuildChangesList(l:changes_map)
+
+ call ale#code_action#HandleCodeAction(
+ \ {
+ \ 'description': 'codeaction',
+ \ 'changes': l:changes,
+ \ },
+ \ {},
+ \)
+ endif
+endfunction
+
+function! ale#codefix#HandleLSPResponse(conn_id, response) abort
+ if has_key(a:response, 'method')
+ \ && a:response.method is# 'workspace/applyEdit'
+ \ && has_key(a:response, 'params')
+ let l:params = a:response.params
+
+ let l:changes_map = ale#code_action#GetChanges(l:params.edit)
+
+ if empty(l:changes_map)
+ return
+ endif
+
+ let l:changes = ale#code_action#BuildChangesList(l:changes_map)
+
+ call ale#code_action#HandleCodeAction(
+ \ {
+ \ 'description': 'applyEdit',
+ \ 'changes': l:changes,
+ \ },
+ \ {}
+ \)
+ elseif has_key(a:response, 'id')
+ \&& has_key(s:codefix_map, a:response.id)
+ let l:data = remove(s:codefix_map, a:response.id)
+ let l:MenuCallback = get(l:data, 'menu_callback', v:null)
+
+ let l:result = get(a:response, 'result')
+
+ if type(l:result) != v:t_list
+ let l:result = []
+ endif
+
+ " Send the results to the menu callback, if set.
+ if l:MenuCallback isnot v:null
+ call l:MenuCallback(map(copy(l:result), '[''lsp'', v:val]'))
+
+ return
+ endif
+
+ if len(l:result) == 0
+ call s:message('No code actions received from server')
+
+ return
+ endif
+
+ let l:codeaction_no = 1
+ let l:codeactionstring = "Code Fixes:\n"
+
+ for l:codeaction in l:result
+ let l:codeactionstring .= l:codeaction_no . ') '
+ \ . l:codeaction.title . "\n"
+ let l:codeaction_no += 1
+ endfor
+
+ let l:codeactionstring .= 'Type number and (empty cancels): '
+
+ let l:codeaction_to_apply = ale#util#Input(l:codeactionstring, '')
+ let l:codeaction_to_apply = str2nr(l:codeaction_to_apply)
+
+ if l:codeaction_to_apply == 0
+ return
+ endif
+
+ let l:item = l:result[l:codeaction_to_apply - 1]
+
+ call ale#codefix#ApplyLSPCodeAction(l:data, l:item)
+ endif
+endfunction
+
+function! s:FindError(buffer, line, column, end_line, end_column) abort
+ let l:nearest_error = v:null
+
+ if a:line == a:end_line
+ \&& a:column == a:end_column
+ \&& has_key(g:ale_buffer_info, a:buffer)
+ let l:nearest_error_diff = -1
+
+ for l:error in get(g:ale_buffer_info[a:buffer], 'loclist', [])
+ if has_key(l:error, 'code') && l:error.lnum == a:line
+ let l:diff = abs(l:error.col - a:column)
+
+ if l:nearest_error_diff == -1 || l:diff < l:nearest_error_diff
+ let l:nearest_error_diff = l:diff
+ let l:nearest_error = l:error
+ endif
+ endif
+ endfor
+ endif
+
+ return l:nearest_error
+endfunction
+
+function! s:OnReady(
+\ line,
+\ column,
+\ end_line,
+\ end_column,
+\ MenuCallback,
+\ linter,
+\ lsp_details,
+\) abort
+ let l:id = a:lsp_details.connection_id
+
+ if !ale#lsp#HasCapability(l:id, 'code_actions')
+ return
+ endif
+
+ let l:buffer = a:lsp_details.buffer
+
+ if a:linter.lsp is# 'tsserver'
+ let l:nearest_error =
+ \ s:FindError(l:buffer, a:line, a:column, a:end_line, a:end_column)
+
+ if l:nearest_error isnot v:null
+ let l:message = ale#lsp#tsserver_message#GetCodeFixes(
+ \ l:buffer,
+ \ a:line,
+ \ a:column,
+ \ a:line,
+ \ a:column,
+ \ [l:nearest_error.code],
+ \)
+ else
+ let l:message = ale#lsp#tsserver_message#GetApplicableRefactors(
+ \ l:buffer,
+ \ a:line,
+ \ a:column,
+ \ a:end_line,
+ \ a:end_column,
+ \)
+ endif
+ else
+ " Send a message saying the buffer has changed first, otherwise
+ " completions won't know what text is nearby.
+ call ale#lsp#NotifyForChanges(l:id, l:buffer)
+
+ let l:diagnostics = []
+ let l:nearest_error =
+ \ s:FindError(l:buffer, a:line, a:column, a:end_line, a:end_column)
+
+ if l:nearest_error isnot v:null
+ let l:diagnostics = [
+ \ {
+ \ 'code': l:nearest_error.code,
+ \ 'message': l:nearest_error.text,
+ \ 'range': {
+ \ 'start': {
+ \ 'line': l:nearest_error.lnum - 1,
+ \ 'character': l:nearest_error.col - 1,
+ \ },
+ \ 'end': {
+ \ 'line': l:nearest_error.end_lnum - 1,
+ \ 'character': l:nearest_error.end_col,
+ \ },
+ \ },
+ \ },
+ \]
+ endif
+
+ let l:message = ale#lsp#message#CodeAction(
+ \ l:buffer,
+ \ a:line,
+ \ a:column,
+ \ a:end_line,
+ \ a:end_column,
+ \ l:diagnostics,
+ \)
+ endif
+
+ let l:Callback = a:linter.lsp is# 'tsserver'
+ \ ? function('ale#codefix#HandleTSServerResponse')
+ \ : function('ale#codefix#HandleLSPResponse')
+
+ call ale#lsp#RegisterCallback(l:id, l:Callback)
+
+ let l:request_id = ale#lsp#Send(l:id, l:message)
+
+ let s:codefix_map[l:request_id] = {
+ \ 'connection_id': l:id,
+ \ 'buffer': l:buffer,
+ \ 'line': a:line,
+ \ 'column': a:column,
+ \ 'end_line': a:end_line,
+ \ 'end_column': a:end_column,
+ \ 'menu_callback': a:MenuCallback,
+ \}
+endfunction
+
+function! s:ExecuteGetCodeFix(linter, range, MenuCallback) abort
+ let l:buffer = bufnr('')
+
+ if a:range == 0
+ let [l:line, l:column] = getpos('.')[1:2]
+ let l:end_line = l:line
+ let l:end_column = l:column
+
+ " Expand the range to cover the current word, if there is one.
+ let l:cword = expand('')
+
+ if !empty(l:cword)
+ let l:search_pos = searchpos('\V' . l:cword, 'bn', l:line)
+
+ if l:search_pos != [0, 0]
+ let l:column = l:search_pos[1]
+ let l:end_column = l:column + len(l:cword) - 1
+ endif
+ endif
+ elseif mode() is# 'v' || mode() is# "\"
+ " You need to get the start and end in a different way when you're in
+ " visual mode.
+ let [l:line, l:column] = getpos('v')[1:2]
+ let [l:end_line, l:end_column] = getpos('.')[1:2]
+ else
+ let [l:line, l:column] = getpos("'<")[1:2]
+ let [l:end_line, l:end_column] = getpos("'>")[1:2]
+ endif
+
+ let l:column = min([l:column, len(getline(l:line))])
+ let l:end_column = min([l:end_column, len(getline(l:end_line))])
+
+ let l:Callback = function(
+ \ 's:OnReady', [l:line, l:column, l:end_line, l:end_column, a:MenuCallback]
+ \)
+
+ call ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback)
+endfunction
+
+function! ale#codefix#Execute(range, ...) abort
+ if a:0 > 1
+ throw 'Too many arguments'
+ endif
+
+ let l:MenuCallback = get(a:000, 0, v:null)
+ let l:lsp_linters = []
+
+ for l:linter in ale#linter#Get(&filetype)
+ if !empty(l:linter.lsp)
+ call add(l:lsp_linters, l:linter)
+ endif
+ endfor
+
+ if empty(l:lsp_linters)
+ if l:MenuCallback is v:null
+ call s:message('No active LSPs')
+ else
+ call l:MenuCallback({}, [])
+ endif
+
+ return
+ endif
+
+ for l:lsp_linter in l:lsp_linters
+ call s:ExecuteGetCodeFix(l:lsp_linter, a:range, l:MenuCallback)
+ endfor
+endfunction
diff --git a/autoload/ale/completion.vim b/autoload/ale/completion.vim
index ecd93600..39bfc094 100644
--- a/autoload/ale/completion.vim
+++ b/autoload/ale/completion.vim
@@ -606,17 +606,21 @@ function! ale#completion#ParseLSPCompletions(response) abort
let l:doc = l:doc.value
endif
+ " Collapse whitespaces and line breaks into a single space.
+ let l:detail = substitute(get(l:item, 'detail', ''), '\_s\+', ' ', 'g')
+
let l:result = {
\ 'word': l:word,
\ 'kind': ale#completion#GetCompletionSymbols(get(l:item, 'kind', '')),
\ 'icase': 1,
- \ 'menu': get(l:item, 'detail', ''),
+ \ 'menu': l:detail,
\ 'info': (type(l:doc) is v:t_string ? l:doc : ''),
\}
" This flag is used to tell if this completion came from ALE or not.
let l:user_data = {'_ale_completion_item': 1}
if has_key(l:item, 'additionalTextEdits')
+ \ && l:item.additionalTextEdits isnot v:null
let l:text_changes = []
for l:edit in l:item.additionalTextEdits
@@ -1006,7 +1010,7 @@ function! ale#completion#HandleUserData(completed_item) abort
\|| l:source is# 'ale-import'
\|| l:source is# 'ale-omnifunc'
for l:code_action in get(l:user_data, 'code_actions', [])
- call ale#code_action#HandleCodeAction(l:code_action, v:false)
+ call ale#code_action#HandleCodeAction(l:code_action, {})
endfor
endif
diff --git a/autoload/ale/fix/registry.vim b/autoload/ale/fix/registry.vim
index d71668f2..0f146faa 100644
--- a/autoload/ale/fix/registry.vim
+++ b/autoload/ale/fix/registry.vim
@@ -12,6 +12,11 @@ let s:default_registry = {
\ 'suggested_filetypes': ['help'],
\ 'description': 'Align help tags to the right margin',
\ },
+\ 'autoimport': {
+\ 'function': 'ale#fixers#autoimport#Fix',
+\ 'suggested_filetypes': ['python'],
+\ 'description': 'Fix import issues with autoimport.',
+\ },
\ 'autopep8': {
\ 'function': 'ale#fixers#autopep8#Fix',
\ 'suggested_filetypes': ['python'],
@@ -105,6 +110,11 @@ let s:default_registry = {
\ 'suggested_filetypes': [],
\ 'description': 'Remove all trailing whitespace characters at the end of every line.',
\ },
+\ 'yamlfix': {
+\ 'function': 'ale#fixers#yamlfix#Fix',
+\ 'suggested_filetypes': ['yaml'],
+\ 'description': 'Fix yaml files with yamlfix.',
+\ },
\ 'yapf': {
\ 'function': 'ale#fixers#yapf#Fix',
\ 'suggested_filetypes': ['python'],
@@ -375,11 +385,21 @@ let s:default_registry = {
\ 'suggested_filetypes': ['html', 'htmldjango'],
\ 'description': 'Fix HTML files with html-beautify.',
\ },
+\ 'luafmt': {
+\ 'function': 'ale#fixers#luafmt#Fix',
+\ 'suggested_filetypes': ['lua'],
+\ 'description': 'Fix Lua files with luafmt.',
+\ },
\ 'dhall': {
\ 'function': 'ale#fixers#dhall#Fix',
\ 'suggested_filetypes': ['dhall'],
\ 'description': 'Fix Dhall files with dhall-format.',
\ },
+\ 'ormolu': {
+\ 'function': 'ale#fixers#ormolu#Fix',
+\ 'suggested_filetypes': ['haskell'],
+\ 'description': 'A formatter for Haskell source code.',
+\ },
\}
" Reset the function registry to the default entries.
diff --git a/autoload/ale/fixers/autoimport.vim b/autoload/ale/fixers/autoimport.vim
new file mode 100644
index 00000000..37a52db8
--- /dev/null
+++ b/autoload/ale/fixers/autoimport.vim
@@ -0,0 +1,25 @@
+" Author: lyz-code
+" Description: Fixing Python imports with autoimport.
+
+call ale#Set('python_autoimport_executable', 'autoimport')
+call ale#Set('python_autoimport_options', '')
+call ale#Set('python_autoimport_use_global', get(g:, 'ale_use_global_executables', 0))
+
+function! ale#fixers#autoimport#Fix(buffer) abort
+ let l:options = ale#Var(a:buffer, 'python_autoimport_options')
+
+ let l:executable = ale#python#FindExecutable(
+ \ a:buffer,
+ \ 'python_autoimport',
+ \ ['autoimport'],
+ \)
+
+ if !executable(l:executable)
+ return 0
+ endif
+
+ return {
+ \ 'command': ale#path#BufferCdString(a:buffer)
+ \ . ale#Escape(l:executable) . (!empty(l:options) ? ' ' . l:options : '') . ' -',
+ \}
+endfunction
diff --git a/autoload/ale/fixers/gofmt.vim b/autoload/ale/fixers/gofmt.vim
index d5a539b9..b9cfbb58 100644
--- a/autoload/ale/fixers/gofmt.vim
+++ b/autoload/ale/fixers/gofmt.vim
@@ -11,9 +11,6 @@ function! ale#fixers#gofmt#Fix(buffer) abort
return {
\ 'command': l:env . ale#Escape(l:executable)
- \ . ' -l -w'
\ . (empty(l:options) ? '' : ' ' . l:options)
- \ . ' %t',
- \ 'read_temporary_file': 1,
\}
endfunction
diff --git a/autoload/ale/fixers/luafmt.vim b/autoload/ale/fixers/luafmt.vim
new file mode 100644
index 00000000..6cb9ef4a
--- /dev/null
+++ b/autoload/ale/fixers/luafmt.vim
@@ -0,0 +1,13 @@
+call ale#Set('lua_luafmt_executable', 'luafmt')
+call ale#Set('lua_luafmt_options', '')
+
+function! ale#fixers#luafmt#Fix(buffer) abort
+ let l:executable = ale#Var(a:buffer, 'lua_luafmt_executable')
+ let l:options = ale#Var(a:buffer, 'lua_luafmt_options')
+
+ return {
+ \ 'command': ale#Escape(l:executable)
+ \ . (empty(l:options) ? '' : ' ' . l:options)
+ \ . ' --stdin',
+ \}
+endfunction
diff --git a/autoload/ale/fixers/ormolu.vim b/autoload/ale/fixers/ormolu.vim
new file mode 100644
index 00000000..69b55c1f
--- /dev/null
+++ b/autoload/ale/fixers/ormolu.vim
@@ -0,0 +1,12 @@
+call ale#Set('haskell_ormolu_executable', 'ormolu')
+call ale#Set('haskell_ormolu_options', '')
+
+function! ale#fixers#ormolu#Fix(buffer) abort
+ let l:executable = ale#Var(a:buffer, 'haskell_ormolu_executable')
+ let l:options = ale#Var(a:buffer, 'haskell_ormolu_options')
+
+ return {
+ \ 'command': ale#Escape(l:executable)
+ \ . (empty(l:options) ? '' : ' ' . l:options),
+ \}
+endfunction
diff --git a/autoload/ale/fixers/phpcbf.vim b/autoload/ale/fixers/phpcbf.vim
index f14b8406..0a61c657 100644
--- a/autoload/ale/fixers/phpcbf.vim
+++ b/autoload/ale/fixers/phpcbf.vim
@@ -2,6 +2,7 @@
" Description: Fixing files with phpcbf.
call ale#Set('php_phpcbf_standard', '')
+call ale#Set('php_phpcbf_options', '')
call ale#Set('php_phpcbf_executable', 'phpcbf')
call ale#Set('php_phpcbf_use_global', get(g:, 'ale_use_global_executables', 0))
@@ -20,6 +21,6 @@ function! ale#fixers#phpcbf#Fix(buffer) abort
\ : ''
return {
- \ 'command': ale#Escape(l:executable) . ' --stdin-path=%s ' . l:standard_option . ' -'
+ \ 'command': ale#Escape(l:executable) . ' --stdin-path=%s ' . l:standard_option . ale#Pad(ale#Var(a:buffer, 'php_phpcbf_options')) . ' -'
\}
endfunction
diff --git a/autoload/ale/fixers/yamlfix.vim b/autoload/ale/fixers/yamlfix.vim
new file mode 100644
index 00000000..966556c9
--- /dev/null
+++ b/autoload/ale/fixers/yamlfix.vim
@@ -0,0 +1,25 @@
+" Author: lyz-code
+" Description: Fixing yaml files with yamlfix.
+
+call ale#Set('yaml_yamlfix_executable', 'yamlfix')
+call ale#Set('yaml_yamlfix_options', '')
+call ale#Set('yaml_yamlfix_use_global', get(g:, 'ale_use_global_executables', 0))
+
+function! ale#fixers#yamlfix#Fix(buffer) abort
+ let l:options = ale#Var(a:buffer, 'yaml_yamlfix_options')
+
+ let l:executable = ale#python#FindExecutable(
+ \ a:buffer,
+ \ 'yaml_yamlfix',
+ \ ['yamlfix'],
+ \)
+
+ if !executable(l:executable)
+ return 0
+ endif
+
+ return {
+ \ 'command': ale#path#BufferCdString(a:buffer)
+ \ . ale#Escape(l:executable) . (!empty(l:options) ? ' ' . l:options : '') . ' -',
+ \}
+endfunction
diff --git a/autoload/ale/handlers/eslint.vim b/autoload/ale/handlers/eslint.vim
index e37d6902..b8610612 100644
--- a/autoload/ale/handlers/eslint.vim
+++ b/autoload/ale/handlers/eslint.vim
@@ -5,6 +5,7 @@ let s:executables = [
\ 'node_modules/.bin/eslint_d',
\ 'node_modules/eslint/bin/eslint.js',
\ 'node_modules/.bin/eslint',
+\ '.yarn/sdks/eslint/bin/eslint',
\]
let s:sep = has('win32') ? '\' : '/'
diff --git a/autoload/ale/handlers/sh.vim b/autoload/ale/handlers/sh.vim
index 1e50cb89..6ed9fea3 100644
--- a/autoload/ale/handlers/sh.vim
+++ b/autoload/ale/handlers/sh.vim
@@ -1,18 +1,28 @@
" Author: w0rp
-" Get the shell type for a buffer, based on the hashbang line.
function! ale#handlers#sh#GetShellType(buffer) abort
- let l:bang_line = get(getbufline(a:buffer, 1), 0, '')
+ let l:shebang = 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# '#!'
+ " Take the shell executable from the shebang, if we can.
+ if l:shebang[:1] is# '#!'
" Remove options like -e, etc.
- let l:command = substitute(l:bang_line, ' --\?[a-zA-Z0-9]\+', '', 'g')
+ let l:command = substitute(l:shebang, ' --\?[a-zA-Z0-9]\+', '', 'g')
endif
- " If we couldn't find a hashbang, try the filetype
+ " With no shebang line, attempt to use Vim's buffer-local variables.
+ if l:command is# ''
+ if getbufvar(a:buffer, 'is_bash', 0)
+ let l:command = 'bash'
+ elseif getbufvar(a:buffer, 'is_sh', 0)
+ let l:command = 'sh'
+ elseif getbufvar(a:buffer, 'is_kornshell', 0)
+ let l:command = 'ksh'
+ endif
+ endif
+
+ " If we couldn't find a shebang, try the filetype
if l:command is# ''
let l:command = &filetype
endif
diff --git a/autoload/ale/handlers/shellcheck.vim b/autoload/ale/handlers/shellcheck.vim
index b16280f0..701c43b2 100644
--- a/autoload/ale/handlers/shellcheck.vim
+++ b/autoload/ale/handlers/shellcheck.vim
@@ -1,8 +1,32 @@
" Author: w0rp
" Description: This file adds support for using the shellcheck linter
+" Shellcheck supports shell directives to define the shell dialect for scripts
+" that do not have a shebang for some reason.
+" https://github.com/koalaman/shellcheck/wiki/Directive#shell
+function! ale#handlers#shellcheck#GetShellcheckDialectDirective(buffer) abort
+ let l:linenr = 0
+ let l:pattern = '\s\{-}#\s\{-}shellcheck\s\{-}shell=\(.*\)'
+ let l:possible_shell = ['bash', 'dash', 'ash', 'tcsh', 'csh', 'zsh', 'ksh', 'sh']
+
+ while l:linenr < min([50, line('$')])
+ let l:linenr += 1
+ let l:match = matchlist(getline(l:linenr), l:pattern)
+
+ if len(l:match) > 1 && index(l:possible_shell, l:match[1]) >= 0
+ return l:match[1]
+ endif
+ endwhile
+
+ return ''
+endfunction
+
function! ale#handlers#shellcheck#GetDialectArgument(buffer) abort
- let l:shell_type = ale#handlers#sh#GetShellType(a:buffer)
+ let l:shell_type = ale#handlers#shellcheck#GetShellcheckDialectDirective(a:buffer)
+
+ if empty(l:shell_type)
+ let l:shell_type = ale#handlers#sh#GetShellType(a:buffer)
+ endif
if !empty(l:shell_type)
" Use the dash dialect for /bin/ash, etc.
@@ -13,15 +37,6 @@ function! ale#handlers#shellcheck#GetDialectArgument(buffer) abort
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
diff --git a/autoload/ale/hover.vim b/autoload/ale/hover.vim
index 38b4b866..1d38f3b9 100644
--- a/autoload/ale/hover.vim
+++ b/autoload/ale/hover.vim
@@ -24,6 +24,8 @@ function! ale#hover#HandleTSServerResponse(conn_id, response) abort
if get(a:response, 'success', v:false) is v:true
\&& get(a:response, 'body', v:null) isnot v:null
+ let l:set_balloons = ale#Var(l:options.buffer, 'set_balloons')
+
" If we pass the show_documentation flag, we should show the full
" documentation, and always in the preview window.
if get(l:options, 'show_documentation', 0)
@@ -40,7 +42,7 @@ function! ale#hover#HandleTSServerResponse(conn_id, response) abort
endif
elseif get(l:options, 'hover_from_balloonexpr', 0)
\&& exists('*balloon_show')
- \&& ale#Var(l:options.buffer, 'set_balloons')
+ \&& (l:set_balloons is 1 || l:set_balloons is# 'hover')
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])
@@ -216,9 +218,11 @@ function! ale#hover#HandleLSPResponse(conn_id, response) abort
let [l:commands, l:lines] = ale#hover#ParseLSPResult(l:result.contents)
if !empty(l:lines)
+ let l:set_balloons = ale#Var(l:options.buffer, 'set_balloons')
+
if get(l:options, 'hover_from_balloonexpr', 0)
\&& exists('*balloon_show')
- \&& ale#Var(l:options.buffer, 'set_balloons')
+ \&& (l:set_balloons is 1 || l:set_balloons is# 'hover')
call balloon_show(join(l:lines, "\n"))
elseif get(l:options, 'truncated_echo', 0)
call ale#cursor#TruncatedEcho(l:lines[0])
diff --git a/autoload/ale/lsp.vim b/autoload/ale/lsp.vim
index 7d99e9d2..cb0573aa 100644
--- a/autoload/ale/lsp.vim
+++ b/autoload/ale/lsp.vim
@@ -44,6 +44,7 @@ function! ale#lsp#Register(executable_or_address, project, init_options) abort
\ 'definition': 0,
\ 'typeDefinition': 0,
\ 'symbol_search': 0,
+ \ 'code_actions': 0,
\ },
\}
endif
@@ -219,6 +220,14 @@ function! s:UpdateCapabilities(conn, capabilities) abort
let a:conn.capabilities.rename = 1
endif
+ if get(a:capabilities, 'codeActionProvider') is v:true
+ let a:conn.capabilities.code_actions = 1
+ endif
+
+ if type(get(a:capabilities, 'codeActionProvider')) is v:t_dict
+ let a:conn.capabilities.code_actions = 1
+ endif
+
if !empty(get(a:capabilities, 'completionProvider'))
let a:conn.capabilities.completion = 1
endif
@@ -350,6 +359,7 @@ function! ale#lsp#MarkConnectionAsTsserver(conn_id) abort
let l:conn.capabilities.definition = 1
let l:conn.capabilities.symbol_search = 1
let l:conn.capabilities.rename = 1
+ let l:conn.capabilities.code_actions = 1
endfunction
function! s:SendInitMessage(conn) abort
diff --git a/autoload/ale/lsp/message.vim b/autoload/ale/lsp/message.vim
index 5b0cb8b7..38be4da6 100644
--- a/autoload/ale/lsp/message.vim
+++ b/autoload/ale/lsp/message.vim
@@ -172,3 +172,25 @@ function! ale#lsp#message#Rename(buffer, line, column, new_name) abort
\ 'newName': a:new_name,
\}]
endfunction
+
+function! ale#lsp#message#CodeAction(buffer, line, column, end_line, end_column, diagnostics) abort
+ return [0, 'textDocument/codeAction', {
+ \ 'textDocument': {
+ \ 'uri': ale#path#ToURI(expand('#' . a:buffer . ':p')),
+ \ },
+ \ 'range': {
+ \ 'start': {'line': a:line - 1, 'character': a:column - 1},
+ \ 'end': {'line': a:end_line - 1, 'character': a:end_column},
+ \ },
+ \ 'context': {
+ \ 'diagnostics': a:diagnostics
+ \ },
+ \}]
+endfunction
+
+function! ale#lsp#message#ExecuteCommand(command, arguments) abort
+ return [0, 'workspace/executeCommand', {
+ \ 'command': a:command,
+ \ 'arguments': a:arguments,
+ \}]
+endfunction
diff --git a/autoload/ale/lsp/response.vim b/autoload/ale/lsp/response.vim
index 30da77e1..a4f80980 100644
--- a/autoload/ale/lsp/response.vim
+++ b/autoload/ale/lsp/response.vim
@@ -56,6 +56,7 @@ function! ale#lsp#response#ReadDiagnostics(response) abort
endif
if has_key(l:diagnostic, 'relatedInformation')
+ \ && l:diagnostic.relatedInformation isnot v:null
let l:related = deepcopy(l:diagnostic.relatedInformation)
call map(l:related, {key, val ->
\ ale#path#FromURI(val.location.uri) .
diff --git a/autoload/ale/lsp/tsserver_message.vim b/autoload/ale/lsp/tsserver_message.vim
index b9fafaa0..3c1b47ed 100644
--- a/autoload/ale/lsp/tsserver_message.vim
+++ b/autoload/ale/lsp/tsserver_message.vim
@@ -103,3 +103,39 @@ function! ale#lsp#tsserver_message#OrganizeImports(buffer) abort
\ },
\}]
endfunction
+
+function! ale#lsp#tsserver_message#GetCodeFixes(buffer, line, column, end_line, end_column, error_codes) abort
+ " The lines and columns are 1-based.
+ " The errors codes must be a list of tsserver error codes to fix.
+ return [0, 'ts@getCodeFixes', {
+ \ 'startLine': a:line,
+ \ 'startOffset': a:column,
+ \ 'endLine': a:end_line,
+ \ 'endOffset': a:end_column + 1,
+ \ 'file': expand('#' . a:buffer . ':p'),
+ \ 'errorCodes': a:error_codes,
+ \}]
+endfunction
+
+function! ale#lsp#tsserver_message#GetApplicableRefactors(buffer, line, column, end_line, end_column) abort
+ " The arguments for this request can also be just 'line' and 'offset'
+ return [0, 'ts@getApplicableRefactors', {
+ \ 'startLine': a:line,
+ \ 'startOffset': a:column,
+ \ 'endLine': a:end_line,
+ \ 'endOffset': a:end_column + 1,
+ \ 'file': expand('#' . a:buffer . ':p'),
+ \}]
+endfunction
+
+function! ale#lsp#tsserver_message#GetEditsForRefactor(buffer, line, column, end_line, end_column, refactor, action) abort
+ return [0, 'ts@getEditsForRefactor', {
+ \ 'startLine': a:line,
+ \ 'startOffset': a:column,
+ \ 'endLine': a:end_line,
+ \ 'endOffset': a:end_column + 1,
+ \ 'file': expand('#' . a:buffer . ':p'),
+ \ 'refactor': a:refactor,
+ \ 'action': a:action,
+ \}]
+endfunction
diff --git a/autoload/ale/organize_imports.vim b/autoload/ale/organize_imports.vim
index e89c832c..e2b1c0d2 100644
--- a/autoload/ale/organize_imports.vim
+++ b/autoload/ale/organize_imports.vim
@@ -12,10 +12,13 @@ function! ale#organize_imports#HandleTSServerResponse(conn_id, response) abort
let l:file_code_edits = a:response.body
- call ale#code_action#HandleCodeAction({
- \ 'description': 'Organize Imports',
- \ 'changes': l:file_code_edits,
- \}, v:false)
+ call ale#code_action#HandleCodeAction(
+ \ {
+ \ 'description': 'Organize Imports',
+ \ 'changes': l:file_code_edits,
+ \ },
+ \ {}
+ \)
endfunction
function! s:OnReady(linter, lsp_details) abort
diff --git a/autoload/ale/rename.vim b/autoload/ale/rename.vim
index 64952e63..9030618e 100644
--- a/autoload/ale/rename.vim
+++ b/autoload/ale/rename.vim
@@ -33,9 +33,10 @@ function! ale#rename#HandleTSServerResponse(conn_id, response) abort
return
endif
- let l:old_name = s:rename_map[a:response.request_seq].old_name
- let l:new_name = s:rename_map[a:response.request_seq].new_name
- call remove(s:rename_map, a:response.request_seq)
+ let l:options = remove(s:rename_map, a:response.request_seq)
+
+ let l:old_name = l:options.old_name
+ let l:new_name = l:options.new_name
if get(a:response, 'success', v:false) is v:false
let l:message = get(a:response, 'message', 'unknown')
@@ -77,41 +78,21 @@ function! ale#rename#HandleTSServerResponse(conn_id, response) abort
return
endif
- 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
+ call ale#code_action#HandleCodeAction(
+ \ {
+ \ 'description': 'rename',
+ \ 'changes': l:changes,
+ \ },
+ \ {
+ \ 'should_save': 1,
+ \ },
+ \)
endfunction
function! ale#rename#HandleLSPResponse(conn_id, response) abort
if has_key(a:response, 'id')
\&& has_key(s:rename_map, a:response.id)
- call remove(s:rename_map, a:response.id)
+ let l:options = remove(s:rename_map, a:response.id)
if !has_key(a:response, 'result')
call s:message('No rename result received from server')
@@ -119,7 +100,7 @@ function! ale#rename#HandleLSPResponse(conn_id, response) abort
return
endif
- let l:changes_map = s:getChanges(a:response.result)
+ let l:changes_map = ale#code_action#GetChanges(a:response.result)
if empty(l:changes_map)
call s:message('No changes received from server')
@@ -127,43 +108,21 @@ function! ale#rename#HandleLSPResponse(conn_id, response) abort
return
endif
- let l:changes = []
+ let l:changes = ale#code_action#BuildChangesList(l:changes_map)
- 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
- let l:range = l:edit.range
- let l:new_text = l:edit.newText
-
- call add(l:text_changes, {
- \ 'start': {
- \ 'line': l:range.start.line + 1,
- \ 'offset': l:range.start.character + 1,
- \ },
- \ 'end': {
- \ 'line': l:range.end.line + 1,
- \ 'offset': l:range.end.character + 1,
- \ },
- \ 'newText': l:new_text,
- \})
- endfor
-
- call add(l:changes, {
- \ 'fileName': ale#path#FromURI(l:file_name),
- \ 'textChanges': l:text_changes,
- \})
- endfor
-
- call ale#code_action#HandleCodeAction({
- \ 'description': 'rename',
- \ 'changes': l:changes,
- \}, v:true)
+ call ale#code_action#HandleCodeAction(
+ \ {
+ \ 'description': 'rename',
+ \ 'changes': l:changes,
+ \ },
+ \ {
+ \ 'should_save': 1,
+ \ },
+ \)
endif
endfunction
-function! s:OnReady(line, column, old_name, new_name, linter, lsp_details) abort
+function! s:OnReady(line, column, options, linter, lsp_details) abort
let l:id = a:lsp_details.connection_id
if !ale#lsp#HasCapability(l:id, 'rename')
@@ -195,19 +154,16 @@ function! s:OnReady(line, column, old_name, new_name, linter, lsp_details) abort
\ l:buffer,
\ a:line,
\ a:column,
- \ a:new_name
+ \ a:options.new_name
\)
endif
let l:request_id = ale#lsp#Send(l:id, l:message)
- let s:rename_map[l:request_id] = {
- \ 'new_name': a:new_name,
- \ 'old_name': a:old_name,
- \}
+ let s:rename_map[l:request_id] = a:options
endfunction
-function! s:ExecuteRename(linter, old_name, new_name) abort
+function! s:ExecuteRename(linter, options) abort
let l:buffer = bufnr('')
let [l:line, l:column] = getpos('.')[1:2]
@@ -215,8 +171,7 @@ function! s:ExecuteRename(linter, old_name, new_name) abort
let l:column = min([l:column, len(getline(l:line))])
endif
- let l:Callback = function(
- \ 's:OnReady', [l:line, l:column, a:old_name, a:new_name])
+ let l:Callback = function('s:OnReady', [l:line, l:column, a:options])
call ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback)
endfunction
@@ -245,6 +200,9 @@ function! ale#rename#Execute() abort
endif
for l:lsp_linter in l:lsp_linters
- call s:ExecuteRename(l:lsp_linter, l:old_name, l:new_name)
+ call s:ExecuteRename(l:lsp_linter, {
+ \ 'old_name': l:old_name,
+ \ 'new_name': l:new_name,
+ \})
endfor
endfunction
diff --git a/autoload/ale/util.vim b/autoload/ale/util.vim
index 1f396377..fcc03eb7 100644
--- a/autoload/ale/util.vim
+++ b/autoload/ale/util.vim
@@ -486,7 +486,7 @@ function! ale#util#Input(message, value) abort
endfunction
function! ale#util#HasBuflineApi() abort
- return exists('*deletebufline') && exists('*setbufline')
+ return exists('*deletebufline') && exists('*appendbufline') && exists('*getpos') && exists('*setpos')
endfunction
" Sets buffer contents to lines
@@ -507,8 +507,11 @@ function! ale#util#SetBufferContents(buffer, lines) abort
" 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, '$')
+ let l:save_cursor = getpos('.')
+ call deletebufline(a:buffer, 1, '$')
+ call appendbufline(a:buffer, 1, l:new_lines)
+ call deletebufline(a:buffer, 1, 1)
+ call setpos('.', l:save_cursor)
" Fall back on setting lines the old way, for the current buffer.
else
let l:old_line_length = line('$')
diff --git a/doc/ale-erlang.txt b/doc/ale-erlang.txt
index 59993a99..38762f08 100644
--- a/doc/ale-erlang.txt
+++ b/doc/ale-erlang.txt
@@ -31,6 +31,18 @@ g:ale_erlang_dialyzer_rebar3_profile *g:ale_erlang_dialyzer_rebar3_profile*
This variable can be changed to specify the profile that is used to
run dialyzer with rebar3.
+
+-------------------------------------------------------------------------------
+elvis *ale-erlang-elvis*
+
+g:ale_erlang_elvis_executable *g:ale_erlang_elvis_executable*
+ *b:ale_erlang_elvis_executable*
+ Type: |String|
+ Default: `'elvis'`
+
+ This variable can be changed to specify the elvis executable.
+
+
-------------------------------------------------------------------------------
erlc *ale-erlang-erlc*
diff --git a/doc/ale-haskell.txt b/doc/ale-haskell.txt
index 5dd3ec15..fde439fe 100644
--- a/doc/ale-haskell.txt
+++ b/doc/ale-haskell.txt
@@ -172,5 +172,25 @@ g:ale_haskell_hie_executable *g:ale_haskell_hie_executable*
ide engine. i.e. `'hie-wrapper'`
+===============================================================================
+ormolu *ale-haskell-ormolu*
+
+g:ale_haskell_ormolu_executable *g:ale_haskell_ormolu_executable*
+ *b:ale_haskell_ormolu_executable*
+ Type: |String|
+ Default: `'ormolu'`
+
+ This variable can be changed to use a different executable for ormolu.
+
+
+g:ale_haskell_ormolu_options *g:ale_haskell_ormolu_options*
+ *b:ale_haskell_ormolu_options*
+ Type: String
+ Default: ''
+
+ This variable can be used to pass extra options to the underlying ormolu
+ executable.
+
+
===============================================================================
vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl:
diff --git a/doc/ale-lua.txt b/doc/ale-lua.txt
index f1286f89..408f0c3c 100644
--- a/doc/ale-lua.txt
+++ b/doc/ale-lua.txt
@@ -30,5 +30,21 @@ g:ale_lua_luacheck_options *g:ale_lua_luacheck_options*
This variable can be set to pass additional options to luacheck.
+===============================================================================
+luafmt *ale-lua-luafmt*
+
+g:ale_lua_luafmt_executable *g:ale_lua_luafmt_executable*
+ *b:ale_lua_luafmt_executable*
+ Type: |String|
+ Default: `'luafmt'`
+
+ This variable can be set to use a different executable for luafmt.
+
+g:ale_lua_luafmt_options *g:ale_lua_luafmt_options*
+ *b:ale_lua_luafmt_options*
+ Type: |String|
+ Default: `''`
+
+ This variable can be set to pass additional options to the luafmt fixer.
===============================================================================
vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl:
diff --git a/doc/ale-php.txt b/doc/ale-php.txt
index 9fe868f8..4ee016fb 100644
--- a/doc/ale-php.txt
+++ b/doc/ale-php.txt
@@ -85,6 +85,14 @@ g:ale_php_phpcbf_use_global *g:ale_php_phpcbf_use_global*
See |ale-integrations-local-executables|
+g:ale_php_phpcbf_options *g:ale_php_phpcbf_options*
+ *b:ale_php_phpcbf_options*
+ Type: |String|
+ Default: `''`
+
+ This variable can be set to pass additional options to php-cbf
+
+
===============================================================================
phpcs *ale-php-phpcs*
@@ -243,5 +251,70 @@ g:ale_php_php_executable *g:ale_php_php_executable*
This variable sets the executable used for php.
+
+===============================================================================
+tlint *ale-php-tlint*
+
+g:ale_php_tlint_executable *g:ale_php_tlint_executable*
+ *b:ale_php_tlint_executable*
+ Type: |String|
+ Default: `'tlint'`
+
+ See |ale-integrations-local-executables|
+
+
+g:ale_php_tlint_use_global *g:ale_php_tlint_use_global*
+ *b:ale_php_tlint_use_global*
+ Type: |Number|
+ Default: `get(g:, 'ale_use_global_executables', 0)`
+
+ See |ale-integrations-local-executables|
+
+
+g:ale_php_tlint_options *g:ale_php_tlint_options*
+ *b:ale_php_tlint_options*
+ Type: |String|
+ Default: `''`
+
+ This variable can be set to pass additional options to tlint
+
+
+===============================================================================
+intelephense *ale-php-intelephense*
+
+g:ale_php_intelephense_executable *g:ale_php_intelephense_executable*
+ *b:ale_php_intelephense_executable*
+ Type: |String|
+ Default: `'intelephense'`
+
+ The variable can be set to configure the executable that will be used for
+ running the intelephense language server. `node_modules` directory
+ executable will be preferred instead of this setting if
+ |g:ale_php_intelephense_use_global| is `0`.
+
+ See: |ale-integrations-local-executables|
+
+
+g:ale_php_intelephense_use_global *g:ale_php_intelephense_use_global*
+ *b:ale_php_intelephense_use_global*
+ Type: |Number|
+ Default: `get(g:, 'ale_use_global_executables', 0)`
+
+ This variable can be set to `1` to force the language server to be run with
+ the executable set for |g:ale_php_intelephense_executable|.
+
+ See: |ale-integrations-local-executables|
+
+
+g:ale_php_intelephense_config *g:ale_php_intelephense_config*
+ *b:ale_php_intelephense_config*
+ Type: |Dictionary|
+ Default: `{}`
+
+ The initialization options config specified by Intelephense. Refer to the
+ installation docs provided by intelephense (github.com/bmewburn/intelephense
+ -docs).
+
+
===============================================================================
vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl:
diff --git a/doc/ale-python.txt b/doc/ale-python.txt
index 6b1a6d33..f0c8bfb8 100644
--- a/doc/ale-python.txt
+++ b/doc/ale-python.txt
@@ -41,6 +41,32 @@ ALE will look for configuration files with the following filenames. >
The first directory containing any of the files named above will be used.
+===============================================================================
+autoimport *ale-python-autoimport*
+
+g:ale_python_autoimport_executable *g:ale_python_autoimport_executable*
+ *b:ale_python_autoimport_executable*
+ Type: |String|
+ Default: `'autoimport'`
+
+ See |ale-integrations-local-executables|
+
+
+g:ale_python_autoimport_options *g:ale_python_autoimport_options*
+ *b:ale_python_autoimport_options*
+ Type: |String|
+ Default: `''`
+
+ This variable can be set to pass extra options to autoimport.
+
+
+g:ale_python_autoimport_use_global *g:ale_python_autoimport_use_global*
+ *b:ale_python_autoimport_use_global*
+ Type: |Number|
+ Default: `get(g:, 'ale_use_global_executables', 0)`
+
+ See |ale-integrations-local-executables|
+
===============================================================================
autopep8 *ale-python-autopep8*
@@ -687,7 +713,7 @@ g:ale_python_pyre_auto_pipenv *g:ale_python_pyre_auto_pipenv*
===============================================================================
pyright *ale-python-pyright*
-The `pyrlight` linter requires a recent version of `pyright` which includes
+The `pyright` linter requires a recent version of `pyright` which includes
the `pyright-langserver` executable. You can install `pyright` on your system
through `npm` with `sudo npm install -g pyright` or similar.
diff --git a/doc/ale-r.txt b/doc/ale-r.txt
index b5ccebe5..3fabf702 100644
--- a/doc/ale-r.txt
+++ b/doc/ale-r.txt
@@ -2,6 +2,29 @@
ALE R Integration *ale-r-options*
+===============================================================================
+languageserver *ale-r-languageserver*
+
+g:ale_r_languageserver_cmd *g:ale_r_languageserver_cmd*
+ *b:ale_r_languageserver_cmd*
+ Type: |String|
+ Default: `'languageserver::run()'`
+
+ This option can be configured to change the execution command for
+ languageserver.
+
+ See the languageserver documentation for more options.
+
+
+g:ale_r_languageserver_config *g:ale_r_languageserver_config*
+ *b:ale_r_languageserver_config*
+ Type: |Dictionary|
+ Default: `{}`
+
+ This option can be configured to change settings for languageserver. See the
+ languageserver documentation for more information.
+
+
===============================================================================
lintr *ale-r-lintr*
@@ -22,7 +45,7 @@ g:ale_r_lintr_lint_package *g:ale_r_lintr_lint_package*
Default: `0`
When set to `1`, the file will be checked with `lintr::lint_package` instead
- of `lintr::lint`. This prevents erroneous namespace warnings when linting
+ of `lintr::lint`. This prevents erroneous namespace warnings when linting
package files.
@@ -36,8 +59,8 @@ g:ale_r_styler_options *g:ale_r_styler_options*
This option can be configured to change the options for styler.
- The value of this option will be used as the `style` argument for the
- `styler::style_file` options. Consult the styler documentation
+ The value of this option will be used as the `style` argument for the
+ `styler::style_file` options. Consult the styler documentation
for more information.
diff --git a/doc/ale-rust.txt b/doc/ale-rust.txt
index f4b4e7b7..3aa63673 100644
--- a/doc/ale-rust.txt
+++ b/doc/ale-rust.txt
@@ -22,12 +22,12 @@ Integration Information
3. rls -- If you have `rls` installed, you might prefer using this linter
over cargo. rls implements the Language Server Protocol for incremental
compilation of Rust code, and can check Rust files while you type. `rls`
- requires Rust files to contained in Cargo projects.
+ requires Rust files to be contained in Cargo projects.
4. analyzer -- If you have rust-analyzer installed, you might prefer using
this linter over cargo and rls. rust-analyzer also implements the
Language Server Protocol for incremental compilation of Rust code, and is
the next iteration of rls. rust-analyzer, like rls, requires Rust files
- to contained in Cargo projects.
+ to be contained in Cargo projects.
5. rustfmt -- If you have `rustfmt` installed, you can use it as a fixer to
consistently reformat your Rust code.
diff --git a/doc/ale-supported-languages-and-tools.txt b/doc/ale-supported-languages-and-tools.txt
index c6bcf421..36e27932 100644
--- a/doc/ale-supported-languages-and-tools.txt
+++ b/doc/ale-supported-languages-and-tools.txt
@@ -140,6 +140,7 @@ Notes:
* `erubis`
* `ruumba`
* Erlang
+ * `elvis`!!
* `erlc`
* `SyntaxErl`
* Fish
@@ -195,6 +196,7 @@ Notes:
* `hie`
* `hindent`
* `hlint`
+ * `ormolu`
* `stack-build`!!
* `stack-ghc`
* `stylish-haskell`
@@ -265,6 +267,7 @@ Notes:
* Lua
* `luac`
* `luacheck`
+ * `luafmt`
* Mail
* `alex`!!
* `languagetool`!!
@@ -324,6 +327,7 @@ Notes:
* Perl6
* `perl6 -c`
* PHP
+ * `intelephense`
* `langserver`
* `phan`
* `phpcbf`
@@ -333,6 +337,7 @@ Notes:
* `phpmd`
* `phpstan`
* `psalm`!!
+ * `tlint`
* PO
* `alex`!!
* `msgfmt`
@@ -361,6 +366,7 @@ Notes:
* `purescript-language-server`
* `purty`
* Python
+ * `autoimport`
* `autopep8`
* `bandit`
* `black`
@@ -383,6 +389,7 @@ Notes:
* `qmlfmt`
* `qmllint`
* R
+ * `languageserver`
* `lintr`
* `styler`
* Racket
@@ -455,9 +462,9 @@ Notes:
* SugarSS
* `stylelint`
* Swift
+ * Apple `swift-format`
* `sourcekit-lsp`
* `swiftformat`
- * `swift-format`
* `swiftlint`
* Tcl
* `nagelfar`!!
@@ -517,6 +524,7 @@ Notes:
* YAML
* `prettier`
* `swaglint`
+ * `yamlfix`
* `yamllint`
* YANG
* `yang-lsp`
diff --git a/doc/ale-yaml.txt b/doc/ale-yaml.txt
index c9a12ea1..61bfc139 100644
--- a/doc/ale-yaml.txt
+++ b/doc/ale-yaml.txt
@@ -15,7 +15,6 @@ Install prettier either globally or locally: >
npm install prettier -g # global
npm install prettier # local
<
-
===============================================================================
swaglint *ale-yaml-swaglint*
@@ -49,6 +48,43 @@ g:ale_yaml_swaglint_use_global *g:ale_yaml_swaglint_use_global*
See |ale-integrations-local-executables|
+===============================================================================
+yamlfix *ale-yaml-yamlfix*
+
+Website: https://lyz-code.github.io/yamlfix
+
+
+Installation
+-------------------------------------------------------------------------------
+
+Install yamlfix: >
+
+ pip install yamlfix
+<
+
+Options
+-------------------------------------------------------------------------------
+g:ale_yaml_yamlfix_executable *g:ale_yaml_yamlfix_executable*
+ *b:ale_yaml_yamlfix_executable*
+ Type: |String|
+ Default: `'yamlfix'`
+
+ See |ale-integrations-local-executables|
+
+
+g:ale_yaml_yamlfix_options *g:ale_yaml_yamlfix_options*
+ *b:ale_yaml_yamlfix_options*
+ Type: |String|
+ Default: `''`
+
+ This variable can be set to pass extra options to yamlfix.
+
+g:ale_yaml_yamlfix_use_global *g:ale_yaml_yamlfix_use_global*
+ *b:ale_yaml_yamlfix_use_global*
+ Type: |Number|
+ Default: `get(g:, 'ale_use_global_executables', 0)`
+
+ See |ale-integrations-local-executables|
===============================================================================
yamllint *ale-yaml-yamllint*
diff --git a/doc/ale.txt b/doc/ale.txt
index b63c51a2..f9f40d12 100644
--- a/doc/ale.txt
+++ b/doc/ale.txt
@@ -20,6 +20,7 @@ CONTENTS *ale-contents*
5.4 Find References...................|ale-find-references|
5.5 Hovering..........................|ale-hover|
5.6 Symbol Search.....................|ale-symbol-search|
+ 5.7 Refactoring: Rename, Actions......|ale-refactor|
6. Global Options.......................|ale-options|
6.1 Highlights........................|ale-highlights|
7. Linter/Fixer Options.................|ale-integration-options|
@@ -669,6 +670,34 @@ ALE supports searching for workspace symbols via LSP linters with the
|ALESymbolSearch| command. See the documentation for the command
for a full list of options.
+-------------------------------------------------------------------------------
+5.7 Refactoring: Rename, Actions *ale-refactor*
+
+ALE supports renaming symbols in code such as variables or class names with
+the |ALERename| command.
+
+|ALECodeAction| will execute actions on the cursor or applied to a visual
+range selection, such as automatically fixing errors.
+
+Actions will appear in the right click mouse menu by default for GUI versions
+of Vim, unless disabled by setting |g:ale_popup_menu_enabled| to `0`.
+
+Make sure to set your Vim to move the cursor position whenever you right
+click, and enable the mouse menu: >
+
+ set mouse=a
+ set mousemodel=popup_setpos
+<
+You may wish to remove some other menu items you don't want to see: >
+
+ silent! aunmenu PopUp.Select\ Word
+ silent! aunmenu PopUp.Select\ Sentence
+ silent! aunmenu PopUp.Select\ Paragraph
+ silent! aunmenu PopUp.Select\ Line
+ silent! aunmenu PopUp.Select\ Block
+ silent! aunmenu PopUp.Select\ Blockwise
+ silent! aunmenu PopUp.Select\ All
+<
===============================================================================
6. Global Options *ale-options*
@@ -1665,6 +1694,7 @@ g:ale_lsp_root *g:ale_lsp_root*
If neither variable yields a result, a linter-specific function is invoked to
detect a project root. If this, too, yields no result, the linter is disabled.
+
g:ale_max_buffer_history_size *g:ale_max_buffer_history_size*
Type: |Number|
@@ -1773,6 +1803,19 @@ g:ale_pattern_options_enabled *g:ale_pattern_options_enabled*
will not set buffer variables per |g:ale_pattern_options|.
+g:ale_popup_menu_enabled *g:ale_popup_menu_enabled*
+
+ Type: |Number|
+ Default: `has('gui_running')`
+
+ When this option is set to `1`, ALE will show code actions and rename
+ capabilities in the right click mouse menu when there's a LSP server or
+ tsserver available. See |ale-refactor|.
+
+ This setting must be set to `1` before ALE is loaded for this behavior
+ to be enabled. See |ale-lint-settings-on-startup|.
+
+
g:ale_rename_tsserver_find_in_comments *g:ale_rename_tsserver_find_in_comments*
Type: |Number|
@@ -1797,7 +1840,7 @@ g:ale_rename_tsserver_find_in_strings *g:ale_rename_tsserver_find_in_strings*
g:ale_set_balloons *g:ale_set_balloons*
*b:ale_set_balloons*
- Type: |Number|
+ Type: |Number| or |String|
Default: `has('balloon_eval') && has('gui_running')`
When this option is set to `1`, balloon messages will be displayed for
@@ -1808,6 +1851,13 @@ g:ale_set_balloons *g:ale_set_balloons*
supporting "Hover" information, per |ale-hover|, then brief information
about the symbol under the cursor will be displayed in a balloon.
+ This option can be set to `'hover'` to only enable balloons for hover
+ message, so diagnostics are never shown in balloons. You may wish to
+ configure use this setting only in GUI Vim like so: >
+
+ let g:ale_set_balloons = has('gui_running') ? 'hover' : 0
+<
+
Balloons can be enabled for terminal versions of Vim that support balloons,
but some versions of Vim will produce strange mouse behavior when balloons
are enabled. To configure balloons for your terminal, you should first
@@ -2589,6 +2639,7 @@ documented in additional help files.
elm-make..............................|ale-elm-elm-make|
erlang..................................|ale-erlang-options|
dialyzer..............................|ale-erlang-dialyzer|
+ elvis.................................|ale-erlang-elvis|
erlc..................................|ale-erlang-erlc|
syntaxerl.............................|ale-erlang-syntaxerl|
eruby...................................|ale-eruby-options|
@@ -2642,6 +2693,7 @@ documented in additional help files.
stack-ghc.............................|ale-haskell-stack-ghc|
stylish-haskell.......................|ale-haskell-stylish-haskell|
hie...................................|ale-haskell-hie|
+ ormolu................................|ale-haskell-ormolu|
hcl.....................................|ale-hcl-options|
terraform-fmt.........................|ale-hcl-terraform-fmt|
html....................................|ale-html-options|
@@ -2701,6 +2753,7 @@ documented in additional help files.
lua.....................................|ale-lua-options|
luac..................................|ale-lua-luac|
luacheck..............................|ale-lua-luacheck|
+ luafmt................................|ale-lua-luafmt|
markdown................................|ale-markdown-options|
markdownlint..........................|ale-markdown-markdownlint|
mdl...................................|ale-markdown-mdl|
@@ -2752,6 +2805,8 @@ documented in additional help files.
psalm.................................|ale-php-psalm|
php-cs-fixer..........................|ale-php-php-cs-fixer|
php...................................|ale-php-php|
+ tlint.................................|ale-php-tlint|
+ intelephense..........................|ale-php-intelephense|
po......................................|ale-po-options|
write-good............................|ale-po-write-good|
pod.....................................|ale-pod-options|
@@ -2777,6 +2832,7 @@ documented in additional help files.
pyrex (cython)..........................|ale-pyrex-options|
cython................................|ale-pyrex-cython|
python..................................|ale-python-options|
+ autoimport............................|ale-python-autoimport|
autopep8..............................|ale-python-autopep8|
bandit................................|ale-python-bandit|
black.................................|ale-python-black|
@@ -2798,6 +2854,7 @@ documented in additional help files.
qml.....................................|ale-qml-options|
qmlfmt................................|ale-qml-qmlfmt|
r.......................................|ale-r-options|
+ languageserver........................|ale-r-languageserver|
lintr.................................|ale-r-lintr|
styler................................|ale-r-styler|
reasonml................................|ale-reasonml-options|
@@ -2914,6 +2971,7 @@ documented in additional help files.
yaml....................................|ale-yaml-options|
prettier..............................|ale-yaml-prettier|
swaglint..............................|ale-yaml-swaglint|
+ yamlfix...............................|ale-yaml-yamlfix|
yamllint..............................|ale-yaml-yamllint|
yang....................................|ale-yang-options|
yang-lsp..............................|ale-yang-lsp|
@@ -3100,6 +3158,18 @@ ALERename *ALERename*
prompt will open to request a new name.
+ALECodeAction *ALECodeAction*
+
+ Apply a code action via LSP servers or `tsserver`.
+
+ If there is an error present on a line that can be fixed, ALE will
+ automatically fix a line, unless there are multiple possible code fixes to
+ apply.
+
+ This command can be run in visual mode apply actions, such as applicable
+ refactors. A menu will be shown to select code action to apply.
+
+
ALERepeatSelection *ALERepeatSelection*
Repeat the last selection displayed in the preview window.
diff --git a/plugin/ale.vim b/plugin/ale.vim
index 18d867ee..5b7be116 100644
--- a/plugin/ale.vim
+++ b/plugin/ale.vim
@@ -158,7 +158,10 @@ let g:ale_python_auto_pipenv = get(g:, 'ale_python_auto_pipenv', 0)
" This variable can be overridden to set the GO111MODULE environment variable.
let g:ale_go_go111module = get(g:, 'ale_go_go111module', '')
-if g:ale_set_balloons
+" If 1, enable a popup menu for commands.
+let g:ale_popup_menu_enabled = get(g:, 'ale_popup_menu_enabled', has('gui_running'))
+
+if g:ale_set_balloons is 1 || g:ale_set_balloons is# 'hover'
call ale#balloon#Enable()
endif
@@ -166,6 +169,10 @@ if g:ale_completion_enabled
call ale#completion#Enable()
endif
+if g:ale_popup_menu_enabled
+ call ale#code_action#EnablePopUpMenu()
+endif
+
" Define commands for moving through warnings and errors.
command! -bar -nargs=* ALEPrevious
\ :call ale#loclist_jumping#WrapJump('before', )
@@ -238,7 +245,10 @@ command! -bar ALEComplete :call ale#completion#GetCompletions('ale-manual')
command! -bar ALEImport :call ale#completion#Import()
" Rename symbols using tsserver and LSP
-command! -bar ALERename :call ale#rename#Execute()
+command! -bar -bang ALERename :call ale#rename#Execute()
+
+" Apply code actions to a range.
+command! -bar -range ALECodeAction :call ale#codefix#Execute()
" Organize import statements using tsserver
command! -bar ALEOrganizeImports :call ale#organize_imports#Execute()
@@ -283,6 +293,7 @@ nnoremap (ale_documentation) :ALEDocumentation
inoremap (ale_complete) :ALEComplete
nnoremap (ale_import) :ALEImport
nnoremap (ale_rename) :ALERename
+nnoremap (ale_code_action) :ALECodeAction
nnoremap (ale_repeat_selection) :ALERepeatSelection
" Set up autocmd groups now.
diff --git a/supported-tools.md b/supported-tools.md
index 66e46348..96ef273b 100644
--- a/supported-tools.md
+++ b/supported-tools.md
@@ -149,6 +149,7 @@ formatting.
* [erubis](https://github.com/kwatch/erubis)
* [ruumba](https://github.com/ericqweinstein/ruumba)
* Erlang
+ * [elvis](https://github.com/inaka/elvis) :floppy_disk:
* [erlc](http://erlang.org/doc/man/erlc.html)
* [SyntaxErl](https://github.com/ten0s/syntaxerl)
* Fish
@@ -204,6 +205,7 @@ formatting.
* [hie](https://github.com/haskell/haskell-ide-engine)
* [hindent](https://hackage.haskell.org/package/hindent)
* [hlint](https://hackage.haskell.org/package/hlint)
+ * [ormolu](https://github.com/tweag/ormolu)
* [stack-build](https://haskellstack.org/) :floppy_disk:
* [stack-ghc](https://haskellstack.org/)
* [stylish-haskell](https://github.com/jaspervdj/stylish-haskell)
@@ -274,6 +276,7 @@ formatting.
* Lua
* [luac](https://www.lua.org/manual/5.1/luac.html)
* [luacheck](https://github.com/mpeterv/luacheck)
+ * [luafmt](https://github.com/trixnz/lua-fmt)
* Mail
* [alex](https://github.com/wooorm/alex) :floppy_disk:
* [languagetool](https://languagetool.org/) :floppy_disk:
@@ -333,6 +336,7 @@ formatting.
* Perl6
* [perl6 -c](https://perl6.org) :warning:
* PHP
+ * [intelephense](https://github.com/bmewburn/intelephense-docs)
* [langserver](https://github.com/felixfbecker/php-language-server)
* [phan](https://github.com/phan/phan) see `:help ale-php-phan` to instructions
* [phpcbf](https://github.com/squizlabs/PHP_CodeSniffer)
@@ -342,6 +346,7 @@ formatting.
* [phpmd](https://phpmd.org)
* [phpstan](https://github.com/phpstan/phpstan)
* [psalm](https://getpsalm.org) :floppy_disk:
+ * [tlint](https://github.com/tightenco/tlint)
* PO
* [alex](https://github.com/wooorm/alex) :floppy_disk:
* [msgfmt](https://www.gnu.org/software/gettext/manual/html_node/msgfmt-Invocation.html)
@@ -370,6 +375,7 @@ formatting.
* [purescript-language-server](https://github.com/nwolverson/purescript-language-server)
* [purty](https://gitlab.com/joneshf/purty)
* Python
+ * [autoimport](https://lyz-code.github.io/autoimport/)
* [autopep8](https://github.com/hhatto/autopep8)
* [bandit](https://github.com/PyCQA/bandit) :warning:
* [black](https://github.com/ambv/black)
@@ -392,6 +398,7 @@ formatting.
* [qmlfmt](https://github.com/jesperhh/qmlfmt)
* [qmllint](https://github.com/qt/qtdeclarative/tree/5.11/tools/qmllint)
* R
+ * [languageserver](https://github.com/REditorSupport/languageserver)
* [lintr](https://github.com/jimhester/lintr)
* [styler](https://github.com/r-lib/styler)
* Racket
@@ -464,9 +471,9 @@ formatting.
* SugarSS
* [stylelint](https://github.com/stylelint/stylelint)
* Swift
+ * [Apple swift-format](https://github.com/apple/swift-format)
* [sourcekit-lsp](https://github.com/apple/sourcekit-lsp)
* [swiftformat](https://github.com/nicklockwood/SwiftFormat)
- * [swift-format](https://github.com/apple/swift-format)
* [swiftlint](https://github.com/realm/SwiftLint)
* Tcl
* [nagelfar](http://nagelfar.sourceforge.net) :floppy_disk:
@@ -526,6 +533,7 @@ formatting.
* YAML
* [prettier](https://github.com/prettier/prettier)
* [swaglint](https://github.com/byCedric/swaglint)
+ * [yamlfix](https://lyz-code.github.io/yamlfix)
* [yamllint](https://yamllint.readthedocs.io/)
* YANG
* [yang-lsp](https://github.com/theia-ide/yang-lsp)
diff --git a/test/command_callback/php-intelephense-project/with-composer/composer.json b/test/command_callback/php-intelephense-project/with-composer/composer.json
new file mode 100644
index 00000000..e69de29b
diff --git a/test/command_callback/python_paths/with_virtualenv/env/Scripts/autoimport.exe b/test/command_callback/python_paths/with_virtualenv/env/Scripts/autoimport.exe
new file mode 100755
index 00000000..e69de29b
diff --git a/test/command_callback/python_paths/with_virtualenv/env/Scripts/yamlfix.exe b/test/command_callback/python_paths/with_virtualenv/env/Scripts/yamlfix.exe
new file mode 100644
index 00000000..e69de29b
diff --git a/test/command_callback/python_paths/with_virtualenv/env/bin/autoimport b/test/command_callback/python_paths/with_virtualenv/env/bin/autoimport
new file mode 100755
index 00000000..e69de29b
diff --git a/test/command_callback/python_paths/with_virtualenv/env/bin/yamlfix b/test/command_callback/python_paths/with_virtualenv/env/bin/yamlfix
new file mode 100755
index 00000000..e69de29b
diff --git a/test/command_callback/r_paths/.Rprofile b/test/command_callback/r_paths/.Rprofile
new file mode 100644
index 00000000..e69de29b
diff --git a/test/command_callback/test_erlang_elvis_command_callback.vader b/test/command_callback/test_erlang_elvis_command_callback.vader
new file mode 100644
index 00000000..4aab49d6
--- /dev/null
+++ b/test/command_callback/test_erlang_elvis_command_callback.vader
@@ -0,0 +1,16 @@
+Before:
+ let b:file = fnamemodify(bufname(''), ':.')
+ call ale#assert#SetUpLinterTest('erlang', 'elvis')
+
+After:
+ call ale#assert#TearDownLinterTest()
+
+Execute(Default command should be correct):
+ AssertLinter 'elvis',
+ \ ale#Escape('elvis') . ' rock --output-format=parsable ' . ale#Escape(b:file)
+
+Execute(Executable should be configurable):
+ let b:ale_erlang_elvis_executable = '/path/to/elvis'
+
+ AssertLinter '/path/to/elvis',
+ \ ale#Escape('/path/to/elvis') . ' rock --output-format=parsable ' . ale#Escape(b:file)
diff --git a/test/command_callback/test_php_intelephense_command_callback.vader b/test/command_callback/test_php_intelephense_command_callback.vader
new file mode 100644
index 00000000..dd6adb3d
--- /dev/null
+++ b/test/command_callback/test_php_intelephense_command_callback.vader
@@ -0,0 +1,26 @@
+Before:
+ call ale#assert#SetUpLinterTest('php', 'intelephense')
+
+After:
+ call ale#assert#TearDownLinterTest()
+
+Execute(The default executable path should be correct):
+ AssertLinter 'intelephense',
+ \ ale#Escape('intelephense') . ' --stdio'
+
+Execute(The project path should be correct for .git directories):
+ call ale#test#SetFilename('php-intelephense-project/with-git/test.php')
+ silent! call mkdir('php-intelephense-project/with-git/.git', 'p')
+
+ AssertLSPProject ale#path#Simplify(g:dir . '/php-intelephense-project/with-git')
+
+Execute(The project path should be correct for composer.json file):
+ call ale#test#SetFilename('php-intelephense-project/with-composer/test.php')
+
+ AssertLSPProject ale#path#Simplify(g:dir . '/php-intelephense-project/with-composer')
+
+Execute(The project cache should be saved in a temp dir):
+ call ale#test#SetFilename('php-intelephense-project/with-composer/test.php')
+ let g:ale_php_intelephense_config = { 'storagePath': '/tmp/intelephense' }
+
+ AssertLSPProject ale#path#Simplify(g:dir . '/php-intelephense-project/with-composer')
diff --git a/test/command_callback/test_r_languageserver_callbacks.vader b/test/command_callback/test_r_languageserver_callbacks.vader
new file mode 100644
index 00000000..9a4a1f87
--- /dev/null
+++ b/test/command_callback/test_r_languageserver_callbacks.vader
@@ -0,0 +1,22 @@
+Before:
+ call ale#assert#SetUpLinterTest('r', 'languageserver')
+
+After:
+ call ale#assert#TearDownLinterTest()
+
+Execute(The default executable path should be correct):
+ AssertLinter 'Rscript', 'Rscript --vanilla -e ' . ale#Escape('languageserver::run()')
+
+Execute(The project root should be detected correctly):
+ AssertLSPProject '.'
+
+ call ale#test#SetFilename('r_paths/dummy/test.R')
+
+ AssertLSPProject ale#path#Simplify(g:dir . '/r_paths')
+
+Execute(Should accept configuration settings):
+ AssertLSPConfig {}
+
+ let b:ale_r_languageserver_config = {'r': {'lsp': {'debug': 'true', 'diagnostics': 'true'}}}
+
+ AssertLSPConfig {'r': {'lsp': {'debug': 'true', 'diagnostics': 'true'}}}
diff --git a/test/completion/test_ale_import_command.vader b/test/completion/test_ale_import_command.vader
index 2ba9b8d7..d36caae2 100644
--- a/test/completion/test_ale_import_command.vader
+++ b/test/completion/test_ale_import_command.vader
@@ -65,8 +65,8 @@ Before:
return g:server_started_value
endfunction
- function! ale#code_action#HandleCodeAction(code_action, should_save) abort
- Assert !a:should_save
+ function! ale#code_action#HandleCodeAction(code_action, options) abort
+ Assert !get(a:options, 'should_save')
call add(g:code_action_list, a:code_action)
endfunction
diff --git a/test/completion/test_completion_events.vader b/test/completion/test_completion_events.vader
index f678e773..30bf603c 100644
--- a/test/completion/test_completion_events.vader
+++ b/test/completion/test_completion_events.vader
@@ -50,8 +50,8 @@ Before:
let g:handle_code_action_called = 0
function! MockHandleCodeAction() abort
" delfunction! ale#code_action#HandleCodeAction
- function! ale#code_action#HandleCodeAction(action, should_save) abort
- AssertEqual v:false, a:should_save
+ function! ale#code_action#HandleCodeAction(action, options) abort
+ Assert !get(a:options, 'should_save')
let g:handle_code_action_called += 1
endfunction
endfunction
diff --git a/test/completion/test_lsp_completion_parsing.vader b/test/completion/test_lsp_completion_parsing.vader
index d989aefe..36228c10 100644
--- a/test/completion/test_lsp_completion_parsing.vader
+++ b/test/completion/test_lsp_completion_parsing.vader
@@ -40,6 +40,7 @@ Execute(Should handle Rust completion results correctly):
\ {'word': 'from', 'menu': 'fn from(s: &''a str) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})},
\ {'word': 'from', 'menu': 'fn from(s: Box) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})},
\ {'word': 'from', 'menu': 'fn from(s: Cow<''a, str>) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})},
+ \ {'word': 'to_vec', 'menu': 'pub fn to_vec(&self) -> Vec where T: Clone,', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})},
\],
\ ale#completion#ParseLSPCompletions({
\ "jsonrpc":"2.0",
@@ -184,6 +185,11 @@ Execute(Should handle Rust completion results correctly):
\ "label":"from",
\ "kind":3,
\ "detail":"fn from(s: Cow<'a, str>) -> String"
+ \ },
+ \ {
+ \ "label":"to_vec",
+ \ "kind":3,
+ \ "detail":"pub fn to_vec(&self) -> Vec\nwhere\n T: Clone,"
\ }
\ ]
\ })
diff --git a/test/fixers/test_autoimport_fixer_callback.vader b/test/fixers/test_autoimport_fixer_callback.vader
new file mode 100644
index 00000000..6952cbb8
--- /dev/null
+++ b/test/fixers/test_autoimport_fixer_callback.vader
@@ -0,0 +1,50 @@
+Before:
+ Save g:ale_python_autoimport_executable
+ Save g:ale_python_autoimport_options
+
+ " Use an invalid global executable, so we don't match it.
+ let g:ale_python_autoimport_executable = 'xxxinvalid'
+ let g:ale_python_autoimport_options = ''
+
+ call ale#test#SetDirectory('/testplugin/test/fixers')
+ silent cd ..
+ silent cd command_callback
+ let g:dir = getcwd()
+
+ let b:bin_dir = has('win32') ? 'Scripts' : 'bin'
+
+After:
+ Restore
+
+ unlet! b:bin_dir
+
+ call ale#test#RestoreDirectory()
+
+Execute(The autoimport callback should return the correct default values):
+ AssertEqual
+ \ 0,
+ \ ale#fixers#autoimport#Fix(bufnr(''))
+
+ silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/bar.py')
+ AssertEqual
+ \ {
+ \ 'command': ale#path#BufferCdString(bufnr(''))
+ \ . ale#Escape(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/env/' . b:bin_dir . '/autoimport')) . ' -',
+ \ },
+ \ ale#fixers#autoimport#Fix(bufnr(''))
+
+Execute(The autoimport callback should respect custom options):
+ let g:ale_python_autoimport_options = '--multi-line=3 --trailing-comma'
+
+ AssertEqual
+ \ 0,
+ \ ale#fixers#autoimport#Fix(bufnr(''))
+
+ silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/bar.py')
+ AssertEqual
+ \ {
+ \ 'command': ale#path#BufferCdString(bufnr(''))
+ \ . ale#Escape(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/env/' . b:bin_dir . '/autoimport'))
+ \ . ' --multi-line=3 --trailing-comma -',
+ \ },
+ \ ale#fixers#autoimport#Fix(bufnr(''))
diff --git a/test/fixers/test_gofmt_fixer_callback.vader b/test/fixers/test_gofmt_fixer_callback.vader
index 16659655..99407173 100644
--- a/test/fixers/test_gofmt_fixer_callback.vader
+++ b/test/fixers/test_gofmt_fixer_callback.vader
@@ -21,10 +21,7 @@ Execute(The gofmt callback should return the correct default values):
AssertEqual
\ {
- \ 'read_temporary_file': 1,
- \ 'command': ale#Escape('xxxinvalid')
- \ . ' -l -w'
- \ . ' %t',
+ \ 'command': ale#Escape('xxxinvalid'),
\ },
\ ale#fixers#gofmt#Fix(bufnr(''))
@@ -35,11 +32,8 @@ Execute(The gofmt callback should include custom gofmt options):
AssertEqual
\ {
- \ 'read_temporary_file': 1,
\ 'command': ale#Escape('xxxinvalid')
- \ . ' -l -w'
- \ . ' ' . g:ale_go_gofmt_options
- \ . ' %t',
+ \ . ' ' . g:ale_go_gofmt_options,
\ },
\ ale#fixers#gofmt#Fix(bufnr(''))
@@ -50,9 +44,7 @@ Execute(The gofmt callback should support Go environment variables):
AssertEqual
\ {
- \ 'read_temporary_file': 1,
\ 'command': ale#Env('GO111MODULE', 'off')
- \ . ale#Escape('xxxinvalid') . ' -l -w'
- \ . ' %t',
+ \ . ale#Escape('xxxinvalid')
\ },
\ ale#fixers#gofmt#Fix(bufnr(''))
diff --git a/test/fixers/test_luafmt_fixer_callback.vader b/test/fixers/test_luafmt_fixer_callback.vader
new file mode 100644
index 00000000..362da118
--- /dev/null
+++ b/test/fixers/test_luafmt_fixer_callback.vader
@@ -0,0 +1,35 @@
+Before:
+ Save g:ale_lua_luafmt_executable
+ Save g:ale_lua_luafmt_options
+
+ " Use an invalid global executable, so we don't match it.
+ let g:ale_lua_luafmt_executable = 'xxxinvalid'
+ let g:ale_lua_luafmt_options = ''
+
+ call ale#test#SetDirectory('/testplugin/test/fixers')
+
+After:
+ Restore
+
+ call ale#test#RestoreDirectory()
+
+Execute(The luafmt callback should return the correct default values):
+ call ale#test#SetFilename('../lua_files/testfile.lua')
+
+ AssertEqual
+ \ {
+ \ 'command': ale#Escape('xxxinvalid') . ' --stdin',
+ \ },
+ \ ale#fixers#luafmt#Fix(bufnr(''))
+
+Execute(The luafmt callback should include custom luafmt options):
+ let g:ale_lua_luafmt_options = "--skip-children"
+ call ale#test#SetFilename('../lua_files/testfile.lua')
+
+ AssertEqual
+ \ {
+ \ 'command': ale#Escape('xxxinvalid')
+ \ . ' ' . g:ale_lua_luafmt_options
+ \ . ' --stdin',
+ \ },
+ \ ale#fixers#luafmt#Fix(bufnr(''))
diff --git a/test/fixers/test_ormolu_fixer_callback.vader b/test/fixers/test_ormolu_fixer_callback.vader
new file mode 100644
index 00000000..8df3fca9
--- /dev/null
+++ b/test/fixers/test_ormolu_fixer_callback.vader
@@ -0,0 +1,24 @@
+Before:
+ Save g:ale_haskell_ormolu_executable
+ Save g:ale_haskell_ormolu_options
+
+After:
+ Restore
+
+Execute(The ormolu callback should return the correct default values):
+ AssertEqual
+ \ {
+ \ 'command': ale#Escape('ormolu')
+ \ },
+ \ ale#fixers#ormolu#Fix(bufnr(''))
+
+Execute(The ormolu executable and options should be configurable):
+ let g:ale_nix_nixpkgsfmt_executable = '/path/to/ormolu'
+ let g:ale_nix_nixpkgsfmt_options = '-h'
+
+ AssertEqual
+ \ {
+ \ 'command': ale#Escape('/path/to/ormolu')
+ \ . ' -h',
+ \ },
+ \ ale#fixers#nixpkgsfmt#Fix(bufnr(''))
diff --git a/test/fixers/test_phpcbf_fixer_callback.vader b/test/fixers/test_phpcbf_fixer_callback.vader
index 1663c89c..f7bcc2d8 100644
--- a/test/fixers/test_phpcbf_fixer_callback.vader
+++ b/test/fixers/test_phpcbf_fixer_callback.vader
@@ -5,6 +5,7 @@ Before:
let g:ale_php_phpcbf_executable = 'phpcbf_test'
let g:ale_php_phpcbf_standard = ''
+ let g:ale_php_phpcbf_options = ''
let g:ale_php_phpcbf_use_global = 0
call ale#test#SetDirectory('/testplugin/test/fixers')
@@ -54,6 +55,15 @@ Execute(The phpcbf callback should include the phpcbf_standard option):
\ {'command': ale#Escape(ale#path#Simplify(g:dir . '/php_paths/project-with-phpcbf/vendor/bin/phpcbf')) . ' --stdin-path=%s ' . '--standard=phpcbf_ruleset.xml' . ' -'},
\ ale#fixers#phpcbf#Fix(bufnr(''))
+Execute(User provided options should be used):
+ let g:ale_php_phpcbf_options = '--my-user-provided-option my-value'
+ call ale#test#SetFilename('php_paths/project-with-phpcbf/foo/test.php')
+
+ AssertEqual
+ \ {'command': ale#Escape(ale#path#Simplify(g:dir . '/php_paths/project-with-phpcbf/vendor/bin/phpcbf')) . ' --stdin-path=%s ' . ale#Pad('--my-user-provided-option my-value') . ' -'},
+ \ ale#fixers#phpcbf#Fix(bufnr(''))
+
+
Before:
Save g:ale_php_phpcbf_executable
Save g:ale_php_phpcbf_standard
@@ -61,6 +71,7 @@ Before:
let g:ale_php_phpcbf_executable = 'phpcbf_test'
let g:ale_php_phpcbf_standard = ''
+ let g:ale_php_phpcbf_options = ''
let g:ale_php_phpcbf_use_global = 0
call ale#test#SetDirectory('/testplugin/test/fixers')
diff --git a/test/fixers/test_yamlfix_fixer_callback.vader b/test/fixers/test_yamlfix_fixer_callback.vader
new file mode 100644
index 00000000..3ffda91e
--- /dev/null
+++ b/test/fixers/test_yamlfix_fixer_callback.vader
@@ -0,0 +1,50 @@
+Before:
+ Save g:ale_python_yamlfix_executable
+ Save g:ale_python_yamlfix_options
+
+ " Use an invalid global executable, so we don't match it.
+ let g:ale_python_yamlfix_executable = 'xxxinvalid'
+ let g:ale_python_yamlfix_options = ''
+
+ call ale#test#SetDirectory('/testplugin/test/fixers')
+ silent cd ..
+ silent cd command_callback
+ let g:dir = getcwd()
+
+ let b:bin_dir = has('win32') ? 'Scripts' : 'bin'
+
+After:
+ Restore
+
+ unlet! b:bin_dir
+
+ call ale#test#RestoreDirectory()
+
+Execute(The yamlfix callback should return the correct default values):
+ AssertEqual
+ \ 0,
+ \ ale#fixers#yamlfix#Fix(bufnr(''))
+
+ silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/bar.yaml')
+ AssertEqual
+ \ {
+ \ 'command': ale#path#BufferCdString(bufnr(''))
+ \ . ale#Escape(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/env/' . b:bin_dir . '/yamlfix')) . ' -',
+ \ },
+ \ ale#fixers#yamlfix#Fix(bufnr(''))
+
+Execute(The yamlfix callback should respect custom options):
+ let g:ale_yaml_yamlfix_options = '--multi-line=3 --trailing-comma'
+
+ AssertEqual
+ \ 0,
+ \ ale#fixers#yamlfix#Fix(bufnr(''))
+
+ silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/bar.yaml')
+ AssertEqual
+ \ {
+ \ 'command': ale#path#BufferCdString(bufnr(''))
+ \ . ale#Escape(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/env/' . b:bin_dir . '/yamlfix'))
+ \ . ' --multi-line=3 --trailing-comma -',
+ \ },
+ \ ale#fixers#yamlfix#Fix(bufnr(''))
diff --git a/test/handler/test_erlang_elvis_handler.vader b/test/handler/test_erlang_elvis_handler.vader
new file mode 100644
index 00000000..365376c8
--- /dev/null
+++ b/test/handler/test_erlang_elvis_handler.vader
@@ -0,0 +1,37 @@
+Before:
+ runtime ale_linters/erlang/elvis.vim
+
+After:
+ call ale#linter#Reset()
+
+Execute(Warning messages should be handled):
+ AssertEqual
+ \ [
+ \ {
+ \ 'lnum': 11,
+ \ 'text': "Replace the 'if' expression on line 11 with a 'case' expression or function clauses.",
+ \ 'type': 'W',
+ \ },
+ \ {
+ \ 'lnum': 20,
+ \ 'text': 'Remove the debug call to io:format/1 on line 20.',
+ \ 'type': 'W',
+ \ },
+ \ ],
+ \ ale_linters#erlang#elvis#Handle(bufnr(''), [
+ \ "src/foo.erl:11:no_if_expression:Replace the 'if' expression on line 11 with a 'case' expression or function clauses.",
+ \ 'src/foo.erl:20:no_debug_call:Remove the debug call to io:format/1 on line 20.',
+ \ ])
+
+Execute(Line length message shouldn't contain the line itself):
+ AssertEqual
+ \ [
+ \ {
+ \ 'lnum': 24,
+ \ 'text': 'Line 24 is too long.',
+ \ 'type': 'W',
+ \ },
+ \ ],
+ \ ale_linters#erlang#elvis#Handle(bufnr(''), [
+ \ 'src/foo.erl:24:line_length:Line 24 is too long: io:format("Look ma, too long!"),.',
+ \ ])
diff --git a/test/handler/test_phpcs_handler.vader b/test/handler/test_phpcs_handler.vader
index 18accece..26d35cb8 100644
--- a/test/handler/test_phpcs_handler.vader
+++ b/test/handler/test_phpcs_handler.vader
@@ -13,7 +13,16 @@ Execute(phpcs errors should be handled):
\ 'type': 'E',
\ 'sub_type': 'style',
\ 'text': 'Line indented incorrectly; expected 4 spaces, found 2 (Generic.WhiteSpace.ScopeIndent.IncorrectExact)',
- \ }],
+ \ },
+ \ {
+ \ 'lnum': 22,
+ \ 'col': 3,
+ \ 'type': 'E',
+ \ 'sub_type': 'style',
+ \ 'text': 'All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks)',
+ \ },
+ \ ],
\ ale_linters#php#phpcs#Handle(bufnr(''), [
\ '/path/to/some-filename.php:18:3: error - Line indented incorrectly; expected 4 spaces, found 2 (Generic.WhiteSpace.ScopeIndent.IncorrectExact)',
+ \ "/path/to/some-filename.php:22:3: error - All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '\"\n'.",
\ ])
diff --git a/test/handler/test_tlint_handler.vader b/test/handler/test_tlint_handler.vader
new file mode 100644
index 00000000..e146346c
--- /dev/null
+++ b/test/handler/test_tlint_handler.vader
@@ -0,0 +1,34 @@
+Before:
+ runtime ale_linters/php/tlint.vim
+
+After:
+ call ale#linter#Reset()
+
+Execute(The tlint handler should calculate line numbers):
+ AssertEqual
+ \ [
+ \ {
+ \ 'lnum': '5',
+ \ 'col': 0,
+ \ 'sub_type':
+ \ 'style',
+ \ 'type': 'W',
+ \ 'text': ['! There should be no unused imports.', 'There should be no unused imports.', '', '', '', '', '', '', '', '']
+ \ },
+ \ {
+ \ 'lnum': '15',
+ \ 'col': 0,
+ \ 'sub_type':
+ \ 'style',
+ \ 'type': 'W',
+ \ 'text': ['! There should be no method visibility in test methods.', 'There should be no method visibility in test methods.', '', '', '', '', '', '', '', '']
+ \ },
+ \ ],
+ \ ale_linters#php#tlint#Handle(347, [
+ \ "Lints for /Users/jose/Code/Tighten/tester/tests/Unit/ExampleTest.php",
+ \ "============",
+ \ "! There should be no unused imports.",
+ \ "5 : `use Illuminate\Foundation\Testing\RefreshDatabase;`",
+ \ "! There should be no method visibility in test methods.",
+ \ "15 : ` public function testBasicTest()`",
+ \ ])
diff --git a/test/lsp/test_other_initialize_message_handling.vader b/test/lsp/test_other_initialize_message_handling.vader
index b6ef852a..f3b53843 100644
--- a/test/lsp/test_other_initialize_message_handling.vader
+++ b/test/lsp/test_other_initialize_message_handling.vader
@@ -23,6 +23,7 @@ Before:
\ 'completion_trigger_characters': [],
\ 'definition': 0,
\ 'symbol_search': 0,
+ \ 'code_actions': 0,
\ },
\}
@@ -102,6 +103,7 @@ Execute(Capabilities should bet set up correctly):
\ 'definition': 1,
\ 'symbol_search': 1,
\ 'rename': 1,
+ \ 'code_actions': 1,
\ },
\ b:conn.capabilities
AssertEqual [[1, 'initialized', {}]], g:message_list
@@ -125,7 +127,7 @@ Execute(Disabled capabilities should be recognised correctly):
\ 'referencesProvider': v:false,
\ 'textDocumentSync': 2,
\ 'documentFormattingProvider': v:true,
- \ 'codeActionProvider': v:true,
+ \ 'codeActionProvider': v:false,
\ 'signatureHelpProvider': {
\ 'triggerCharacters': ['(', ','],
\ },
@@ -146,6 +148,7 @@ Execute(Disabled capabilities should be recognised correctly):
\ 'definition': 0,
\ 'symbol_search': 0,
\ 'rename': 0,
+ \ 'code_actions': 0,
\ },
\ b:conn.capabilities
AssertEqual [[1, 'initialized', {}]], g:message_list
@@ -197,6 +200,7 @@ Execute(Capabilities should be enabled when send as Dictionaries):
\ 'typeDefinition': 1,
\ 'symbol_search': 1,
\ 'rename': 1,
+ \ 'code_actions': 1,
\ },
\ b:conn.capabilities
AssertEqual [[1, 'initialized', {}]], g:message_list
diff --git a/test/lua_files/testfile.lua b/test/lua_files/testfile.lua
new file mode 100644
index 00000000..e69de29b
diff --git a/test/test_code_action.vader b/test/test_code_action.vader
index 19de7268..7eabb759 100644
--- a/test/test_code_action.vader
+++ b/test/test_code_action.vader
@@ -3,6 +3,9 @@ Before:
let g:ale_enabled = 0
+ " Enable fix end-of-line as tests below expect that
+ set fixeol
+
runtime autoload/ale/code_action.vim
runtime autoload/ale/util.vim
@@ -85,7 +88,8 @@ Execute(It should modify and save multiple files):
\ 'import D from "D"',
\], g:file2, 'S')
- call ale#code_action#HandleCodeAction({
+ call ale#code_action#HandleCodeAction(
+ \ {
\ 'changes': [{
\ 'fileName': g:file1,
\ 'textChanges': [{
@@ -122,8 +126,10 @@ Execute(It should modify and save multiple files):
\ },
\ 'newText': "import {A, B} from 'module'\n\n",
\ }]
- \ }],
- \}, v:true)
+ \ }],
+ \ },
+ \ {'should_save': 1},
+ \)
AssertEqual [
\ 'class Value {',
@@ -153,7 +159,8 @@ Execute(Beginning of file can be modified):
\]
call writefile(g:test.text, g:file1, 'S')
- call ale#code_action#HandleCodeAction({
+ call ale#code_action#HandleCodeAction(
+ \ {
\ 'changes': [{
\ 'fileName': g:file1,
\ 'textChanges': [{
@@ -168,7 +175,9 @@ Execute(Beginning of file can be modified):
\ 'newText': "type A: string\ntype B: number\n",
\ }],
\ }]
- \}, v:true)
+ \ },
+ \ {'should_save': 1},
+ \)
AssertEqual [
\ 'type A: string',
@@ -184,24 +193,28 @@ Execute(End of file can be modified):
\]
call writefile(g:test.text, g:file1, 'S')
- call ale#code_action#HandleCodeAction({
+ call ale#code_action#HandleCodeAction(
+ \ {
\ 'changes': [{
- \ 'fileName': g:file1,
- \ 'textChanges': [{
- \ 'start': {
- \ 'line': 4,
- \ 'offset': 1,
- \ },
- \ 'end': {
- \ 'line': 4,
- \ 'offset': 1,
- \ },
- \ 'newText': "type A: string\ntype B: number\n",
- \ }],
+ \ 'fileName': g:file1,
+ \ 'textChanges': [{
+ \ 'start': {
+ \ 'line': 4,
+ \ 'offset': 1,
+ \ },
+ \ 'end': {
+ \ 'line': 4,
+ \ 'offset': 1,
+ \ },
+ \ 'newText': "type A: string\ntype B: number\n",
+ \ }],
\ }]
- \}, v:true)
+ \ },
+ \ {'should_save': 1},
+ \)
AssertEqual g:test.text + [
+ \ '',
\ 'type A: string',
\ 'type B: number',
\ '',
@@ -219,7 +232,8 @@ Execute(Current buffer contents will be reloaded):
execute 'edit ' . g:file1
let g:test.buffer = bufnr(g:file1)
- call ale#code_action#HandleCodeAction({
+ call ale#code_action#HandleCodeAction(
+ \ {
\ 'changes': [{
\ 'fileName': g:file1,
\ 'textChanges': [{
@@ -234,7 +248,9 @@ Execute(Current buffer contents will be reloaded):
\ 'newText': "type A: string\ntype B: number\n",
\ }],
\ }]
- \}, v:true)
+ \ },
+ \ {'should_save': 1},
+ \)
AssertEqual [
\ 'type A: string',
@@ -256,11 +272,11 @@ Execute(Cursor will not move when it is before text change):
let g:test.changes = g:test.create_change(2, 3, 2, 8, 'value2')
call setpos('.', [0, 1, 1, 0])
- call ale#code_action#HandleCodeAction(g:test.changes, v:true)
+ call ale#code_action#HandleCodeAction(g:test.changes, {'should_save': 1})
AssertEqual [1, 1], getpos('.')[1:2]
call setpos('.', [0, 2, 2, 0])
- call ale#code_action#HandleCodeAction(g:test.changes, v:true)
+ call ale#code_action#HandleCodeAction(g:test.changes, {'should_save': 1})
AssertEqual [2, 2], getpos('.')[1:2]
# ====C====
@@ -271,7 +287,7 @@ Execute(Cursor column will move to the change end when cursor between start/end)
call WriteFileAndEdit()
call setpos('.', [0, 2, r, 0])
AssertEqual ' value: string', getline('.')
- call ale#code_action#HandleCodeAction(g:test.changes, v:true)
+ call ale#code_action#HandleCodeAction(g:test.changes, {'should_save': 1})
AssertEqual ' value2: string', getline('.')
AssertEqual [2, 9], getpos('.')[1:2]
endfor
@@ -283,7 +299,9 @@ Execute(Cursor column will move back when new text is shorter):
call setpos('.', [0, 2, 8, 0])
AssertEqual ' value: string', getline('.')
call ale#code_action#HandleCodeAction(
- \ g:test.create_change(2, 3, 2, 8, 'val'), v:true)
+ \ g:test.create_change(2, 3, 2, 8, 'val'),
+ \ {'should_save': 1},
+ \)
AssertEqual ' val: string', getline('.')
AssertEqual [2, 6], getpos('.')[1:2]
@@ -295,7 +313,7 @@ Execute(Cursor column will move forward when new text is longer):
call setpos('.', [0, 2, 8, 0])
AssertEqual ' value: string', getline('.')
call ale#code_action#HandleCodeAction(
- \ g:test.create_change(2, 3, 2, 8, 'longValue'), v:true)
+ \ g:test.create_change(2, 3, 2, 8, 'longValue'), {'should_save': 1})
AssertEqual ' longValue: string', getline('.')
AssertEqual [2, 12], getpos('.')[1:2]
@@ -307,7 +325,7 @@ Execute(Cursor line will move when updates are happening on lines above):
call setpos('.', [0, 3, 1, 0])
AssertEqual '}', getline('.')
call ale#code_action#HandleCodeAction(
- \ g:test.create_change(1, 1, 2, 1, "test\ntest\n"), v:true)
+ \ g:test.create_change(1, 1, 2, 1, "test\ntest\n"), {'should_save': 1})
AssertEqual '}', getline('.')
AssertEqual [4, 1], getpos('.')[1:2]
@@ -319,7 +337,7 @@ Execute(Cursor line and column will move when change on lines above and just bef
call setpos('.', [0, 2, 2, 0])
AssertEqual ' value: string', getline('.')
call ale#code_action#HandleCodeAction(
- \ g:test.create_change(1, 1, 2, 1, "test\ntest\n123"), v:true)
+ \ g:test.create_change(1, 1, 2, 1, "test\ntest\n123"), {'should_save': 1})
AssertEqual '123 value: string', getline('.')
AssertEqual [3, 5], getpos('.')[1:2]
@@ -331,7 +349,7 @@ Execute(Cursor line and column will move at the end of changes):
call setpos('.', [0, 2, 10, 0])
AssertEqual ' value: string', getline('.')
call ale#code_action#HandleCodeAction(
- \ g:test.create_change(1, 1, 3, 1, "test\n"), v:true)
+ \ g:test.create_change(1, 1, 3, 1, "test\n"), {'should_save': 1})
AssertEqual '}', getline('.')
AssertEqual [2, 1], getpos('.')[1:2]
@@ -342,14 +360,14 @@ Execute(Cursor will not move when changes happening on lines >= cursor, but afte
call setpos('.', [0, 2, 3, 0])
AssertEqual ' value: string', getline('.')
call ale#code_action#HandleCodeAction(
- \ g:test.create_change(2, 10, 3, 1, "number\n"), v:true)
+ \ g:test.create_change(2, 10, 3, 1, "number\n"), {'should_save': 1})
AssertEqual ' value: number', getline('.')
AssertEqual [2, 3], getpos('.')[1:2]
Execute(It should just modify file when should_save is set to v:false):
call WriteFileAndEdit()
let g:test.change = g:test.create_change(1, 1, 1, 1, "import { writeFile } from 'fs';\n")
- call ale#code_action#HandleCodeAction(g:test.change, v:false)
+ call ale#code_action#HandleCodeAction(g:test.change, {})
AssertEqual 1, getbufvar(bufnr(''), '&modified')
AssertEqual [
\ 'import { writeFile } from ''fs'';',
diff --git a/test/test_code_action_python.vader b/test/test_code_action_python.vader
new file mode 100644
index 00000000..fd30633d
--- /dev/null
+++ b/test/test_code_action_python.vader
@@ -0,0 +1,59 @@
+Given python(An example Python file):
+ def main():
+ a = 1
+ c = a + 1
+
+Execute():
+ let g:changes = [
+ \ {'end': {'offset': 7, 'line': 1}, 'newText': 'func_qtffgsv', 'start': {'offset': 5, 'line': 1}},
+ \ {'end': {'offset': 9, 'line': 1}, 'newText': '', 'start': {'offset': 8, 'line': 1}},
+ \ {'end': {'offset': 15, 'line': 3}, 'newText': " return c\n\n\ndef main():\n c = func_qtffgsvi()\n", 'start': {'offset': 15, 'line': 3}}
+ \]
+
+ call ale#code_action#ApplyChanges(expand('%:p'), g:changes, 0)
+
+Expect(The changes should be applied correctly):
+ def func_qtffgsvi():
+ a = 1
+ c = a + 1
+ return c
+
+
+ def main():
+ c = func_qtffgsvi()
+
+
+Given python(Second python example):
+ import sys
+ import exifread
+
+ def main():
+ with open(sys.argv[1], 'rb') as f:
+ exif = exifread.process_file(f)
+ dt = str(exif['Image DateTime'])
+ date = dt[:10].replace(':', '-')
+
+Execute():
+ let g:changes = [
+ \ {'end': {'offset': 16, 'line': 2}, 'newText': "\n\ndef func_ivlpdpao(f):\n exif = exifread.process_file(f)\n dt = str(exif['Image DateTime'])\n date = dt[:10].replace(':', '-')\n return date\n", 'start': {'offset': 16, 'line': 2}},
+ \ {'end': {'offset': 32, 'line': 6}, 'newText': 'date = func', 'start': {'offset': 9, 'line': 6}},
+ \ {'end': {'offset': 42, 'line': 8}, 'newText': "ivlpdpao(f)\n", 'start': {'offset': 33, 'line': 6}}
+ \]
+
+ call ale#code_action#ApplyChanges(expand('%:p'), g:changes, 0)
+
+Expect(The changes should be applied correctly):
+ import sys
+ import exifread
+
+
+ def func_ivlpdpao(f):
+ exif = exifread.process_file(f)
+ dt = str(exif['Image DateTime'])
+ date = dt[:10].replace(':', '-')
+ return date
+
+
+ def main():
+ with open(sys.argv[1], 'rb') as f:
+ date = func_ivlpdpao(f)
diff --git a/test/test_codefix.vader b/test/test_codefix.vader
new file mode 100644
index 00000000..fc5470aa
--- /dev/null
+++ b/test/test_codefix.vader
@@ -0,0 +1,549 @@
+Before:
+ call ale#test#SetDirectory('/testplugin/test')
+ call ale#test#SetFilename('dummy.txt')
+ Save g:ale_buffer_info
+
+ let g:ale_buffer_info = {}
+
+ let g:old_filename = expand('%:p')
+ let g:Callback = ''
+ let g:expr_list = []
+ let g:message_list = []
+ let g:handle_code_action_called = 0
+ let g:code_actions = []
+ let g:options = {}
+ let g:capability_checked = ''
+ let g:conn_id = v:null
+ let g:InitCallback = v:null
+
+ runtime autoload/ale/lsp_linter.vim
+ runtime autoload/ale/lsp.vim
+ runtime autoload/ale/util.vim
+ runtime autoload/ale/codefix.vim
+ runtime autoload/ale/code_action.vim
+
+ function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort
+ let g:conn_id = ale#lsp#Register('executable', '/foo/bar', {})
+ call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer)
+
+ if a:linter.lsp is# 'tsserver'
+ call ale#lsp#MarkConnectionAsTsserver(g:conn_id)
+ endif
+
+ let l:details = {
+ \ 'command': 'foobar',
+ \ 'buffer': a:buffer,
+ \ 'connection_id': g:conn_id,
+ \ 'project_root': '/foo/bar',
+ \}
+
+ let g:InitCallback = {-> ale#lsp_linter#OnInit(a:linter, l:details, a:Callback)}
+ endfunction
+
+ function! ale#lsp#HasCapability(conn_id, capability) abort
+ let g:capability_checked = a:capability
+
+ return 1
+ endfunction
+
+ function! ale#lsp#RegisterCallback(conn_id, callback) abort
+ let g:Callback = a:callback
+ endfunction
+
+ function! ale#lsp#Send(conn_id, message) abort
+ call add(g:message_list, a:message)
+
+ return 42
+ endfunction
+
+ function! ale#util#Execute(expr) abort
+ call add(g:expr_list, a:expr)
+ endfunction
+
+ function! ale#code_action#HandleCodeAction(code_action, options) abort
+ let g:handle_code_action_called = 1
+ Assert !get(a:options, 'should_save')
+ call add(g:code_actions, a:code_action)
+ endfunction
+
+ function! ale#util#Input(message, value) abort
+ return '2'
+ endfunction
+
+After:
+ Restore
+
+ if g:conn_id isnot v:null
+ call ale#lsp#RemoveConnectionWithID(g:conn_id)
+ endif
+
+ call ale#test#RestoreDirectory()
+ call ale#linter#Reset()
+
+ unlet! g:capability_checked
+ unlet! g:InitCallback
+ unlet! g:old_filename
+ unlet! g:conn_id
+ unlet! g:Callback
+ unlet! g:message_list
+ unlet! g:expr_list
+ unlet! b:ale_linters
+ unlet! g:options
+ unlet! g:code_actions
+ unlet! g:handle_code_action_called
+
+ runtime autoload/ale/lsp_linter.vim
+ runtime autoload/ale/lsp.vim
+ runtime autoload/ale/util.vim
+ runtime autoload/ale/codefix.vim
+ runtime autoload/ale/code_action.vim
+
+Execute(Failed codefix responses should be handled correctly):
+ call ale#codefix#HandleTSServerResponse(
+ \ 1,
+ \ {'command': 'getCodeFixes', 'request_seq': 3}
+ \)
+ AssertEqual g:handle_code_action_called, 0
+
+Given typescript(Some typescript file):
+ foo
+ somelongerline ()
+ bazxyzxyzxyz
+
+Execute(getCodeFixes from tsserver should be handled):
+ call ale#codefix#SetMap({3: {}})
+ call ale#codefix#HandleTSServerResponse(1, {
+ \ 'command': 'getCodeFixes',
+ \ 'request_seq': 3,
+ \ 'success': v:true,
+ \ 'type': 'response',
+ \ 'body': [
+ \ {
+ \ 'description': 'Import default "x" from module "./z"',
+ \ 'fixName': 'import',
+ \ 'changes': [
+ \ {
+ \ 'fileName': "/foo/bar/file1.ts",
+ \ 'textChanges': [
+ \ {
+ \ 'end': {
+ \ 'line': 2,
+ \ 'offset': 1,
+ \ },
+ \ 'newText': 'import x from "./z";^@',
+ \ 'start': {
+ \ 'line': 2,
+ \ 'offset': 1,
+ \ }
+ \ }
+ \ ]
+ \ }
+ \ ]
+ \ }
+ \ ]
+ \})
+
+ AssertEqual g:handle_code_action_called, 1
+ AssertEqual
+ \ [
+ \ {
+ \ 'description': 'codefix',
+ \ 'changes': [
+ \ {
+ \ 'fileName': "/foo/bar/file1.ts",
+ \ 'textChanges': [
+ \ {
+ \ 'end': {
+ \ 'line': 2,
+ \ 'offset': 1
+ \ },
+ \ 'newText': 'import x from "./z";^@',
+ \ 'start': {
+ \ 'line': 2,
+ \ 'offset': 1
+ \ }
+ \ }
+ \ ]
+ \ }
+ \ ]
+ \ }
+ \ ],
+ \ g:code_actions
+
+Execute(getCodeFixes from tsserver should be handled with user input if there are more than one action):
+ call ale#codefix#SetMap({3: {}})
+ call ale#codefix#HandleTSServerResponse(1, {
+ \ 'command': 'getCodeFixes',
+ \ 'request_seq': 3,
+ \ 'success': v:true,
+ \ 'type': 'response',
+ \ 'body': [
+ \ {
+ \ 'description': 'Import default "x" from module "./z"',
+ \ 'fixName': 'import',
+ \ 'changes': [
+ \ {
+ \ 'fileName': "/foo/bar/file1.ts",
+ \ 'textChanges': [
+ \ {
+ \ 'end': {
+ \ 'line': 2,
+ \ 'offset': 1,
+ \ },
+ \ 'newText': 'import x from "./z";^@',
+ \ 'start': {
+ \ 'line': 2,
+ \ 'offset': 1,
+ \ }
+ \ }
+ \ ]
+ \ }
+ \ ]
+ \ },
+ \ {
+ \ 'description': 'Import default "x" from module "./y"',
+ \ 'fixName': 'import',
+ \ 'changes': [
+ \ {
+ \ 'fileName': "/foo/bar/file1.ts",
+ \ 'textChanges': [
+ \ {
+ \ 'end': {
+ \ 'line': 2,
+ \ 'offset': 1,
+ \ },
+ \ 'newText': 'import x from "./y";^@',
+ \ 'start': {
+ \ 'line': 2,
+ \ 'offset': 1,
+ \ }
+ \ }
+ \ ]
+ \ }
+ \ ]
+ \ }
+ \ ]
+ \})
+
+ AssertEqual g:handle_code_action_called, 1
+ AssertEqual
+ \ [
+ \ {
+ \ 'description': 'codefix',
+ \ 'changes': [
+ \ {
+ \ 'fileName': "/foo/bar/file1.ts",
+ \ 'textChanges': [
+ \ {
+ \ 'end': {
+ \ 'line': 2,
+ \ 'offset': 1
+ \ },
+ \ 'newText': 'import x from "./y";^@',
+ \ 'start': {
+ \ 'line': 2,
+ \ 'offset': 1
+ \ }
+ \ }
+ \ ]
+ \ }
+ \ ]
+ \ }
+ \ ],
+ \ g:code_actions
+
+Execute(Prints a tsserver error message when getCodeFixes unsuccessful):
+ call ale#codefix#SetMap({3: {}})
+ call ale#codefix#HandleTSServerResponse(1, {
+ \ 'command': 'getCodeFixes',
+ \ 'request_seq': 3,
+ \ 'success': v:false,
+ \ 'message': 'something is wrong',
+ \})
+
+ AssertEqual g:handle_code_action_called, 0
+ AssertEqual ['echom ''Error while getting code fixes. Reason: something is wrong'''], g:expr_list
+
+Execute(Does nothing when where are no code fixes):
+ call ale#codefix#SetMap({3: {}})
+ call ale#codefix#HandleTSServerResponse(1, {
+ \ 'command': 'getCodeFixes',
+ \ 'request_seq': 3,
+ \ 'success': v:true,
+ \ 'body': []
+ \})
+
+ AssertEqual g:handle_code_action_called, 0
+ AssertEqual ['echom ''No code fixes available.'''], g:expr_list
+
+Execute(tsserver codefix requests should be sent):
+ call ale#linter#Reset()
+
+ runtime ale_linters/typescript/tsserver.vim
+ let g:ale_buffer_info = {bufnr(''): {'loclist': [{'lnum': 2, 'col': 5, 'code': 2304}]}}
+ call setpos('.', [bufnr(''), 2, 16, 0])
+
+ " ALECodeAction
+ call ale#codefix#Execute(0)
+
+ " We shouldn't register the callback yet.
+ AssertEqual '''''', string(g:Callback)
+
+ AssertEqual type(function('type')), type(g:InitCallback)
+ call g:InitCallback()
+
+ AssertEqual 'code_actions', g:capability_checked
+ AssertEqual
+ \ 'function(''ale#codefix#HandleTSServerResponse'')',
+ \ string(g:Callback)
+ AssertEqual
+ \ [
+ \ ale#lsp#tsserver_message#Change(bufnr('')),
+ \ [0, 'ts@getCodeFixes', {
+ \ 'startLine': 2,
+ \ 'startOffset': 16,
+ \ 'endLine': 2,
+ \ 'endOffset': 17,
+ \ 'file': expand('%:p'),
+ \ 'errorCodes': [2304],
+ \ }]
+ \ ],
+ \ g:message_list
+
+Execute(tsserver codefix requests should be sent only for error with code):
+ call ale#linter#Reset()
+
+ runtime ale_linters/typescript/tsserver.vim
+ let g:ale_buffer_info = {bufnr(''): {'loclist': [{'lnum': 2, 'col': 16}, {'lnum': 2, 'col': 16, 'code': 2304}]}}
+ call setpos('.', [bufnr(''), 2, 16, 0])
+
+ " ALECodeAction
+ call ale#codefix#Execute(0)
+
+ " We shouldn't register the callback yet.
+ AssertEqual '''''', string(g:Callback)
+
+ AssertEqual type(function('type')), type(g:InitCallback)
+ call g:InitCallback()
+
+ AssertEqual 'code_actions', g:capability_checked
+ AssertEqual
+ \ 'function(''ale#codefix#HandleTSServerResponse'')',
+ \ string(g:Callback)
+ AssertEqual
+ \ [
+ \ ale#lsp#tsserver_message#Change(bufnr('')),
+ \ [0, 'ts@getCodeFixes', {
+ \ 'startLine': 2,
+ \ 'startOffset': 16,
+ \ 'endLine': 2,
+ \ 'endOffset': 17,
+ \ 'file': expand('%:p'),
+ \ 'errorCodes': [2304],
+ \ }]
+ \ ],
+ \ g:message_list
+
+Execute(getApplicableRefactors from tsserver should be handled):
+ call ale#codefix#SetMap({3: {
+ \ 'buffer': expand('%:p'),
+ \ 'line': 1,
+ \ 'column': 2,
+ \ 'end_line': 3,
+ \ 'end_column': 4,
+ \ 'connection_id': 0,
+ \}})
+ call ale#codefix#HandleTSServerResponse(1,
+ \ {'seq': 0, 'request_seq': 3, 'type': 'response', 'success': v:true, 'body': [{'actions': [{'description': 'Extract to constant in enclosing scope', 'name': 'constant_scope_0'}], 'description': 'Extract constant', 'name': 'Extract Symbol'}, {'actions': [{'description': 'Extract to function in module scope', 'name': 'function_scope_1'}], 'description': 'Extract function', 'name': 'Extract Symbol'}], 'command': 'getApplicableRefactors'})
+
+ AssertEqual
+ \ [
+ \ [0, 'ts@getEditsForRefactor', {
+ \ 'startLine': 1,
+ \ 'startOffset': 2,
+ \ 'endLine': 3,
+ \ 'endOffset': 5,
+ \ 'file': expand('%:p'),
+ \ 'refactor': 'Extract Symbol',
+ \ 'action': 'function_scope_1',
+ \ }]
+ \ ],
+ \ g:message_list
+
+Execute(getApplicableRefactors should print error on failure):
+ call ale#codefix#SetMap({3: {
+ \ 'buffer': expand('%:p'),
+ \ 'line': 1,
+ \ 'column': 2,
+ \ 'end_line': 3,
+ \ 'end_column': 4,
+ \ 'connection_id': 0,
+ \}})
+ call ale#codefix#HandleTSServerResponse(1,
+ \ {'seq': 0, 'request_seq': 3, 'type': 'response', 'success': v:false, 'message': 'oops', 'command': 'getApplicableRefactors'})
+
+ AssertEqual ['echom ''Error while getting applicable refactors. Reason: oops'''], g:expr_list
+
+Execute(getApplicableRefactors should do nothing if there are no refactors):
+ call ale#codefix#SetMap({3: {
+ \ 'buffer': expand('%:p'),
+ \ 'line': 1,
+ \ 'column': 2,
+ \ 'end_line': 3,
+ \ 'end_column': 4,
+ \ 'connection_id': 0,
+ \}})
+ call ale#codefix#HandleTSServerResponse(1,
+ \ {'seq': 0, 'request_seq': 3, 'type': 'response', 'success': v:true, 'body': [], 'command': 'getApplicableRefactors'})
+
+ AssertEqual ['echom ''No applicable refactors available.'''], g:expr_list
+
+Execute(getEditsForRefactor from tsserver should be handled):
+ call ale#codefix#SetMap({3: {}})
+ call ale#codefix#HandleTSServerResponse(1,
+ \{'seq': 0, 'request_seq': 3, 'type': 'response', 'success': v:true, 'body': {'edits': [{'fileName': '/foo/bar/file.ts', 'textChanges': [{'end': {'offset': 35, 'line': 9}, 'newText': 'newFunction(app);', 'start': {'offset': 3, 'line': 8}}, {'end': {'offset': 4, 'line': 19}, 'newText': '^@function newFunction(app: Router) {^@ app.use(booExpressCsrf());^@ app.use(booExpressRequireHttps);^@}^@', 'start': {'offset': 4, 'line': 19}}]}], 'renameLocation': {'offset': 3, 'line': 8}, 'renameFilename': '/foo/bar/file.ts'}, 'command': 'getEditsForRefactor' }
+ \)
+
+ AssertEqual g:handle_code_action_called, 1
+ AssertEqual
+ \ [
+ \ {
+ \ 'description': 'editsForRefactor',
+ \ 'changes': [{'fileName': '/foo/bar/file.ts', 'textChanges': [{'end': {'offset': 35, 'line': 9}, 'newText': 'newFunction(app);', 'start': {'offset': 3, 'line': 8}}, {'end': {'offset': 4, 'line': 19}, 'newText': '^@function newFunction(app: Router) {^@ app.use(booExpressCsrf());^@ app.use(booExpressRequireHttps);^@}^@', 'start': {'offset': 4, 'line': 19}}]}],
+ \ }
+ \ ],
+ \ g:code_actions
+
+Execute(getEditsForRefactor should print error on failure):
+ call ale#codefix#SetMap({3: {}})
+ call ale#codefix#HandleTSServerResponse(1,
+ \{'seq': 0, 'request_seq': 3, 'type': 'response', 'success': v:false, 'message': 'oops', 'command': 'getEditsForRefactor' }
+ \)
+
+ AssertEqual ['echom ''Error while getting edits for refactor. Reason: oops'''], g:expr_list
+
+Execute(Failed LSP responses should be handled correctly):
+ call ale#codefix#HandleLSPResponse(
+ \ 1,
+ \ {'method': 'workspace/applyEdit', 'request_seq': 3}
+ \)
+ AssertEqual g:handle_code_action_called, 0
+
+Given python(Some python file):
+ def main():
+ a = 1
+ b = a + 2
+
+Execute("workspace/applyEdit" from LSP should be handled):
+ call ale#codefix#SetMap({3: {}})
+ call ale#codefix#HandleLSPResponse(1,
+ \ {'id': 0, 'jsonrpc': '2.0', 'method': 'workspace/applyEdit', 'params': {'edit': {'changes': {'file:///foo/bar/file.ts': [{'range': {'end': {'character': 27, 'line': 7}, 'start': {'character': 27, 'line': 7}}, 'newText': ', Config'}, {'range': {'end': {'character': 12, 'line': 96}, 'start': {'character': 2, 'line': 94}}, 'newText': 'await newFunction(redis, imageKey, cover, config);'}, {'range': {'end': {'character': 2, 'line': 99}, 'start': {'character': 2, 'line': 99}}, 'newText': '^@async function newFunction(redis: IRedis, imageKey: string, cover: Buffer, config: Config) {^@ try {^@ await redis.set(imageKey, cover, ''ex'', parseInt(config.coverKeyTTL, 10));^@ }^@ catch { }^@}^@'}]}}}})
+
+ AssertEqual g:handle_code_action_called, 1
+ AssertEqual
+ \ [{'description': 'applyEdit', 'changes': [{'fileName': '/foo/bar/file.ts', 'textChanges': [{'end': {'offset': 28, 'line': 8}, 'newText': ', Config', 'start': {'offset': 28, 'line': 8}}, {'end': {'offset': 13, 'line': 97}, 'newText': 'await newFunction(redis, imageKey, cover, config);', 'start': {'offset': 3, 'line': 95}}, {'end': {'offset': 3, 'line': 100}, 'newText': '^@async function newFunction(redis: IRedis, imageKey: string, cover: Buffer, config: Config) {^@ try {^@ await redis.set(imageKey, cover, ''ex'', parseInt(config.coverKeyTTL, 10));^@ }^@ catch { }^@}^@', 'start': {'offset': 3, 'line': 100}}]}]}],
+ \ g:code_actions
+
+Execute(Code Actions from LSP should be handled with user input if there are more than one action):
+ call ale#codefix#SetMap({2: {}})
+ call ale#codefix#HandleLSPResponse(1,
+ \ {'id': 2, 'jsonrpc': '2.0', 'result': [{'title': 'fake for testing'}, {'arguments': [{'documentChanges': [{'edits': [{'range': {'end': {'character': 31, 'line': 2}, 'start': {'character': 31, 'line': 2}}, 'newText': ', createVideo'}], 'textDocument': {'uri': 'file:///foo/bar/file.ts', 'version': 1}}]}], 'title': 'Add ''createVideo'' to existing import declaration from "./video"', 'command': '_typescript.applyWorkspaceEdit'}]})
+
+ AssertEqual g:handle_code_action_called, 1
+ AssertEqual
+ \ [{'description': 'codeaction', 'changes': [{'fileName': '/foo/bar/file.ts', 'textChanges': [{'end': {'offset': 32, 'line': 3}, 'newText': ', createVideo', 'start': {'offset': 32, 'line': 3}}]}]}],
+ \ g:code_actions
+
+Execute(Code Actions from LSP should be handled when returned with documentChanges):
+ call ale#codefix#SetMap({2: {}})
+ call ale#codefix#HandleLSPResponse(1,
+ \ {'id': 2, 'jsonrpc': '2.0', 'result': [{'diagnostics': v:null, 'edit': {'changes': v:null, 'documentChanges': [{'edits': [{'range': {'end': {'character': 4, 'line': 2}, 'start': {'character': 4, 'line': 1}}, 'newText': ''}, {'range': {'end': {'character': 9, 'line': 2}, 'start': {'character': 8, 'line': 2}}, 'newText': '(1)'}], 'textDocument': {'uri': 'file:///foo/bar/test.py', 'version': v:null}}]}, 'kind': 'refactor.inline', 'title': 'Inline variable', 'command': v:null}, {'diagnostics': v:null, 'edit': {'changes': v:null, 'documentChanges': [{'edits': [{'range': {'end': {'character': 0, 'line': 0}, 'start': {'character': 0, 'line': 0}}, 'newText': 'def func_bomdjnxh():^@ a = 1return a^@^@^@'}, {'range': {'end': {'character': 9, 'line': 1}, 'start': {'character': 8, 'line': 1}}, 'newText': 'func_bomdjnxh()^@'}], 'textDocument': {'uri': 'file:///foo/bar/test.py', 'version': v:null}}]}, 'kind': 'refactor.extract', 'title': 'Extract expression into function ''func_bomdjnxh''', 'command': v:null}]})
+
+ AssertEqual g:handle_code_action_called, 1
+ AssertEqual
+ \ [{'description': 'codeaction', 'changes': [{'fileName': '/foo/bar/test.py', 'textChanges': [{'end': {'offset': 1, 'line': 1}, 'newText': 'def func_bomdjnxh():^@ a = 1return a^@^@^@', 'start': {'offset': 1, 'line': 1}}, {'end': {'offset': 10, 'line': 2}, 'newText': 'func_bomdjnxh()^@', 'start': {'offset': 9, 'line': 2}}]}]}],
+ \ g:code_actions
+
+Execute(LSP Code Actions handles command responses):
+ call ale#codefix#SetMap({3: {
+ \ 'connection_id': 0,
+ \}})
+ call ale#codefix#HandleLSPResponse(1,
+ \ {'id': 3, 'jsonrpc': '2.0', 'result': [{'kind': 'refactor', 'title': 'Extract to inner function in function ''getVideo''', 'command': {'arguments': [{'file': '/foo/bar/file.ts', 'endOffset': 0, 'action': 'function_scope_0', 'startOffset': 1, 'startLine': 65, 'refactor': 'Extract Symbol', 'endLine': 68}], 'title': 'Extract to inner function in function ''getVideo''', 'command': '_typescript.applyRefactoring'}}, {'kind': 'refactor', 'title': 'Extract to function in module scope', 'command': {'arguments': [{'file': '/foo/bar/file.ts', 'endOffset': 0, 'action': 'function_scope_1', 'startOffset': 1, 'startLine': 65, 'refactor': 'Extract Symbol', 'endLine': 68}], 'title': 'Extract to function in module scope', 'command': '_typescript.applyRefactoring'}}]})
+
+ AssertEqual
+ \ [[0, 'workspace/executeCommand', {'arguments': [{'file': '/foo/bar/file.ts', 'action': 'function_scope_1', 'endOffset': 0, 'refactor': 'Extract Symbol', 'endLine': 68, 'startLine': 65, 'startOffset': 1}], 'command': '_typescript.applyRefactoring'}]],
+ \ g:message_list
+
+
+Execute(Prints message when LSP code action returns no results):
+ call ale#codefix#SetMap({3: {}})
+ call ale#codefix#HandleLSPResponse(1,
+ \ {'id': 3, 'jsonrpc': '2.0', 'result': []})
+
+ AssertEqual g:handle_code_action_called, 0
+ AssertEqual ['echom ''No code actions received from server'''], g:expr_list
+
+Execute(LSP code action requests should be sent):
+ call ale#linter#Reset()
+
+ runtime ale_linters/python/jedils.vim
+ let g:ale_buffer_info = {bufnr(''): {'loclist': [{'lnum': 2, 'col': 5, 'end_lnum': 2, 'end_col': 6, 'code': 2304, 'text': 'oops'}]}}
+ call setpos('.', [bufnr(''), 2, 5, 0])
+
+ " ALECodeAction
+ call ale#codefix#Execute(0)
+
+ " We shouldn't register the callback yet.
+ AssertEqual '''''', string(g:Callback)
+
+ AssertEqual type(function('type')), type(g:InitCallback)
+ call g:InitCallback()
+
+ AssertEqual 'code_actions', g:capability_checked
+ AssertEqual
+ \ 'function(''ale#codefix#HandleLSPResponse'')',
+ \ string(g:Callback)
+ AssertEqual
+ \ [
+ \ [0, 'textDocument/codeAction', {
+ \ 'context': {
+ \ 'diagnostics': [{'range': {'end': {'character': 6, 'line': 1}, 'start': {'character': 4, 'line': 1}}, 'code': 2304, 'message': 'oops'}]
+ \ },
+ \ 'range': {'end': {'character': 5, 'line': 1}, 'start': {'character': 4, 'line': 1}},
+ \ 'textDocument': {'uri': ale#path#ToURI(expand('%:p'))}
+ \ }]
+ \ ],
+ \ g:message_list[-1:]
+
+Execute(LSP code action requests should be sent only for error with code):
+ call ale#linter#Reset()
+
+ runtime ale_linters/python/jedils.vim
+ let g:ale_buffer_info = {bufnr(''): {'loclist': [{'lnum': 2, 'col': 5, 'end_lnum': 2, 'end_col': 6, 'code': 2304, 'text': 'oops'}]}}
+ call setpos('.', [bufnr(''), 2, 5, 0])
+
+ " ALECodeAction
+ call ale#codefix#Execute(0)
+
+ " We shouldn't register the callback yet.
+ AssertEqual '''''', string(g:Callback)
+
+ AssertEqual type(function('type')), type(g:InitCallback)
+ call g:InitCallback()
+
+ AssertEqual 'code_actions', g:capability_checked
+ AssertEqual
+ \ 'function(''ale#codefix#HandleLSPResponse'')',
+ \ string(g:Callback)
+ AssertEqual
+ \ [
+ \ [0, 'textDocument/codeAction', {
+ \ 'context': {
+ \ 'diagnostics': [{'range': {'end': {'character': 6, 'line': 1}, 'start': {'character': 4, 'line': 1}}, 'code': 2304, 'message': 'oops'}]
+ \ },
+ \ 'range': {'end': {'character': 5, 'line': 1}, 'start': {'character': 4, 'line': 1}},
+ \ 'textDocument': {'uri': ale#path#ToURI(expand('%:p'))}
+ \ }]
+ \ ],
+ \ g:message_list[-1:]
diff --git a/test/test_hover.vader b/test/test_hover.vader
index 9689cda2..ed756396 100644
--- a/test/test_hover.vader
+++ b/test/test_hover.vader
@@ -101,7 +101,7 @@ Execute(tsserver quickinfo responses will null missing bodies should be handled)
AssertEqual {}, ale#hover#GetMap()
Execute(tsserver quickinfo displayString values should be displayed):
- call ale#hover#SetMap({3: {}})
+ call ale#hover#SetMap({3: {'buffer': bufnr('')}})
call ale#hover#HandleTSServerResponse(
\ 1,
\ {
@@ -169,7 +169,7 @@ Execute(LSP hover response with lists of strings and marked strings should be ha
AssertEqual {}, ale#hover#GetMap()
Execute(tsserver responses for documentation requests should be handled):
- call ale#hover#SetMap({3: {'show_documentation': 1}})
+ call ale#hover#SetMap({3: {'show_documentation': 1, 'buffer': bufnr('')}})
call ale#hover#HandleTSServerResponse(
\ 1,
diff --git a/test/test_lint_on_enter_when_file_changed.vader b/test/test_lint_on_enter_when_file_changed.vader
index 88493005..0d4c4af8 100644
--- a/test/test_lint_on_enter_when_file_changed.vader
+++ b/test/test_lint_on_enter_when_file_changed.vader
@@ -35,7 +35,7 @@ After:
Execute(The file changed event function should set b:ale_file_changed):
let g:ale_lint_on_enter = 0
- if has('gui')
+ if has('gui_running')
new
else
e test
diff --git a/test/test_organize_imports.vader b/test/test_organize_imports.vader
index c51ff1c0..35cd99ff 100644
--- a/test/test_organize_imports.vader
+++ b/test/test_organize_imports.vader
@@ -57,9 +57,9 @@ Before:
call add(g:expr_list, a:expr)
endfunction
- function! ale#code_action#HandleCodeAction(code_action, should_save) abort
+ function! ale#code_action#HandleCodeAction(code_action, options) abort
let g:handle_code_action_called = 1
- AssertEqual v:false, a:should_save
+ Assert !get(a:options, 'should_save')
call add(g:code_actions, a:code_action)
endfunction
diff --git a/test/test_rename.vader b/test/test_rename.vader
index 34d9e32e..5bc655f4 100644
--- a/test/test_rename.vader
+++ b/test/test_rename.vader
@@ -57,9 +57,9 @@ Before:
call add(g:expr_list, a:expr)
endfunction
- function! ale#code_action#HandleCodeAction(code_action, should_save) abort
+ function! ale#code_action#HandleCodeAction(code_action, options) abort
let g:handle_code_action_called = 1
- AssertEqual v:true, a:should_save
+ Assert get(a:options, 'should_save')
call add(g:code_actions, a:code_action)
endfunction
diff --git a/test/test_shell_detection.vader b/test/test_shell_detection.vader
index 697054d0..11d801c3 100644
--- a/test/test_shell_detection.vader
+++ b/test/test_shell_detection.vader
@@ -127,3 +127,51 @@ Execute(The dash dialect should be used for the shell and the base function):
Execute(dash should be used for shellcheck):
AssertEqual 'dash', ale#handlers#shellcheck#GetDialectArgument(bufnr(''))
+
+Given(A file with a Bash shellcheck shell directive):
+ # shellcheck shell=bash
+
+Execute(bash dialect should be detected appropriately):
+ AssertEqual 'bash', ale#handlers#shellcheck#GetDialectArgument(bufnr(''))
+
+Given(A file with a sh shellcheck shell directive):
+ #shellcheck shell=sh
+
+Execute(sh dialect should be detected appropriately):
+ AssertEqual 'sh', ale#handlers#shellcheck#GetDialectArgument(bufnr(''))
+
+Given(A file with a tcsh shellcheck shell directive):
+ # shellcheck shell=tcsh
+
+Execute(tcsh dialect should be detected appropriately):
+ AssertEqual 'tcsh', ale#handlers#shellcheck#GetDialectArgument(bufnr(''))
+
+Given(A file with a zsh shellcheck shell directive):
+ # shellcheck shell=zsh
+
+Execute(zsh dialect should be detected appropriately):
+ AssertEqual 'zsh', ale#handlers#shellcheck#GetDialectArgument(bufnr(''))
+
+Given(A file with a csh shellcheck shell directive):
+ # shellcheck shell=csh
+
+Execute(zsh dialect should be detected appropriately):
+ AssertEqual 'csh', ale#handlers#shellcheck#GetDialectArgument(bufnr(''))
+
+Given(A file with a ksh shellcheck shell directive):
+ # shellcheck shell=ksh
+
+Execute(ksh dialect should be detected appropriately):
+ AssertEqual 'ksh', ale#handlers#shellcheck#GetDialectArgument(bufnr(''))
+
+Given(A file with a dash shellcheck shell directive):
+ # shellcheck shell=dash
+
+Execute(dash dialect should be detected appropriately):
+ AssertEqual 'dash', ale#handlers#shellcheck#GetDialectArgument(bufnr(''))
+
+Given(A file with a ash shellcheck shell directive):
+ # shellcheck shell=ash
+
+Execute(dash dialect should be detected for ash that shellcheck does not support):
+ AssertEqual 'dash', ale#handlers#shellcheck#GetDialectArgument(bufnr(''))