]> Sergey Matveev's repositories - vim-lsp.git/commitdiff
Add an async RPC function with a callback function. Use it for hover and signature...
authorYegappan Lakshmanan <yegappan@yahoo.com>
Fri, 28 Oct 2022 05:59:55 +0000 (22:59 -0700)
committerYegappan Lakshmanan <yegappan@yahoo.com>
Fri, 28 Oct 2022 05:59:55 +0000 (22:59 -0700)
autoload/lsp/handlers.vim
autoload/lsp/hover.vim [new file with mode: 0644]
autoload/lsp/lspserver.vim
autoload/lsp/signature.vim
autoload/lsp/util.vim

index 0501384410d463ba6fe8272fd28f64e54735e49f..6d19b771736413422d488dbbd01e7e8a0ef75360 100644 (file)
@@ -12,7 +12,6 @@ import './textedit.vim'
 import './symbol.vim'
 import './codeaction.vim'
 import './callhierarchy.vim' as callhier
-import './signature.vim'
 
 # process the 'initialize' method reply from the LSP server
 # Result: InitializeResult
@@ -45,15 +44,6 @@ def ProcessInitializeReply(lspserver: dict<any>, req: dict<any>, reply: dict<any
   endif
 enddef
 
-# process the 'textDocument/signatureHelp' reply from the LSP server
-# Result: SignatureHelp | null
-def ProcessSignaturehelpReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>): void
-  if reply.result->empty()
-    return
-  endif
-  signature.SignatureDisplay(lspserver, reply.result)
-enddef
-
 # Map LSP complete item kind to a character
 def LspCompleteItemKindChar(kind: number): string
   var kindMap: list<string> = ['',
@@ -270,69 +260,6 @@ def ProcessResolveReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>):
   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
-  if reply.result->empty()
-    return
-  endif
-
-  var hoverText: list<string>
-  var hoverKind: string
-
-  if reply.result.contents->type() == v:t_dict
-    if reply.result.contents->has_key('kind')
-      # MarkupContent
-      if reply.result.contents.kind == 'plaintext'
-        hoverText = reply.result.contents.value->split("\n")
-        hoverKind = 'text'
-      elseif reply.result.contents.kind == 'markdown'
-        hoverText = reply.result.contents.value->split("\n")
-        hoverKind = 'markdown'
-      else
-        util.ErrMsg($'Error: Unsupported hover contents type ({reply.result.contents.kind})')
-        return
-      endif
-    elseif reply.result.contents->has_key('value')
-      # MarkedString
-      hoverText = reply.result.contents.value->split("\n")
-    else
-      util.ErrMsg($'Error: Unsupported hover contents ({reply.result.contents})')
-      return
-    endif
-  elseif reply.result.contents->type() == v:t_list
-    # interface MarkedString[]
-    for e in reply.result.contents
-      if e->type() == v:t_string
-        hoverText->extend(e->split("\n"))
-      else
-        hoverText->extend(e.value->split("\n"))
-      endif
-    endfor
-  elseif reply.result.contents->type() == v:t_string
-    if reply.result.contents->empty()
-      return
-    endif
-    hoverText->extend(reply.result.contents->split("\n"))
-  else
-    util.ErrMsg($'Error: Unsupported hover contents ({reply.result.contents})')
-    return
-  endif
-  if opt.lspOptions.hoverInPreview
-    silent! pedit HoverReply
-    wincmd P
-    setlocal buftype=nofile
-    setlocal bufhidden=delete
-    exe $'setlocal ft={hoverKind}'
-    bufnr()->deletebufline(1, '$')
-    append(0, hoverText)
-    cursor(1, 1)
-    wincmd p
-  else
-    hoverText->popup_atcursor({moved: 'word'})
-  endif
-enddef
-
 # process the 'textDocument/documentHighlight' reply from the LSP server
 # Result: DocumentHighlight[] | null
 def ProcessDocHighlightReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>): void
@@ -511,10 +438,8 @@ export def ProcessReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>):
   var lsp_reply_handlers: dict<func> =
     {
       'initialize': ProcessInitializeReply,
-      'textDocument/signatureHelp': ProcessSignaturehelpReply,
       'textDocument/completion': ProcessCompletionReply,
       'completionItem/resolve': ProcessResolveReply,
-      'textDocument/hover': ProcessHoverReply,
       'textDocument/documentHighlight': ProcessDocHighlightReply,
       'textDocument/documentSymbol': ProcessDocSymbolReply,
       'textDocument/codeAction': ProcessCodeActionReply,
diff --git a/autoload/lsp/hover.vim b/autoload/lsp/hover.vim
new file mode 100644 (file)
index 0000000..ab6ab8d
--- /dev/null
@@ -0,0 +1,72 @@
+vim9script
+
+# Functions related to displaying hover symbol information.
+
+import './util.vim'
+import './options.vim' as opt
+
+# process the 'textDocument/hover' reply from the LSP server
+# Result: Hover | null
+export def HoverReply(lspserver: dict<any>, _: any, reply: dict<any>): void
+  if !util.SanitizeReply('textDocument/hover', reply)
+    return
+  endif
+
+  var hoverText: list<string>
+  var hoverKind: string
+
+  if reply.result.contents->type() == v:t_dict
+    if reply.result.contents->has_key('kind')
+      # MarkupContent
+      if reply.result.contents.kind == 'plaintext'
+        hoverText = reply.result.contents.value->split("\n")
+        hoverKind = 'text'
+      elseif reply.result.contents.kind == 'markdown'
+        hoverText = reply.result.contents.value->split("\n")
+        hoverKind = 'markdown'
+      else
+        util.ErrMsg($'Error: Unsupported hover contents type ({reply.result.contents.kind})')
+        return
+      endif
+    elseif reply.result.contents->has_key('value')
+      # MarkedString
+      hoverText = reply.result.contents.value->split("\n")
+    else
+      util.ErrMsg($'Error: Unsupported hover contents ({reply.result.contents})')
+      return
+    endif
+  elseif reply.result.contents->type() == v:t_list
+    # interface MarkedString[]
+    for e in reply.result.contents
+      if e->type() == v:t_string
+        hoverText->extend(e->split("\n"))
+      else
+        hoverText->extend(e.value->split("\n"))
+      endif
+    endfor
+  elseif reply.result.contents->type() == v:t_string
+    if reply.result.contents->empty()
+      return
+    endif
+    hoverText->extend(reply.result.contents->split("\n"))
+  else
+    util.ErrMsg($'Error: Unsupported hover contents ({reply.result.contents})')
+    return
+  endif
+
+  if opt.lspOptions.hoverInPreview
+    silent! pedit LspHoverReply
+    wincmd P
+    setlocal buftype=nofile
+    setlocal bufhidden=delete
+    exe $'setlocal ft={hoverKind}'
+    bufnr()->deletebufline(1, '$')
+    append(0, hoverText)
+    cursor(1, 1)
+    wincmd p
+  else
+    hoverText->popup_atcursor({moved: 'word'})
+  endif
+enddef
+
+# vim: tabstop=8 shiftwidth=2 softtabstop=2
index 58d2884e2bb45defc0031617a0f80ea68f38c66d..c1330778ecf6cf6e937e006714ce128d6f1a7b66 100644 (file)
@@ -4,12 +4,15 @@ vim9script
 # Refer to https://microsoft.github.io/language-server-protocol/specification
 # for the Language Server Protocol (LSP) specificaiton.
 
+import './options.vim' as opt
 import './handlers.vim'
 import './util.vim'
 import './diag.vim'
 import './selection.vim'
 import './symbol.vim'
 import './textedit.vim'
+import './hover.vim'
+import './signature.vim'
 import './callhierarchy.vim' as callhier
 
 # LSP server standard output handler
@@ -96,7 +99,7 @@ def InitServer(lspserver: dict<any>)
        completionItem: {
          documentationFormat: ['plaintext', 'markdown'],
          resolveSupport: {properties: ['detail', 'documentation']},
-         snippetSupport: false
+         snippetSupport: true
        },
        completionItemKind: {valueSet: range(1, 25)}
       },
@@ -265,7 +268,7 @@ enddef
 
 # Send a sync RPC request message to the LSP server and return the received
 # reply.  In case of an error, an empty Dict is returned.
-def RpcCall(lspserver: dict<any>, method: string, params: any): dict<any>
+def Rpc(lspserver: dict<any>, method: string, params: any): dict<any>
   var req = {}
   req.method = method
   req.params = {}
@@ -297,6 +300,27 @@ def RpcCall(lspserver: dict<any>, method: string, params: any): dict<any>
   return {}
 enddef
 
+# Send a async RPC request message to the LSP server with a callback function.
+def AsyncRpc(lspserver: dict<any>, method: string, params: any, Cbfunc: func): number
+  var req = {}
+  req.method = method
+  req.params = {}
+  req.params->extend(params)
+
+  var ch = lspserver.job->job_getchannel()
+  if ch_status(ch) != 'open'
+    # LSP server has exited
+    return -1
+  endif
+
+  var reply = ch->ch_sendexpr(req, {callback: Cbfunc})
+  if reply->empty()
+    return -1
+  endif
+
+  return reply.id
+enddef
+
 # Wait for a response message from the LSP server for the request "req"
 # Waits for a maximum of 5 seconds
 def WaitForResponse(lspserver: dict<any>, req: dict<any>)
@@ -441,7 +465,7 @@ def GetCompletion(lspserver: dict<any>, triggerKind_arg: number, triggerChar: st
   req.params.context = {triggerKind: triggerKind_arg, triggerCharacter: triggerChar}
 
   lspserver.sendMessage(req)
-  if exists('g:LSPTest') && g:LSPTest
+  if get(g:, 'LSPTest')
     # When running LSP tests, make this a synchronous call
     lspserver.waitForResponse(req)
   endif
@@ -465,7 +489,7 @@ def ResolveCompletion(lspserver: dict<any>, item: dict<any>): void
   req.params = item
 
   lspserver.sendMessage(req)
-  if exists('g:LSPTest') && g:LSPTest
+  if get(g:, 'LSPTest')
     # When running LSP tests, make this a synchronous call
     lspserver.waitForResponse(req)
   endif
@@ -613,16 +637,16 @@ def ShowSignature(lspserver: dict<any>): void
     return
   endif
 
-  var req = lspserver.createRequest('textDocument/signatureHelp')
   # interface SignatureHelpParams
   #   interface TextDocumentPositionParams
-  req.params->extend(GetLspTextDocPosition())
-
-  lspserver.sendMessage(req)
-
-  if exists('g:LSPTest') && g:LSPTest
+  var params = GetLspTextDocPosition()
+  if get(g:, 'LSPTest')
     # When running LSP tests, make this a synchronous call
-    lspserver.waitForResponse(req)
+    var reply = lspserver.rpc('textDocument/signatureHelp', params)
+    signature.SignatureHelp(lspserver, 0, reply)
+  else
+    lspserver.rpc_a('textDocument/signatureHelp', params,
+                   function(signature.SignatureHelp, [lspserver]))
   endif
 enddef
 
@@ -652,14 +676,16 @@ def ShowHoverInfo(lspserver: dict<any>): void
     return
   endif
 
-  var req = lspserver.createRequest('textDocument/hover')
   # interface HoverParams
   #   interface TextDocumentPositionParams
-  req.params->extend(GetLspTextDocPosition())
-  lspserver.sendMessage(req)
-  if exists('g:LSPTest') && g:LSPTest
+  var params = GetLspTextDocPosition()
+  if get(g:, 'LSPTest')
     # When running LSP tests, make this a synchronous call
-    lspserver.waitForResponse(req)
+    var reply = lspserver.rpc('textDocument/hover', params)
+    hover.HoverReply(lspserver, 0, reply)
+  else
+    lspserver.rpc_a('textDocument/hover', params,
+                   function(hover.HoverReply, [lspserver]))
   endif
 enddef
 
@@ -708,7 +734,7 @@ def DocHighlight(lspserver: dict<any>): void
   #   interface TextDocumentPositionParams
   req.params->extend(GetLspTextDocPosition())
   lspserver.sendMessage(req)
-  if exists('g:LSPTest') && g:LSPTest
+  if get(g:, 'LSPTest')
     # When running LSP tests, make this a synchronous call
     lspserver.waitForResponse(req)
   endif
@@ -729,7 +755,7 @@ def GetDocSymbols(lspserver: dict<any>, fname: string): void
   # interface TextDocumentIdentifier
   req.params->extend({textDocument: {uri: util.LspFileToUri(fname)}})
   lspserver.sendMessage(req)
-  if exists('g:LSPTest') && g:LSPTest
+  if get(g:, 'LSPTest')
     # When running LSP tests, make this a synchronous call
     lspserver.waitForResponse(req)
   endif
@@ -935,7 +961,7 @@ def CodeAction(lspserver: dict<any>, fname_arg: string)
   req.params->extend({context: {diagnostics: d}})
 
   lspserver.sendMessage(req)
-  if exists('g:LSPTest') && g:LSPTest
+  if get(g:, 'LSPTest')
     # When running LSP tests, make this a synchronous call
     lspserver.waitForResponse(req)
   endif
@@ -1086,7 +1112,7 @@ def FoldRange(lspserver: dict<any>, fname: string)
   # interface TextDocumentIdentifier
   req.params->extend({textDocument: {uri: util.LspFileToUri(fname)}})
   lspserver.sendMessage(req)
-  if exists('g:LSPTest') && g:LSPTest
+  if get(g:, 'LSPTest')
     # When running LSP tests, make this a synchronous call
     lspserver.waitForResponse(req)
   endif
@@ -1099,7 +1125,7 @@ def ExecuteCommand(lspserver: dict<any>, cmd: dict<any>)
   var req = lspserver.createRequest('workspace/executeCommand')
   req.params->extend(cmd)
   lspserver.sendMessage(req)
-  if exists('g:LSPTest') && g:LSPTest
+  if get(g:, 'LSPTest')
     # When running LSP tests, make this a synchronous call
     lspserver.waitForResponse(req)
   endif
@@ -1179,7 +1205,8 @@ export def NewLspServer(path: string, args: list<string>, isSync: bool, initiali
     createNotification: function(CreateNotification, [lspserver]),
     sendResponse: function(SendResponse, [lspserver]),
     sendMessage: function(SendMessage, [lspserver]),
-    rpc: function(RpcCall, [lspserver]),
+    rpc: function(Rpc, [lspserver]),
+    rpc_a: function(AsyncRpc, [lspserver]),
     waitForResponse: function(WaitForResponse, [lspserver]),
     processReply: function(handlers.ProcessReply, [lspserver]),
     processNotif: function(handlers.ProcessNotif, [lspserver]),
index 826542d703451ec7505ca901793defc130336491..9acecb861a8ded462674552835b8899b10af73aa 100644 (file)
@@ -38,8 +38,15 @@ export def SignatureInit(lspserver: dict<any>)
   autocmd InsertLeave <buffer> call CloseCurBufSignaturePopup()
 enddef
 
-# Display the symbol signature help
-export def SignatureDisplay(lspserver: dict<any>, sighelp: dict<any>): void
+# process the 'textDocument/signatureHelp' reply from the LSP server and
+# display the symbol signature help.
+# Result: SignatureHelp | null
+export def SignatureHelp(lspserver: dict<any>, _: any, reply: dict<any>): void
+  if !util.SanitizeReply('textDocument/signatureHelp', reply)
+    return
+  endif
+
+  var sighelp: dict<any> = reply.result
   if sighelp->empty()
     CloseSignaturePopup(lspserver)
     return
index 679bee60d2f61a80923c67c95c356a217e106d1d..a567cbce4ab96a734c3e2d389b8e943fac5df8c2 100644 (file)
@@ -151,4 +151,27 @@ export def PushCursorToTagStack()
                         }]}, 't')
 enddef
 
+export def SanitizeReply(reqmsg: string, reply: dict<any>): bool
+  if reply->empty()
+    return false
+  endif
+
+  if reply->has_key('error')
+    # request failed
+    var emsg: string
+    emsg = $'{reply.error.message}, code = {reply.error.code}'
+    if reply.error->has_key('data')
+      emsg ..= $', data = {reply.error.data->string()}'
+    endif
+    ErrMsg($'Error(LSP): request {reqmsg} failed ({emsg})')
+    return false
+  endif
+
+  if reply.result->empty()
+    return false
+  endif
+
+  return true
+enddef
+
 # vim: tabstop=8 shiftwidth=2 softtabstop=2