Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
f156548c16
16 changed files with 1276 additions and 77 deletions
13
README.md
13
README.md
|
@ -53,6 +53,7 @@ other content at [w0rp.com](https://w0rp.com).
|
|||
5. [Find References](#usage-find-references)
|
||||
6. [Hovering](#usage-hover)
|
||||
7. [Symbol Search](#usage-symbol-search)
|
||||
8. [Refactoring: Rename, Actions](#usage-refactoring)
|
||||
3. [Installation](#installation)
|
||||
1. [Installation with Vim package management](#standard-installation)
|
||||
2. [Installation with Pathogen](#installation-with-pathogen)
|
||||
|
@ -253,6 +254,18 @@ similar to a given query string.
|
|||
|
||||
See `:help ale-symbol-search` for more information.
|
||||
|
||||
<a name="usage-refactoring"></a>
|
||||
|
||||
### 2.viii Refactoring: Rename, Actions
|
||||
|
||||
ALE supports renaming symbols in symbols in code such as variables or class
|
||||
names with the `ALERename` command.
|
||||
|
||||
`ALECodeAction` will execute actions on the cursor or applied to a visual
|
||||
range selection, such as automatically fixing errors.
|
||||
|
||||
See `:help ale-refactor` for more information.
|
||||
|
||||
<a name="installation"></a>
|
||||
|
||||
## 3. Installation
|
||||
|
|
34
ale_linters/python/jedils.vim
Normal file
34
ale_linters/python/jedils.vim
Normal 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',
|
||||
\})
|
|
@ -85,29 +85,14 @@ function! ale#code_action#ApplyChanges(filename, changes, should_save) abort
|
|||
let l:pos = [1, 1]
|
||||
endif
|
||||
|
||||
" We have to keep track of how many lines we have added, and offset
|
||||
" changes accordingly.
|
||||
let l:line_offset = 0
|
||||
let l:column_offset = 0
|
||||
let l:last_end_line = 0
|
||||
|
||||
" Changes have to be sorted so we apply them from top-to-bottom.
|
||||
for l:code_edit in sort(copy(a:changes), function('s:ChangeCmp'))
|
||||
if l:code_edit.start.line isnot l:last_end_line
|
||||
let l:column_offset = 0
|
||||
endif
|
||||
|
||||
let l:line = l:code_edit.start.line + l:line_offset
|
||||
let l:column = l:code_edit.start.offset + l:column_offset
|
||||
let l:end_line = l:code_edit.end.line + l:line_offset
|
||||
let l:end_column = l:code_edit.end.offset + l:column_offset
|
||||
" Changes have to be sorted so we apply them from bottom-to-top
|
||||
for l:code_edit in reverse(sort(copy(a:changes), function('s:ChangeCmp')))
|
||||
let l:line = l:code_edit.start.line
|
||||
let l:column = l:code_edit.start.offset
|
||||
let l:end_line = l:code_edit.end.line
|
||||
let l:end_column = l:code_edit.end.offset
|
||||
let l:text = l:code_edit.newText
|
||||
|
||||
let l:cur_line = l:pos[0]
|
||||
let l:cur_column = l:pos[1]
|
||||
|
||||
let l:last_end_line = l:end_line
|
||||
|
||||
" Adjust the ends according to previous edits.
|
||||
if l:end_line > len(l:lines)
|
||||
let l:end_line_len = 0
|
||||
|
@ -125,6 +110,12 @@ function! ale#code_action#ApplyChanges(filename, changes, should_save) abort
|
|||
let l:start = l:lines[: l:line - 2]
|
||||
endif
|
||||
|
||||
" Special case when text must be added after new line
|
||||
if l:column > len(l:lines[l:line - 1])
|
||||
call extend(l:start, [l:lines[l:line - 1]])
|
||||
let l:column = 1
|
||||
endif
|
||||
|
||||
if l:column is 1
|
||||
" We need to handle column 1 specially, because we can't slice an
|
||||
" empty string ending on index 0.
|
||||
|
@ -140,7 +131,6 @@ function! ale#code_action#ApplyChanges(filename, changes, should_save) abort
|
|||
let l:lines = l:start + l:middle + l:lines[l:end_line :]
|
||||
|
||||
let l:current_line_offset = len(l:lines) - l:lines_before_change
|
||||
let l:line_offset += l:current_line_offset
|
||||
let l:column_offset = len(l:middle[-1]) - l:end_line_len
|
||||
|
||||
let l:pos = s:UpdateCursor(l:pos,
|
||||
|
@ -215,3 +205,61 @@ function! s:UpdateCursor(cursor, start, end, offset) abort
|
|||
|
||||
return [l:cur_line, l:cur_column]
|
||||
endfunction
|
||||
|
||||
function! ale#code_action#GetChanges(workspace_edit) abort
|
||||
let l:changes = {}
|
||||
|
||||
if has_key(a:workspace_edit, 'changes') && !empty(a:workspace_edit.changes)
|
||||
return a:workspace_edit.changes
|
||||
elseif has_key(a:workspace_edit, 'documentChanges')
|
||||
let l:document_changes = []
|
||||
|
||||
if type(a:workspace_edit.documentChanges) is v:t_dict
|
||||
\ && has_key(a:workspace_edit.documentChanges, 'edits')
|
||||
call add(l:document_changes, a:workspace_edit.documentChanges)
|
||||
elseif type(a:workspace_edit.documentChanges) is v:t_list
|
||||
let l:document_changes = a:workspace_edit.documentChanges
|
||||
endif
|
||||
|
||||
for l:text_document_edit in l:document_changes
|
||||
let l:filename = l:text_document_edit.textDocument.uri
|
||||
let l:edits = l:text_document_edit.edits
|
||||
let l:changes[l:filename] = l:edits
|
||||
endfor
|
||||
endif
|
||||
|
||||
return l:changes
|
||||
endfunction
|
||||
|
||||
function! ale#code_action#BuildChangesList(changes_map) abort
|
||||
let l:changes = []
|
||||
|
||||
for l:file_name in keys(a:changes_map)
|
||||
let l:text_edits = a:changes_map[l:file_name]
|
||||
let l:text_changes = []
|
||||
|
||||
for l:edit in l:text_edits
|
||||
let l:range = l:edit.range
|
||||
let l:new_text = l:edit.newText
|
||||
|
||||
call add(l:text_changes, {
|
||||
\ 'start': {
|
||||
\ 'line': l:range.start.line + 1,
|
||||
\ 'offset': l:range.start.character + 1,
|
||||
\ },
|
||||
\ 'end': {
|
||||
\ 'line': l:range.end.line + 1,
|
||||
\ 'offset': l:range.end.character + 1,
|
||||
\ },
|
||||
\ 'newText': l:new_text,
|
||||
\})
|
||||
endfor
|
||||
|
||||
call add(l:changes, {
|
||||
\ 'fileName': ale#path#FromURI(l:file_name),
|
||||
\ 'textChanges': l:text_changes,
|
||||
\})
|
||||
endfor
|
||||
|
||||
return l:changes
|
||||
endfunction
|
||||
|
|
388
autoload/ale/codefix.vim
Normal file
388
autoload/ale/codefix.vim
Normal file
|
@ -0,0 +1,388 @@
|
|||
" Author: Dalius Dobravolskas <dalius.dobravolskas@gmail.com>
|
||||
" Description: Code Fix support for tsserver
|
||||
|
||||
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#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:location = remove(s:codefix_map, a:response.request_seq)
|
||||
|
||||
if get(a:response, 'command', '') is# 'getCodeFixes'
|
||||
if get(a:response, 'success', v:false) is v:false
|
||||
let l:message = get(a:response, 'message', 'unknown')
|
||||
call s:message('Error while getting code fixes. Reason: ' . l:message)
|
||||
|
||||
return
|
||||
endif
|
||||
|
||||
if len(a:response.body) == 0
|
||||
call s:message('No code fixes available.')
|
||||
|
||||
return
|
||||
endif
|
||||
|
||||
let l:code_fix_to_apply = 0
|
||||
|
||||
if len(a:response.body) == 1
|
||||
let l:code_fix_to_apply = 1
|
||||
else
|
||||
let l:codefix_no = 1
|
||||
let l:codefixstring = "Code Fixes:\n"
|
||||
|
||||
for l:codefix in a:response.body
|
||||
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
|
||||
|
||||
let l:changes = a:response.body[l:code_fix_to_apply - 1].changes
|
||||
|
||||
call ale#code_action#HandleCodeAction({
|
||||
\ 'description': 'codefix',
|
||||
\ 'changes': l:changes,
|
||||
\}, {})
|
||||
elseif get(a:response, 'command', '') is# 'getApplicableRefactors'
|
||||
if get(a:response, 'success', v:false) is v:false
|
||||
let l:message = get(a:response, 'message', 'unknown')
|
||||
call s:message('Error while getting applicable refactors. Reason: ' . l:message)
|
||||
|
||||
return
|
||||
endif
|
||||
|
||||
if len(a:response.body) == 0
|
||||
call s:message('No applicable refactors available.')
|
||||
|
||||
return
|
||||
endif
|
||||
|
||||
let l:refactors = []
|
||||
|
||||
for l:item in a:response.body
|
||||
for l:action in l:item.actions
|
||||
call add(l:refactors, {
|
||||
\ 'name': l:action.description,
|
||||
\ 'id': [l:item.name, l:action.name],
|
||||
\})
|
||||
endfor
|
||||
endfor
|
||||
|
||||
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
|
||||
|
||||
let l:message = ale#lsp#tsserver_message#GetEditsForRefactor(
|
||||
\ l:location.buffer,
|
||||
\ l:location.line,
|
||||
\ l:location.column,
|
||||
\ l:location.end_line,
|
||||
\ l:location.end_column,
|
||||
\ l:id[0],
|
||||
\ l:id[1],
|
||||
\)
|
||||
|
||||
let l:request_id = ale#lsp#Send(l:location.connection_id, l:message)
|
||||
|
||||
let s:codefix_map[l:request_id] = l:location
|
||||
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#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:location = remove(s:codefix_map, a:response.id)
|
||||
|
||||
if !has_key(a:response, 'result')
|
||||
\ || type(a:response.result) != v:t_list
|
||||
\ || len(a:response.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 a:response.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 = a:response.result[l:codeaction_to_apply - 1]
|
||||
|
||||
if has_key(l:item, 'command')
|
||||
\ && type(l:item.command) == v:t_dict
|
||||
let l:command = l:item.command
|
||||
let l:message = ale#lsp#message#ExecuteCommand(
|
||||
\ l:command.command,
|
||||
\ l:command.arguments,
|
||||
\)
|
||||
|
||||
let l:request_id = ale#lsp#Send(l:location.connection_id, l:message)
|
||||
elseif has_key(l:item, 'edit') || has_key(l:item, 'arguments')
|
||||
if has_key(l:item, 'edit')
|
||||
let l:topass = l:item.edit
|
||||
else
|
||||
let l:topass = l: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
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:OnReady(line, column, end_line, end_column, 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'
|
||||
if a:line == a:end_line && a:column == a:end_column
|
||||
if !has_key(g:ale_buffer_info, l:buffer)
|
||||
return
|
||||
endif
|
||||
|
||||
let l:nearest_error = v:null
|
||||
let l:nearest_error_diff = -1
|
||||
|
||||
for l:error in get(g:ale_buffer_info[l: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.code
|
||||
endif
|
||||
endif
|
||||
endfor
|
||||
|
||||
let l:message = ale#lsp#tsserver_message#GetCodeFixes(
|
||||
\ l:buffer,
|
||||
\ a:line,
|
||||
\ a:column,
|
||||
\ a:line,
|
||||
\ a:column,
|
||||
\ [l:nearest_error],
|
||||
\)
|
||||
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)
|
||||
|
||||
if a:line == a:end_line && a:column == a:end_column
|
||||
if !has_key(g:ale_buffer_info, l:buffer)
|
||||
return
|
||||
endif
|
||||
|
||||
let l:nearest_error = v:null
|
||||
let l:nearest_error_diff = -1
|
||||
|
||||
for l:error in get(g:ale_buffer_info[l: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
|
||||
|
||||
let l:diagnostics = []
|
||||
|
||||
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 - 1 }
|
||||
\}
|
||||
\}]
|
||||
endif
|
||||
|
||||
let l:message = ale#lsp#message#CodeAction(
|
||||
\ l:buffer,
|
||||
\ a:line,
|
||||
\ a:column,
|
||||
\ a:end_line,
|
||||
\ a:end_column,
|
||||
\ l:diagnostics,
|
||||
\)
|
||||
else
|
||||
let l:message = ale#lsp#message#CodeAction(
|
||||
\ l:buffer,
|
||||
\ a:line,
|
||||
\ a:column,
|
||||
\ a:end_line,
|
||||
\ a:end_column,
|
||||
\ [],
|
||||
\)
|
||||
endif
|
||||
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,
|
||||
\}
|
||||
endfunction
|
||||
|
||||
function! s:ExecuteGetCodeFix(linter, range) 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
|
||||
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])
|
||||
call ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback)
|
||||
endfunction
|
||||
|
||||
function! ale#codefix#Execute(range) abort
|
||||
let l:lsp_linters = []
|
||||
|
||||
for l:linter in ale#linter#Get(&filetype)
|
||||
if !empty(l:linter.lsp)
|
||||
call add(l:lsp_linters, l:linter)
|
||||
endif
|
||||
endfor
|
||||
|
||||
if empty(l:lsp_linters)
|
||||
call s:message('No active LSPs')
|
||||
|
||||
return
|
||||
endif
|
||||
|
||||
for l:lsp_linter in l:lsp_linters
|
||||
call s:ExecuteGetCodeFix(l:lsp_linter, a:range)
|
||||
endfor
|
||||
endfunction
|
|
@ -617,6 +617,7 @@ function! ale#completion#ParseLSPCompletions(response) abort
|
|||
let l:user_data = {'_ale_completion_item': 1}
|
||||
|
||||
if has_key(l:item, 'additionalTextEdits')
|
||||
\ && l:item.additionalTextEdits isnot v:null
|
||||
let l:text_changes = []
|
||||
|
||||
for l:edit in l:item.additionalTextEdits
|
||||
|
|
|
@ -44,6 +44,7 @@ function! ale#lsp#Register(executable_or_address, project, init_options) abort
|
|||
\ 'definition': 0,
|
||||
\ 'typeDefinition': 0,
|
||||
\ 'symbol_search': 0,
|
||||
\ 'code_actions': 0,
|
||||
\ },
|
||||
\}
|
||||
endif
|
||||
|
@ -219,6 +220,14 @@ function! s:UpdateCapabilities(conn, capabilities) abort
|
|||
let a:conn.capabilities.rename = 1
|
||||
endif
|
||||
|
||||
if get(a:capabilities, 'codeActionProvider') is v:true
|
||||
let a:conn.capabilities.code_actions = 1
|
||||
endif
|
||||
|
||||
if type(get(a:capabilities, 'codeActionProvider')) is v:t_dict
|
||||
let a:conn.capabilities.code_actions = 1
|
||||
endif
|
||||
|
||||
if !empty(get(a:capabilities, 'completionProvider'))
|
||||
let a:conn.capabilities.completion = 1
|
||||
endif
|
||||
|
@ -350,6 +359,7 @@ function! ale#lsp#MarkConnectionAsTsserver(conn_id) abort
|
|||
let l:conn.capabilities.definition = 1
|
||||
let l:conn.capabilities.symbol_search = 1
|
||||
let l:conn.capabilities.rename = 1
|
||||
let l:conn.capabilities.code_actions = 1
|
||||
endfunction
|
||||
|
||||
function! s:SendInitMessage(conn) abort
|
||||
|
|
|
@ -172,3 +172,25 @@ function! ale#lsp#message#Rename(buffer, line, column, new_name) abort
|
|||
\ 'newName': a:new_name,
|
||||
\}]
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#message#CodeAction(buffer, line, column, end_line, end_column, diagnostics) abort
|
||||
return [0, 'textDocument/codeAction', {
|
||||
\ 'textDocument': {
|
||||
\ 'uri': ale#path#ToURI(expand('#' . a:buffer . ':p')),
|
||||
\ },
|
||||
\ 'range': {
|
||||
\ 'start': {'line': a:line - 1, 'character': a:column - 1},
|
||||
\ 'end': {'line': a:end_line - 1, 'character': a:end_column},
|
||||
\ },
|
||||
\ 'context': {
|
||||
\ 'diagnostics': a:diagnostics
|
||||
\ },
|
||||
\}]
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#message#ExecuteCommand(command, arguments) abort
|
||||
return [0, 'workspace/executeCommand', {
|
||||
\ 'command': a:command,
|
||||
\ 'arguments': a:arguments,
|
||||
\}]
|
||||
endfunction
|
||||
|
|
|
@ -56,6 +56,7 @@ function! ale#lsp#response#ReadDiagnostics(response) abort
|
|||
endif
|
||||
|
||||
if has_key(l:diagnostic, 'relatedInformation')
|
||||
\ && l:diagnostic.relatedInformation isnot v:null
|
||||
let l:related = deepcopy(l:diagnostic.relatedInformation)
|
||||
call map(l:related, {key, val ->
|
||||
\ ale#path#FromURI(val.location.uri) .
|
||||
|
|
|
@ -103,3 +103,39 @@ function! ale#lsp#tsserver_message#OrganizeImports(buffer) abort
|
|||
\ },
|
||||
\}]
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#tsserver_message#GetCodeFixes(buffer, line, column, end_line, end_column, error_codes) abort
|
||||
" The lines and columns are 1-based.
|
||||
" The errors codes must be a list of tsserver error codes to fix.
|
||||
return [0, 'ts@getCodeFixes', {
|
||||
\ 'startLine': a:line,
|
||||
\ 'startOffset': a:column,
|
||||
\ 'endLine': a:end_line,
|
||||
\ 'endOffset': a:end_column + 1,
|
||||
\ 'file': expand('#' . a:buffer . ':p'),
|
||||
\ 'errorCodes': a:error_codes,
|
||||
\}]
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#tsserver_message#GetApplicableRefactors(buffer, line, column, end_line, end_column) abort
|
||||
" The arguments for this request can also be just 'line' and 'offset'
|
||||
return [0, 'ts@getApplicableRefactors', {
|
||||
\ 'startLine': a:line,
|
||||
\ 'startOffset': a:column,
|
||||
\ 'endLine': a:end_line,
|
||||
\ 'endOffset': a:end_column + 1,
|
||||
\ 'file': expand('#' . a:buffer . ':p'),
|
||||
\}]
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#tsserver_message#GetEditsForRefactor(buffer, line, column, end_line, end_column, refactor, action) abort
|
||||
return [0, 'ts@getEditsForRefactor', {
|
||||
\ 'startLine': a:line,
|
||||
\ 'startOffset': a:column,
|
||||
\ 'endLine': a:end_line,
|
||||
\ 'endOffset': a:end_column + 1,
|
||||
\ 'file': expand('#' . a:buffer . ':p'),
|
||||
\ 'refactor': a:refactor,
|
||||
\ 'action': a:action,
|
||||
\}]
|
||||
endfunction
|
||||
|
|
|
@ -90,31 +90,6 @@ function! ale#rename#HandleTSServerResponse(conn_id, response) abort
|
|||
\)
|
||||
endfunction
|
||||
|
||||
function! s:getChanges(workspace_edit) abort
|
||||
let l:changes = {}
|
||||
|
||||
if has_key(a:workspace_edit, 'changes') && !empty(a:workspace_edit.changes)
|
||||
return a:workspace_edit.changes
|
||||
elseif has_key(a:workspace_edit, 'documentChanges')
|
||||
let l:document_changes = []
|
||||
|
||||
if type(a:workspace_edit.documentChanges) is v:t_dict
|
||||
\ && has_key(a:workspace_edit.documentChanges, 'edits')
|
||||
call add(l:document_changes, a:workspace_edit.documentChanges)
|
||||
elseif type(a:workspace_edit.documentChanges) is v:t_list
|
||||
let l:document_changes = a:workspace_edit.documentChanges
|
||||
endif
|
||||
|
||||
for l:text_document_edit in l:document_changes
|
||||
let l:filename = l:text_document_edit.textDocument.uri
|
||||
let l:edits = l:text_document_edit.edits
|
||||
let l:changes[l:filename] = l:edits
|
||||
endfor
|
||||
endif
|
||||
|
||||
return l:changes
|
||||
endfunction
|
||||
|
||||
function! ale#rename#HandleLSPResponse(conn_id, response) abort
|
||||
if has_key(a:response, 'id')
|
||||
\&& has_key(s:rename_map, a:response.id)
|
||||
|
@ -126,7 +101,7 @@ function! ale#rename#HandleLSPResponse(conn_id, response) abort
|
|||
return
|
||||
endif
|
||||
|
||||
let l:changes_map = s:getChanges(a:response.result)
|
||||
let l:changes_map = ale#code_action#GetChanges(a:response.result)
|
||||
|
||||
if empty(l:changes_map)
|
||||
call s:message('No changes received from server')
|
||||
|
@ -134,34 +109,7 @@ function! ale#rename#HandleLSPResponse(conn_id, response) abort
|
|||
return
|
||||
endif
|
||||
|
||||
let l:changes = []
|
||||
|
||||
for l:file_name in keys(l:changes_map)
|
||||
let l:text_edits = l:changes_map[l:file_name]
|
||||
let l:text_changes = []
|
||||
|
||||
for l:edit in l:text_edits
|
||||
let l:range = l:edit.range
|
||||
let l:new_text = l:edit.newText
|
||||
|
||||
call add(l:text_changes, {
|
||||
\ 'start': {
|
||||
\ 'line': l:range.start.line + 1,
|
||||
\ 'offset': l:range.start.character + 1,
|
||||
\ },
|
||||
\ 'end': {
|
||||
\ 'line': l:range.end.line + 1,
|
||||
\ 'offset': l:range.end.character + 1,
|
||||
\ },
|
||||
\ 'newText': l:new_text,
|
||||
\})
|
||||
endfor
|
||||
|
||||
call add(l:changes, {
|
||||
\ 'fileName': ale#path#FromURI(l:file_name),
|
||||
\ 'textChanges': l:text_changes,
|
||||
\})
|
||||
endfor
|
||||
let l:changes = ale#code_action#BuildChangesList(l:changes_map)
|
||||
|
||||
call ale#code_action#HandleCodeAction(
|
||||
\ {
|
||||
|
|
23
doc/ale.txt
23
doc/ale.txt
|
@ -20,6 +20,7 @@ CONTENTS *ale-contents*
|
|||
5.4 Find References...................|ale-find-references|
|
||||
5.5 Hovering..........................|ale-hover|
|
||||
5.6 Symbol Search.....................|ale-symbol-search|
|
||||
5.7 Refactoring: Rename, Actions......|ale-refactor|
|
||||
6. Global Options.......................|ale-options|
|
||||
6.1 Highlights........................|ale-highlights|
|
||||
7. Linter/Fixer Options.................|ale-integration-options|
|
||||
|
@ -669,6 +670,16 @@ ALE supports searching for workspace symbols via LSP linters with the
|
|||
|ALESymbolSearch| command. See the documentation for the command
|
||||
for a full list of options.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
5.7 Refactoring: Rename, Actions *ale-refactor*
|
||||
|
||||
ALE supports renaming symbols in 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.
|
||||
|
||||
|
||||
===============================================================================
|
||||
6. Global Options *ale-options*
|
||||
|
||||
|
@ -3107,6 +3118,18 @@ ALERename *ALERename*
|
|||
in those files will not be updated.
|
||||
|
||||
|
||||
ALECodeAction *ALECodeAction*
|
||||
|
||||
Apply a code action via LSP servers or `tsserver`.
|
||||
|
||||
If there is an error present on a line that can be fixed, ALE will
|
||||
automatically fix a line, unless there are multiple possible code fixes to
|
||||
apply.
|
||||
|
||||
This command can be run in visual mode apply actions, such as applicable
|
||||
refactors. A menu will be shown to select code action to apply.
|
||||
|
||||
|
||||
ALERepeatSelection *ALERepeatSelection*
|
||||
|
||||
Repeat the last selection displayed in the preview window.
|
||||
|
|
|
@ -240,6 +240,9 @@ command! -bar ALEImport :call ale#completion#Import()
|
|||
" Rename symbols using tsserver and LSP
|
||||
command! -bar -bang ALERename :call ale#rename#Execute({'force_save': '<bang>' is# '!'})
|
||||
|
||||
" Apply code actions to a range.
|
||||
command! -bar -range ALECodeAction :call ale#codefix#Execute(<range>)
|
||||
|
||||
" Organize import statements using tsserver
|
||||
command! -bar ALEOrganizeImports :call ale#organize_imports#Execute()
|
||||
|
||||
|
@ -283,6 +286,7 @@ nnoremap <silent> <Plug>(ale_documentation) :ALEDocumentation<Return>
|
|||
inoremap <silent> <Plug>(ale_complete) <C-\><C-O>:ALEComplete<Return>
|
||||
nnoremap <silent> <Plug>(ale_import) :ALEImport<Return>
|
||||
nnoremap <silent> <Plug>(ale_rename) :ALERename<Return>
|
||||
nnoremap <silent> <Plug>(ale_code_action) :ALECodeAction<Return>
|
||||
nnoremap <silent> <Plug>(ale_repeat_selection) :ALERepeatSelection<Return>
|
||||
|
||||
" Set up autocmd groups now.
|
||||
|
|
|
@ -23,6 +23,7 @@ Before:
|
|||
\ 'completion_trigger_characters': [],
|
||||
\ 'definition': 0,
|
||||
\ 'symbol_search': 0,
|
||||
\ 'code_actions': 0,
|
||||
\ },
|
||||
\}
|
||||
|
||||
|
@ -102,6 +103,7 @@ Execute(Capabilities should bet set up correctly):
|
|||
\ 'definition': 1,
|
||||
\ 'symbol_search': 1,
|
||||
\ 'rename': 1,
|
||||
\ 'code_actions': 1,
|
||||
\ },
|
||||
\ b:conn.capabilities
|
||||
AssertEqual [[1, 'initialized', {}]], g:message_list
|
||||
|
@ -125,7 +127,7 @@ Execute(Disabled capabilities should be recognised correctly):
|
|||
\ 'referencesProvider': v:false,
|
||||
\ 'textDocumentSync': 2,
|
||||
\ 'documentFormattingProvider': v:true,
|
||||
\ 'codeActionProvider': v:true,
|
||||
\ 'codeActionProvider': v:false,
|
||||
\ 'signatureHelpProvider': {
|
||||
\ 'triggerCharacters': ['(', ','],
|
||||
\ },
|
||||
|
@ -146,6 +148,7 @@ Execute(Disabled capabilities should be recognised correctly):
|
|||
\ 'definition': 0,
|
||||
\ 'symbol_search': 0,
|
||||
\ 'rename': 0,
|
||||
\ 'code_actions': 0,
|
||||
\ },
|
||||
\ b:conn.capabilities
|
||||
AssertEqual [[1, 'initialized', {}]], g:message_list
|
||||
|
@ -197,6 +200,7 @@ Execute(Capabilities should be enabled when send as Dictionaries):
|
|||
\ 'typeDefinition': 1,
|
||||
\ 'symbol_search': 1,
|
||||
\ 'rename': 1,
|
||||
\ 'code_actions': 1,
|
||||
\ },
|
||||
\ b:conn.capabilities
|
||||
AssertEqual [[1, 'initialized', {}]], g:message_list
|
||||
|
|
|
@ -3,6 +3,9 @@ Before:
|
|||
|
||||
let g:ale_enabled = 0
|
||||
|
||||
" Enable fix end-of-line as tests below expect that
|
||||
set fixeol
|
||||
|
||||
runtime autoload/ale/code_action.vim
|
||||
runtime autoload/ale/util.vim
|
||||
|
||||
|
@ -211,6 +214,7 @@ Execute(End of file can be modified):
|
|||
\)
|
||||
|
||||
AssertEqual g:test.text + [
|
||||
\ '',
|
||||
\ 'type A: string',
|
||||
\ 'type B: number',
|
||||
\ '',
|
||||
|
|
59
test/test_code_action_python.vader
Normal file
59
test/test_code_action_python.vader
Normal 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)
|
604
test/test_codefix.vader
Normal file
604
test/test_codefix.vader
Normal file
|
@ -0,0 +1,604 @@
|
|||
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, 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#HandleTSServerResponse'')',
|
||||
\ string(g:Callback)
|
||||
AssertEqual
|
||||
\ [
|
||||
\ ale#lsp#tsserver_message#Change(bufnr('')),
|
||||
\ [0, 'ts@getCodeFixes', {
|
||||
\ 'startLine': 2,
|
||||
\ 'startOffset': 5,
|
||||
\ 'endLine': 2,
|
||||
\ 'endOffset': 6,
|
||||
\ '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': 5}, {'lnum': 2, 'col': 5, 'code': 2304}]}}
|
||||
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#HandleTSServerResponse'')',
|
||||
\ string(g:Callback)
|
||||
AssertEqual
|
||||
\ [
|
||||
\ ale#lsp#tsserver_message#Change(bufnr('')),
|
||||
\ [0, 'ts@getCodeFixes', {
|
||||
\ 'startLine': 2,
|
||||
\ 'startOffset': 5,
|
||||
\ 'endLine': 2,
|
||||
\ 'endOffset': 6,
|
||||
\ '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
|
||||
|
||||
" TODO: I can't figure out how to run ALECodeAction on range
|
||||
" in test function. Therefore I can't write properly working
|
||||
" test. If somebody knows how to do that help is appreciated.
|
||||
"
|
||||
" Execute(tsserver getApplicableRefactors requests should be sent):
|
||||
" call ale#linter#Reset()
|
||||
"
|
||||
" runtime ale_linters/typescript/tsserver.vim
|
||||
" let g:ale_buffer_info = {bufnr(''): {'loclist': []}}
|
||||
" call setpos('.', [bufnr(''), 2, 5, 0])
|
||||
" normal "v$"
|
||||
"
|
||||
" execute "ALECodeAction"
|
||||
"
|
||||
" " 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@getApplicableRefactors', {
|
||||
" \ 'startLine': 2,
|
||||
" \ 'startOffset': 5,
|
||||
" \ 'endLine': 2,
|
||||
" \ 'endOffset': 15,
|
||||
" \ 'file': expand('%:p'),
|
||||
" \ }]
|
||||
" \ ],
|
||||
" \ g:message_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
|
||||
\ [
|
||||
\ [1, 'workspace/didChangeConfiguration', {'settings': {'python': {}}}],
|
||||
\ [1, 'textDocument/didChange', {
|
||||
\ 'contentChanges': [{'text': "def main():\n a = 1\n b = a + 2\n"}],
|
||||
\ 'textDocument': {
|
||||
\ 'uri': ale#path#ToURI(expand('%:p')),
|
||||
\ 'version': g:ale_lsp_next_version_id - 1,
|
||||
\ },
|
||||
\ }],
|
||||
\ [0, 'textDocument/codeAction', {
|
||||
\ 'context': {
|
||||
\ 'diagnostics': [{'range': {'end': {'character': 5, '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
|
||||
|
||||
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
|
||||
\ [
|
||||
\ [1, 'workspace/didChangeConfiguration', {'settings': {'python': {}}}],
|
||||
\ [1, 'textDocument/didChange', {
|
||||
\ 'contentChanges': [{'text': "def main():\n a = 1\n b = a + 2\n"}],
|
||||
\ 'textDocument': {
|
||||
\ 'uri': ale#path#ToURI(expand('%:p')),
|
||||
\ 'version': g:ale_lsp_next_version_id - 1,
|
||||
\ },
|
||||
\ }],
|
||||
\ [0, 'textDocument/codeAction', {
|
||||
\ 'context': {
|
||||
\ 'diagnostics': [{'range': {'end': {'character': 5, '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
|
Reference in a new issue