summaryrefslogtreecommitdiff
path: root/autoload
diff options
context:
space:
mode:
Diffstat (limited to 'autoload')
-rw-r--r--autoload/ale.vim2
-rw-r--r--autoload/ale/code_action.vim217
-rw-r--r--autoload/ale/codefix.vim484
-rw-r--r--autoload/ale/completion.vim3
-rw-r--r--autoload/ale/engine.vim83
-rw-r--r--autoload/ale/fix/registry.vim5
-rw-r--r--autoload/ale/fixers/luafmt.vim13
-rw-r--r--autoload/ale/handlers/eslint.vim1
-rw-r--r--autoload/ale/handlers/sh.vim22
-rw-r--r--autoload/ale/handlers/shellcheck.vim9
-rw-r--r--autoload/ale/lsp.vim10
-rw-r--r--autoload/ale/lsp/message.vim22
-rw-r--r--autoload/ale/lsp/response.vim1
-rw-r--r--autoload/ale/lsp/tsserver_message.vim36
-rw-r--r--autoload/ale/maven.vim51
-rw-r--r--autoload/ale/organize_imports.vim11
-rw-r--r--autoload/ale/rename.vim117
17 files changed, 924 insertions, 163 deletions
diff --git a/autoload/ale.vim b/autoload/ale.vim
index 84003993..3f59a6a4 100644
--- a/autoload/ale.vim
+++ b/autoload/ale.vim
@@ -157,7 +157,7 @@ function! ale#Queue(delay, ...) abort
endif
endfunction
-let s:current_ale_version = [2, 7, 0]
+let s:current_ale_version = [3, 0, 0]
" A function used to check for ALE features in files outside of the project.
function! ale#Has(feature) abort
diff --git a/autoload/ale/code_action.vim b/autoload/ale/code_action.vim
index 359bc0d6..6b808b34 100644
--- a/autoload/ale/code_action.vim
+++ b/autoload/ale/code_action.vim
@@ -1,26 +1,33 @@
" Author: Jerko Steiner <jerko.steiner@gmail.com>
" Description: Code action support for LSP / tsserver
-function! ale#code_action#HandleCodeAction(code_action, should_save) abort
+function! ale#code_action#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')
- call ale#util#Execute('echom ''Aborting action, file is unsaved''')
+ if !l:force_save
+ call ale#util#Execute('echom ''Aborting action, file is unsaved''')
- return
+ return
+ endif
+ else
+ call add(l:safe_changes, l:file_code_edit)
endif
endfor
- for l:file_code_edit in l:changes
+ for l:file_code_edit in l:safe_changes
call ale#code_action#ApplyChanges(
- \ l:file_code_edit.fileName,
- \ l:file_code_edit.textChanges,
- \ a:should_save,
- \ )
+ \ l:file_code_edit.fileName,
+ \ l:file_code_edit.textChanges,
+ \ l:should_save,
+ \)
endfor
endfunction
@@ -78,29 +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
@@ -118,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.
@@ -138,7 +136,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,
@@ -213,3 +210,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
diff --git a/autoload/ale/codefix.vim b/autoload/ale/codefix.vim
new file mode 100644
index 00000000..69bf36fa
--- /dev/null
+++ b/autoload/ale/codefix.vim
@@ -0,0 +1,484 @@
+" Author: Dalius Dobravolskas <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
diff --git a/autoload/ale/completion.vim b/autoload/ale/completion.vim
index ecd93600..48e9bf7c 100644
--- a/autoload/ale/completion.vim
+++ b/autoload/ale/completion.vim
@@ -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
@@ -1006,7 +1007,7 @@ function! ale#completion#HandleUserData(completed_item) abort
\|| l:source is# 'ale-import'
\|| l:source is# 'ale-omnifunc'
for l:code_action in get(l:user_data, 'code_actions', [])
- call ale#code_action#HandleCodeAction(l:code_action, v:false)
+ call ale#code_action#HandleCodeAction(l:code_action, {})
endfor
endif
diff --git a/autoload/ale/engine.vim b/autoload/ale/engine.vim
index 63195d0f..3cafa25c 100644
--- a/autoload/ale/engine.vim
+++ b/autoload/ale/engine.vim
@@ -444,7 +444,7 @@ function! s:RunJob(command, options) abort
return 1
endfunction
-function! s:StopCurrentJobs(buffer, clear_lint_file_jobs) abort
+function! s:StopCurrentJobs(buffer, clear_lint_file_jobs, linter_slots) abort
let l:info = get(g:ale_buffer_info, a:buffer, {})
call ale#command#StopJobs(a:buffer, 'linter')
@@ -453,13 +453,23 @@ function! s:StopCurrentJobs(buffer, clear_lint_file_jobs) abort
call ale#command#StopJobs(a:buffer, 'file_linter')
let l:info.active_linter_list = []
else
+ let l:lint_file_map = {}
+
+ " Use a previously computed map of `lint_file` values to find
+ " linters that are used for linting files.
+ for [l:lint_file, l:linter] in a:linter_slots
+ if l:lint_file is 1
+ let l:lint_file_map[l:linter.name] = 1
+ endif
+ endfor
+
" Keep jobs for linting files when we're only linting buffers.
- call filter(l:info.active_linter_list, 'get(v:val, ''lint_file'')')
+ call filter(l:info.active_linter_list, 'get(l:lint_file_map, v:val.name)')
endif
endfunction
function! ale#engine#Stop(buffer) abort
- call s:StopCurrentJobs(a:buffer, 1)
+ call s:StopCurrentJobs(a:buffer, 1, [])
endfunction
function! s:RemoveProblemsForDisabledLinters(buffer, linters) abort
@@ -562,6 +572,22 @@ function! s:RunLinter(buffer, linter, lint_file) abort
return 0
endfunction
+function! s:GetLintFileSlots(buffer, linters) abort
+ let l:linter_slots = []
+
+ for l:linter in a:linters
+ let l:LintFile = l:linter.lint_file
+
+ if type(l:LintFile) is v:t_func
+ let l:LintFile = l:LintFile(a:buffer)
+ endif
+
+ call add(l:linter_slots, [l:LintFile, l:linter])
+ endfor
+
+ return l:linter_slots
+endfunction
+
function! s:GetLintFileValues(slots, Callback) abort
let l:deferred_list = []
let l:new_slots = []
@@ -595,12 +621,18 @@ endfunction
function! s:RunLinters(
\ buffer,
+\ linters,
\ slots,
\ should_lint_file,
\ new_buffer,
-\ can_clear_results
\) abort
- let l:can_clear_results = a:can_clear_results
+ call s:StopCurrentJobs(a:buffer, a:should_lint_file, a:slots)
+ call s:RemoveProblemsForDisabledLinters(a:buffer, a:linters)
+
+ " We can only clear the results if we aren't checking the buffer.
+ let l:can_clear_results = !ale#engine#IsCheckingBuffer(a:buffer)
+
+ silent doautocmd <nomodeline> User ALELintPre
for [l:lint_file, l:linter] in a:slots
" Only run lint_file linters if we should.
@@ -631,36 +663,19 @@ endfunction
function! ale#engine#RunLinters(buffer, linters, should_lint_file) abort
" Initialise the buffer information if needed.
let l:new_buffer = ale#engine#InitBufferInfo(a:buffer)
- call s:StopCurrentJobs(a:buffer, a:should_lint_file)
- call s:RemoveProblemsForDisabledLinters(a:buffer, a:linters)
-
- " We can only clear the results if we aren't checking the buffer.
- let l:can_clear_results = !ale#engine#IsCheckingBuffer(a:buffer)
-
- silent doautocmd <nomodeline> User ALELintPre
-
- " Handle `lint_file` callbacks first.
- let l:linter_slots = []
-
- for l:linter in a:linters
- let l:LintFile = l:linter.lint_file
-
- if type(l:LintFile) is v:t_func
- let l:LintFile = l:LintFile(a:buffer)
- endif
- call add(l:linter_slots, [l:LintFile, l:linter])
- endfor
-
- call s:GetLintFileValues(l:linter_slots, {
- \ new_slots -> s:RunLinters(
- \ a:buffer,
- \ new_slots,
- \ a:should_lint_file,
- \ l:new_buffer,
- \ l:can_clear_results,
- \ )
- \})
+ call s:GetLintFileValues(
+ \ s:GetLintFileSlots(a:buffer, a:linters),
+ \ {
+ \ slots -> s:RunLinters(
+ \ a:buffer,
+ \ a:linters,
+ \ slots,
+ \ a:should_lint_file,
+ \ l:new_buffer,
+ \ )
+ \ }
+ \)
endfunction
" Clean up a buffer.
diff --git a/autoload/ale/fix/registry.vim b/autoload/ale/fix/registry.vim
index d71668f2..9ea5331b 100644
--- a/autoload/ale/fix/registry.vim
+++ b/autoload/ale/fix/registry.vim
@@ -375,6 +375,11 @@ let s:default_registry = {
\ 'suggested_filetypes': ['html', 'htmldjango'],
\ 'description': 'Fix HTML files with html-beautify.',
\ },
+\ 'luafmt': {
+\ 'function': 'ale#fixers#luafmt#Fix',
+\ 'suggested_filetypes': ['lua'],
+\ 'description': 'Fix Lua files with luafmt.',
+\ },
\ 'dhall': {
\ 'function': 'ale#fixers#dhall#Fix',
\ 'suggested_filetypes': ['dhall'],
diff --git a/autoload/ale/fixers/luafmt.vim b/autoload/ale/fixers/luafmt.vim
new file mode 100644
index 00000000..6cb9ef4a
--- /dev/null
+++ b/autoload/ale/fixers/luafmt.vim
@@ -0,0 +1,13 @@
+call ale#Set('lua_luafmt_executable', 'luafmt')
+call ale#Set('lua_luafmt_options', '')
+
+function! ale#fixers#luafmt#Fix(buffer) abort
+ let l:executable = ale#Var(a:buffer, 'lua_luafmt_executable')
+ let l:options = ale#Var(a:buffer, 'lua_luafmt_options')
+
+ return {
+ \ 'command': ale#Escape(l:executable)
+ \ . (empty(l:options) ? '' : ' ' . l:options)
+ \ . ' --stdin',
+ \}
+endfunction
diff --git a/autoload/ale/handlers/eslint.vim b/autoload/ale/handlers/eslint.vim
index e37d6902..b8610612 100644
--- a/autoload/ale/handlers/eslint.vim
+++ b/autoload/ale/handlers/eslint.vim
@@ -5,6 +5,7 @@ let s:executables = [
\ 'node_modules/.bin/eslint_d',
\ 'node_modules/eslint/bin/eslint.js',
\ 'node_modules/.bin/eslint',
+\ '.yarn/sdks/eslint/bin/eslint',
\]
let s:sep = has('win32') ? '\' : '/'
diff --git a/autoload/ale/handlers/sh.vim b/autoload/ale/handlers/sh.vim
index 1e50cb89..6ed9fea3 100644
--- a/autoload/ale/handlers/sh.vim
+++ b/autoload/ale/handlers/sh.vim
@@ -1,18 +1,28 @@
" Author: w0rp <devw0rp@gmail.com>
-" Get the shell type for a buffer, based on the hashbang line.
function! ale#handlers#sh#GetShellType(buffer) abort
- let l:bang_line = get(getbufline(a:buffer, 1), 0, '')
+ let l:shebang = get(getbufline(a:buffer, 1), 0, '')
let l:command = ''
- " Take the shell executable from the hashbang, if we can.
- if l:bang_line[:1] is# '#!'
+ " Take the shell executable from the shebang, if we can.
+ if l:shebang[:1] is# '#!'
" Remove options like -e, etc.
- let l:command = substitute(l:bang_line, ' --\?[a-zA-Z0-9]\+', '', 'g')
+ let l:command = substitute(l:shebang, ' --\?[a-zA-Z0-9]\+', '', 'g')
endif
- " If we couldn't find a hashbang, try the filetype
+ " With no shebang line, attempt to use Vim's buffer-local variables.
+ if l:command is# ''
+ if getbufvar(a:buffer, 'is_bash', 0)
+ let l:command = 'bash'
+ elseif getbufvar(a:buffer, 'is_sh', 0)
+ let l:command = 'sh'
+ elseif getbufvar(a:buffer, 'is_kornshell', 0)
+ let l:command = 'ksh'
+ endif
+ endif
+
+ " If we couldn't find a shebang, try the filetype
if l:command is# ''
let l:command = &filetype
endif
diff --git a/autoload/ale/handlers/shellcheck.vim b/autoload/ale/handlers/shellcheck.vim
index b16280f0..351d6d3f 100644
--- a/autoload/ale/handlers/shellcheck.vim
+++ b/autoload/ale/handlers/shellcheck.vim
@@ -13,15 +13,6 @@ function! ale#handlers#shellcheck#GetDialectArgument(buffer) abort
return l:shell_type
endif
- " If there's no hashbang, try using Vim's buffer variables.
- if getbufvar(a:buffer, 'is_bash', 0)
- return 'bash'
- elseif getbufvar(a:buffer, 'is_sh', 0)
- return 'sh'
- elseif getbufvar(a:buffer, 'is_kornshell', 0)
- return 'ksh'
- endif
-
return ''
endfunction
diff --git a/autoload/ale/lsp.vim b/autoload/ale/lsp.vim
index 7d99e9d2..cb0573aa 100644
--- a/autoload/ale/lsp.vim
+++ b/autoload/ale/lsp.vim
@@ -44,6 +44,7 @@ function! ale#lsp#Register(executable_or_address, project, init_options) abort
\ 'definition': 0,
\ 'typeDefinition': 0,
\ 'symbol_search': 0,
+ \ 'code_actions': 0,
\ },
\}
endif
@@ -219,6 +220,14 @@ function! s:UpdateCapabilities(conn, capabilities) abort
let a:conn.capabilities.rename = 1
endif
+ if get(a:capabilities, 'codeActionProvider') is v:true
+ let a:conn.capabilities.code_actions = 1
+ endif
+
+ if type(get(a:capabilities, 'codeActionProvider')) is v:t_dict
+ let a:conn.capabilities.code_actions = 1
+ endif
+
if !empty(get(a:capabilities, 'completionProvider'))
let a:conn.capabilities.completion = 1
endif
@@ -350,6 +359,7 @@ function! ale#lsp#MarkConnectionAsTsserver(conn_id) abort
let l:conn.capabilities.definition = 1
let l:conn.capabilities.symbol_search = 1
let l:conn.capabilities.rename = 1
+ let l:conn.capabilities.code_actions = 1
endfunction
function! s:SendInitMessage(conn) abort
diff --git a/autoload/ale/lsp/message.vim b/autoload/ale/lsp/message.vim
index 5b0cb8b7..38be4da6 100644
--- a/autoload/ale/lsp/message.vim
+++ b/autoload/ale/lsp/message.vim
@@ -172,3 +172,25 @@ function! ale#lsp#message#Rename(buffer, line, column, new_name) abort
\ 'newName': a:new_name,
\}]
endfunction
+
+function! ale#lsp#message#CodeAction(buffer, line, column, end_line, end_column, diagnostics) abort
+ return [0, 'textDocument/codeAction', {
+ \ 'textDocument': {
+ \ 'uri': ale#path#ToURI(expand('#' . a:buffer . ':p')),
+ \ },
+ \ 'range': {
+ \ 'start': {'line': a:line - 1, 'character': a:column - 1},
+ \ 'end': {'line': a:end_line - 1, 'character': a:end_column},
+ \ },
+ \ 'context': {
+ \ 'diagnostics': a:diagnostics
+ \ },
+ \}]
+endfunction
+
+function! ale#lsp#message#ExecuteCommand(command, arguments) abort
+ return [0, 'workspace/executeCommand', {
+ \ 'command': a:command,
+ \ 'arguments': a:arguments,
+ \}]
+endfunction
diff --git a/autoload/ale/lsp/response.vim b/autoload/ale/lsp/response.vim
index 30da77e1..a4f80980 100644
--- a/autoload/ale/lsp/response.vim
+++ b/autoload/ale/lsp/response.vim
@@ -56,6 +56,7 @@ function! ale#lsp#response#ReadDiagnostics(response) abort
endif
if has_key(l:diagnostic, 'relatedInformation')
+ \ && l:diagnostic.relatedInformation isnot v:null
let l:related = deepcopy(l:diagnostic.relatedInformation)
call map(l:related, {key, val ->
\ ale#path#FromURI(val.location.uri) .
diff --git a/autoload/ale/lsp/tsserver_message.vim b/autoload/ale/lsp/tsserver_message.vim
index b9fafaa0..3c1b47ed 100644
--- a/autoload/ale/lsp/tsserver_message.vim
+++ b/autoload/ale/lsp/tsserver_message.vim
@@ -103,3 +103,39 @@ function! ale#lsp#tsserver_message#OrganizeImports(buffer) abort
\ },
\}]
endfunction
+
+function! ale#lsp#tsserver_message#GetCodeFixes(buffer, line, column, end_line, end_column, error_codes) abort
+ " The lines and columns are 1-based.
+ " The errors codes must be a list of tsserver error codes to fix.
+ return [0, 'ts@getCodeFixes', {
+ \ 'startLine': a:line,
+ \ 'startOffset': a:column,
+ \ 'endLine': a:end_line,
+ \ 'endOffset': a:end_column + 1,
+ \ 'file': expand('#' . a:buffer . ':p'),
+ \ 'errorCodes': a:error_codes,
+ \}]
+endfunction
+
+function! ale#lsp#tsserver_message#GetApplicableRefactors(buffer, line, column, end_line, end_column) abort
+ " The arguments for this request can also be just 'line' and 'offset'
+ return [0, 'ts@getApplicableRefactors', {
+ \ 'startLine': a:line,
+ \ 'startOffset': a:column,
+ \ 'endLine': a:end_line,
+ \ 'endOffset': a:end_column + 1,
+ \ 'file': expand('#' . a:buffer . ':p'),
+ \}]
+endfunction
+
+function! ale#lsp#tsserver_message#GetEditsForRefactor(buffer, line, column, end_line, end_column, refactor, action) abort
+ return [0, 'ts@getEditsForRefactor', {
+ \ 'startLine': a:line,
+ \ 'startOffset': a:column,
+ \ 'endLine': a:end_line,
+ \ 'endOffset': a:end_column + 1,
+ \ 'file': expand('#' . a:buffer . ':p'),
+ \ 'refactor': a:refactor,
+ \ 'action': a:action,
+ \}]
+endfunction
diff --git a/autoload/ale/maven.vim b/autoload/ale/maven.vim
new file mode 100644
index 00000000..745f8c93
--- /dev/null
+++ b/autoload/ale/maven.vim
@@ -0,0 +1,51 @@
+" Description: Functions for working with Maven projects.
+"
+" Given a buffer number, find a Maven project root.
+function! ale#maven#FindProjectRoot(buffer) abort
+ let l:wrapper_path = ale#path#FindNearestFile(a:buffer, 'mvnw')
+
+ if !empty(l:wrapper_path)
+ return fnamemodify(l:wrapper_path, ':h')
+ endif
+
+ let l:pom_path = ale#path#FindNearestFile(a:buffer, 'pom.xml')
+
+ if !empty(l:pom_path)
+ return fnamemodify(l:pom_path, ':h')
+ endif
+
+ return ''
+endfunction
+
+
+" Given a buffer number, find the path to the executable.
+" First search on the path for 'mvnw' (mvnw.cmd on Windows), if nothing is found,
+" try the global command. Returns an empty string if cannot find the executable.
+function! ale#maven#FindExecutable(buffer) abort
+ let l:wrapper_cmd = has('unix') ? 'mvnw' : 'mvnw.cmd'
+ let l:wrapper_path = ale#path#FindNearestFile(a:buffer, l:wrapper_cmd)
+
+ if executable(l:wrapper_path)
+ return l:wrapper_path
+ endif
+
+ if executable('mvn')
+ return 'mvn'
+ endif
+
+ return ''
+endfunction
+
+" Given a buffer number, build a command to print the classpath of the root
+" project. Returns an empty string if cannot build the command.
+function! ale#maven#BuildClasspathCommand(buffer) abort
+ let l:executable = ale#maven#FindExecutable(a:buffer)
+ let l:project_root = ale#maven#FindProjectRoot(a:buffer)
+
+ if !empty(l:executable) && !empty(l:project_root)
+ return ale#path#CdString(l:project_root)
+ \ . l:executable . ' dependency:build-classpath'
+ endif
+
+ return ''
+endfunction
diff --git a/autoload/ale/organize_imports.vim b/autoload/ale/organize_imports.vim
index e89c832c..e2b1c0d2 100644
--- a/autoload/ale/organize_imports.vim
+++ b/autoload/ale/organize_imports.vim
@@ -12,10 +12,13 @@ function! ale#organize_imports#HandleTSServerResponse(conn_id, response) abort
let l:file_code_edits = a:response.body
- call ale#code_action#HandleCodeAction({
- \ 'description': 'Organize Imports',
- \ 'changes': l:file_code_edits,
- \}, v:false)
+ call ale#code_action#HandleCodeAction(
+ \ {
+ \ 'description': 'Organize Imports',
+ \ 'changes': l:file_code_edits,
+ \ },
+ \ {}
+ \)
endfunction
function! s:OnReady(linter, lsp_details) abort
diff --git a/autoload/ale/rename.vim b/autoload/ale/rename.vim
index 64952e63..0d074c24 100644
--- a/autoload/ale/rename.vim
+++ b/autoload/ale/rename.vim
@@ -33,9 +33,10 @@ function! ale#rename#HandleTSServerResponse(conn_id, response) abort
return
endif
- let l:old_name = s:rename_map[a:response.request_seq].old_name
- let l:new_name = s:rename_map[a:response.request_seq].new_name
- call remove(s:rename_map, a:response.request_seq)
+ let l:options = remove(s:rename_map, a:response.request_seq)
+
+ let l:old_name = l:options.old_name
+ let l:new_name = l:options.new_name
if get(a:response, 'success', v:false) is v:false
let l:message = get(a:response, 'message', 'unknown')
@@ -77,41 +78,22 @@ function! ale#rename#HandleTSServerResponse(conn_id, response) abort
return
endif
- call ale#code_action#HandleCodeAction({
- \ 'description': 'rename',
- \ 'changes': l:changes,
- \}, v:true)
-endfunction
-
-function! s:getChanges(workspace_edit) abort
- let l:changes = {}
-
- if has_key(a:workspace_edit, 'changes') && !empty(a:workspace_edit.changes)
- return a:workspace_edit.changes
- elseif has_key(a:workspace_edit, 'documentChanges')
- let l:document_changes = []
-
- if type(a:workspace_edit.documentChanges) is v:t_dict
- \ && has_key(a:workspace_edit.documentChanges, 'edits')
- call add(l:document_changes, a:workspace_edit.documentChanges)
- elseif type(a:workspace_edit.documentChanges) is v:t_list
- let l:document_changes = a:workspace_edit.documentChanges
- endif
-
- for l:text_document_edit in l:document_changes
- let l:filename = l:text_document_edit.textDocument.uri
- let l:edits = l:text_document_edit.edits
- let l:changes[l:filename] = l:edits
- endfor
- endif
-
- return l:changes
+ call ale#code_action#HandleCodeAction(
+ \ {
+ \ 'description': 'rename',
+ \ 'changes': l:changes,
+ \ },
+ \ {
+ \ 'should_save': 1,
+ \ 'force_save': get(l:options, 'force_save'),
+ \ },
+ \)
endfunction
function! ale#rename#HandleLSPResponse(conn_id, response) abort
if has_key(a:response, 'id')
\&& has_key(s:rename_map, a:response.id)
- call remove(s:rename_map, a:response.id)
+ let l:options = remove(s:rename_map, a:response.id)
if !has_key(a:response, 'result')
call s:message('No rename result received from server')
@@ -119,7 +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')
@@ -127,43 +109,22 @@ 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
-
- call ale#code_action#HandleCodeAction({
- \ 'description': 'rename',
- \ 'changes': l:changes,
- \}, v:true)
+ let l:changes = ale#code_action#BuildChangesList(l:changes_map)
+
+ call ale#code_action#HandleCodeAction(
+ \ {
+ \ 'description': 'rename',
+ \ 'changes': l:changes,
+ \ },
+ \ {
+ \ 'should_save': 1,
+ \ 'force_save': get(l:options, 'force_save'),
+ \ },
+ \)
endif
endfunction
-function! s:OnReady(line, column, old_name, new_name, linter, lsp_details) abort
+function! s:OnReady(line, column, options, linter, lsp_details) abort
let l:id = a:lsp_details.connection_id
if !ale#lsp#HasCapability(l:id, 'rename')
@@ -195,19 +156,16 @@ function! s:OnReady(line, column, old_name, new_name, linter, lsp_details) abort
\ l:buffer,
\ a:line,
\ a:column,
- \ a:new_name
+ \ a:options.new_name
\)
endif
let l:request_id = ale#lsp#Send(l:id, l:message)
- let s:rename_map[l:request_id] = {
- \ 'new_name': a:new_name,
- \ 'old_name': a:old_name,
- \}
+ let s:rename_map[l:request_id] = a:options
endfunction
-function! s:ExecuteRename(linter, old_name, new_name) abort
+function! s:ExecuteRename(linter, options) abort
let l:buffer = bufnr('')
let [l:line, l:column] = getpos('.')[1:2]
@@ -215,12 +173,11 @@ function! s:ExecuteRename(linter, old_name, new_name) abort
let l:column = min([l:column, len(getline(l:line))])
endif
- let l:Callback = function(
- \ 's:OnReady', [l:line, l:column, a:old_name, a:new_name])
+ let l:Callback = function('s:OnReady', [l:line, l:column, a:options])
call ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback)
endfunction
-function! ale#rename#Execute() abort
+function! ale#rename#Execute(options) abort
let l:lsp_linters = []
for l:linter in ale#linter#Get(&filetype)
@@ -245,6 +202,10 @@ function! ale#rename#Execute() abort
endif
for l:lsp_linter in l:lsp_linters
- call s:ExecuteRename(l:lsp_linter, l:old_name, l:new_name)
+ call s:ExecuteRename(l:lsp_linter, {
+ \ 'old_name': l:old_name,
+ \ 'new_name': l:new_name,
+ \ 'force_save': get(a:options, 'force_save') is 1,
+ \})
endfor
endfunction