From: Yegappan Lakshmanan Date: Sat, 8 Jul 2023 02:33:58 +0000 (-0700) Subject: Add support for enabling/disabling inlay hints X-Git-Url: http://www.git.stargrave.org/?a=commitdiff_plain;h=e5b32d6549c43e0dfa777b5a2ddb288e612e78a3;p=vim-lsp.git Add support for enabling/disabling inlay hints --- diff --git a/autoload/lsp/buffer.vim b/autoload/lsp/buffer.vim index 85a07ca..d2ccc8f 100644 --- a/autoload/lsp/buffer.vim +++ b/autoload/lsp/buffer.vim @@ -48,7 +48,8 @@ var SupportedCheckFns = { foldingRange: (lspserver) => lspserver.isFoldingRangeProvider, hover: (lspserver) => lspserver.isHoverProvider, implementation: (lspserver) => lspserver.isImplementationProvider, - inlayHint: (lspserver) => lspserver.isInlayHintProvider, + inlayHint: (lspserver) => lspserver.isInlayHintProvider || + lspserver.isClangdInlayHintsProvider, references: (lspserver) => lspserver.isReferencesProvider, rename: (lspserver) => lspserver.isRenameProvider, selectionRange: (lspserver) => lspserver.isSelectionRangeProvider, diff --git a/autoload/lsp/diag.vim b/autoload/lsp/diag.vim index 81e9936..583c020 100644 --- a/autoload/lsp/diag.vim +++ b/autoload/lsp/diag.vim @@ -782,7 +782,7 @@ export def DiagsHighlightDisable() # turn off all diags highlight opt.lspOptions.autoHighlightDiags = false for binfo in getbufinfo({bufloaded: true}) - RemoveDiagVisualsForBuffer(binfo.bufnr) + RemoveDiagVisualsForBuffer(binfo.bufnr) endfor enddef diff --git a/autoload/lsp/inlayhints.vim b/autoload/lsp/inlayhints.vim index 37efdc4..bff0e93 100644 --- a/autoload/lsp/inlayhints.vim +++ b/autoload/lsp/inlayhints.vim @@ -15,28 +15,32 @@ export def InitOnce() ]) prop_type_add('LspInlayHintsType', {highlight: 'LspInlayHintsType'}) prop_type_add('LspInlayHintsParam', {highlight: 'LspInlayHintsParam'}) + + autocmd_add([{group: 'LspOptionsChanged', + event: 'User', + pattern: '*', + cmd: 'LspInlayHintsOptionsChanged()'}]) enddef # Clear all the inlay hints text properties in the current buffer -def InlayHintsClear(lspserver: dict) - prop_remove({type: 'LspInlayHintsType', bufnr: bufnr('%'), all: true}) - prop_remove({type: 'LspInlayHintsParam', bufnr: bufnr('%'), all: true}) +def InlayHintsClear(bnr: number) + prop_remove({type: 'LspInlayHintsType', bufnr: bnr, all: true}) + prop_remove({type: 'LspInlayHintsParam', bufnr: bnr, all: true}) enddef # LSP inlay hints reply message handler -export def InlayHintsReply(lspserver: dict, inlayHints: any) +export def InlayHintsReply(lspserver: dict, bnr: number, inlayHints: any) if inlayHints->empty() return endif - InlayHintsClear(lspserver) + InlayHintsClear(bnr) if mode() != 'n' # Update inlay hints only in normal mode return endif - var bnr = bufnr('%') for hint in inlayHints var label = '' if hint.label->type() == v:t_list @@ -65,56 +69,61 @@ export def InlayHintsReply(lspserver: dict, inlayHints: any) enddef # Timer callback to display the inlay hints. -def InlayHintsCallback(lspserver: dict, timerid: number) - lspserver.inlayHintsShow() - b:LspInlayHintsNeedsUpdate = false +def InlayHintsTimerCb(lspserver: dict, bnr: number, timerid: number) + lspserver.inlayHintsShow(bnr) + setbufvar(bnr, 'LspInlayHintsNeedsUpdate', false) enddef # Update all the inlay hints. A timer is used to throttle the updates. -def LspInlayHintsUpdate() - if !get(b:, 'LspInlayHintsNeedsUpdate', true) +def LspInlayHintsUpdate(bnr: number) + if !getbufvar(bnr, 'LspInlayHintsNeedsUpdate', true) return endif - var timerid = get(b:, 'LspInlayHintsTimer', -1) + var timerid = getbufvar(bnr, 'LspInlayHintsTimer', -1) if timerid != -1 timerid->timer_stop() - b:LspInlayHintsTimer = -1 + setbufvar(bnr, 'LspInlayHintsTimer', -1) endif - var lspserver: dict = buf.CurbufGetServerChecked('inlayHint') + var lspserver: dict = buf.BufLspServerGet(bnr, 'inlayHint') if lspserver->empty() return endif - timerid = timer_start(300, function('InlayHintsCallback', [lspserver])) - b:LspInlayHintsTimer = timerid + if get(g:, 'LSPTest') + # When running tests, update the inlay hints immediately + InlayHintsTimerCb(lspserver, bnr, -1) + else + timerid = timer_start(300, function('InlayHintsTimerCb', [lspserver, bnr])) + setbufvar(bnr, 'LspInlayHintsTimer', timerid) + endif enddef # Text is modified. Need to update the inlay hints. -def LspInlayHintsChanged() - b:LspInlayHintsNeedsUpdate = true +def LspInlayHintsChanged(bnr: number) + setbufvar(bnr, 'LspInlayHintsNeedsUpdate', true) enddef # Trigger an update of the inlay hints in the current buffer. -export def LspInlayHintsUpdateNow() - b:LspInlayHintsNeedsUpdate = true - LspInlayHintsUpdate() +export def LspInlayHintsUpdateNow(bnr: number) + setbufvar(bnr, 'LspInlayHintsNeedsUpdate', true) + LspInlayHintsUpdate(bnr) enddef # Stop updating the inlay hints. -def LspInlayHintsUpdateStop() - var timerid = get(b:, 'LspInlayHintsTimer', -1) +def LspInlayHintsUpdateStop(bnr: number) + var timerid = getbufvar(bnr, 'LspInlayHintsTimer', -1) if timerid != -1 timerid->timer_stop() - b:LspInlayHintsTimer = -1 + setbufvar(bnr, 'LspInlayHintsTimer', -1) endif enddef # Do buffer-local initialization for displaying inlay hints export def BufferInit(lspserver: dict, bnr: number) if !lspserver.isInlayHintProvider && !lspserver.isClangdInlayHintsProvider - # no support for inley hints + # no support for inlay hints return endif @@ -129,26 +138,75 @@ export def BufferInit(lspserver: dict, bnr: number) # time. acmds->add({bufnr: bnr, event: ['CursorHold'], - group: 'LSPBufferAutocmds', - cmd: 'LspInlayHintsUpdate()'}) + group: 'LspInlayHints', + cmd: $'LspInlayHintsUpdate({bnr})'}) # After the text in the current buffer is modified, the inlay hints need to # be updated. acmds->add({bufnr: bnr, event: ['TextChanged'], - group: 'LSPBufferAutocmds', - cmd: 'LspInlayHintsChanged()'}) + group: 'LspInlayHints', + cmd: $'LspInlayHintsChanged({bnr})'}) # Editing a file should trigger an inlay hint update. acmds->add({bufnr: bnr, event: ['BufReadPost'], - group: 'LSPBufferAutocmds', - cmd: 'LspInlayHintsUpdateNow()'}) + group: 'LspInlayHints', + cmd: $'LspInlayHintsUpdateNow({bnr})'}) # Inlay hints need not be updated if a buffer is no longer active. acmds->add({bufnr: bnr, event: ['BufLeave'], - group: 'LSPBufferAutocmds', - cmd: 'LspInlayHintsUpdateStop()'}) + group: 'LspInlayHints', + cmd: $'LspInlayHintsUpdateStop({bnr})'}) autocmd_add(acmds) enddef +var inlayHintsEnabled = opt.lspOptions.showInlayHints + +# Enable inlay hints. For all the buffers with an attached language server +# that supports inlay hints, refresh the inlay hints. +export def InlayHintsEnable() + opt.lspOptions.showInlayHints = true + for binfo in getbufinfo() + var lspservers: list> = buf.BufLspServersGet(binfo.bufnr) + if lspservers->empty() + continue + endif + for lspserver in lspservers + if !lspserver.isInlayHintProvider && + !lspserver.isClangdInlayHintsProvider + continue + endif + BufferInit(lspserver, binfo.bufnr) + LspInlayHintsUpdateNow(binfo.bufnr) + endfor + endfor + inlayHintsEnabled = true +enddef + +# Disable inlay hints for the current Vim session. Clear the inlay hints in +# all the buffers. +export def InlayHintsDisable() + opt.lspOptions.showInlayHints = false + for binfo in getbufinfo() + var lspserver: dict = buf.BufLspServerGet(binfo.bufnr, 'inlayHint') + if lspserver->empty() + continue + endif + LspInlayHintsUpdateStop(binfo.bufnr) + :silent! autocmd_delete([{bufnr: binfo.bufnr, group: 'LspInlayHints'}]) + InlayHintsClear(binfo.bufnr) + endfor + inlayHintsEnabled = false +enddef + +# Some options are changed. If 'showInlayHints' option is changed, then +# either enable or disable inlay hints. +export def LspInlayHintsOptionsChanged() + if inlayHintsEnabled && !opt.lspOptions.showInlayHints + InlayHintsDisable() + elseif !inlayHintsEnabled && opt.lspOptions.showInlayHints + InlayHintsEnable() + endif +enddef + # vim: tabstop=8 shiftwidth=2 softtabstop=2 diff --git a/autoload/lsp/lsp.vim b/autoload/lsp/lsp.vim index adc8e7c..7b9ed68 100644 --- a/autoload/lsp/lsp.vim +++ b/autoload/lsp/lsp.vim @@ -799,6 +799,23 @@ export def Hover(cmdmods: string) lspserver.hover(cmdmods) enddef +# Enable or disable inlay hints +export def InlayHints(ctl: string) + if ctl == 'enable' + inlayhints.InlayHintsEnable() + elseif ctl == 'disable' + inlayhints.InlayHintsDisable() + else + util.ErrMsg($'LspInlayHints - Unsupported argument "{ctl}"') + endif +enddef + +# Command-line completion for the ":LspInlayHints" command +export def LspInlayHintsComplete(arglead: string, cmdline: string, cursorPos: number): list + var l = ['enable', 'disable'] + return filter(l, (_, val) => val =~ $'^{arglead}') +enddef + # show symbol references export def ShowReferences(peek: bool) var lspserver: dict = buf.CurbufGetServerChecked('references') diff --git a/autoload/lsp/lspserver.vim b/autoload/lsp/lspserver.vim index 7097674..4366d73 100644 --- a/autoload/lsp/lspserver.vim +++ b/autoload/lsp/lspserver.vim @@ -151,7 +151,7 @@ def ServerInitReply(lspserver: dict, initResult: dict): void # Update the inlay hints (if enabled) if opt.lspOptions.showInlayHints && (lspserver.isInlayHintProvider || lspserver.isClangdInlayHintsProvider) - inlayhints.LspInlayHintsUpdateNow() + inlayhints.LspInlayHintsUpdateNow(bufnr()) endif enddef @@ -1197,24 +1197,33 @@ enddef # Request: "textDocument/inlayHint" # Inlay hints. -def InlayHintsShow(lspserver: dict) +def InlayHintsShow(lspserver: dict, bnr: number) # Check whether LSP server supports type hierarchy if !lspserver.isInlayHintProvider && !lspserver.isClangdInlayHintsProvider util.ErrMsg('LSP server does not support inlay hint') return endif - var lastlnum = line('$') + var binfo = bnr->getbufinfo() + if binfo->empty() + return + endif + var lastlnum = binfo[0].linecount + var lastline = bnr->getbufline('$') + var lastcol = 1 + if !lastline->empty() && !lastline[0]->empty() + lastcol = lastline[0]->strchars() + endif var param = { - textDocument: {uri: util.LspFileToUri(@%)}, + textDocument: {uri: util.LspBufnrToUri(bnr)}, range: { start: {line: 0, character: 0}, - end: {line: lastlnum - 1, character: charcol([lastlnum, '$']) - 1} + end: {line: lastlnum - 1, character: lastcol - 1} } } - lspserver.encodeRange(bufnr(), param.range) + lspserver.encodeRange(bnr, param.range) var msg: string if lspserver.isClangdInlayHintsProvider @@ -1223,7 +1232,9 @@ def InlayHintsShow(lspserver: dict) else msg = 'textDocument/inlayHint' endif - var reply = lspserver.rpc_a(msg, param, inlayhints.InlayHintsReply) + var reply = lspserver.rpc_a(msg, param, (_, reply) => { + inlayhints.InlayHintsReply(lspserver, bnr, reply) + }) enddef def DecodeTypeHierarchy(lspserver: dict, isSuper: bool, typeHier: dict) diff --git a/autoload/lsp/options.vim b/autoload/lsp/options.vim index 5a787e8..754a6fb 100644 --- a/autoload/lsp/options.vim +++ b/autoload/lsp/options.vim @@ -113,6 +113,11 @@ export def OptionsSet(opts: dict) else lspOptions.completionMatcherValue = COMPLETIONMATCHER_CASE endif + + # Apply the changed options + if exists('#LspOptionsChanged#User') + :doautocmd LspOptionsChanged User + endif enddef # return a copy of the LSP plugin options diff --git a/doc/lsp.txt b/doc/lsp.txt index cec1a23..b530f5b 100644 --- a/doc/lsp.txt +++ b/doc/lsp.txt @@ -107,6 +107,7 @@ The following commands are provided: in a popup window. :LspIncomingCalls Display the list of symbols calling the current symbol in a window. +:LspInlayHints Enable or disable inlay hints. :LspOutgoingCalls Display the list of symbols called by the current symbol in a window. :LspOutline Show the list of symbols defined in the current file @@ -871,6 +872,13 @@ can map these commands to keys and make it easier to invoke them. |lsp-call-hierarchy| for more information. Note that not all the language servers support this feature. + *:LspInlayHints* +:LspInlayHints Enable or disable inlay hints. Supports the "enable" + and "disable" arguments. When "enable" is specified, + enables the inlay hints for all the buffers with a + language server that supports inlay hints. When + "disable" is specified, disables the inlay hints. + *:LspOutoingCalls* :LspOutoingCalls Display a hierarchy of symbols called by the symbol under the cursor in a window. See diff --git a/plugin/lsp.vim b/plugin/lsp.vim index d89f3ae..2de7df5 100644 --- a/plugin/lsp.vim +++ b/plugin/lsp.vim @@ -77,6 +77,7 @@ command! -nargs=0 -bar -count LspGotoTypeDef lsp.GotoTypedef(v:false, , command! -nargs=0 -bar LspHighlight call LspDocHighlight() command! -nargs=0 -bar LspHighlightClear call LspDocHighlightClear() command! -nargs=? -bar LspHover lsp.Hover() +command! -nargs=1 -bar -complete=customlist,lsp.LspInlayHintsComplete LspInlayHints lsp.InlayHints() command! -nargs=0 -bar LspIncomingCalls lsp.IncomingCalls() command! -nargs=0 -bar LspOutgoingCalls lsp.OutgoingCalls() command! -nargs=0 -bar -count LspOutline lsp.Outline(, ) diff --git a/test/clangd_tests.vim b/test/clangd_tests.vim index 2ee69e0..b32f06b 100644 --- a/test/clangd_tests.vim +++ b/test/clangd_tests.vim @@ -1499,6 +1499,60 @@ def g:Test_OmniComplete_Struct() :%bw! enddef +# Test for inlay hints +def g:Test_InlayHints() + :silent! edit XinlayHints.c + sleep 200m + var lines: list =<< trim END + void func1(int a, int b) + { + } + + void func2() + { + func1(10, 20); + } + END + setline(1, lines) + g:WaitForServerFileLoad(0) + redraw! + + :LspInlayHints enable + assert_equal([{id: -1, col: 9, type_bufnr: 0, end: 1, + type: 'LspInlayHintsParam', length: 1, start: 1}, + {id: -2, col: 13, type_bufnr: 0, end: 1, + type: 'LspInlayHintsParam', length: 1, start: 1}], + prop_list(7)) + + :LspInlayHints disable + assert_equal([], prop_list(7)) + + g:LspOptionsSet({showInlayHints: true}) + assert_equal([{id: -1, col: 9, type_bufnr: 0, end: 1, + type: 'LspInlayHintsParam', length: 1, start: 1}, + {id: -2, col: 13, type_bufnr: 0, end: 1, + type: 'LspInlayHintsParam', length: 1, start: 1}], + prop_list(7)) + + g:LspOptionsSet({showInlayHints: false}) + assert_equal([], prop_list(7)) + + :hide enew + :LspInlayHints enable + :bprev + assert_equal([{id: -1, col: 9, type_bufnr: 0, end: 1, + type: 'LspInlayHintsParam', length: 1, start: 1}, + {id: -2, col: 13, type_bufnr: 0, end: 1, + type: 'LspInlayHintsParam', length: 1, start: 1}], + prop_list(7)) + :hide enew + :LspInlayHints disable + :bprev + assert_equal([], prop_list(7)) + + :%bw! +enddef + # Test for the :LspServer command. def g:Test_LspServer() new a.raku