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) 5. [Find References](#usage-find-references)
6. [Hovering](#usage-hover) 6. [Hovering](#usage-hover)
7. [Symbol Search](#usage-symbol-search) 7. [Symbol Search](#usage-symbol-search)
8. [Refactoring: Rename, Actions](#usage-refactoring)
3. [Installation](#installation) 3. [Installation](#installation)
1. [Installation with Vim package management](#standard-installation) 1. [Installation with Vim package management](#standard-installation)
2. [Installation with Pathogen](#installation-with-pathogen) 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. 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> <a name="installation"></a>
## 3. Installation ## 3. Installation
@ -328,12 +341,14 @@ git clone https://github.com/dense-analysis/ale.git
### 3.iii. Installation with Vundle ### 3.iii. Installation with Vundle
You can install this plugin using [Vundle](https://github.com/VundleVim/Vundle.vim) 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 ```vim
Plugin 'dense-analysis/ale' Plugin 'dense-analysis/ale'
``` ```
Then run the command `:PluginInstall` in Vim.
See the Vundle documentation for more information. See the Vundle documentation for more information.
<a name="installation-with-vim-plug"></a> <a name="installation-with-vim-plug"></a>
@ -341,13 +356,16 @@ See the Vundle documentation for more information.
### 3.iiii. Installation with Vim-Plug ### 3.iiii. Installation with Vim-Plug
You can install this plugin using [Vim-Plug](https://github.com/junegunn/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` by adding the GitHub path for this repository to your `~/.vimrc`:
and running `:PlugInstall`.
```vim ```vim
Plug 'dense-analysis/ale' Plug 'dense-analysis/ale'
``` ```
Then run the command `:PlugInstall` in Vim.
See the Vim-Plug documentation for more information.
<a name="contributing"></a> <a name="contributing"></a>
## 4. Contributing ## 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: " 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) " /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 = [] let l:output = []
for l:match in ale#util#GetMatches(a:lines, l:pattern) 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', \ 'name': 'tsserver',
\ 'lsp': 'tsserver', \ 'lsp': 'tsserver',
\ 'executable': {b -> ale#node#FindExecutable(b, 'typescript_tsserver', [ \ 'executable': {b -> ale#node#FindExecutable(b, 'typescript_tsserver', [
\ '.yarn/sdks/typescript/bin/tsserver',
\ 'node_modules/.bin/tsserver', \ 'node_modules/.bin/tsserver',
\ ])}, \ ])},
\ 'command': '%e', \ 'command': '%e',

View File

@ -2,23 +2,39 @@
" Description: balloonexpr support for ALE. " Description: balloonexpr support for ALE.
function! ale#balloon#MessageForPos(bufnr, lnum, col) abort 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. " 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 \|| !g:ale_enabled
\|| !getbufvar(a:bufnr, 'ale_enabled', 1) \|| !getbufvar(a:bufnr, 'ale_enabled', 1)
return '' return ''
endif endif
let l:loclist = get(g:ale_buffer_info, a:bufnr, {'loclist': []}).loclist if l:show_problems
let l:index = ale#util#BinarySearch(l:loclist, a:bufnr, a:lnum, a:col) 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 " 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 return l:loclist[l:index].text
elseif exists('*balloon_show') || getbufvar( elseif l:show_hover && (
\ a:bufnr, \ exists('*balloon_show')
\ 'ale_set_balloons_legacy_echo', \ || getbufvar(
\ get(g:, 'ale_set_balloons_legacy_echo', 0) \ 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 " Request LSP/tsserver hover information, but only if this version of
" Vim supports the balloon_show function, or if we turned a legacy " Vim supports the balloon_show function, or if we turned a legacy

View File

@ -1,26 +1,29 @@
" Author: Jerko Steiner <jerko.steiner@gmail.com> " Author: Jerko Steiner <jerko.steiner@gmail.com>
" Description: Code action support for LSP / tsserver " 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:current_buffer = bufnr('')
let l:changes = a:code_action.changes let l:changes = a:code_action.changes
let l:should_save = get(a:options, 'should_save')
for l:file_code_edit in l:changes
let l:buf = bufnr(l:file_code_edit.fileName)
if l:buf != -1 && l:buf != l:current_buffer && getbufvar(l:buf, '&mod')
call ale#util#Execute('echom ''Aborting action, file is unsaved''')
return
endif
endfor
for l:file_code_edit in l:changes for l:file_code_edit in l:changes
call ale#code_action#ApplyChanges( call ale#code_action#ApplyChanges(
\ l:file_code_edit.fileName, \ l:file_code_edit.fileName,
\ l:file_code_edit.textChanges, \ l:file_code_edit.textChanges,
\ a:should_save, \ l:should_save,
\ ) \)
endfor endfor
endfunction endfunction
@ -78,29 +81,14 @@ function! ale#code_action#ApplyChanges(filename, changes, should_save) abort
let l:pos = [1, 1] let l:pos = [1, 1]
endif endif
" We have to keep track of how many lines we have added, and offset " Changes have to be sorted so we apply them from bottom-to-top
" changes accordingly. for l:code_edit in reverse(sort(copy(a:changes), function('s:ChangeCmp')))
let l:line_offset = 0 let l:line = l:code_edit.start.line
let l:column_offset = 0 let l:column = l:code_edit.start.offset
let l:last_end_line = 0 let l:end_line = l:code_edit.end.line
let l:end_column = l:code_edit.end.offset
" 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
let l:text = l:code_edit.newText 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. " Adjust the ends according to previous edits.
if l:end_line > len(l:lines) if l:end_line > len(l:lines)
let l:end_line_len = 0 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] let l:start = l:lines[: l:line - 2]
endif 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 if l:column is 1
" We need to handle column 1 specially, because we can't slice an " We need to handle column 1 specially, because we can't slice an
" empty string ending on index 0. " empty string ending on index 0.
@ -127,13 +121,17 @@ function! ale#code_action#ApplyChanges(filename, changes, should_save) abort
endif endif
call extend(l:middle, l:insertions[1:]) call extend(l:middle, l:insertions[1:])
let l:middle[-1] .= l:lines[l:end_line - 1][l:end_column - 1 :]
if l:end_line <= len(l:lines)
" Only extend the last line if end_line is within the range of
" lines.
let l:middle[-1] .= l:lines[l:end_line - 1][l:end_column - 1 :]
endif
let l:lines_before_change = len(l:lines) let l:lines_before_change = len(l:lines)
let l:lines = l:start + l:middle + l:lines[l:end_line :] 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: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:column_offset = len(l:middle[-1]) - l:end_line_len
let l:pos = s:UpdateCursor(l:pos, 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]) call setpos('.', [0, l:pos[0], l:pos[1], 0])
endif 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 endfunction
function! s:UpdateCursor(cursor, start, end, offset) abort 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] return [l:cur_line, l:cur_column]
endfunction 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 let l:doc = l:doc.value
endif endif
" Collapse whitespaces and line breaks into a single space.
let l:detail = substitute(get(l:item, 'detail', ''), '\_s\+', ' ', 'g')
let l:result = { let l:result = {
\ 'word': l:word, \ 'word': l:word,
\ 'kind': ale#completion#GetCompletionSymbols(get(l:item, 'kind', '')), \ 'kind': ale#completion#GetCompletionSymbols(get(l:item, 'kind', '')),
\ 'icase': 1, \ 'icase': 1,
\ 'menu': get(l:item, 'detail', ''), \ 'menu': l:detail,
\ 'info': (type(l:doc) is v:t_string ? l:doc : ''), \ 'info': (type(l:doc) is v:t_string ? l:doc : ''),
\} \}
" This flag is used to tell if this completion came from ALE or not. " This flag is used to tell if this completion came from ALE or not.
let l:user_data = {'_ale_completion_item': 1} let l:user_data = {'_ale_completion_item': 1}
if has_key(l:item, 'additionalTextEdits') if has_key(l:item, 'additionalTextEdits')
\ && l:item.additionalTextEdits isnot v:null
let l:text_changes = [] let l:text_changes = []
for l:edit in l:item.additionalTextEdits 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-import'
\|| l:source is# 'ale-omnifunc' \|| l:source is# 'ale-omnifunc'
for l:code_action in get(l:user_data, 'code_actions', []) 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 endfor
endif endif

View File

@ -12,6 +12,11 @@ let s:default_registry = {
\ 'suggested_filetypes': ['help'], \ 'suggested_filetypes': ['help'],
\ 'description': 'Align help tags to the right margin', \ 'description': 'Align help tags to the right margin',
\ }, \ },
\ 'autoimport': {
\ 'function': 'ale#fixers#autoimport#Fix',
\ 'suggested_filetypes': ['python'],
\ 'description': 'Fix import issues with autoimport.',
\ },
\ 'autopep8': { \ 'autopep8': {
\ 'function': 'ale#fixers#autopep8#Fix', \ 'function': 'ale#fixers#autopep8#Fix',
\ 'suggested_filetypes': ['python'], \ 'suggested_filetypes': ['python'],
@ -105,6 +110,11 @@ let s:default_registry = {
\ 'suggested_filetypes': [], \ 'suggested_filetypes': [],
\ 'description': 'Remove all trailing whitespace characters at the end of every line.', \ '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': { \ 'yapf': {
\ 'function': 'ale#fixers#yapf#Fix', \ 'function': 'ale#fixers#yapf#Fix',
\ 'suggested_filetypes': ['python'], \ 'suggested_filetypes': ['python'],
@ -375,11 +385,21 @@ let s:default_registry = {
\ 'suggested_filetypes': ['html', 'htmldjango'], \ 'suggested_filetypes': ['html', 'htmldjango'],
\ 'description': 'Fix HTML files with html-beautify.', \ 'description': 'Fix HTML files with html-beautify.',
\ }, \ },
\ 'luafmt': {
\ 'function': 'ale#fixers#luafmt#Fix',
\ 'suggested_filetypes': ['lua'],
\ 'description': 'Fix Lua files with luafmt.',
\ },
\ 'dhall': { \ 'dhall': {
\ 'function': 'ale#fixers#dhall#Fix', \ 'function': 'ale#fixers#dhall#Fix',
\ 'suggested_filetypes': ['dhall'], \ 'suggested_filetypes': ['dhall'],
\ 'description': 'Fix Dhall files with dhall-format.', \ '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. " 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 { return {
\ 'command': l:env . ale#Escape(l:executable) \ 'command': l:env . ale#Escape(l:executable)
\ . ' -l -w'
\ . (empty(l:options) ? '' : ' ' . l:options) \ . (empty(l:options) ? '' : ' ' . l:options)
\ . ' %t',
\ 'read_temporary_file': 1,
\} \}
endfunction 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. " Description: Fixing files with phpcbf.
call ale#Set('php_phpcbf_standard', '') call ale#Set('php_phpcbf_standard', '')
call ale#Set('php_phpcbf_options', '')
call ale#Set('php_phpcbf_executable', 'phpcbf') call ale#Set('php_phpcbf_executable', 'phpcbf')
call ale#Set('php_phpcbf_use_global', get(g:, 'ale_use_global_executables', 0)) 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 { 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 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/.bin/eslint_d',
\ 'node_modules/eslint/bin/eslint.js', \ 'node_modules/eslint/bin/eslint.js',
\ 'node_modules/.bin/eslint', \ 'node_modules/.bin/eslint',
\ '.yarn/sdks/eslint/bin/eslint',
\] \]
let s:sep = has('win32') ? '\' : '/' let s:sep = has('win32') ? '\' : '/'

View File

@ -1,18 +1,28 @@
" Author: w0rp <devw0rp@gmail.com> " Author: w0rp <devw0rp@gmail.com>
" Get the shell type for a buffer, based on the hashbang line.
function! ale#handlers#sh#GetShellType(buffer) abort 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 = '' let l:command = ''
" Take the shell executable from the hashbang, if we can. " Take the shell executable from the shebang, if we can.
if l:bang_line[:1] is# '#!' if l:shebang[:1] is# '#!'
" Remove options like -e, etc. " 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 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# '' if l:command is# ''
let l:command = &filetype let l:command = &filetype
endif endif

View File

@ -1,8 +1,32 @@
" Author: w0rp <devw0rp@gmail.com> " Author: w0rp <devw0rp@gmail.com>
" Description: This file adds support for using the shellcheck linter " 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 function! ale#handlers#shellcheck#GetDialectArgument(buffer) abort
let l:shell_type = ale#handlers#sh#GetShellType(a:buffer) let l:shell_type = ale#handlers#shellcheck#GetShellcheckDialectDirective(a:buffer)
if empty(l:shell_type)
let l:shell_type = ale#handlers#sh#GetShellType(a:buffer)
endif
if !empty(l:shell_type) if !empty(l:shell_type)
" Use the dash dialect for /bin/ash, etc. " Use the dash dialect for /bin/ash, etc.
@ -13,15 +37,6 @@ function! ale#handlers#shellcheck#GetDialectArgument(buffer) abort
return l:shell_type return l:shell_type
endif 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 '' return ''
endfunction 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 if get(a:response, 'success', v:false) is v:true
\&& get(a:response, 'body', v:null) isnot v:null \&& 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 " If we pass the show_documentation flag, we should show the full
" documentation, and always in the preview window. " documentation, and always in the preview window.
if get(l:options, 'show_documentation', 0) if get(l:options, 'show_documentation', 0)
@ -40,7 +42,7 @@ function! ale#hover#HandleTSServerResponse(conn_id, response) abort
endif endif
elseif get(l:options, 'hover_from_balloonexpr', 0) elseif get(l:options, 'hover_from_balloonexpr', 0)
\&& exists('*balloon_show') \&& 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) call balloon_show(a:response.body.displayString)
elseif get(l:options, 'truncated_echo', 0) elseif get(l:options, 'truncated_echo', 0)
call ale#cursor#TruncatedEcho(split(a:response.body.displayString, "\n")[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) let [l:commands, l:lines] = ale#hover#ParseLSPResult(l:result.contents)
if !empty(l:lines) if !empty(l:lines)
let l:set_balloons = ale#Var(l:options.buffer, 'set_balloons')
if get(l:options, 'hover_from_balloonexpr', 0) if get(l:options, 'hover_from_balloonexpr', 0)
\&& exists('*balloon_show') \&& 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")) call balloon_show(join(l:lines, "\n"))
elseif get(l:options, 'truncated_echo', 0) elseif get(l:options, 'truncated_echo', 0)
call ale#cursor#TruncatedEcho(l:lines[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, \ 'definition': 0,
\ 'typeDefinition': 0, \ 'typeDefinition': 0,
\ 'symbol_search': 0, \ 'symbol_search': 0,
\ 'code_actions': 0,
\ }, \ },
\} \}
endif endif
@ -219,6 +220,14 @@ function! s:UpdateCapabilities(conn, capabilities) abort
let a:conn.capabilities.rename = 1 let a:conn.capabilities.rename = 1
endif 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')) if !empty(get(a:capabilities, 'completionProvider'))
let a:conn.capabilities.completion = 1 let a:conn.capabilities.completion = 1
endif endif
@ -350,6 +359,7 @@ function! ale#lsp#MarkConnectionAsTsserver(conn_id) abort
let l:conn.capabilities.definition = 1 let l:conn.capabilities.definition = 1
let l:conn.capabilities.symbol_search = 1 let l:conn.capabilities.symbol_search = 1
let l:conn.capabilities.rename = 1 let l:conn.capabilities.rename = 1
let l:conn.capabilities.code_actions = 1
endfunction endfunction
function! s:SendInitMessage(conn) abort 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, \ 'newName': a:new_name,
\}] \}]
endfunction 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 endif
if has_key(l:diagnostic, 'relatedInformation') if has_key(l:diagnostic, 'relatedInformation')
\ && l:diagnostic.relatedInformation isnot v:null
let l:related = deepcopy(l:diagnostic.relatedInformation) let l:related = deepcopy(l:diagnostic.relatedInformation)
call map(l:related, {key, val -> call map(l:related, {key, val ->
\ ale#path#FromURI(val.location.uri) . \ ale#path#FromURI(val.location.uri) .

View File

@ -103,3 +103,39 @@ function! ale#lsp#tsserver_message#OrganizeImports(buffer) abort
\ }, \ },
\}] \}]
endfunction 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 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, \ 'description': 'Organize Imports',
\}, v:false) \ 'changes': l:file_code_edits,
\ },
\ {}
\)
endfunction endfunction
function! s:OnReady(linter, lsp_details) abort function! s:OnReady(linter, lsp_details) abort

View File

@ -33,9 +33,10 @@ function! ale#rename#HandleTSServerResponse(conn_id, response) abort
return return
endif endif
let l:old_name = s:rename_map[a:response.request_seq].old_name let l:options = remove(s:rename_map, a:response.request_seq)
let l:new_name = s:rename_map[a:response.request_seq].new_name
call 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 if get(a:response, 'success', v:false) is v:false
let l:message = get(a:response, 'message', 'unknown') let l:message = get(a:response, 'message', 'unknown')
@ -77,41 +78,21 @@ function! ale#rename#HandleTSServerResponse(conn_id, response) abort
return return
endif endif
call ale#code_action#HandleCodeAction({ call ale#code_action#HandleCodeAction(
\ 'description': 'rename', \ {
\ 'changes': l:changes, \ 'description': 'rename',
\}, v:true) \ 'changes': l:changes,
endfunction \ },
\ {
function! s:getChanges(workspace_edit) abort \ 'should_save': 1,
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 endfunction
function! ale#rename#HandleLSPResponse(conn_id, response) abort function! ale#rename#HandleLSPResponse(conn_id, response) abort
if has_key(a:response, 'id') if has_key(a:response, 'id')
\&& has_key(s:rename_map, 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') if !has_key(a:response, 'result')
call s:message('No rename result received from server') call s:message('No rename result received from server')
@ -119,7 +100,7 @@ function! ale#rename#HandleLSPResponse(conn_id, response) abort
return return
endif 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) if empty(l:changes_map)
call s:message('No changes received from server') call s:message('No changes received from server')
@ -127,43 +108,21 @@ function! ale#rename#HandleLSPResponse(conn_id, response) abort
return return
endif endif
let l:changes = [] let l:changes = ale#code_action#BuildChangesList(l:changes_map)
for l:file_name in keys(l:changes_map) call ale#code_action#HandleCodeAction(
let l:text_edits = l:changes_map[l:file_name] \ {
let l:text_changes = [] \ 'description': 'rename',
\ 'changes': l:changes,
for l:edit in l:text_edits \ },
let l:range = l:edit.range \ {
let l:new_text = l:edit.newText \ 'should_save': 1,
\ },
call add(l:text_changes, { \)
\ 'start': {
\ 'line': l:range.start.line + 1,
\ 'offset': l:range.start.character + 1,
\ },
\ 'end': {
\ 'line': l:range.end.line + 1,
\ 'offset': l:range.end.character + 1,
\ },
\ 'newText': l:new_text,
\})
endfor
call add(l:changes, {
\ 'fileName': ale#path#FromURI(l:file_name),
\ 'textChanges': l:text_changes,
\})
endfor
call ale#code_action#HandleCodeAction({
\ 'description': 'rename',
\ 'changes': l:changes,
\}, v:true)
endif endif
endfunction 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 let l:id = a:lsp_details.connection_id
if !ale#lsp#HasCapability(l:id, 'rename') 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, \ l:buffer,
\ a:line, \ a:line,
\ a:column, \ a:column,
\ a:new_name \ a:options.new_name
\) \)
endif endif
let l:request_id = ale#lsp#Send(l:id, l:message) let l:request_id = ale#lsp#Send(l:id, l:message)
let s:rename_map[l:request_id] = { let s:rename_map[l:request_id] = a:options
\ 'new_name': a:new_name,
\ 'old_name': a:old_name,
\}
endfunction endfunction
function! s:ExecuteRename(linter, old_name, new_name) abort function! s:ExecuteRename(linter, options) abort
let l:buffer = bufnr('') let l:buffer = bufnr('')
let [l:line, l:column] = getpos('.')[1:2] 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))]) let l:column = min([l:column, len(getline(l:line))])
endif endif
let l:Callback = function( let l:Callback = function('s:OnReady', [l:line, l:column, a:options])
\ 's:OnReady', [l:line, l:column, a:old_name, a:new_name])
call ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback) call ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback)
endfunction endfunction
@ -245,6 +200,9 @@ function! ale#rename#Execute() abort
endif endif
for l:lsp_linter in l:lsp_linters 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 endfor
endfunction endfunction

View File

@ -486,7 +486,7 @@ function! ale#util#Input(message, value) abort
endfunction endfunction
function! ale#util#HasBuflineApi() abort function! ale#util#HasBuflineApi() abort
return exists('*deletebufline') && exists('*setbufline') return exists('*deletebufline') && exists('*appendbufline') && exists('*getpos') && exists('*setpos')
endfunction endfunction
" Sets buffer contents to lines " 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. " Use a Vim API for setting lines in other buffers, if available.
if l:has_bufline_api if l:has_bufline_api
call setbufline(a:buffer, 1, l:new_lines) let l:save_cursor = getpos('.')
call deletebufline(a:buffer, l:first_line_to_remove, '$') 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. " Fall back on setting lines the old way, for the current buffer.
else else
let l:old_line_length = line('$') 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 This variable can be changed to specify the profile that is used to
run dialyzer with rebar3. 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* 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'` 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: 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. 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: 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| 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* 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. 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: 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. 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* autopep8 *ale-python-autopep8*
@ -687,7 +713,7 @@ g:ale_python_pyre_auto_pipenv *g:ale_python_pyre_auto_pipenv*
=============================================================================== ===============================================================================
pyright *ale-python-pyright* 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 the `pyright-langserver` executable. You can install `pyright` on your system
through `npm` with `sudo npm install -g pyright` or similar. through `npm` with `sudo npm install -g pyright` or similar.

View File

@ -2,6 +2,29 @@
ALE R Integration *ale-r-options* 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* lintr *ale-r-lintr*
@ -22,7 +45,7 @@ g:ale_r_lintr_lint_package *g:ale_r_lintr_lint_package*
Default: `0` Default: `0`
When set to `1`, the file will be checked with `lintr::lint_package` instead When set to `1`, the file will be checked with `lintr::lint_package` instead
of `lintr::lint`. This prevents erroneous namespace warnings when linting of `lintr::lint`. This prevents erroneous namespace warnings when linting
package files. package files.
@ -36,8 +59,8 @@ g:ale_r_styler_options *g:ale_r_styler_options*
This option can be configured to change the options for styler. This option can be configured to change the options for styler.
The value of this option will be used as the `style` argument for the The value of this option will be used as the `style` argument for the
`styler::style_file` options. Consult the styler documentation `styler::style_file` options. Consult the styler documentation
for more information. for more information.

View File

@ -22,12 +22,12 @@ Integration Information
3. rls -- If you have `rls` installed, you might prefer using this linter 3. rls -- If you have `rls` installed, you might prefer using this linter
over cargo. rls implements the Language Server Protocol for incremental over cargo. rls implements the Language Server Protocol for incremental
compilation of Rust code, and can check Rust files while you type. `rls` 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 4. analyzer -- If you have rust-analyzer installed, you might prefer using
this linter over cargo and rls. rust-analyzer also implements the this linter over cargo and rls. rust-analyzer also implements the
Language Server Protocol for incremental compilation of Rust code, and is Language Server Protocol for incremental compilation of Rust code, and is
the next iteration of rls. rust-analyzer, like rls, requires Rust files 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 5. rustfmt -- If you have `rustfmt` installed, you can use it as a fixer to
consistently reformat your Rust code. consistently reformat your Rust code.

View File

@ -140,6 +140,7 @@ Notes:
* `erubis` * `erubis`
* `ruumba` * `ruumba`
* Erlang * Erlang
* `elvis`!!
* `erlc` * `erlc`
* `SyntaxErl` * `SyntaxErl`
* Fish * Fish
@ -195,6 +196,7 @@ Notes:
* `hie` * `hie`
* `hindent` * `hindent`
* `hlint` * `hlint`
* `ormolu`
* `stack-build`!! * `stack-build`!!
* `stack-ghc` * `stack-ghc`
* `stylish-haskell` * `stylish-haskell`
@ -265,6 +267,7 @@ Notes:
* Lua * Lua
* `luac` * `luac`
* `luacheck` * `luacheck`
* `luafmt`
* Mail * Mail
* `alex`!! * `alex`!!
* `languagetool`!! * `languagetool`!!
@ -324,6 +327,7 @@ Notes:
* Perl6 * Perl6
* `perl6 -c` * `perl6 -c`
* PHP * PHP
* `intelephense`
* `langserver` * `langserver`
* `phan` * `phan`
* `phpcbf` * `phpcbf`
@ -333,6 +337,7 @@ Notes:
* `phpmd` * `phpmd`
* `phpstan` * `phpstan`
* `psalm`!! * `psalm`!!
* `tlint`
* PO * PO
* `alex`!! * `alex`!!
* `msgfmt` * `msgfmt`
@ -361,6 +366,7 @@ Notes:
* `purescript-language-server` * `purescript-language-server`
* `purty` * `purty`
* Python * Python
* `autoimport`
* `autopep8` * `autopep8`
* `bandit` * `bandit`
* `black` * `black`
@ -383,6 +389,7 @@ Notes:
* `qmlfmt` * `qmlfmt`
* `qmllint` * `qmllint`
* R * R
* `languageserver`
* `lintr` * `lintr`
* `styler` * `styler`
* Racket * Racket
@ -455,9 +462,9 @@ Notes:
* SugarSS * SugarSS
* `stylelint` * `stylelint`
* Swift * Swift
* Apple `swift-format`
* `sourcekit-lsp` * `sourcekit-lsp`
* `swiftformat` * `swiftformat`
* `swift-format`
* `swiftlint` * `swiftlint`
* Tcl * Tcl
* `nagelfar`!! * `nagelfar`!!
@ -517,6 +524,7 @@ Notes:
* YAML * YAML
* `prettier` * `prettier`
* `swaglint` * `swaglint`
* `yamlfix`
* `yamllint` * `yamllint`
* YANG * YANG
* `yang-lsp` * `yang-lsp`

View File

@ -15,7 +15,6 @@ Install prettier either globally or locally: >
npm install prettier -g # global npm install prettier -g # global
npm install prettier # local npm install prettier # local
< <
=============================================================================== ===============================================================================
swaglint *ale-yaml-swaglint* 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| 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* yamllint *ale-yaml-yamllint*

View File

@ -20,6 +20,7 @@ CONTENTS *ale-contents*
5.4 Find References...................|ale-find-references| 5.4 Find References...................|ale-find-references|
5.5 Hovering..........................|ale-hover| 5.5 Hovering..........................|ale-hover|
5.6 Symbol Search.....................|ale-symbol-search| 5.6 Symbol Search.....................|ale-symbol-search|
5.7 Refactoring: Rename, Actions......|ale-refactor|
6. Global Options.......................|ale-options| 6. Global Options.......................|ale-options|
6.1 Highlights........................|ale-highlights| 6.1 Highlights........................|ale-highlights|
7. Linter/Fixer Options.................|ale-integration-options| 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 |ALESymbolSearch| command. See the documentation for the command
for a full list of options. 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* 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 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. 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* g:ale_max_buffer_history_size *g:ale_max_buffer_history_size*
Type: |Number| 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|. 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* g:ale_rename_tsserver_find_in_comments *g:ale_rename_tsserver_find_in_comments*
Type: |Number| 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* g:ale_set_balloons *g:ale_set_balloons*
*b:ale_set_balloons* *b:ale_set_balloons*
Type: |Number| Type: |Number| or |String|
Default: `has('balloon_eval') && has('gui_running')` Default: `has('balloon_eval') && has('gui_running')`
When this option is set to `1`, balloon messages will be displayed for 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 supporting "Hover" information, per |ale-hover|, then brief information
about the symbol under the cursor will be displayed in a balloon. 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, Balloons can be enabled for terminal versions of Vim that support balloons,
but some versions of Vim will produce strange mouse behavior when balloons but some versions of Vim will produce strange mouse behavior when balloons
are enabled. To configure balloons for your terminal, you should first 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| elm-make..............................|ale-elm-elm-make|
erlang..................................|ale-erlang-options| erlang..................................|ale-erlang-options|
dialyzer..............................|ale-erlang-dialyzer| dialyzer..............................|ale-erlang-dialyzer|
elvis.................................|ale-erlang-elvis|
erlc..................................|ale-erlang-erlc| erlc..................................|ale-erlang-erlc|
syntaxerl.............................|ale-erlang-syntaxerl| syntaxerl.............................|ale-erlang-syntaxerl|
eruby...................................|ale-eruby-options| eruby...................................|ale-eruby-options|
@ -2642,6 +2693,7 @@ documented in additional help files.
stack-ghc.............................|ale-haskell-stack-ghc| stack-ghc.............................|ale-haskell-stack-ghc|
stylish-haskell.......................|ale-haskell-stylish-haskell| stylish-haskell.......................|ale-haskell-stylish-haskell|
hie...................................|ale-haskell-hie| hie...................................|ale-haskell-hie|
ormolu................................|ale-haskell-ormolu|
hcl.....................................|ale-hcl-options| hcl.....................................|ale-hcl-options|
terraform-fmt.........................|ale-hcl-terraform-fmt| terraform-fmt.........................|ale-hcl-terraform-fmt|
html....................................|ale-html-options| html....................................|ale-html-options|
@ -2701,6 +2753,7 @@ documented in additional help files.
lua.....................................|ale-lua-options| lua.....................................|ale-lua-options|
luac..................................|ale-lua-luac| luac..................................|ale-lua-luac|
luacheck..............................|ale-lua-luacheck| luacheck..............................|ale-lua-luacheck|
luafmt................................|ale-lua-luafmt|
markdown................................|ale-markdown-options| markdown................................|ale-markdown-options|
markdownlint..........................|ale-markdown-markdownlint| markdownlint..........................|ale-markdown-markdownlint|
mdl...................................|ale-markdown-mdl| mdl...................................|ale-markdown-mdl|
@ -2752,6 +2805,8 @@ documented in additional help files.
psalm.................................|ale-php-psalm| psalm.................................|ale-php-psalm|
php-cs-fixer..........................|ale-php-php-cs-fixer| php-cs-fixer..........................|ale-php-php-cs-fixer|
php...................................|ale-php-php| php...................................|ale-php-php|
tlint.................................|ale-php-tlint|
intelephense..........................|ale-php-intelephense|
po......................................|ale-po-options| po......................................|ale-po-options|
write-good............................|ale-po-write-good| write-good............................|ale-po-write-good|
pod.....................................|ale-pod-options| pod.....................................|ale-pod-options|
@ -2777,6 +2832,7 @@ documented in additional help files.
pyrex (cython)..........................|ale-pyrex-options| pyrex (cython)..........................|ale-pyrex-options|
cython................................|ale-pyrex-cython| cython................................|ale-pyrex-cython|
python..................................|ale-python-options| python..................................|ale-python-options|
autoimport............................|ale-python-autoimport|
autopep8..............................|ale-python-autopep8| autopep8..............................|ale-python-autopep8|
bandit................................|ale-python-bandit| bandit................................|ale-python-bandit|
black.................................|ale-python-black| black.................................|ale-python-black|
@ -2798,6 +2854,7 @@ documented in additional help files.
qml.....................................|ale-qml-options| qml.....................................|ale-qml-options|
qmlfmt................................|ale-qml-qmlfmt| qmlfmt................................|ale-qml-qmlfmt|
r.......................................|ale-r-options| r.......................................|ale-r-options|
languageserver........................|ale-r-languageserver|
lintr.................................|ale-r-lintr| lintr.................................|ale-r-lintr|
styler................................|ale-r-styler| styler................................|ale-r-styler|
reasonml................................|ale-reasonml-options| reasonml................................|ale-reasonml-options|
@ -2914,6 +2971,7 @@ documented in additional help files.
yaml....................................|ale-yaml-options| yaml....................................|ale-yaml-options|
prettier..............................|ale-yaml-prettier| prettier..............................|ale-yaml-prettier|
swaglint..............................|ale-yaml-swaglint| swaglint..............................|ale-yaml-swaglint|
yamlfix...............................|ale-yaml-yamlfix|
yamllint..............................|ale-yaml-yamllint| yamllint..............................|ale-yaml-yamllint|
yang....................................|ale-yang-options| yang....................................|ale-yang-options|
yang-lsp..............................|ale-yang-lsp| yang-lsp..............................|ale-yang-lsp|
@ -3100,6 +3158,18 @@ ALERename *ALERename*
prompt will open to request a new name. 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* ALERepeatSelection *ALERepeatSelection*
Repeat the last selection displayed in the preview window. 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. " This variable can be overridden to set the GO111MODULE environment variable.
let g:ale_go_go111module = get(g:, 'ale_go_go111module', '') 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() call ale#balloon#Enable()
endif endif
@ -166,6 +169,10 @@ if g:ale_completion_enabled
call ale#completion#Enable() call ale#completion#Enable()
endif endif
if g:ale_popup_menu_enabled
call ale#code_action#EnablePopUpMenu()
endif
" Define commands for moving through warnings and errors. " Define commands for moving through warnings and errors.
command! -bar -nargs=* ALEPrevious command! -bar -nargs=* ALEPrevious
\ :call ale#loclist_jumping#WrapJump('before', <q-args>) \ :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() command! -bar ALEImport :call ale#completion#Import()
" Rename symbols using tsserver and LSP " 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 " Organize import statements using tsserver
command! -bar ALEOrganizeImports :call ale#organize_imports#Execute() 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> inoremap <silent> <Plug>(ale_complete) <C-\><C-O>:ALEComplete<Return>
nnoremap <silent> <Plug>(ale_import) :ALEImport<Return> nnoremap <silent> <Plug>(ale_import) :ALEImport<Return>
nnoremap <silent> <Plug>(ale_rename) :ALERename<Return> nnoremap <silent> <Plug>(ale_rename) :ALERename<Return>
nnoremap <silent> <Plug>(ale_code_action) :ALECodeAction<Return>
nnoremap <silent> <Plug>(ale_repeat_selection) :ALERepeatSelection<Return> nnoremap <silent> <Plug>(ale_repeat_selection) :ALERepeatSelection<Return>
" Set up autocmd groups now. " Set up autocmd groups now.

View File

@ -149,6 +149,7 @@ formatting.
* [erubis](https://github.com/kwatch/erubis) * [erubis](https://github.com/kwatch/erubis)
* [ruumba](https://github.com/ericqweinstein/ruumba) * [ruumba](https://github.com/ericqweinstein/ruumba)
* Erlang * Erlang
* [elvis](https://github.com/inaka/elvis) :floppy_disk:
* [erlc](http://erlang.org/doc/man/erlc.html) * [erlc](http://erlang.org/doc/man/erlc.html)
* [SyntaxErl](https://github.com/ten0s/syntaxerl) * [SyntaxErl](https://github.com/ten0s/syntaxerl)
* Fish * Fish
@ -204,6 +205,7 @@ formatting.
* [hie](https://github.com/haskell/haskell-ide-engine) * [hie](https://github.com/haskell/haskell-ide-engine)
* [hindent](https://hackage.haskell.org/package/hindent) * [hindent](https://hackage.haskell.org/package/hindent)
* [hlint](https://hackage.haskell.org/package/hlint) * [hlint](https://hackage.haskell.org/package/hlint)
* [ormolu](https://github.com/tweag/ormolu)
* [stack-build](https://haskellstack.org/) :floppy_disk: * [stack-build](https://haskellstack.org/) :floppy_disk:
* [stack-ghc](https://haskellstack.org/) * [stack-ghc](https://haskellstack.org/)
* [stylish-haskell](https://github.com/jaspervdj/stylish-haskell) * [stylish-haskell](https://github.com/jaspervdj/stylish-haskell)
@ -274,6 +276,7 @@ formatting.
* Lua * Lua
* [luac](https://www.lua.org/manual/5.1/luac.html) * [luac](https://www.lua.org/manual/5.1/luac.html)
* [luacheck](https://github.com/mpeterv/luacheck) * [luacheck](https://github.com/mpeterv/luacheck)
* [luafmt](https://github.com/trixnz/lua-fmt)
* Mail * Mail
* [alex](https://github.com/wooorm/alex) :floppy_disk: * [alex](https://github.com/wooorm/alex) :floppy_disk:
* [languagetool](https://languagetool.org/) :floppy_disk: * [languagetool](https://languagetool.org/) :floppy_disk:
@ -333,6 +336,7 @@ formatting.
* Perl6 * Perl6
* [perl6 -c](https://perl6.org) :warning: * [perl6 -c](https://perl6.org) :warning:
* PHP * PHP
* [intelephense](https://github.com/bmewburn/intelephense-docs)
* [langserver](https://github.com/felixfbecker/php-language-server) * [langserver](https://github.com/felixfbecker/php-language-server)
* [phan](https://github.com/phan/phan) see `:help ale-php-phan` to instructions * [phan](https://github.com/phan/phan) see `:help ale-php-phan` to instructions
* [phpcbf](https://github.com/squizlabs/PHP_CodeSniffer) * [phpcbf](https://github.com/squizlabs/PHP_CodeSniffer)
@ -342,6 +346,7 @@ formatting.
* [phpmd](https://phpmd.org) * [phpmd](https://phpmd.org)
* [phpstan](https://github.com/phpstan/phpstan) * [phpstan](https://github.com/phpstan/phpstan)
* [psalm](https://getpsalm.org) :floppy_disk: * [psalm](https://getpsalm.org) :floppy_disk:
* [tlint](https://github.com/tightenco/tlint)
* PO * PO
* [alex](https://github.com/wooorm/alex) :floppy_disk: * [alex](https://github.com/wooorm/alex) :floppy_disk:
* [msgfmt](https://www.gnu.org/software/gettext/manual/html_node/msgfmt-Invocation.html) * [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) * [purescript-language-server](https://github.com/nwolverson/purescript-language-server)
* [purty](https://gitlab.com/joneshf/purty) * [purty](https://gitlab.com/joneshf/purty)
* Python * Python
* [autoimport](https://lyz-code.github.io/autoimport/)
* [autopep8](https://github.com/hhatto/autopep8) * [autopep8](https://github.com/hhatto/autopep8)
* [bandit](https://github.com/PyCQA/bandit) :warning: * [bandit](https://github.com/PyCQA/bandit) :warning:
* [black](https://github.com/ambv/black) * [black](https://github.com/ambv/black)
@ -392,6 +398,7 @@ formatting.
* [qmlfmt](https://github.com/jesperhh/qmlfmt) * [qmlfmt](https://github.com/jesperhh/qmlfmt)
* [qmllint](https://github.com/qt/qtdeclarative/tree/5.11/tools/qmllint) * [qmllint](https://github.com/qt/qtdeclarative/tree/5.11/tools/qmllint)
* R * R
* [languageserver](https://github.com/REditorSupport/languageserver)
* [lintr](https://github.com/jimhester/lintr) * [lintr](https://github.com/jimhester/lintr)
* [styler](https://github.com/r-lib/styler) * [styler](https://github.com/r-lib/styler)
* Racket * Racket
@ -464,9 +471,9 @@ formatting.
* SugarSS * SugarSS
* [stylelint](https://github.com/stylelint/stylelint) * [stylelint](https://github.com/stylelint/stylelint)
* Swift * Swift
* [Apple swift-format](https://github.com/apple/swift-format)
* [sourcekit-lsp](https://github.com/apple/sourcekit-lsp) * [sourcekit-lsp](https://github.com/apple/sourcekit-lsp)
* [swiftformat](https://github.com/nicklockwood/SwiftFormat) * [swiftformat](https://github.com/nicklockwood/SwiftFormat)
* [swift-format](https://github.com/apple/swift-format)
* [swiftlint](https://github.com/realm/SwiftLint) * [swiftlint](https://github.com/realm/SwiftLint)
* Tcl * Tcl
* [nagelfar](http://nagelfar.sourceforge.net) :floppy_disk: * [nagelfar](http://nagelfar.sourceforge.net) :floppy_disk:
@ -526,6 +533,7 @@ formatting.
* YAML * YAML
* [prettier](https://github.com/prettier/prettier) * [prettier](https://github.com/prettier/prettier)
* [swaglint](https://github.com/byCedric/swaglint) * [swaglint](https://github.com/byCedric/swaglint)
* [yamlfix](https://lyz-code.github.io/yamlfix)
* [yamllint](https://yamllint.readthedocs.io/) * [yamllint](https://yamllint.readthedocs.io/)
* YANG * YANG
* [yang-lsp](https://github.com/theia-ide/yang-lsp) * [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 return g:server_started_value
endfunction endfunction
function! ale#code_action#HandleCodeAction(code_action, should_save) abort function! ale#code_action#HandleCodeAction(code_action, options) abort
Assert !a:should_save Assert !get(a:options, 'should_save')
call add(g:code_action_list, a:code_action) call add(g:code_action_list, a:code_action)
endfunction endfunction

View File

@ -50,8 +50,8 @@ Before:
let g:handle_code_action_called = 0 let g:handle_code_action_called = 0
function! MockHandleCodeAction() abort function! MockHandleCodeAction() abort
" delfunction! ale#code_action#HandleCodeAction " delfunction! ale#code_action#HandleCodeAction
function! ale#code_action#HandleCodeAction(action, should_save) abort function! ale#code_action#HandleCodeAction(action, options) abort
AssertEqual v:false, a:should_save Assert !get(a:options, 'should_save')
let g:handle_code_action_called += 1 let g:handle_code_action_called += 1
endfunction endfunction
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: &''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: 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': '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({ \ ale#completion#ParseLSPCompletions({
\ "jsonrpc":"2.0", \ "jsonrpc":"2.0",
@ -184,6 +185,11 @@ Execute(Should handle Rust completion results correctly):
\ "label":"from", \ "label":"from",
\ "kind":3, \ "kind":3,
\ "detail":"fn from(s: Cow<'a, str>) -> String" \ "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 AssertEqual
\ { \ {
\ 'read_temporary_file': 1, \ 'command': ale#Escape('xxxinvalid'),
\ 'command': ale#Escape('xxxinvalid')
\ . ' -l -w'
\ . ' %t',
\ }, \ },
\ ale#fixers#gofmt#Fix(bufnr('')) \ ale#fixers#gofmt#Fix(bufnr(''))
@ -35,11 +32,8 @@ Execute(The gofmt callback should include custom gofmt options):
AssertEqual AssertEqual
\ { \ {
\ 'read_temporary_file': 1,
\ 'command': ale#Escape('xxxinvalid') \ 'command': ale#Escape('xxxinvalid')
\ . ' -l -w' \ . ' ' . g:ale_go_gofmt_options,
\ . ' ' . g:ale_go_gofmt_options
\ . ' %t',
\ }, \ },
\ ale#fixers#gofmt#Fix(bufnr('')) \ ale#fixers#gofmt#Fix(bufnr(''))
@ -50,9 +44,7 @@ Execute(The gofmt callback should support Go environment variables):
AssertEqual AssertEqual
\ { \ {
\ 'read_temporary_file': 1,
\ 'command': ale#Env('GO111MODULE', 'off') \ 'command': ale#Env('GO111MODULE', 'off')
\ . ale#Escape('xxxinvalid') . ' -l -w' \ . ale#Escape('xxxinvalid')
\ . ' %t',
\ }, \ },
\ ale#fixers#gofmt#Fix(bufnr('')) \ 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_executable = 'phpcbf_test'
let g:ale_php_phpcbf_standard = '' let g:ale_php_phpcbf_standard = ''
let g:ale_php_phpcbf_options = ''
let g:ale_php_phpcbf_use_global = 0 let g:ale_php_phpcbf_use_global = 0
call ale#test#SetDirectory('/testplugin/test/fixers') 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' . ' -'}, \ {'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('')) \ 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: Before:
Save g:ale_php_phpcbf_executable Save g:ale_php_phpcbf_executable
Save g:ale_php_phpcbf_standard Save g:ale_php_phpcbf_standard
@ -61,6 +71,7 @@ Before:
let g:ale_php_phpcbf_executable = 'phpcbf_test' let g:ale_php_phpcbf_executable = 'phpcbf_test'
let g:ale_php_phpcbf_standard = '' let g:ale_php_phpcbf_standard = ''
let g:ale_php_phpcbf_options = ''
let g:ale_php_phpcbf_use_global = 0 let g:ale_php_phpcbf_use_global = 0
call ale#test#SetDirectory('/testplugin/test/fixers') 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', \ 'type': 'E',
\ 'sub_type': 'style', \ 'sub_type': 'style',
\ 'text': 'Line indented incorrectly; expected 4 spaces, found 2 (Generic.WhiteSpace.ScopeIndent.IncorrectExact)', \ '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(''), [ \ 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: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': [], \ 'completion_trigger_characters': [],
\ 'definition': 0, \ 'definition': 0,
\ 'symbol_search': 0, \ 'symbol_search': 0,
\ 'code_actions': 0,
\ }, \ },
\} \}
@ -102,6 +103,7 @@ Execute(Capabilities should bet set up correctly):
\ 'definition': 1, \ 'definition': 1,
\ 'symbol_search': 1, \ 'symbol_search': 1,
\ 'rename': 1, \ 'rename': 1,
\ 'code_actions': 1,
\ }, \ },
\ b:conn.capabilities \ b:conn.capabilities
AssertEqual [[1, 'initialized', {}]], g:message_list AssertEqual [[1, 'initialized', {}]], g:message_list
@ -125,7 +127,7 @@ Execute(Disabled capabilities should be recognised correctly):
\ 'referencesProvider': v:false, \ 'referencesProvider': v:false,
\ 'textDocumentSync': 2, \ 'textDocumentSync': 2,
\ 'documentFormattingProvider': v:true, \ 'documentFormattingProvider': v:true,
\ 'codeActionProvider': v:true, \ 'codeActionProvider': v:false,
\ 'signatureHelpProvider': { \ 'signatureHelpProvider': {
\ 'triggerCharacters': ['(', ','], \ 'triggerCharacters': ['(', ','],
\ }, \ },
@ -146,6 +148,7 @@ Execute(Disabled capabilities should be recognised correctly):
\ 'definition': 0, \ 'definition': 0,
\ 'symbol_search': 0, \ 'symbol_search': 0,
\ 'rename': 0, \ 'rename': 0,
\ 'code_actions': 0,
\ }, \ },
\ b:conn.capabilities \ b:conn.capabilities
AssertEqual [[1, 'initialized', {}]], g:message_list AssertEqual [[1, 'initialized', {}]], g:message_list
@ -197,6 +200,7 @@ Execute(Capabilities should be enabled when send as Dictionaries):
\ 'typeDefinition': 1, \ 'typeDefinition': 1,
\ 'symbol_search': 1, \ 'symbol_search': 1,
\ 'rename': 1, \ 'rename': 1,
\ 'code_actions': 1,
\ }, \ },
\ b:conn.capabilities \ b:conn.capabilities
AssertEqual [[1, 'initialized', {}]], g:message_list AssertEqual [[1, 'initialized', {}]], g:message_list

View File

View File

@ -3,6 +3,9 @@ Before:
let g:ale_enabled = 0 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/code_action.vim
runtime autoload/ale/util.vim runtime autoload/ale/util.vim
@ -85,7 +88,8 @@ Execute(It should modify and save multiple files):
\ 'import D from "D"', \ 'import D from "D"',
\], g:file2, 'S') \], g:file2, 'S')
call ale#code_action#HandleCodeAction({ call ale#code_action#HandleCodeAction(
\ {
\ 'changes': [{ \ 'changes': [{
\ 'fileName': g:file1, \ 'fileName': g:file1,
\ 'textChanges': [{ \ 'textChanges': [{
@ -122,8 +126,10 @@ Execute(It should modify and save multiple files):
\ }, \ },
\ 'newText': "import {A, B} from 'module'\n\n", \ 'newText': "import {A, B} from 'module'\n\n",
\ }] \ }]
\ }], \ }],
\}, v:true) \ },
\ {'should_save': 1},
\)
AssertEqual [ AssertEqual [
\ 'class Value {', \ 'class Value {',
@ -153,7 +159,8 @@ Execute(Beginning of file can be modified):
\] \]
call writefile(g:test.text, g:file1, 'S') call writefile(g:test.text, g:file1, 'S')
call ale#code_action#HandleCodeAction({ call ale#code_action#HandleCodeAction(
\ {
\ 'changes': [{ \ 'changes': [{
\ 'fileName': g:file1, \ 'fileName': g:file1,
\ 'textChanges': [{ \ 'textChanges': [{
@ -168,7 +175,9 @@ Execute(Beginning of file can be modified):
\ 'newText': "type A: string\ntype B: number\n", \ 'newText': "type A: string\ntype B: number\n",
\ }], \ }],
\ }] \ }]
\}, v:true) \ },
\ {'should_save': 1},
\)
AssertEqual [ AssertEqual [
\ 'type A: string', \ 'type A: string',
@ -184,24 +193,28 @@ Execute(End of file can be modified):
\] \]
call writefile(g:test.text, g:file1, 'S') call writefile(g:test.text, g:file1, 'S')
call ale#code_action#HandleCodeAction({ call ale#code_action#HandleCodeAction(
\ {
\ 'changes': [{ \ 'changes': [{
\ 'fileName': g:file1, \ 'fileName': g:file1,
\ 'textChanges': [{ \ 'textChanges': [{
\ 'start': { \ 'start': {
\ 'line': 4, \ 'line': 4,
\ 'offset': 1, \ 'offset': 1,
\ }, \ },
\ 'end': { \ 'end': {
\ 'line': 4, \ 'line': 4,
\ 'offset': 1, \ 'offset': 1,
\ }, \ },
\ 'newText': "type A: string\ntype B: number\n", \ 'newText': "type A: string\ntype B: number\n",
\ }], \ }],
\ }] \ }]
\}, v:true) \ },
\ {'should_save': 1},
\)
AssertEqual g:test.text + [ AssertEqual g:test.text + [
\ '',
\ 'type A: string', \ 'type A: string',
\ 'type B: number', \ 'type B: number',
\ '', \ '',
@ -219,7 +232,8 @@ Execute(Current buffer contents will be reloaded):
execute 'edit ' . g:file1 execute 'edit ' . g:file1
let g:test.buffer = bufnr(g:file1) let g:test.buffer = bufnr(g:file1)
call ale#code_action#HandleCodeAction({ call ale#code_action#HandleCodeAction(
\ {
\ 'changes': [{ \ 'changes': [{
\ 'fileName': g:file1, \ 'fileName': g:file1,
\ 'textChanges': [{ \ 'textChanges': [{
@ -234,7 +248,9 @@ Execute(Current buffer contents will be reloaded):
\ 'newText': "type A: string\ntype B: number\n", \ 'newText': "type A: string\ntype B: number\n",
\ }], \ }],
\ }] \ }]
\}, v:true) \ },
\ {'should_save': 1},
\)
AssertEqual [ AssertEqual [
\ 'type A: string', \ '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') let g:test.changes = g:test.create_change(2, 3, 2, 8, 'value2')
call setpos('.', [0, 1, 1, 0]) 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] AssertEqual [1, 1], getpos('.')[1:2]
call setpos('.', [0, 2, 2, 0]) 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] AssertEqual [2, 2], getpos('.')[1:2]
# ====C==== # ====C====
@ -271,7 +287,7 @@ Execute(Cursor column will move to the change end when cursor between start/end)
call WriteFileAndEdit() call WriteFileAndEdit()
call setpos('.', [0, 2, r, 0]) call setpos('.', [0, 2, r, 0])
AssertEqual ' value: string', getline('.') 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 ' value2: string', getline('.')
AssertEqual [2, 9], getpos('.')[1:2] AssertEqual [2, 9], getpos('.')[1:2]
endfor endfor
@ -283,7 +299,9 @@ Execute(Cursor column will move back when new text is shorter):
call setpos('.', [0, 2, 8, 0]) call setpos('.', [0, 2, 8, 0])
AssertEqual ' value: string', getline('.') AssertEqual ' value: string', getline('.')
call ale#code_action#HandleCodeAction( 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 ' val: string', getline('.')
AssertEqual [2, 6], getpos('.')[1:2] 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]) call setpos('.', [0, 2, 8, 0])
AssertEqual ' value: string', getline('.') AssertEqual ' value: string', getline('.')
call ale#code_action#HandleCodeAction( 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 ' longValue: string', getline('.')
AssertEqual [2, 12], getpos('.')[1:2] 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]) call setpos('.', [0, 3, 1, 0])
AssertEqual '}', getline('.') AssertEqual '}', getline('.')
call ale#code_action#HandleCodeAction( 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 '}', getline('.')
AssertEqual [4, 1], getpos('.')[1:2] 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]) call setpos('.', [0, 2, 2, 0])
AssertEqual ' value: string', getline('.') AssertEqual ' value: string', getline('.')
call ale#code_action#HandleCodeAction( 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 '123 value: string', getline('.')
AssertEqual [3, 5], getpos('.')[1:2] 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]) call setpos('.', [0, 2, 10, 0])
AssertEqual ' value: string', getline('.') AssertEqual ' value: string', getline('.')
call ale#code_action#HandleCodeAction( 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 '}', getline('.')
AssertEqual [2, 1], getpos('.')[1:2] 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]) call setpos('.', [0, 2, 3, 0])
AssertEqual ' value: string', getline('.') AssertEqual ' value: string', getline('.')
call ale#code_action#HandleCodeAction( 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 ' value: number', getline('.')
AssertEqual [2, 3], getpos('.')[1:2] AssertEqual [2, 3], getpos('.')[1:2]
Execute(It should just modify file when should_save is set to v:false): Execute(It should just modify file when should_save is set to v:false):
call WriteFileAndEdit() call WriteFileAndEdit()
let g:test.change = g:test.create_change(1, 1, 1, 1, "import { writeFile } from 'fs';\n") 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 1, getbufvar(bufnr(''), '&modified')
AssertEqual [ AssertEqual [
\ 'import { writeFile } from ''fs'';', \ '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() AssertEqual {}, ale#hover#GetMap()
Execute(tsserver quickinfo displayString values should be displayed): Execute(tsserver quickinfo displayString values should be displayed):
call ale#hover#SetMap({3: {}}) call ale#hover#SetMap({3: {'buffer': bufnr('')}})
call ale#hover#HandleTSServerResponse( call ale#hover#HandleTSServerResponse(
\ 1, \ 1,
\ { \ {
@ -169,7 +169,7 @@ Execute(LSP hover response with lists of strings and marked strings should be ha
AssertEqual {}, ale#hover#GetMap() AssertEqual {}, ale#hover#GetMap()
Execute(tsserver responses for documentation requests should be handled): 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( call ale#hover#HandleTSServerResponse(
\ 1, \ 1,

View File

@ -35,7 +35,7 @@ After:
Execute(The file changed event function should set b:ale_file_changed): Execute(The file changed event function should set b:ale_file_changed):
let g:ale_lint_on_enter = 0 let g:ale_lint_on_enter = 0
if has('gui') if has('gui_running')
new new
else else
e test e test

View File

@ -57,9 +57,9 @@ Before:
call add(g:expr_list, a:expr) call add(g:expr_list, a:expr)
endfunction 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 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) call add(g:code_actions, a:code_action)
endfunction endfunction

View File

@ -57,9 +57,9 @@ Before:
call add(g:expr_list, a:expr) call add(g:expr_list, a:expr)
endfunction 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 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) call add(g:code_actions, a:code_action)
endfunction 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): Execute(dash should be used for shellcheck):
AssertEqual 'dash', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) 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(''))