]> Sergey Matveev's repositories - vim-lsp.git/commitdiff
Lazy documentation for selected completion item
authorRoberto Castagnola <roberto.castagnola@gmail.com>
Sun, 23 Oct 2022 00:48:45 +0000 (02:48 +0200)
committerRoberto Castagnola <roberto.castagnola@gmail.com>
Sun, 23 Oct 2022 00:48:45 +0000 (02:48 +0200)
autoload/lsp/handlers.vim
autoload/lsp/lsp.vim
autoload/lsp/lspserver.vim

index 73838fd334366edd120a1d233167548cbd3e526c..49637fd36c38025420cf214f02d17bc39a230a0f 100644 (file)
@@ -27,6 +27,8 @@ def ProcessInitializeReply(lspserver: dict<any>, req: dict<any>, reply: dict<any
   if opt.lspOptions.autoComplete && caps->has_key('completionProvider')
     var triggers = caps.completionProvider.triggerCharacters
     lspserver.completionTriggerChars = triggers
+    lspserver.completionLazyDoc = lspserver.caps.completionProvider->has_key('resolveProvider')
+        && lspserver.caps.completionProvider.resolveProvider
   endif
 
   # send a "initialized" notification to server
@@ -112,20 +114,25 @@ def ProcessCompletionReply(lspserver: dict<any>, req: dict<any>, reply: dict<any
       d.word = item.label
     endif
     d.abbr = item.label
+    d.dup = 1
     if item->has_key('kind')
       # namespace CompletionItemKind
       # map LSP kind to complete-item-kind
       d.kind = LspCompleteItemKindChar(item.kind)
     endif
-    if item->has_key('detail')
-      d.menu = item.detail
-    endif
-    if item->has_key('documentation')
-      if item.documentation->type() == v:t_string && item.documentation != ''
-       d.info = item.documentation
-      elseif item.documentation->type() == v:t_dict
-                       && item.documentation.value->type() == v:t_string
-       d.info = item.documentation.value
+    if lspserver.completionLazyDoc
+      d.info = 'Lazy doc'
+    else
+      if item->has_key('detail')
+        d.menu = item.detail
+      endif
+      if item->has_key('documentation')
+        if item.documentation->type() == v:t_string && item.documentation != ''
+          d.info = item.documentation
+        elseif item.documentation->type() == v:t_dict
+            && item.documentation.value->type() == v:t_string
+          d.info = item.documentation.value
+        endif
       endif
     endif
     d.user_data = item
@@ -182,6 +189,80 @@ def ProcessCompletionReply(lspserver: dict<any>, req: dict<any>, reply: dict<any
   endif
 enddef
 
+# process the 'completionItem/resolve' reply from the LSP server
+# Result: CompletionItem
+def ProcessResolveReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>): void
+  if reply.result->empty()
+    return
+  endif
+
+  # check if completion item is still selected
+  var cInfo = complete_info()
+  if cInfo->empty()
+      || !cInfo.pum_visible
+      || cInfo.selected == -1
+      || cInfo.items[cInfo.selected].user_data.label != reply.result.label
+    return
+  endif
+
+  var infoText: list<string>
+  var infoKind: string
+
+  if reply.result->has_key('detail')
+    infoText->extend([reply.result.detail])
+  endif
+
+  if reply.result->has_key('documentation')
+    if !infoText->empty()
+      infoText->extend(['- - -'])
+    endif
+    if reply.result.documentation->type() == v:t_dict
+      # MarkupContent
+      if reply.result.documentation.kind == 'plaintext'
+        infoText->extend(reply.result.documentation.value->split("\n"))
+        infoKind = 'text'
+      elseif reply.result.documentation.kind == 'markdown'
+        infoText->extend(reply.result.documentation.value->split("\n"))
+        infoKind = 'markdown'
+      else
+        util.ErrMsg($'Error: Unsupported documentation type ({reply.result.documentation.kind})')
+        return
+      endif
+    elseif reply.result.documentation->type() == v:t_string
+      infoText->extend(reply.result.documentation->split("\n"))
+    else
+      util.ErrMsg($'Error: Unsupported documentation ({reply.result.documentation})')
+      return
+    endif
+  endif
+
+  if infoText->empty()
+    return
+  endif
+
+  # check if completion item is changed in meantime
+  cInfo = complete_info()
+  if cInfo->empty()
+      || !cInfo.pum_visible
+      || cInfo.selected == -1
+      || cInfo.items[cInfo.selected].user_data.label != reply.result.label
+    return
+  endif
+
+  var id = popup_findinfo()
+  if id > 0
+    var bufnr = id->winbufnr()
+    infoKind->setbufvar(bufnr, '&ft')
+    if infoKind == 'markdown'
+      3->setwinvar(id, '&cole')
+    else
+      0->setwinvar(id, '&cole')
+    endif
+    id->popup_settext(infoText)
+    id->popup_show()
+  endif
+enddef
+
 # process the 'textDocument/hover' reply from the LSP server
 # Result: Hover | null
 def ProcessHoverReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>): void
@@ -425,6 +506,7 @@ export def ProcessReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>):
       'initialize': ProcessInitializeReply,
       'textDocument/signatureHelp': ProcessSignaturehelpReply,
       'textDocument/completion': ProcessCompletionReply,
+      'completionItem/resolve': ProcessResolveReply,
       'textDocument/hover': ProcessHoverReply,
       'textDocument/documentHighlight': ProcessDocHighlightReply,
       'textDocument/documentSymbol': ProcessDocSymbolReply,
index 80dc97105ec5d8cc086a704e9e08b5e590c51dde..4a0025aee9f556c19b8dc23c9cc617c91aebdc92 100644 (file)
@@ -254,8 +254,13 @@ def BufferInit(bnr: number): void
 
   # set options for insert mode completion
   if opt.lspOptions.autoComplete
-    setbufvar(bnr, '&completeopt', 'menuone,popup,noinsert,noselect')
-    setbufvar(bnr, '&completepopup', 'border:off')
+    if lspserver.completionLazyDoc
+      setbufvar(bnr, '&completeopt', 'menuone,popuphidden,noinsert,noselect')
+      setbufvar(bnr, '&completepopup', 'width:80,highlight:Pmenu,align:menu,border:off')
+    else
+      setbufvar(bnr, '&completeopt', 'menuone,popup,noinsert,noselect')
+      setbufvar(bnr, '&completepopup', 'border:off')
+    endif
     # <Enter> in insert mode stops completion and inserts a <Enter>
     if !opt.lspOptions.noNewlineInCompletion
       inoremap <expr> <buffer> <CR> pumvisible() ? "\<C-Y>\<CR>" : "\<CR>"
@@ -279,6 +284,9 @@ def BufferInit(bnr: number): void
     if opt.lspOptions.autoComplete
       # Trigger 24x7 insert mode completion when text is changed
       exe $'autocmd TextChangedI <buffer={bnr}> call LspComplete()'
+      if lspserver.completionLazyDoc
+        exe $'autocmd CompleteChanged <buffer={bnr}> call LspResolve()'
+      endif
     endif
 
     # Update the diagnostics when insert mode is stopped
@@ -590,6 +598,19 @@ def g:LspComplete()
   return
 enddef
 
+# Lazy complete documentation handler
+def g:LspResolve()
+  var lspserver: dict<any> = CurbufGetServerChecked()
+  if lspserver->empty()
+    return
+  endif
+
+  var item = v:event.completed_item
+  if item->has_key('user_data')
+    lspserver.resolveCompletion(item.user_data)
+  endif
+enddef
+
 # omni complete handler
 def g:LspOmniFunc(findstart: number, base: string): any
   var lspserver: dict<any> = CurbufGetServerChecked()
index c420471935e46ba54f57cd3643bd3739cdd3793f..61248fc9b868bca1a4a5757d2facf39fd949c6a1 100644 (file)
@@ -56,6 +56,7 @@ def StartServer(lspserver: dict<any>): number
   lspserver.nextID = 1
   lspserver.requests = {}
   lspserver.omniCompletePending = false
+  lspserver.completionLazyDoc = false
   lspserver.completionTriggerChars = []
   lspserver.signaturePopup = -1
   lspserver.workspaceFolders = [getcwd()]
@@ -94,6 +95,7 @@ def InitServer(lspserver: dict<any>)
       completion: {
        completionItem: {
          documentationFormat: ['plaintext', 'markdown'],
+      resolveSupport: {properties: ['detail', 'documentation']},
          snippetSupport: false
        },
        completionItemKind: {valueSet: range(1, 25)}
@@ -445,6 +447,30 @@ def GetCompletion(lspserver: dict<any>, triggerKind_arg: number, triggerChar: st
   endif
 enddef
 
+# Get lazy properties for a completion item.
+# Request: "completionItem/resolve"
+# Param: CompletionItem
+def ResolveCompletion(lspserver: dict<any>, item: dict<any>): void
+  # Check whether LSP server supports completion item resolve
+  if !lspserver.caps->has_key('completionProvider')
+      || !lspserver.caps.completionProvider->has_key('resolveProvider')
+      || !lspserver.caps.completionProvider.resolveProvider
+    util.ErrMsg("Error: LSP server does not support completion item resolve")
+    return
+  endif
+
+  var req = lspserver.createRequest('completionItem/resolve')
+
+  # interface CompletionItem
+  req.params = item
+
+  lspserver.sendMessage(req)
+  if exists('g:LSPTest') && g:LSPTest
+    # When running LSP tests, make this a synchronous call
+    lspserver.waitForResponse(req)
+  endif
+enddef
+
 # Jump to or peek a symbol location.
 #
 # Send 'msg' to a LSP server and process the reply.  'msg' is one of the
@@ -1136,6 +1162,7 @@ export def NewLspServer(path: string, args: list<string>, isSync: bool, initiali
     textdocDidChange: function(TextdocDidChange, [lspserver]),
     sendInitializedNotif: function(SendInitializedNotif, [lspserver]),
     getCompletion: function(GetCompletion, [lspserver]),
+    resolveCompletion: function(ResolveCompletion, [lspserver]),
     gotoDefinition: function(GotoDefinition, [lspserver]),
     switchSourceHeader: function(SwitchSourceHeader, [lspserver]),
     gotoDeclaration: function(GotoDeclaration, [lspserver]),