Merge remote-tracking branch 'origin/master' into fix-swipl

* origin/master: (40 commits)
  fix: correct suggested filetype for yamlfix
  feat: add yamlfix fixer
  Use _config for LSP config options
  Add support for R languageserver (#3370)
  Fix 3103 - add shellcheck shell directive detection. (#3216)
  Added the Vundle command in installation instructions (#3400)
  Adds support for Tlint - A Tighten Opinionated PHP Linter (#3291)
  Add php phpcbf options (#3383)
  Use has('gui_running') instead of has('gui')
  Close #2727 - Add a hover-only setting for balloons
  Fix #3332 - Modify everything for rename/actions
  Add a missing blank line in documentation
  Add luafmt fixer (#3289)
  #3442 Fix code fix clangd issue
  Close #1466 - Add GVIM refactor menu support
  Look for node packages in .yarn/sdks as well
  Update documentation for code actions and rename
  cmp forwards, and reverse the code actions
  Support for LSP/tsserver Code Actions (#3437)
  Move the test for buffer-local variables
  ...
This commit is contained in:
D. Ben Knoble 2020-11-30 13:42:21 -05:00
commit 7e12be0c64
73 changed files with 2579 additions and 224 deletions

View file

@ -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.
<a name="usage-refactoring"></a>
### 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.
<a name="installation"></a>
## 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.
<a name="installation-with-vim-plug"></a>
@ -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.
<a name="contributing"></a>
## 4. Contributing

View file

@ -0,0 +1,39 @@
" Author: Dmitri Vereshchagin <dmitri.vereshchagin@gmail.com>
" 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,
\})

View file

@ -0,0 +1,32 @@
" Author: Eric Stern <eric@ericstern.com>,
" Arnold Chand <creativenull@outlook.com>
" 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'),
\})

View file

@ -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)

80
ale_linters/php/tlint.vim Normal file
View file

@ -0,0 +1,80 @@
" Author: Jose Soto <jose@tighten.co>
"
" 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'),
\})

View file

@ -0,0 +1,34 @@
" Author: Dalius Dobravolskas <dalius.dobravolskas@gmail.com>
" 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',
\})

View file

@ -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')
\})

View file

@ -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',

View file

@ -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
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(
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

View file

@ -1,25 +1,28 @@
" Author: Jerko Steiner <jerko.steiner@gmail.com>
" 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: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:])
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 <buffer=%d>'
\ . ' 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 <silent> PopUp.&Refactor\.\.\..%s'
\ . ' :call %s(%s, %s)<CR>',
\ 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('<cword>'))
silent! anoremenu <silent> PopUp.Rename :ALERename<CR>
endif
silent! anoremenu <silent> PopUp.Refactor\.\.\..(None) :silent<CR>
call ale#codefix#Execute(
\ mode() is# 'v' || mode() is# "\<C-V>",
\ 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

484
autoload/ale/codefix.vim Normal file
View file

@ -0,0 +1,484 @@
" Author: Dalius Dobravolskas <dalius.dobravolskas@gmail.com>
" 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 <Enter> (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 <Enter> (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 <Enter> (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('<cword>')
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# "\<C-V>"
" 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

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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') ? '\' : '/'

View file

@ -1,18 +1,28 @@
" Author: w0rp <devw0rp@gmail.com>
" 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

View file

@ -1,8 +1,32 @@
" Author: w0rp <devw0rp@gmail.com>
" 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#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

View file

@ -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])

View file

@ -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

View file

@ -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

View file

@ -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) .

View file

@ -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

View file

@ -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({
call ale#code_action#HandleCodeAction(
\ {
\ 'description': 'Organize Imports',
\ 'changes': l:file_code_edits,
\}, v:false)
\ },
\ {}
\)
endfunction
function! s:OnReady(linter, lsp_details) abort

View file

@ -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({
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
\ },
\ {
\ '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({
call ale#code_action#HandleCodeAction(
\ {
\ 'description': 'rename',
\ 'changes': l:changes,
\}, v:true)
\ },
\ {
\ '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

View file

@ -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('$')

View file

@ -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*

View file

@ -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:

View file

@ -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:

View file

@ -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:

View file

@ -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.

View file

@ -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*

View file

@ -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.

View file

@ -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`

View file

@ -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*

View file

@ -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.

View file

@ -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', <q-args>)
@ -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(<range>)
" Organize import statements using tsserver
command! -bar ALEOrganizeImports :call ale#organize_imports#Execute()
@ -283,6 +293,7 @@ nnoremap <silent> <Plug>(ale_documentation) :ALEDocumentation<Return>
inoremap <silent> <Plug>(ale_complete) <C-\><C-O>:ALEComplete<Return>
nnoremap <silent> <Plug>(ale_import) :ALEImport<Return>
nnoremap <silent> <Plug>(ale_rename) :ALERename<Return>
nnoremap <silent> <Plug>(ale_code_action) :ALECodeAction<Return>
nnoremap <silent> <Plug>(ale_repeat_selection) :ALERepeatSelection<Return>
" Set up autocmd groups now.

View file

@ -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)

View file

View file

@ -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)

View file

@ -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')

View file

@ -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'}}}

View file

@ -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

View file

@ -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

View file

@ -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<str>) -> 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<T> 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<T>\nwhere\n T: Clone,"
\ }
\ ]
\ })

View file

@ -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(''))

View file

@ -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(''))

View file

@ -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(''))

View file

@ -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(''))

View file

@ -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')

View file

@ -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(''))

View file

@ -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!"),.',
\ ])

View file

@ -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'.",
\ ])

View file

@ -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()`",
\ ])

View file

@ -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

View file

View file

@ -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': [{
@ -123,7 +127,9 @@ 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,7 +193,8 @@ 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': [{
@ -199,9 +209,12 @@ Execute(End of file can be modified):
\ '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'';',

View file

@ -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)

549
test/test_codefix.vader Normal file
View file

@ -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:]

View file

@ -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,

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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(''))