Close #1466 - Add GVIM refactor menu support

Code actions and ALERename now appear in the right click context menu
for GVim by default.
This commit is contained in:
w0rp 2020-11-21 01:18:27 +00:00
parent 48fe0dd4f6
commit 7c04ee5c20
No known key found for this signature in database
GPG Key ID: 0FC1ECAA8C81CD83
5 changed files with 398 additions and 217 deletions

View File

@ -263,3 +263,105 @@ function! ale#code_action#BuildChangesList(changes_map) abort
return l:changes return l:changes
endfunction 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

View File

@ -1,5 +1,5 @@
" Author: Dalius Dobravolskas <dalius.dobravolskas@gmail.com> " Author: Dalius Dobravolskas <dalius.dobravolskas@gmail.com>
" Description: Code Fix support for tsserver " Description: Code Fix support for tsserver and LSP servers
let s:codefix_map = {} let s:codefix_map = {}
@ -21,23 +21,65 @@ function! s:message(message) abort
call ale#util#Execute('echom ' . string(a:message)) call ale#util#Execute('echom ' . string(a:message))
endfunction 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 function! ale#codefix#HandleTSServerResponse(conn_id, response) abort
if !has_key(a:response, 'request_seq') if !has_key(a:response, 'request_seq')
\ || !has_key(s:codefix_map, a:response.request_seq) \ || !has_key(s:codefix_map, a:response.request_seq)
return return
endif endif
let l:location = remove(s:codefix_map, a:response.request_seq) 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, 'command', '') is# 'getCodeFixes'
if get(a:response, 'success', v:false) is v:false if get(a:response, 'success', v:false) is v:false
\&& l:MenuCallback is v:null
let l:message = get(a:response, 'message', 'unknown') let l:message = get(a:response, 'message', 'unknown')
call s:message('Error while getting code fixes. Reason: ' . l:message) call s:message('Error while getting code fixes. Reason: ' . l:message)
return return
endif endif
if len(a:response.body) == 0 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.') call s:message('No code fixes available.')
return return
@ -45,14 +87,15 @@ function! ale#codefix#HandleTSServerResponse(conn_id, response) abort
let l:code_fix_to_apply = 0 let l:code_fix_to_apply = 0
if len(a:response.body) == 1 if len(l:result) == 1
let l:code_fix_to_apply = 1 let l:code_fix_to_apply = 1
else else
let l:codefix_no = 1 let l:codefix_no = 1
let l:codefixstring = "Code Fixes:\n" let l:codefixstring = "Code Fixes:\n"
for l:codefix in a:response.body for l:codefix in l:result
let l:codefixstring .= l:codefix_no . ') ' . l:codefix.description . "\n" let l:codefixstring .= l:codefix_no . ') '
\ . l:codefix.description . "\n"
let l:codefix_no += 1 let l:codefix_no += 1
endfor endfor
@ -66,21 +109,22 @@ function! ale#codefix#HandleTSServerResponse(conn_id, response) abort
endif endif
endif endif
let l:changes = a:response.body[l:code_fix_to_apply - 1].changes call ale#codefix#ApplyTSServerCodeAction(
\ l:data,
call ale#code_action#HandleCodeAction({ \ l:result[l:code_fix_to_apply - 1],
\ 'description': 'codefix', \)
\ 'changes': l:changes,
\}, {})
elseif get(a:response, 'command', '') is# 'getApplicableRefactors' elseif get(a:response, 'command', '') is# 'getApplicableRefactors'
if get(a:response, 'success', v:false) is v:false if get(a:response, 'success', v:false) is v:false
\&& l:MenuCallback is v:null
let l:message = get(a:response, 'message', 'unknown') let l:message = get(a:response, 'message', 'unknown')
call s:message('Error while getting applicable refactors. Reason: ' . l:message) call s:message('Error while getting applicable refactors. Reason: ' . l:message)
return return
endif endif
if len(a:response.body) == 0 let l:result = get(a:response, 'body', [])
if len(l:result) == 0
call s:message('No applicable refactors available.') call s:message('No applicable refactors available.')
return return
@ -88,7 +132,7 @@ function! ale#codefix#HandleTSServerResponse(conn_id, response) abort
let l:refactors = [] let l:refactors = []
for l:item in a:response.body for l:item in l:result
for l:action in l:item.actions for l:action in l:item.actions
call add(l:refactors, { call add(l:refactors, {
\ 'name': l:action.description, \ 'name': l:action.description,
@ -97,11 +141,21 @@ function! ale#codefix#HandleTSServerResponse(conn_id, response) abort
endfor endfor
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:refactor_no = 1
let l:refactorstring = "Applicable refactors:\n" let l:refactorstring = "Applicable refactors:\n"
for l:refactor in l:refactors for l:refactor in l:refactors
let l:refactorstring .= l:refactor_no . ') ' . l:refactor.name . "\n" let l:refactorstring .= l:refactor_no . ') '
\ . l:refactor.name . "\n"
let l:refactor_no += 1 let l:refactor_no += 1
endfor endfor
@ -116,19 +170,10 @@ function! ale#codefix#HandleTSServerResponse(conn_id, response) abort
let l:id = l:refactors[l:refactor_to_apply - 1].id let l:id = l:refactors[l:refactor_to_apply - 1].id
let l:message = ale#lsp#tsserver_message#GetEditsForRefactor( call ale#codefix#ApplyTSServerCodeAction(
\ l:location.buffer, \ l:data,
\ l:location.line, \ l:refactors[l:refactor_to_apply - 1],
\ 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' elseif get(a:response, 'command', '') is# 'getEditsForRefactor'
if get(a:response, 'success', v:false) is v:false if get(a:response, 'success', v:false) is v:false
let l:message = get(a:response, 'message', 'unknown') let l:message = get(a:response, 'message', 'unknown')
@ -137,10 +182,48 @@ function! ale#codefix#HandleTSServerResponse(conn_id, response) abort
return return
endif endif
call ale#code_action#HandleCodeAction({ call ale#code_action#HandleCodeAction(
\ 'description': 'editsForRefactor', \ {
\ 'changes': a:response.body.edits, \ '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 endif
endfunction endfunction
@ -158,17 +241,32 @@ function! ale#codefix#HandleLSPResponse(conn_id, response) abort
let l:changes = ale#code_action#BuildChangesList(l:changes_map) let l:changes = ale#code_action#BuildChangesList(l:changes_map)
call ale#code_action#HandleCodeAction({ call ale#code_action#HandleCodeAction(
\ 'description': 'applyEdit', \ {
\ 'changes': l:changes, \ 'description': 'applyEdit',
\}, {}) \ 'changes': l:changes,
\ },
\ {}
\)
elseif has_key(a:response, 'id') elseif has_key(a:response, 'id')
\&& has_key(s:codefix_map, a:response.id) \&& has_key(s:codefix_map, a:response.id)
let l:location = remove(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)
if !has_key(a:response, 'result') let l:result = get(a:response, 'result')
\ || type(a:response.result) != v:t_list
\ || len(a:response.result) == 0 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') call s:message('No code actions received from server')
return return
@ -177,8 +275,9 @@ function! ale#codefix#HandleLSPResponse(conn_id, response) abort
let l:codeaction_no = 1 let l:codeaction_no = 1
let l:codeactionstring = "Code Fixes:\n" let l:codeactionstring = "Code Fixes:\n"
for l:codeaction in a:response.result for l:codeaction in l:result
let l:codeactionstring .= l:codeaction_no . ') ' . l:codeaction.title . "\n" let l:codeactionstring .= l:codeaction_no . ') '
\ . l:codeaction.title . "\n"
let l:codeaction_no += 1 let l:codeaction_no += 1
endfor endfor
@ -191,42 +290,44 @@ function! ale#codefix#HandleLSPResponse(conn_id, response) abort
return return
endif endif
let l:item = a:response.result[l:codeaction_to_apply - 1] let l:item = l:result[l:codeaction_to_apply - 1]
if has_key(l:item, 'command') call ale#codefix#ApplyLSPCodeAction(l:data, l:item)
\ && 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 endif
endfunction endfunction
function! s:FindError(buffer, line, column, end_line, end_column) abort
let l:nearest_error = v:null
function! s:OnReady(line, column, end_line, end_column, linter, lsp_details) abort 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 let l:id = a:lsp_details.connection_id
if !ale#lsp#HasCapability(l:id, 'code_actions') if !ale#lsp#HasCapability(l:id, 'code_actions')
@ -236,32 +337,17 @@ function! s:OnReady(line, column, end_line, end_column, linter, lsp_details) abo
let l:buffer = a:lsp_details.buffer let l:buffer = a:lsp_details.buffer
if a:linter.lsp is# 'tsserver' if a:linter.lsp is# 'tsserver'
if a:line == a:end_line && a:column == a:end_column let l:nearest_error =
if !has_key(g:ale_buffer_info, l:buffer) \ s:FindError(l:buffer, a:line, a:column, a:end_line, a:end_column)
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
if l:nearest_error isnot v:null
let l:message = ale#lsp#tsserver_message#GetCodeFixes( let l:message = ale#lsp#tsserver_message#GetCodeFixes(
\ l:buffer, \ l:buffer,
\ a:line, \ a:line,
\ a:column, \ a:column,
\ a:line, \ a:line,
\ a:column, \ a:column,
\ [l:nearest_error], \ [l:nearest_error.code],
\) \)
else else
let l:message = ale#lsp#tsserver_message#GetApplicableRefactors( let l:message = ale#lsp#tsserver_message#GetApplicableRefactors(
@ -277,56 +363,37 @@ function! s:OnReady(line, column, end_line, end_column, linter, lsp_details) abo
" completions won't know what text is nearby. " completions won't know what text is nearby.
call ale#lsp#NotifyForChanges(l:id, l:buffer) call ale#lsp#NotifyForChanges(l:id, l:buffer)
if a:line == a:end_line && a:column == a:end_column let l:diagnostics = []
if !has_key(g:ale_buffer_info, l:buffer) let l:nearest_error =
return \ s:FindError(l:buffer, a:line, a:column, a:end_line, a:end_column)
endif
let l:nearest_error = v:null if l:nearest_error isnot v:null
let l:nearest_error_diff = -1 let l:diagnostics = [
\ {
for l:error in get(g:ale_buffer_info[l:buffer], 'loclist', []) \ 'code': l:nearest_error.code,
if has_key(l:error, 'code') && l:error.lnum == a:line \ 'message': l:nearest_error.text,
let l:diff = abs(l:error.col - a:column) \ 'range': {
\ 'start': {
if l:nearest_error_diff == -1 || l:diff < l:nearest_error_diff \ 'line': l:nearest_error.lnum - 1,
let l:nearest_error_diff = l:diff \ 'character': l:nearest_error.col - 1,
let l:nearest_error = l:error \ },
endif \ 'end': {
endif \ 'line': l:nearest_error.end_lnum - 1,
endfor \ 'character': l:nearest_error.end_col - 1,
\ },
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:message = ale#lsp#message#CodeAction(
\ l:buffer,
\ a:line,
\ a:column,
\ a:end_line,
\ a:end_column,
\ l:diagnostics,
\)
endif endif
let l:Callback = a:linter.lsp is# 'tsserver' let l:Callback = a:linter.lsp is# 'tsserver'
@ -338,22 +405,40 @@ function! s:OnReady(line, column, end_line, end_column, linter, lsp_details) abo
let l:request_id = ale#lsp#Send(l:id, l:message) let l:request_id = ale#lsp#Send(l:id, l:message)
let s:codefix_map[l:request_id] = { let s:codefix_map[l:request_id] = {
\ 'connection_id': l:id, \ 'connection_id': l:id,
\ 'buffer': l:buffer, \ 'buffer': l:buffer,
\ 'line': a:line, \ 'line': a:line,
\ 'column': a:column, \ 'column': a:column,
\ 'end_line': a:end_line, \ 'end_line': a:end_line,
\ 'end_column': a:end_column, \ 'end_column': a:end_column,
\ 'menu_callback': a:MenuCallback,
\} \}
endfunction endfunction
function! s:ExecuteGetCodeFix(linter, range) abort function! s:ExecuteGetCodeFix(linter, range, MenuCallback) abort
let l:buffer = bufnr('') let l:buffer = bufnr('')
if a:range == 0 if a:range == 0
let [l:line, l:column] = getpos('.')[1:2] let [l:line, l:column] = getpos('.')[1:2]
let l:end_line = l:line let l:end_line = l:line
let l:end_column = l:column 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 else
let [l:line, l:column] = getpos("'<")[1:2] let [l:line, l:column] = getpos("'<")[1:2]
let [l:end_line, l:end_column] = getpos("'>")[1:2] let [l:end_line, l:end_column] = getpos("'>")[1:2]
@ -363,11 +448,18 @@ function! s:ExecuteGetCodeFix(linter, range) abort
let l:end_column = min([l:end_column, len(getline(l:end_line))]) let l:end_column = min([l:end_column, len(getline(l:end_line))])
let l:Callback = function( let l:Callback = function(
\ 's:OnReady', [l:line, l:column, l:end_line, l:end_column]) \ '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) call ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback)
endfunction endfunction
function! ale#codefix#Execute(range) abort 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 = [] let l:lsp_linters = []
for l:linter in ale#linter#Get(&filetype) for l:linter in ale#linter#Get(&filetype)
@ -377,12 +469,16 @@ function! ale#codefix#Execute(range) abort
endfor endfor
if empty(l:lsp_linters) if empty(l:lsp_linters)
call s:message('No active LSPs') if l:MenuCallback is v:null
call s:message('No active LSPs')
else
call l:MenuCallback({}, [])
endif
return return
endif endif
for l:lsp_linter in l:lsp_linters for l:lsp_linter in l:lsp_linters
call s:ExecuteGetCodeFix(l:lsp_linter, a:range) call s:ExecuteGetCodeFix(l:lsp_linter, a:range, l:MenuCallback)
endfor endfor
endfunction endfunction

View File

@ -673,13 +673,31 @@ for a full list of options.
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
5.7 Refactoring: Rename, Actions *ale-refactor* 5.7 Refactoring: Rename, Actions *ale-refactor*
ALE supports renaming symbols in symbols in code such as variables or class ALE supports renaming symbols in code such as variables or class names with
names with the |ALERename| command. the |ALERename| command.
|ALECodeAction| will execute actions on the cursor or applied to a visual |ALECodeAction| will execute actions on the cursor or applied to a visual
range selection, such as automatically fixing errors. range selection, such as automatically fixing errors.
Actions will appear in the right click mouse menu by default for GUI versions
of Vim, unless disabled by setting |g:ale_popup_menu_enabled| to `0`.
Make sure to set your Vim to move the cursor position whenever you right
click, and enable the mouse menu: >
set mouse=a
set mousemodel=popup_setpos
<
You may wish to remove some other menu items you don't want to see: >
silent! aunmenu PopUp.Select\ Word
silent! aunmenu PopUp.Select\ Sentence
silent! aunmenu PopUp.Select\ Paragraph
silent! aunmenu PopUp.Select\ Line
silent! aunmenu PopUp.Select\ Block
silent! aunmenu PopUp.Select\ Blockwise
silent! aunmenu PopUp.Select\ All
<
=============================================================================== ===============================================================================
6. Global Options *ale-options* 6. Global Options *ale-options*
@ -1784,6 +1802,19 @@ g:ale_pattern_options_enabled *g:ale_pattern_options_enabled*
will not set buffer variables per |g:ale_pattern_options|. will not set buffer variables per |g:ale_pattern_options|.
g:ale_popup_menu_enabled *g:ale_popup_menu_enabled*
Type: |Number|
Default: `has('gui')`
When this option is set to `1`, ALE will show code actions and rename
capabilities in the right click mouse menu when there's a LSP server or
tsserver available. See |ale-refactor|.
This setting must be set to `1` before ALE is loaded for this behavior
to be enabled. See |ale-lint-settings-on-startup|.
g:ale_rename_tsserver_find_in_comments *g:ale_rename_tsserver_find_in_comments* g:ale_rename_tsserver_find_in_comments *g:ale_rename_tsserver_find_in_comments*
Type: |Number| Type: |Number|

View File

@ -158,6 +158,9 @@ let g:ale_python_auto_pipenv = get(g:, 'ale_python_auto_pipenv', 0)
" This variable can be overridden to set the GO111MODULE environment variable. " This variable can be overridden to set the GO111MODULE environment variable.
let g:ale_go_go111module = get(g:, 'ale_go_go111module', '') let g:ale_go_go111module = get(g:, 'ale_go_go111module', '')
" If 1, enable a popup menu for commands.
let g:ale_popup_menu_enabled = get(g:, 'ale_popup_menu_enabled', has('gui'))
if g:ale_set_balloons if g:ale_set_balloons
call ale#balloon#Enable() call ale#balloon#Enable()
endif endif
@ -166,6 +169,10 @@ if g:ale_completion_enabled
call ale#completion#Enable() call ale#completion#Enable()
endif endif
if g:ale_popup_menu_enabled
call ale#code_action#EnablePopUpMenu()
endif
" Define commands for moving through warnings and errors. " Define commands for moving through warnings and errors.
command! -bar -nargs=* ALEPrevious command! -bar -nargs=* ALEPrevious
\ :call ale#loclist_jumping#WrapJump('before', <q-args>) \ :call ale#loclist_jumping#WrapJump('before', <q-args>)

View File

@ -105,11 +105,9 @@ Execute(Failed codefix responses should be handled correctly):
\) \)
AssertEqual g:handle_code_action_called, 0 AssertEqual g:handle_code_action_called, 0
Given typescript(Some typescript file): Given typescript(Some typescript file):
foo foo
somelongerline somelongerline ()
bazxyzxyzxyz bazxyzxyzxyz
Execute(getCodeFixes from tsserver should be handled): Execute(getCodeFixes from tsserver should be handled):
@ -283,7 +281,7 @@ Execute(tsserver codefix requests should be sent):
runtime ale_linters/typescript/tsserver.vim runtime ale_linters/typescript/tsserver.vim
let g:ale_buffer_info = {bufnr(''): {'loclist': [{'lnum': 2, 'col': 5, 'code': 2304}]}} let g:ale_buffer_info = {bufnr(''): {'loclist': [{'lnum': 2, 'col': 5, 'code': 2304}]}}
call setpos('.', [bufnr(''), 2, 5, 0]) call setpos('.', [bufnr(''), 2, 16, 0])
" ALECodeAction " ALECodeAction
call ale#codefix#Execute(0) call ale#codefix#Execute(0)
@ -303,9 +301,9 @@ Execute(tsserver codefix requests should be sent):
\ ale#lsp#tsserver_message#Change(bufnr('')), \ ale#lsp#tsserver_message#Change(bufnr('')),
\ [0, 'ts@getCodeFixes', { \ [0, 'ts@getCodeFixes', {
\ 'startLine': 2, \ 'startLine': 2,
\ 'startOffset': 5, \ 'startOffset': 16,
\ 'endLine': 2, \ 'endLine': 2,
\ 'endOffset': 6, \ 'endOffset': 17,
\ 'file': expand('%:p'), \ 'file': expand('%:p'),
\ 'errorCodes': [2304], \ 'errorCodes': [2304],
\ }] \ }]
@ -316,8 +314,8 @@ Execute(tsserver codefix requests should be sent only for error with code):
call ale#linter#Reset() call ale#linter#Reset()
runtime ale_linters/typescript/tsserver.vim runtime ale_linters/typescript/tsserver.vim
let g:ale_buffer_info = {bufnr(''): {'loclist': [{'lnum': 2, 'col': 5}, {'lnum': 2, 'col': 5, 'code': 2304}]}} let g:ale_buffer_info = {bufnr(''): {'loclist': [{'lnum': 2, 'col': 16}, {'lnum': 2, 'col': 16, 'code': 2304}]}}
call setpos('.', [bufnr(''), 2, 5, 0]) call setpos('.', [bufnr(''), 2, 16, 0])
" ALECodeAction " ALECodeAction
call ale#codefix#Execute(0) call ale#codefix#Execute(0)
@ -337,9 +335,9 @@ Execute(tsserver codefix requests should be sent only for error with code):
\ ale#lsp#tsserver_message#Change(bufnr('')), \ ale#lsp#tsserver_message#Change(bufnr('')),
\ [0, 'ts@getCodeFixes', { \ [0, 'ts@getCodeFixes', {
\ 'startLine': 2, \ 'startLine': 2,
\ 'startOffset': 5, \ 'startOffset': 16,
\ 'endLine': 2, \ 'endLine': 2,
\ 'endOffset': 6, \ 'endOffset': 17,
\ 'file': expand('%:p'), \ 'file': expand('%:p'),
\ 'errorCodes': [2304], \ 'errorCodes': [2304],
\ }] \ }]
@ -424,43 +422,6 @@ Execute(getEditsForRefactor should print error on failure):
AssertEqual ['echom ''Error while getting edits for refactor. Reason: oops'''], g:expr_list 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): Execute(Failed LSP responses should be handled correctly):
call ale#codefix#HandleLSPResponse( call ale#codefix#HandleLSPResponse(
\ 1, \ 1,
@ -545,14 +506,6 @@ Execute(LSP code action requests should be sent):
\ string(g:Callback) \ string(g:Callback)
AssertEqual 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', { \ [0, 'textDocument/codeAction', {
\ 'context': { \ 'context': {
\ 'diagnostics': [{'range': {'end': {'character': 5, 'line': 1}, 'start': {'character': 4, 'line': 1}}, 'code': 2304, 'message': 'oops'}] \ 'diagnostics': [{'range': {'end': {'character': 5, 'line': 1}, 'start': {'character': 4, 'line': 1}}, 'code': 2304, 'message': 'oops'}]
@ -561,7 +514,7 @@ Execute(LSP code action requests should be sent):
\ 'textDocument': {'uri': ale#path#ToURI(expand('%:p'))} \ 'textDocument': {'uri': ale#path#ToURI(expand('%:p'))}
\ }] \ }]
\ ], \ ],
\ g:message_list \ g:message_list[-1:]
Execute(LSP code action requests should be sent only for error with code): Execute(LSP code action requests should be sent only for error with code):
call ale#linter#Reset() call ale#linter#Reset()
@ -585,14 +538,6 @@ Execute(LSP code action requests should be sent only for error with code):
\ string(g:Callback) \ string(g:Callback)
AssertEqual 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', { \ [0, 'textDocument/codeAction', {
\ 'context': { \ 'context': {
\ 'diagnostics': [{'range': {'end': {'character': 5, 'line': 1}, 'start': {'character': 4, 'line': 1}}, 'code': 2304, 'message': 'oops'}] \ 'diagnostics': [{'range': {'end': {'character': 5, 'line': 1}, 'start': {'character': 4, 'line': 1}}, 'code': 2304, 'message': 'oops'}]
@ -601,4 +546,4 @@ Execute(LSP code action requests should be sent only for error with code):
\ 'textDocument': {'uri': ale#path#ToURI(expand('%:p'))} \ 'textDocument': {'uri': ale#path#ToURI(expand('%:p'))}
\ }] \ }]
\ ], \ ],
\ g:message_list \ g:message_list[-1:]