merging master

This commit is contained in:
toastal
2020-12-21 10:26:09 +07:00
71 changed files with 2491 additions and 155 deletions

View File

@@ -2,23 +2,39 @@
" Description: balloonexpr support for ALE.
function! ale#balloon#MessageForPos(bufnr, lnum, col) abort
let l:set_balloons = ale#Var(a:bufnr, 'set_balloons')
let l:show_problems = 0
let l:show_hover = 0
if l:set_balloons is 1
let l:show_problems = 1
let l:show_hover = 1
elseif l:set_balloons is# 'hover'
let l:show_hover = 1
endif
" Don't show balloons if they are disabled, or linting is disabled.
if !ale#Var(a:bufnr, 'set_balloons')
if !(l:show_problems || l:show_hover)
\|| !g:ale_enabled
\|| !getbufvar(a:bufnr, 'ale_enabled', 1)
return ''
endif
let l:loclist = get(g:ale_buffer_info, a:bufnr, {'loclist': []}).loclist
let l:index = ale#util#BinarySearch(l:loclist, a:bufnr, a:lnum, a:col)
if l:show_problems
let l:loclist = get(g:ale_buffer_info, a:bufnr, {'loclist': []}).loclist
let l:index = ale#util#BinarySearch(l:loclist, a:bufnr, a:lnum, a:col)
endif
" Show the diagnostics message if found, 'Hover' output otherwise
if l:index >= 0
if l:show_problems && l:index >= 0
return l:loclist[l:index].text
elseif exists('*balloon_show') || getbufvar(
\ a:bufnr,
\ 'ale_set_balloons_legacy_echo',
\ get(g:, 'ale_set_balloons_legacy_echo', 0)
elseif l:show_hover && (
\ exists('*balloon_show')
\ || getbufvar(
\ a:bufnr,
\ 'ale_set_balloons_legacy_echo',
\ get(g:, 'ale_set_balloons_legacy_echo', 0)
\ )
\)
" Request LSP/tsserver hover information, but only if this version of
" Vim supports the balloon_show function, or if we turned a legacy

View File

@@ -1,28 +1,24 @@
" Author: Jerko Steiner <jerko.steiner@gmail.com>
" Description: Code action support for LSP / tsserver
function! ale#code_action#ReloadBuffer() abort
let l:buffer = bufnr('')
execute 'augroup ALECodeActionReloadGroup' . l:buffer
autocmd!
augroup END
silent! execute 'augroup! ALECodeActionReloadGroup' . l:buffer
call ale#util#Execute(':e!')
endfunction
function! ale#code_action#HandleCodeAction(code_action, options) abort
let l:current_buffer = bufnr('')
let l:changes = a:code_action.changes
let l:should_save = get(a:options, 'should_save')
let l:force_save = get(a:options, 'force_save')
let l:safe_changes = []
for l:file_code_edit in l:changes
let l:buf = bufnr(l:file_code_edit.fileName)
if l:buf != -1 && l:buf != l:current_buffer && getbufvar(l:buf, '&mod')
if !l:force_save
call ale#util#Execute('echom ''Aborting action, file is unsaved''')
return
endif
else
call add(l:safe_changes, l:file_code_edit)
endif
endfor
for l:file_code_edit in l:safe_changes
call ale#code_action#ApplyChanges(
\ l:file_code_edit.fileName,
\ l:file_code_edit.textChanges,
@@ -85,29 +81,14 @@ function! ale#code_action#ApplyChanges(filename, changes, should_save) abort
let l:pos = [1, 1]
endif
" We have to keep track of how many lines we have added, and offset
" changes accordingly.
let l:line_offset = 0
let l:column_offset = 0
let l:last_end_line = 0
" Changes have to be sorted so we apply them from top-to-bottom.
for l:code_edit in sort(copy(a:changes), function('s:ChangeCmp'))
if l:code_edit.start.line isnot l:last_end_line
let l:column_offset = 0
endif
let l:line = l:code_edit.start.line + l:line_offset
let l:column = l:code_edit.start.offset + l:column_offset
let l:end_line = l:code_edit.end.line + l:line_offset
let l:end_column = l:code_edit.end.offset + l:column_offset
" Changes have to be sorted so we apply them from bottom-to-top
for l:code_edit in reverse(sort(copy(a:changes), function('s:ChangeCmp')))
let l:line = l:code_edit.start.line
let l:column = l:code_edit.start.offset
let l:end_line = l:code_edit.end.line
let l:end_column = l:code_edit.end.offset
let l:text = l:code_edit.newText
let l:cur_line = l:pos[0]
let l:cur_column = l:pos[1]
let l:last_end_line = l:end_line
" Adjust the ends according to previous edits.
if l:end_line > len(l:lines)
let l:end_line_len = 0
@@ -125,6 +106,12 @@ function! ale#code_action#ApplyChanges(filename, changes, should_save) abort
let l:start = l:lines[: l:line - 2]
endif
" Special case when text must be added after new line
if l:column > len(l:lines[l:line - 1])
call extend(l:start, [l:lines[l:line - 1]])
let l:column = 1
endif
if l:column is 1
" We need to handle column 1 specially, because we can't slice an
" empty string ending on index 0.
@@ -134,13 +121,17 @@ function! ale#code_action#ApplyChanges(filename, changes, should_save) abort
endif
call extend(l:middle, l:insertions[1:])
let l:middle[-1] .= l:lines[l:end_line - 1][l:end_column - 1 :]
if l:end_line <= len(l:lines)
" Only extend the last line if end_line is within the range of
" lines.
let l:middle[-1] .= l:lines[l:end_line - 1][l:end_column - 1 :]
endif
let l:lines_before_change = len(l:lines)
let l:lines = l:start + l:middle + l:lines[l:end_line :]
let l:current_line_offset = len(l:lines) - l:lines_before_change
let l:line_offset += l:current_line_offset
let l:column_offset = len(l:middle[-1]) - l:end_line_len
let l:pos = s:UpdateCursor(l:pos,
@@ -166,6 +157,20 @@ function! ale#code_action#ApplyChanges(filename, changes, should_save) abort
call setpos('.', [0, l:pos[0], l:pos[1], 0])
endif
if a:should_save && l:buffer > 0 && !l:is_current_buffer
" Set up a one-time use event that will delete itself to reload the
" buffer next time it's entered to view the changes made to it.
execute 'augroup ALECodeActionReloadGroup' . l:buffer
autocmd!
execute printf(
\ 'autocmd BufEnter <buffer=%d>'
\ . ' call ale#code_action#ReloadBuffer()',
\ l:buffer
\)
augroup END
endif
endfunction
function! s:UpdateCursor(cursor, start, end, offset) abort
@@ -215,3 +220,163 @@ function! s:UpdateCursor(cursor, start, end, offset) abort
return [l:cur_line, l:cur_column]
endfunction
function! ale#code_action#GetChanges(workspace_edit) abort
let l:changes = {}
if has_key(a:workspace_edit, 'changes') && !empty(a:workspace_edit.changes)
return a:workspace_edit.changes
elseif has_key(a:workspace_edit, 'documentChanges')
let l:document_changes = []
if type(a:workspace_edit.documentChanges) is v:t_dict
\ && has_key(a:workspace_edit.documentChanges, 'edits')
call add(l:document_changes, a:workspace_edit.documentChanges)
elseif type(a:workspace_edit.documentChanges) is v:t_list
let l:document_changes = a:workspace_edit.documentChanges
endif
for l:text_document_edit in l:document_changes
let l:filename = l:text_document_edit.textDocument.uri
let l:edits = l:text_document_edit.edits
let l:changes[l:filename] = l:edits
endfor
endif
return l:changes
endfunction
function! ale#code_action#BuildChangesList(changes_map) abort
let l:changes = []
for l:file_name in keys(a:changes_map)
let l:text_edits = a:changes_map[l:file_name]
let l:text_changes = []
for l:edit in l:text_edits
let l:range = l:edit.range
let l:new_text = l:edit.newText
call add(l:text_changes, {
\ 'start': {
\ 'line': l:range.start.line + 1,
\ 'offset': l:range.start.character + 1,
\ },
\ 'end': {
\ 'line': l:range.end.line + 1,
\ 'offset': l:range.end.character + 1,
\ },
\ 'newText': l:new_text,
\})
endfor
call add(l:changes, {
\ 'fileName': ale#path#FromURI(l:file_name),
\ 'textChanges': l:text_changes,
\})
endfor
return l:changes
endfunction
function! s:EscapeMenuName(text) abort
return substitute(a:text, '\\\| \|\.\|&', '\\\0', 'g')
endfunction
function! s:UpdateMenu(data, menu_items) abort
silent! aunmenu PopUp.Refactor\.\.\.
if empty(a:data)
return
endif
for [l:type, l:item] in a:menu_items
let l:name = l:type is# 'tsserver' ? l:item.name : l:item.title
let l:func_name = l:type is# 'tsserver'
\ ? 'ale#codefix#ApplyTSServerCodeAction'
\ : 'ale#codefix#ApplyLSPCodeAction'
execute printf(
\ 'anoremenu <silent> PopUp.&Refactor\.\.\..%s'
\ . ' :call %s(%s, %s)<CR>',
\ s:EscapeMenuName(l:name),
\ l:func_name,
\ string(a:data),
\ string(l:item),
\)
endfor
if empty(a:menu_items)
silent! anoremenu PopUp.Refactor\.\.\..(None) :silent
endif
endfunction
function! s:GetCodeActions(linter, options) abort
let l:buffer = bufnr('')
let [l:line, l:column] = getpos('.')[1:2]
let l:column = min([l:column, len(getline(l:line))])
let l:location = {
\ 'buffer': l:buffer,
\ 'line': l:line,
\ 'column': l:column,
\ 'end_line': l:line,
\ 'end_column': l:column,
\}
let l:Callback = function('s:OnReady', [l:location, a:options])
call ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback)
endfunction
function! ale#code_action#GetCodeActions(options) abort
silent! aunmenu PopUp.Rename
silent! aunmenu PopUp.Refactor\.\.\.
" Only display the menu items if there's an LSP server.
let l:has_lsp = 0
for l:linter in ale#linter#Get(&filetype)
if !empty(l:linter.lsp)
let l:has_lsp = 1
break
endif
endfor
if l:has_lsp
if !empty(expand('<cword>'))
silent! anoremenu <silent> PopUp.Rename :ALERename<CR>
endif
silent! anoremenu <silent> PopUp.Refactor\.\.\..(None) :silent<CR>
call ale#codefix#Execute(
\ mode() is# 'v' || mode() is# "\<C-V>",
\ function('s:UpdateMenu')
\)
endif
endfunction
function! s:Setup(enabled) abort
augroup ALECodeActionsGroup
autocmd!
if a:enabled
autocmd MenuPopup * :call ale#code_action#GetCodeActions({})
endif
augroup END
if !a:enabled
silent! augroup! ALECodeActionsGroup
silent! aunmenu PopUp.Rename
silent! aunmenu PopUp.Refactor\.\.\.
endif
endfunction
function! ale#code_action#EnablePopUpMenu() abort
call s:Setup(1)
endfunction
function! ale#code_action#DisablePopUpMenu() abort
call s:Setup(0)
endfunction

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

@@ -0,0 +1,484 @@
" Author: Dalius Dobravolskas <dalius.dobravolskas@gmail.com>
" Description: Code Fix support for tsserver and LSP servers
let s:codefix_map = {}
" Used to get the codefix map in tests.
function! ale#codefix#GetMap() abort
return deepcopy(s:codefix_map)
endfunction
" Used to set the codefix map in tests.
function! ale#codefix#SetMap(map) abort
let s:codefix_map = a:map
endfunction
function! ale#codefix#ClearLSPData() abort
let s:codefix_map = {}
endfunction
function! s:message(message) abort
call ale#util#Execute('echom ' . string(a:message))
endfunction
function! ale#codefix#ApplyTSServerCodeAction(data, item) abort
if has_key(a:item, 'changes')
let l:changes = a:item.changes
call ale#code_action#HandleCodeAction(
\ {
\ 'description': 'codefix',
\ 'changes': l:changes,
\ },
\ {},
\)
else
let l:message = ale#lsp#tsserver_message#GetEditsForRefactor(
\ a:data.buffer,
\ a:data.line,
\ a:data.column,
\ a:data.end_line,
\ a:data.end_column,
\ a:item.id[0],
\ a:item.id[1],
\)
let l:request_id = ale#lsp#Send(a:data.connection_id, l:message)
let s:codefix_map[l:request_id] = a:data
endif
endfunction
function! ale#codefix#HandleTSServerResponse(conn_id, response) abort
if !has_key(a:response, 'request_seq')
\ || !has_key(s:codefix_map, a:response.request_seq)
return
endif
let l:data = remove(s:codefix_map, a:response.request_seq)
let l:MenuCallback = get(l:data, 'menu_callback', v:null)
if get(a:response, 'command', '') is# 'getCodeFixes'
if get(a:response, 'success', v:false) is v:false
\&& l:MenuCallback is v:null
let l:message = get(a:response, 'message', 'unknown')
call s:message('Error while getting code fixes. Reason: ' . l:message)
return
endif
let l:result = get(a:response, 'body', [])
call filter(l:result, 'has_key(v:val, ''changes'')')
if l:MenuCallback isnot v:null
call l:MenuCallback(
\ l:data,
\ map(copy(l:result), '[''tsserver'', v:val]')
\)
return
endif
if len(l:result) == 0
call s:message('No code fixes available.')
return
endif
let l:code_fix_to_apply = 0
if len(l:result) == 1
let l:code_fix_to_apply = 1
else
let l:codefix_no = 1
let l:codefixstring = "Code Fixes:\n"
for l:codefix in l:result
let l:codefixstring .= l:codefix_no . ') '
\ . l:codefix.description . "\n"
let l:codefix_no += 1
endfor
let l:codefixstring .= 'Type number and <Enter> (empty cancels): '
let l:code_fix_to_apply = ale#util#Input(l:codefixstring, '')
let l:code_fix_to_apply = str2nr(l:code_fix_to_apply)
if l:code_fix_to_apply == 0
return
endif
endif
call ale#codefix#ApplyTSServerCodeAction(
\ l:data,
\ l:result[l:code_fix_to_apply - 1],
\)
elseif get(a:response, 'command', '') is# 'getApplicableRefactors'
if get(a:response, 'success', v:false) is v:false
\&& l:MenuCallback is v:null
let l:message = get(a:response, 'message', 'unknown')
call s:message('Error while getting applicable refactors. Reason: ' . l:message)
return
endif
let l:result = get(a:response, 'body', [])
if len(l:result) == 0
call s:message('No applicable refactors available.')
return
endif
let l:refactors = []
for l:item in l:result
for l:action in l:item.actions
call add(l:refactors, {
\ 'name': l:action.description,
\ 'id': [l:item.name, l:action.name],
\})
endfor
endfor
if l:MenuCallback isnot v:null
call l:MenuCallback(
\ l:data,
\ map(copy(l:refactors), '[''tsserver'', v:val]')
\)
return
endif
let l:refactor_no = 1
let l:refactorstring = "Applicable refactors:\n"
for l:refactor in l:refactors
let l:refactorstring .= l:refactor_no . ') '
\ . l:refactor.name . "\n"
let l:refactor_no += 1
endfor
let l:refactorstring .= 'Type number and <Enter> (empty cancels): '
let l:refactor_to_apply = ale#util#Input(l:refactorstring, '')
let l:refactor_to_apply = str2nr(l:refactor_to_apply)
if l:refactor_to_apply == 0
return
endif
let l:id = l:refactors[l:refactor_to_apply - 1].id
call ale#codefix#ApplyTSServerCodeAction(
\ l:data,
\ l:refactors[l:refactor_to_apply - 1],
\)
elseif get(a:response, 'command', '') is# 'getEditsForRefactor'
if get(a:response, 'success', v:false) is v:false
let l:message = get(a:response, 'message', 'unknown')
call s:message('Error while getting edits for refactor. Reason: ' . l:message)
return
endif
call ale#code_action#HandleCodeAction(
\ {
\ 'description': 'editsForRefactor',
\ 'changes': a:response.body.edits,
\ },
\ {},
\)
endif
endfunction
function! ale#codefix#ApplyLSPCodeAction(data, item) abort
if has_key(a:item, 'command')
\&& type(a:item.command) == v:t_dict
let l:command = a:item.command
let l:message = ale#lsp#message#ExecuteCommand(
\ l:command.command,
\ l:command.arguments,
\)
let l:request_id = ale#lsp#Send(a:data.connection_id, l:message)
elseif has_key(a:item, 'edit') || has_key(a:item, 'arguments')
if has_key(a:item, 'edit')
let l:topass = a:item.edit
else
let l:topass = a:item.arguments[0]
endif
let l:changes_map = ale#code_action#GetChanges(l:topass)
if empty(l:changes_map)
return
endif
let l:changes = ale#code_action#BuildChangesList(l:changes_map)
call ale#code_action#HandleCodeAction(
\ {
\ 'description': 'codeaction',
\ 'changes': l:changes,
\ },
\ {},
\)
endif
endfunction
function! ale#codefix#HandleLSPResponse(conn_id, response) abort
if has_key(a:response, 'method')
\ && a:response.method is# 'workspace/applyEdit'
\ && has_key(a:response, 'params')
let l:params = a:response.params
let l:changes_map = ale#code_action#GetChanges(l:params.edit)
if empty(l:changes_map)
return
endif
let l:changes = ale#code_action#BuildChangesList(l:changes_map)
call ale#code_action#HandleCodeAction(
\ {
\ 'description': 'applyEdit',
\ 'changes': l:changes,
\ },
\ {}
\)
elseif has_key(a:response, 'id')
\&& has_key(s:codefix_map, a:response.id)
let l:data = remove(s:codefix_map, a:response.id)
let l:MenuCallback = get(l:data, 'menu_callback', v:null)
let l:result = get(a:response, 'result')
if type(l:result) != v:t_list
let l:result = []
endif
" Send the results to the menu callback, if set.
if l:MenuCallback isnot v:null
call l:MenuCallback(map(copy(l:result), '[''lsp'', v:val]'))
return
endif
if len(l:result) == 0
call s:message('No code actions received from server')
return
endif
let l:codeaction_no = 1
let l:codeactionstring = "Code Fixes:\n"
for l:codeaction in l:result
let l:codeactionstring .= l:codeaction_no . ') '
\ . l:codeaction.title . "\n"
let l:codeaction_no += 1
endfor
let l:codeactionstring .= 'Type number and <Enter> (empty cancels): '
let l:codeaction_to_apply = ale#util#Input(l:codeactionstring, '')
let l:codeaction_to_apply = str2nr(l:codeaction_to_apply)
if l:codeaction_to_apply == 0
return
endif
let l:item = l:result[l:codeaction_to_apply - 1]
call ale#codefix#ApplyLSPCodeAction(l:data, l:item)
endif
endfunction
function! s:FindError(buffer, line, column, end_line, end_column) abort
let l:nearest_error = v:null
if a:line == a:end_line
\&& a:column == a:end_column
\&& has_key(g:ale_buffer_info, a:buffer)
let l:nearest_error_diff = -1
for l:error in get(g:ale_buffer_info[a:buffer], 'loclist', [])
if has_key(l:error, 'code') && l:error.lnum == a:line
let l:diff = abs(l:error.col - a:column)
if l:nearest_error_diff == -1 || l:diff < l:nearest_error_diff
let l:nearest_error_diff = l:diff
let l:nearest_error = l:error
endif
endif
endfor
endif
return l:nearest_error
endfunction
function! s:OnReady(
\ line,
\ column,
\ end_line,
\ end_column,
\ MenuCallback,
\ linter,
\ lsp_details,
\) abort
let l:id = a:lsp_details.connection_id
if !ale#lsp#HasCapability(l:id, 'code_actions')
return
endif
let l:buffer = a:lsp_details.buffer
if a:linter.lsp is# 'tsserver'
let l:nearest_error =
\ s:FindError(l:buffer, a:line, a:column, a:end_line, a:end_column)
if l:nearest_error isnot v:null
let l:message = ale#lsp#tsserver_message#GetCodeFixes(
\ l:buffer,
\ a:line,
\ a:column,
\ a:line,
\ a:column,
\ [l:nearest_error.code],
\)
else
let l:message = ale#lsp#tsserver_message#GetApplicableRefactors(
\ l:buffer,
\ a:line,
\ a:column,
\ a:end_line,
\ a:end_column,
\)
endif
else
" Send a message saying the buffer has changed first, otherwise
" completions won't know what text is nearby.
call ale#lsp#NotifyForChanges(l:id, l:buffer)
let l:diagnostics = []
let l:nearest_error =
\ s:FindError(l:buffer, a:line, a:column, a:end_line, a:end_column)
if l:nearest_error isnot v:null
let l:diagnostics = [
\ {
\ 'code': l:nearest_error.code,
\ 'message': l:nearest_error.text,
\ 'range': {
\ 'start': {
\ 'line': l:nearest_error.lnum - 1,
\ 'character': l:nearest_error.col - 1,
\ },
\ 'end': {
\ 'line': l:nearest_error.end_lnum - 1,
\ 'character': l:nearest_error.end_col,
\ },
\ },
\ },
\]
endif
let l:message = ale#lsp#message#CodeAction(
\ l:buffer,
\ a:line,
\ a:column,
\ a:end_line,
\ a:end_column,
\ l:diagnostics,
\)
endif
let l:Callback = a:linter.lsp is# 'tsserver'
\ ? function('ale#codefix#HandleTSServerResponse')
\ : function('ale#codefix#HandleLSPResponse')
call ale#lsp#RegisterCallback(l:id, l:Callback)
let l:request_id = ale#lsp#Send(l:id, l:message)
let s:codefix_map[l:request_id] = {
\ 'connection_id': l:id,
\ 'buffer': l:buffer,
\ 'line': a:line,
\ 'column': a:column,
\ 'end_line': a:end_line,
\ 'end_column': a:end_column,
\ 'menu_callback': a:MenuCallback,
\}
endfunction
function! s:ExecuteGetCodeFix(linter, range, MenuCallback) abort
let l:buffer = bufnr('')
if a:range == 0
let [l:line, l:column] = getpos('.')[1:2]
let l:end_line = l:line
let l:end_column = l:column
" Expand the range to cover the current word, if there is one.
let l:cword = expand('<cword>')
if !empty(l:cword)
let l:search_pos = searchpos('\V' . l:cword, 'bn', l:line)
if l:search_pos != [0, 0]
let l:column = l:search_pos[1]
let l:end_column = l:column + len(l:cword) - 1
endif
endif
elseif mode() is# 'v' || mode() is# "\<C-V>"
" You need to get the start and end in a different way when you're in
" visual mode.
let [l:line, l:column] = getpos('v')[1:2]
let [l:end_line, l:end_column] = getpos('.')[1:2]
else
let [l:line, l:column] = getpos("'<")[1:2]
let [l:end_line, l:end_column] = getpos("'>")[1:2]
endif
let l:column = min([l:column, len(getline(l:line))])
let l:end_column = min([l:end_column, len(getline(l:end_line))])
let l:Callback = function(
\ 's:OnReady', [l:line, l:column, l:end_line, l:end_column, a:MenuCallback]
\)
call ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback)
endfunction
function! ale#codefix#Execute(range, ...) abort
if a:0 > 1
throw 'Too many arguments'
endif
let l:MenuCallback = get(a:000, 0, v:null)
let l:lsp_linters = []
for l:linter in ale#linter#Get(&filetype)
if !empty(l:linter.lsp)
call add(l:lsp_linters, l:linter)
endif
endfor
if empty(l:lsp_linters)
if l:MenuCallback is v:null
call s:message('No active LSPs')
else
call l:MenuCallback({}, [])
endif
return
endif
for l:lsp_linter in l:lsp_linters
call s:ExecuteGetCodeFix(l:lsp_linter, a:range, l:MenuCallback)
endfor
endfunction

View File

@@ -606,17 +606,21 @@ function! ale#completion#ParseLSPCompletions(response) abort
let l:doc = l:doc.value
endif
" Collapse whitespaces and line breaks into a single space.
let l:detail = substitute(get(l:item, 'detail', ''), '\_s\+', ' ', 'g')
let l:result = {
\ 'word': l:word,
\ 'kind': ale#completion#GetCompletionSymbols(get(l:item, 'kind', '')),
\ 'icase': 1,
\ 'menu': get(l:item, 'detail', ''),
\ 'menu': l:detail,
\ 'info': (type(l:doc) is v:t_string ? l:doc : ''),
\}
" This flag is used to tell if this completion came from ALE or not.
let l:user_data = {'_ale_completion_item': 1}
if has_key(l:item, 'additionalTextEdits')
\ && l:item.additionalTextEdits isnot v:null
let l:text_changes = []
for l:edit in l:item.additionalTextEdits

View File

@@ -12,6 +12,11 @@ let s:default_registry = {
\ 'suggested_filetypes': ['help'],
\ 'description': 'Align help tags to the right margin',
\ },
\ 'autoimport': {
\ 'function': 'ale#fixers#autoimport#Fix',
\ 'suggested_filetypes': ['python'],
\ 'description': 'Fix import issues with autoimport.',
\ },
\ 'autopep8': {
\ 'function': 'ale#fixers#autopep8#Fix',
\ 'suggested_filetypes': ['python'],
@@ -32,6 +37,11 @@ let s:default_registry = {
\ 'suggested_filetypes': ['d'],
\ 'description': 'Fix D files with dfmt.',
\ },
\ 'dhall': {
\ 'function': 'ale#fixers#dhall#Fix',
\ 'suggested_filetypes': ['dhall'],
\ 'description': 'Fix Dhall files with dhall-format.',
\ },
\ 'dhall-format': {
\ 'function': 'ale#fixers#dhall_format#Fix',
\ 'suggested_filetypes': ['dhall'],
@@ -121,6 +131,11 @@ let s:default_registry = {
\ 'suggested_filetypes': [],
\ 'description': 'Remove all trailing whitespace characters at the end of every line.',
\ },
\ 'yamlfix': {
\ 'function': 'ale#fixers#yamlfix#Fix',
\ 'suggested_filetypes': ['yaml'],
\ 'description': 'Fix yaml files with yamlfix.',
\ },
\ 'yapf': {
\ 'function': 'ale#fixers#yapf#Fix',
\ 'suggested_filetypes': ['python'],
@@ -391,6 +406,16 @@ let s:default_registry = {
\ 'suggested_filetypes': ['html', 'htmldjango'],
\ 'description': 'Fix HTML files with html-beautify.',
\ },
\ 'luafmt': {
\ 'function': 'ale#fixers#luafmt#Fix',
\ 'suggested_filetypes': ['lua'],
\ 'description': 'Fix Lua files with luafmt.',
\ },
\ 'ormolu': {
\ 'function': 'ale#fixers#ormolu#Fix',
\ 'suggested_filetypes': ['haskell'],
\ 'description': 'A formatter for Haskell source code.',
\ }
\}
" Reset the function registry to the default entries.

View File

@@ -0,0 +1,25 @@
" Author: lyz-code
" Description: Fixing Python imports with autoimport.
call ale#Set('python_autoimport_executable', 'autoimport')
call ale#Set('python_autoimport_options', '')
call ale#Set('python_autoimport_use_global', get(g:, 'ale_use_global_executables', 0))
function! ale#fixers#autoimport#Fix(buffer) abort
let l:options = ale#Var(a:buffer, 'python_autoimport_options')
let l:executable = ale#python#FindExecutable(
\ a:buffer,
\ 'python_autoimport',
\ ['autoimport'],
\)
if !executable(l:executable)
return 0
endif
return {
\ 'command': ale#path#BufferCdString(a:buffer)
\ . ale#Escape(l:executable) . (!empty(l:options) ? ' ' . l:options : '') . ' -',
\}
endfunction

View File

@@ -11,9 +11,6 @@ function! ale#fixers#gofmt#Fix(buffer) abort
return {
\ 'command': l:env . ale#Escape(l:executable)
\ . ' -l -w'
\ . (empty(l:options) ? '' : ' ' . l:options)
\ . ' %t',
\ 'read_temporary_file': 1,
\}
endfunction

View File

@@ -0,0 +1,13 @@
call ale#Set('lua_luafmt_executable', 'luafmt')
call ale#Set('lua_luafmt_options', '')
function! ale#fixers#luafmt#Fix(buffer) abort
let l:executable = ale#Var(a:buffer, 'lua_luafmt_executable')
let l:options = ale#Var(a:buffer, 'lua_luafmt_options')
return {
\ 'command': ale#Escape(l:executable)
\ . (empty(l:options) ? '' : ' ' . l:options)
\ . ' --stdin',
\}
endfunction

View File

@@ -0,0 +1,12 @@
call ale#Set('haskell_ormolu_executable', 'ormolu')
call ale#Set('haskell_ormolu_options', '')
function! ale#fixers#ormolu#Fix(buffer) abort
let l:executable = ale#Var(a:buffer, 'haskell_ormolu_executable')
let l:options = ale#Var(a:buffer, 'haskell_ormolu_options')
return {
\ 'command': ale#Escape(l:executable)
\ . (empty(l:options) ? '' : ' ' . l:options),
\}
endfunction

View File

@@ -2,6 +2,7 @@
" Description: Fixing files with phpcbf.
call ale#Set('php_phpcbf_standard', '')
call ale#Set('php_phpcbf_options', '')
call ale#Set('php_phpcbf_executable', 'phpcbf')
call ale#Set('php_phpcbf_use_global', get(g:, 'ale_use_global_executables', 0))
@@ -20,6 +21,6 @@ function! ale#fixers#phpcbf#Fix(buffer) abort
\ : ''
return {
\ 'command': ale#Escape(l:executable) . ' --stdin-path=%s ' . l:standard_option . ' -'
\ 'command': ale#Escape(l:executable) . ' --stdin-path=%s ' . l:standard_option . ale#Pad(ale#Var(a:buffer, 'php_phpcbf_options')) . ' -'
\}
endfunction

View File

@@ -0,0 +1,25 @@
" Author: lyz-code
" Description: Fixing yaml files with yamlfix.
call ale#Set('yaml_yamlfix_executable', 'yamlfix')
call ale#Set('yaml_yamlfix_options', '')
call ale#Set('yaml_yamlfix_use_global', get(g:, 'ale_use_global_executables', 0))
function! ale#fixers#yamlfix#Fix(buffer) abort
let l:options = ale#Var(a:buffer, 'yaml_yamlfix_options')
let l:executable = ale#python#FindExecutable(
\ a:buffer,
\ 'yaml_yamlfix',
\ ['yamlfix'],
\)
if !executable(l:executable)
return 0
endif
return {
\ 'command': ale#path#BufferCdString(a:buffer)
\ . ale#Escape(l:executable) . (!empty(l:options) ? ' ' . l:options : '') . ' -',
\}
endfunction

View File

@@ -5,6 +5,7 @@ let s:executables = [
\ 'node_modules/.bin/eslint_d',
\ 'node_modules/eslint/bin/eslint.js',
\ 'node_modules/.bin/eslint',
\ '.yarn/sdks/eslint/bin/eslint',
\]
let s:sep = has('win32') ? '\' : '/'

View File

@@ -1,8 +1,32 @@
" Author: w0rp <devw0rp@gmail.com>
" Description: This file adds support for using the shellcheck linter
" Shellcheck supports shell directives to define the shell dialect for scripts
" that do not have a shebang for some reason.
" https://github.com/koalaman/shellcheck/wiki/Directive#shell
function! ale#handlers#shellcheck#GetShellcheckDialectDirective(buffer) abort
let l:linenr = 0
let l:pattern = '\s\{-}#\s\{-}shellcheck\s\{-}shell=\(.*\)'
let l:possible_shell = ['bash', 'dash', 'ash', 'tcsh', 'csh', 'zsh', 'ksh', 'sh']
while l:linenr < min([50, line('$')])
let l:linenr += 1
let l:match = matchlist(getline(l:linenr), l:pattern)
if len(l:match) > 1 && index(l:possible_shell, l:match[1]) >= 0
return l:match[1]
endif
endwhile
return ''
endfunction
function! ale#handlers#shellcheck#GetDialectArgument(buffer) abort
let l:shell_type = ale#handlers#sh#GetShellType(a:buffer)
let l:shell_type = ale#handlers#shellcheck#GetShellcheckDialectDirective(a:buffer)
if empty(l:shell_type)
let l:shell_type = ale#handlers#sh#GetShellType(a:buffer)
endif
if !empty(l:shell_type)
" Use the dash dialect for /bin/ash, etc.

View File

@@ -24,6 +24,8 @@ function! ale#hover#HandleTSServerResponse(conn_id, response) abort
if get(a:response, 'success', v:false) is v:true
\&& get(a:response, 'body', v:null) isnot v:null
let l:set_balloons = ale#Var(l:options.buffer, 'set_balloons')
" If we pass the show_documentation flag, we should show the full
" documentation, and always in the preview window.
if get(l:options, 'show_documentation', 0)
@@ -40,7 +42,7 @@ function! ale#hover#HandleTSServerResponse(conn_id, response) abort
endif
elseif get(l:options, 'hover_from_balloonexpr', 0)
\&& exists('*balloon_show')
\&& ale#Var(l:options.buffer, 'set_balloons')
\&& (l:set_balloons is 1 || l:set_balloons is# 'hover')
call balloon_show(a:response.body.displayString)
elseif get(l:options, 'truncated_echo', 0)
call ale#cursor#TruncatedEcho(split(a:response.body.displayString, "\n")[0])
@@ -216,9 +218,11 @@ function! ale#hover#HandleLSPResponse(conn_id, response) abort
let [l:commands, l:lines] = ale#hover#ParseLSPResult(l:result.contents)
if !empty(l:lines)
let l:set_balloons = ale#Var(l:options.buffer, 'set_balloons')
if get(l:options, 'hover_from_balloonexpr', 0)
\&& exists('*balloon_show')
\&& ale#Var(l:options.buffer, 'set_balloons')
\&& (l:set_balloons is 1 || l:set_balloons is# 'hover')
call balloon_show(join(l:lines, "\n"))
elseif get(l:options, 'truncated_echo', 0)
call ale#cursor#TruncatedEcho(l:lines[0])

View File

@@ -44,6 +44,7 @@ function! ale#lsp#Register(executable_or_address, project, init_options) abort
\ 'definition': 0,
\ 'typeDefinition': 0,
\ 'symbol_search': 0,
\ 'code_actions': 0,
\ },
\}
endif
@@ -219,6 +220,14 @@ function! s:UpdateCapabilities(conn, capabilities) abort
let a:conn.capabilities.rename = 1
endif
if get(a:capabilities, 'codeActionProvider') is v:true
let a:conn.capabilities.code_actions = 1
endif
if type(get(a:capabilities, 'codeActionProvider')) is v:t_dict
let a:conn.capabilities.code_actions = 1
endif
if !empty(get(a:capabilities, 'completionProvider'))
let a:conn.capabilities.completion = 1
endif
@@ -350,6 +359,7 @@ function! ale#lsp#MarkConnectionAsTsserver(conn_id) abort
let l:conn.capabilities.definition = 1
let l:conn.capabilities.symbol_search = 1
let l:conn.capabilities.rename = 1
let l:conn.capabilities.code_actions = 1
endfunction
function! s:SendInitMessage(conn) abort

View File

@@ -172,3 +172,25 @@ function! ale#lsp#message#Rename(buffer, line, column, new_name) abort
\ 'newName': a:new_name,
\}]
endfunction
function! ale#lsp#message#CodeAction(buffer, line, column, end_line, end_column, diagnostics) abort
return [0, 'textDocument/codeAction', {
\ 'textDocument': {
\ 'uri': ale#path#ToURI(expand('#' . a:buffer . ':p')),
\ },
\ 'range': {
\ 'start': {'line': a:line - 1, 'character': a:column - 1},
\ 'end': {'line': a:end_line - 1, 'character': a:end_column},
\ },
\ 'context': {
\ 'diagnostics': a:diagnostics
\ },
\}]
endfunction
function! ale#lsp#message#ExecuteCommand(command, arguments) abort
return [0, 'workspace/executeCommand', {
\ 'command': a:command,
\ 'arguments': a:arguments,
\}]
endfunction

View File

@@ -56,6 +56,7 @@ function! ale#lsp#response#ReadDiagnostics(response) abort
endif
if has_key(l:diagnostic, 'relatedInformation')
\ && l:diagnostic.relatedInformation isnot v:null
let l:related = deepcopy(l:diagnostic.relatedInformation)
call map(l:related, {key, val ->
\ ale#path#FromURI(val.location.uri) .

View File

@@ -103,3 +103,39 @@ function! ale#lsp#tsserver_message#OrganizeImports(buffer) abort
\ },
\}]
endfunction
function! ale#lsp#tsserver_message#GetCodeFixes(buffer, line, column, end_line, end_column, error_codes) abort
" The lines and columns are 1-based.
" The errors codes must be a list of tsserver error codes to fix.
return [0, 'ts@getCodeFixes', {
\ 'startLine': a:line,
\ 'startOffset': a:column,
\ 'endLine': a:end_line,
\ 'endOffset': a:end_column + 1,
\ 'file': expand('#' . a:buffer . ':p'),
\ 'errorCodes': a:error_codes,
\}]
endfunction
function! ale#lsp#tsserver_message#GetApplicableRefactors(buffer, line, column, end_line, end_column) abort
" The arguments for this request can also be just 'line' and 'offset'
return [0, 'ts@getApplicableRefactors', {
\ 'startLine': a:line,
\ 'startOffset': a:column,
\ 'endLine': a:end_line,
\ 'endOffset': a:end_column + 1,
\ 'file': expand('#' . a:buffer . ':p'),
\}]
endfunction
function! ale#lsp#tsserver_message#GetEditsForRefactor(buffer, line, column, end_line, end_column, refactor, action) abort
return [0, 'ts@getEditsForRefactor', {
\ 'startLine': a:line,
\ 'startOffset': a:column,
\ 'endLine': a:end_line,
\ 'endOffset': a:end_column + 1,
\ 'file': expand('#' . a:buffer . ':p'),
\ 'refactor': a:refactor,
\ 'action': a:action,
\}]
endfunction

View File

@@ -85,36 +85,10 @@ function! ale#rename#HandleTSServerResponse(conn_id, response) abort
\ },
\ {
\ 'should_save': 1,
\ 'force_save': get(l:options, 'force_save'),
\ },
\)
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 +100,7 @@ function! ale#rename#HandleLSPResponse(conn_id, response) abort
return
endif
let l:changes_map = s:getChanges(a:response.result)
let l:changes_map = ale#code_action#GetChanges(a:response.result)
if empty(l:changes_map)
call s:message('No changes received from server')
@@ -134,34 +108,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(
\ {
@@ -170,7 +117,6 @@ function! ale#rename#HandleLSPResponse(conn_id, response) abort
\ },
\ {
\ 'should_save': 1,
\ 'force_save': get(l:options, 'force_save'),
\ },
\)
endif
@@ -229,7 +175,7 @@ function! s:ExecuteRename(linter, options) abort
call ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback)
endfunction
function! ale#rename#Execute(options) abort
function! ale#rename#Execute() abort
let l:lsp_linters = []
for l:linter in ale#linter#Get(&filetype)
@@ -257,7 +203,6 @@ function! ale#rename#Execute(options) abort
call s:ExecuteRename(l:lsp_linter, {
\ 'old_name': l:old_name,
\ 'new_name': l:new_name,
\ 'force_save': get(a:options, 'force_save') is 1,
\})
endfor
endfunction

View File

@@ -486,7 +486,7 @@ function! ale#util#Input(message, value) abort
endfunction
function! ale#util#HasBuflineApi() abort
return exists('*deletebufline') && exists('*setbufline')
return exists('*deletebufline') && exists('*appendbufline') && exists('*getpos') && exists('*setpos')
endfunction
" Sets buffer contents to lines
@@ -507,8 +507,11 @@ function! ale#util#SetBufferContents(buffer, lines) abort
" Use a Vim API for setting lines in other buffers, if available.
if l:has_bufline_api
call setbufline(a:buffer, 1, l:new_lines)
call deletebufline(a:buffer, l:first_line_to_remove, '$')
let l:save_cursor = getpos('.')
call deletebufline(a:buffer, 1, '$')
call appendbufline(a:buffer, 1, l:new_lines)
call deletebufline(a:buffer, 1, 1)
call setpos('.', l:save_cursor)
" Fall back on setting lines the old way, for the current buffer.
else
let l:old_line_length = line('$')