Files
2021-03-16 00:20:34 +09:00

132 lines
4.8 KiB
VimL

" https://microsoft.github.io/language-server-protocol/specification#textDocument_codeAction
" internal state for whether it is enabled or not to avoid multiple subscriptions
let s:enabled = 0
let s:sign_group = 'vim_lsp_document_code_action_signs'
if !hlexists('LspCodeActionText')
highlight link LspCodeActionText Normal
endif
function! lsp#internal#document_code_action#signs#_enable() abort
if !lsp#utils#_has_signs() | return | endif
" don't even bother registering if the feature is disabled
if !g:lsp_document_code_action_signs_enabled | return | endif
if s:enabled | return | endif
let s:enabled = 1
call s:define_sign('LspCodeAction', 'A>', g:lsp_document_code_action_signs_hint)
" Note:
" - update CodeAction signs when CusorMoved or CursorHold
" - clear signs when InsertEnter or BufLeave
" - debounce code action requests
" - automatically switch to latest code action request via switchMap()
" - cancel code action request via takeUntil() when BufLeave
let s:Dispose = lsp#callbag#pipe(
\ lsp#callbag#merge(
\ lsp#callbag#fromEvent(['CursorMoved', 'CursorHold']),
\ lsp#callbag#pipe(
\ lsp#callbag#fromEvent(['InsertEnter', 'BufLeave']),
\ lsp#callbag#tap({_ -> s:clear_signs() }),
\ )
\ ),
\ lsp#callbag#filter({_ -> g:lsp_document_code_action_signs_enabled }),
\ lsp#callbag#debounceTime(g:lsp_document_code_action_signs_delay),
\ lsp#callbag#map({_->{'bufnr': bufnr('%'), 'curpos': getcurpos()[0:2], 'changedtick': b:changedtick }}),
\ lsp#callbag#distinctUntilChanged({a,b -> a['bufnr'] == b['bufnr'] && a['curpos'] == b['curpos'] && a['changedtick'] == b['changedtick']}),
\ lsp#callbag#filter({_->mode() is# 'n' && getbufvar(bufnr('%'), '&buftype') !=# 'terminal' }),
\ lsp#callbag#switchMap({_->
\ lsp#callbag#pipe(
\ s:send_request(),
\ lsp#callbag#materialize(),
\ lsp#callbag#filter({x->lsp#callbag#isNextNotification(x)}),
\ lsp#callbag#map({x->x['value']}),
\ lsp#callbag#takeUntil(
\ lsp#callbag#fromEvent('BufLeave')
\ )
\ )
\ }),
\ lsp#callbag#subscribe({x->s:set_signs(x)}),
\)
endfunction
function! lsp#internal#document_code_action#signs#_disable() abort
if !s:enabled | return | endif
if exists('s:Dispose')
call s:Dispose()
unlet s:Dispose
endif
endfunction
function! s:send_request() abort
let l:servers = filter(lsp#get_allowed_servers(), 'lsp#capabilities#has_code_action_provider(v:val)')
if empty(l:servers)
return lsp#callbag#empty()
endif
let l:range = lsp#utils#range#_get_current_line_range()
return lsp#callbag#pipe(
\ lsp#callbag#fromList(l:servers),
\ lsp#callbag#flatMap({server->
\ lsp#request(server, {
\ 'method': 'textDocument/codeAction',
\ 'params': {
\ 'textDocument': lsp#get_text_document_identifier(),
\ 'range': l:range,
\ 'context': {
\ 'diagnostics': [],
\ 'only': ['', 'quickfix', 'refactor', 'refactor.extract', 'refactor.inline', 'refactor.rewrite'],
\ }
\ }
\ })
\ }),
\ lsp#callbag#filter({x->!empty(x['response']['result'])}),
\ lsp#callbag#take(1),
\ )
endfunction
function! s:clear_signs() abort
call sign_unplace(s:sign_group)
endfunction
function! s:set_signs(data) abort
call s:clear_signs()
if lsp#client#is_error(a:data['response']) | return | endif
if empty(a:data['response']['result'])
return
endif
let l:bufnr = bufnr(lsp#utils#uri_to_path(a:data['request']['params']['textDocument']['uri']))
call s:place_signs(a:data, l:bufnr)
endfunction
" Set default sign text to handle case when user provides empty dict
function! s:define_sign(sign_name, sign_default_text, sign_options) abort
let l:options = {
\ 'text': get(a:sign_options, 'text', a:sign_default_text),
\ 'texthl': a:sign_name . 'Text',
\ 'linehl': a:sign_name . 'Line',
\ }
let l:sign_icon = get(a:sign_options, 'icon', '')
if !empty(l:sign_icon)
let l:options['icon'] = l:sign_icon
endif
call sign_define(a:sign_name, l:options)
endfunction
function! s:place_signs(data, bufnr) abort
if !bufexists(a:bufnr) || !bufloaded(a:bufnr)
return
endif
let l:sign_priority = g:lsp_document_code_action_signs_priority
let l:line = lsp#utils#position#lsp_line_to_vim(a:bufnr, a:data['request']['params']['range']['start'])
let l:sign_id = sign_place(0, s:sign_group, 'LspCodeAction', a:bufnr,
\ { 'lnum': l:line, 'priority': l:sign_priority })
endfunction