From: Yegappan Lakshmanan Date: Sun, 27 Nov 2022 00:42:42 +0000 (-0800) Subject: Add support for displaying clangd inlay hints X-Git-Url: http://www.git.stargrave.org/?a=commitdiff_plain;h=d96518f8d9d38ff57c974ba8e2f45b5096998951;p=vim-lsp.git Add support for displaying clangd inlay hints --- diff --git a/autoload/lsp/inlayhints.vim b/autoload/lsp/inlayhints.vim new file mode 100644 index 0000000..e669ba8 --- /dev/null +++ b/autoload/lsp/inlayhints.vim @@ -0,0 +1,122 @@ +vim9script + +# Functions for dealing with inlay hints + +import './util.vim' +import './buffer.vim' as buf + +# Initialize the highlight group and the text property type used for +# inlay hints. +export def InitOnce() + if !hlexists('LspInlayHintsType') + hlset([{name: 'LspInlayHintsType', linksto: 'Label'}]) + endif + if !hlexists('LspInlayHintsParam') + hlset([{name: 'LspInlayHintsParam', linksto: 'Conceal'}]) + endif + prop_type_add('LspInlayHintsType', {highlight: 'LspInlayHintsType'}) + prop_type_add('LspInlayHintsParam', {highlight: 'LspInlayHintsParam'}) +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}) +enddef + +# LSP inlay hints reply message handler +export def InlayHintsReply(lspserver: dict, inlayHints: any) + if inlayHints->empty() + return + endif + + #echomsg inlayHints->string + + InlayHintsClear(lspserver) + + if mode() !=# 'n' + # Update inlay hints only in normal mode + return + endif + + var bufnum = bufnr('%') + for hint in inlayHints + var label = '' + if hint.label->type() == v:t_list + label = hint.label->copy()->map((_, v) => v.value)->join(', ') + else + label = hint.label + endif + + if hint.kind ==# 'type' + prop_add(hint.position.line + 1, hint.position.character + 1, + {type: 'LspInlayHintsType', text: label, bufnr: bufnum}) + elseif hint.kind ==# 'parameter' + prop_add(hint.position.line + 1, hint.position.character + 1, + {type: 'LspInlayHintsParam', text: label, bufnr: bufnum}) + endif + endfor +enddef + +# Timer callback to display the inlay hints. +def InlayHintsCallback(lspserver: dict, timerid: number) + lspserver.inlayHintsShow() + b:LspInlayHintsNeedsUpdate = false +enddef + +# Update all the inlay hints. A timer is used to throttle the updates. +def InlayHintsUpdate() + if !get(b:, 'LspInlayHintsNeedsUpdate', true) + return + endif + + var timerid = get(b:, 'LspInlayHintsTimer', -1) + if timerid != -1 + timerid->timer_stop() + b:LspInlayHintsTimer = -1 + endif + + var lspserver: dict = buf.CurbufGetServerChecked() + if lspserver->empty() + return + endif + + timerid = timer_start(300, function('InlayHintsCallback', [lspserver])) + b:LspInlayHintsTimer = timerid +enddef + +# Text is modified. Need to update the inlay hints. +def InlayHintsChanged() + b:LspInlayHintsNeedsUpdate = true +enddef + +# Stop updating the inlay hints. +def InlayHintsUpdateStop() + var timerid = get(b:, 'LspInlayHintsTimer', -1) + if timerid != -1 + timerid->timer_stop() + b:LspInlayHintsTimer = -1 + endif +enddef + +# Do buffer-local initialization for displaying inlay hints +export def BufferInit(bnr: number) + var acmds: list> = [] + + acmds->add({bufnr: bnr, + event: ['CursorHold'], + group: 'LSPBufferAutocmds', + cmd: 'InlayHintsUpdate()'}) + acmds->add({bufnr: bnr, + event: ['TextChanged'], + group: 'LSPBufferAutocmds', + cmd: 'InlayHintsChanged()'}) + acmds->add({bufnr: bnr, + event: ['BufLeave'], + group: 'LSPBufferAutocmds', + cmd: 'InlayHintsUpdateStop()'}) + + autocmd_add(acmds) +enddef + +# vim: tabstop=8 shiftwidth=2 softtabstop=2 diff --git a/autoload/lsp/lsp.vim b/autoload/lsp/lsp.vim index cd2ec7b..314e117 100644 --- a/autoload/lsp/lsp.vim +++ b/autoload/lsp/lsp.vim @@ -20,6 +20,7 @@ import './symbol.vim' import './outline.vim' import './signature.vim' import './codeaction.vim' +import './inlayhints.vim' # LSP server information var lspServers: list> = [] @@ -40,9 +41,11 @@ def LspInitOnce() {name: 'LspDiagHint', text: 'H>', texthl: 'Question', linehl: 'MatchParen'}]) - prop_type_add('LspTextRef', {'highlight': 'Search'}) - prop_type_add('LspReadRef', {'highlight': 'DiffChange'}) - prop_type_add('LspWriteRef', {'highlight': 'DiffDelete'}) + prop_type_add('LspTextRef', {highlight: 'Search'}) + prop_type_add('LspReadRef', {highlight: 'DiffChange'}) + prop_type_add('LspWriteRef', {highlight: 'DiffDelete'}) + + inlayhints.InitOnce() :set ballooneval balloonevalterm lspInitializedOnce = true @@ -245,14 +248,20 @@ def AddBufLocalAutocmds(lspserver: dict, bnr: number): void # Auto highlight all the occurrences of the current keyword if opt.lspOptions.autoHighlight && lspserver.isDocumentHighlightProvider - acmds->add({bufnr: bnr, - event: 'CursorMoved', - group: 'LSPBufferAutocmds', - cmd: 'call LspDocHighlightClear() | call LspDocHighlight()'}) + acmds->add({bufnr: bnr, + event: 'CursorMoved', + group: 'LSPBufferAutocmds', + cmd: 'call LspDocHighlightClear() | call LspDocHighlight()'}) endif - autocmd_add(acmds) + # Displaying inlay hints needs the Vim virtual text support. + if has('patch-9.0.0178') && opt.lspOptions.showInlayHints + && (lspserver.isInlayHintProvider + || lspserver.isClangdInlayHintsProvider) + inlayhints.BufferInit(bnr) + endif + autocmd_add(acmds) enddef def BufferInit(bnr: number): void diff --git a/autoload/lsp/lspserver.vim b/autoload/lsp/lspserver.vim index b33b0b0..4a9148c 100644 --- a/autoload/lsp/lspserver.vim +++ b/autoload/lsp/lspserver.vim @@ -21,6 +21,7 @@ import './signature.vim' import './codeaction.vim' import './callhierarchy.vim' as callhier import './typehierarchy.vim' as typehier +import './inlayhints.vim' # LSP server standard output handler def Output_cb(lspserver: dict, chan: channel, msg: any): void @@ -289,6 +290,25 @@ def ProcessServerCaps(lspserver: dict, caps: dict) lspserver.isFoldingRangeProvider = false endif + # inlayHintProvider + if lspserver.caps->has_key('inlayHintProvider') + if lspserver.caps.inlayHintProvider->type() == v:t_bool + lspserver.isInlayHintProvider = lspserver.caps.inlayHintProvider + else + lspserver.isInlayHintProvider = true + endif + else + lspserver.isInlayHintProvider = false + endif + + # clangdInlayHintsProvider + if lspserver.caps->has_key('clangdInlayHintsProvider') + lspserver.isClangdInlayHintsProvider = + lspserver.caps.clangdInlayHintsProvider + else + lspserver.isClangdInlayHintsProvider = false + endif + # textDocument/didSave notification if lspserver.caps->has_key('textDocumentSync') if lspserver.caps.textDocumentSync->type() == v:t_bool @@ -395,6 +415,7 @@ def InitServer(lspserver: dict) contentFormat: ['plaintext', 'markdown'] }, foldingRange: {lineFoldingOnly: true}, + inlayHint: {dynamicRegistration: false}, synchronization: { didSave: true } @@ -1212,6 +1233,35 @@ def GetOutgoingCalls(lspserver: dict, item: dict): any return reply.result enddef +# Request: "textDocument/inlayHint" +# Inlay hints. +def InlayHintsShow(lspserver: dict) + # Check whether LSP server supports type hierarchy + if !lspserver.isInlayHintProvider && !lspserver.isClangdInlayHintsProvider + util.ErrMsg("Error: LSP server does not support inlay hint") + return + endif + + var lastlnum = line('$') + var param = { + textDocument: {uri: util.LspFileToUri(@%)}, + range: + { + start: {line: 0, character: 0}, + end: {line: lastlnum - 1, character: charcol([lastlnum, '$']) - 1} + } + } + + var msg: string + if lspserver.isClangdInlayHintsProvider + # clangd-style inlay hints + msg = 'clangd/inlayHints' + else + msg = 'textDocument/inlayHint' + endif + var reply = lspserver.rpc_a(msg, param, inlayhints.InlayHintsReply) +enddef + # Request: "textDocument/typehierarchy" # Support the clangd version of type hierarchy retrieval method. # The method described in the LSP 3.17.0 standard is not supported as clangd @@ -1604,6 +1654,7 @@ export def NewLspServer(path: string, args: list, isSync: bool, initiali getIncomingCalls: function(GetIncomingCalls, [lspserver]), outgoingCalls: function(OutgoingCalls, [lspserver]), getOutgoingCalls: function(GetOutgoingCalls, [lspserver]), + inlayHintsShow: function(InlayHintsShow, [lspserver]), typeHierarchy: function(TypeHiearchy, [lspserver]), renameSymbol: function(RenameSymbol, [lspserver]), codeAction: function(CodeAction, [lspserver]), diff --git a/autoload/lsp/options.vim b/autoload/lsp/options.vim index e24bda2..655358e 100644 --- a/autoload/lsp/options.vim +++ b/autoload/lsp/options.vim @@ -39,7 +39,9 @@ export var lspOptions: dict = { # Use a floating menu to show the code action menu instead of asking for input usePopupInCodeAction: false, # enable snippet completion support - snippetSupport: false + snippetSupport: false, + # enable inlay hints + showInlayHints: false } # set the LSP plugin options from the user provided option values diff --git a/doc/lsp.txt b/doc/lsp.txt index bf8021c..a17f785 100644 --- a/doc/lsp.txt +++ b/doc/lsp.txt @@ -2,7 +2,7 @@ Author: Yegappan Lakshmanan (yegappan AT yahoo DOT com) For Vim version 9.0 and above -Last change: Nov 24, 2022 +Last change: Nov 26, 2022 ============================================================================== *lsp-license* @@ -208,17 +208,23 @@ Shell script, Vim script and PHP file types: > < To add a language server, the following information is needed: - filetype One or more file types supported by the language - server. This can be a |String| or a |List|. To specify - multiple multiple file types, use a List. - path complete path to the language server executable - (without any arguments). args a list of command-line arguments passed to the language server. Each argument is a separate List item. + filetype One or more file types supported by the language + server. This can be a |String| or a |List|. To specify + multiple multiple file types, use a List. + initializationOptions + (Optional) for lsp servers (e.g. intelephense) some + additional initialization options may be required + or useful for initialization. Those can be provided in + this dictionary and if present will be transmitted to + the lsp server. omnicompl (Optional) a boolean value that enables (true) or disables (false) omni-completion for this file types. By default this is set to 'v:true'. + path complete path to the language server executable + (without any arguments). syncInit (Optional) for language servers (e.g. rust analyzer, gopls, etc.) that take time to initialize and reply to a 'initialize' request message this should be set to @@ -226,12 +232,6 @@ To add a language server, the following information is needed: call is used to initialize the language server, otherwise the server is initialized asynchronously. By default this is set to 'v:false'. - initializationOptions - (Optional) for lsp servers (e.g. intelephense) some - additional initialization options may be required - or useful for initialization. Those can be provided in - this dictionary and if present will be transmitted to - the lsp server. The language servers are added using the LspAddServer() function. This function accepts a list of language servers with the above information. @@ -290,6 +290,9 @@ showDiagInPopup When using the |:LspDiagCurrent| command to display true. showDiagOnStatusLine Show a diagnostic message on a status line. By default this is set to false. +showInlayHints Show inlay hints from the language server. By default + this is set to false. The inlay hint text is + displayed as a virtual text. showSignature In insert mode, automatically show the current symbol signature in a popup. By default this is set to true. snippetSupport Enable snippet completion support. Need a snippet @@ -836,7 +839,18 @@ LspDiagsUpdated A |User| autocommand invoked when new has processed the diagnostics. ============================================================================== -11. Debugging *lsp-debug* +11. Highlight Groups *lsp-highlight-groups* + +The following highlight groups are used by the LSP plugin. You can define +these highlight groups in your .vimrc file before sourcing this plugin to +override them. + +LspInlayHintsParam Used to highlight inlay hints of kind + "parameter". +LspInlayHintsType Used to highlight inlay hints of kind "type". + +============================================================================== +12. Debugging *lsp-debug* To debug this plugin, you can log the language server protocol messages sent and received by the plugin from the language server. The following command