]> Sergey Matveev's repositories - vim-lsp.git/commitdiff
Add support for displaying clangd inlay hints
authorYegappan Lakshmanan <yegappan@yahoo.com>
Sun, 27 Nov 2022 00:42:42 +0000 (16:42 -0800)
committerYegappan Lakshmanan <yegappan@yahoo.com>
Sun, 27 Nov 2022 00:42:42 +0000 (16:42 -0800)
autoload/lsp/inlayhints.vim [new file with mode: 0644]
autoload/lsp/lsp.vim
autoload/lsp/lspserver.vim
autoload/lsp/options.vim
doc/lsp.txt

diff --git a/autoload/lsp/inlayhints.vim b/autoload/lsp/inlayhints.vim
new file mode 100644 (file)
index 0000000..e669ba8
--- /dev/null
@@ -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<any>)
+  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<any>, 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<any>, 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<any> = 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<dict<any>> = []
+
+  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
index cd2ec7bb717451c4760c598d57a81f33d86b5107..314e117e6beca68ed607d402c32ab238292cdb67 100644 (file)
@@ -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<dict<any>> = []
@@ -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<any>, 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
index b33b0b0eb7fee6dbf14a2eb7adc9398da4719e28..4a9148c9e531faac4738218c0b4933ac59ba4411 100644 (file)
@@ -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<any>, chan: channel, msg: any): void
@@ -289,6 +290,25 @@ def ProcessServerCaps(lspserver: dict<any>, caps: dict<any>)
     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<any>)
         contentFormat: ['plaintext', 'markdown']
       },
       foldingRange: {lineFoldingOnly: true},
+      inlayHint: {dynamicRegistration: false},
       synchronization: {
        didSave: true
       }
@@ -1212,6 +1233,35 @@ def GetOutgoingCalls(lspserver: dict<any>, item: dict<any>): any
   return reply.result
 enddef
 
+# Request: "textDocument/inlayHint"
+# Inlay hints.
+def InlayHintsShow(lspserver: dict<any>)
+  # 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<string>, 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]),
index e24bda26479afc34d77716a6ffb164ae90467b7d..655358e13173019aca3bcd489c0bd9c3d6976c14 100644 (file)
@@ -39,7 +39,9 @@ export var lspOptions: dict<any> = {
   # 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
index bf8021cba3b100cf3a1e98c9d0dd69b133b34f32..a17f7859b590d693b24027db6881b84cfedcdc5f 100644 (file)
@@ -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