From 16c7cefcbb5f48c3117da9963352aeffe5dbd4a4 Mon Sep 17 00:00:00 2001 From: Yegappan Lakshmanan Date: Thu, 27 Oct 2022 22:59:55 -0700 Subject: [PATCH] Add an async RPC function with a callback function. Use it for hover and signature help requests --- autoload/lsp/handlers.vim | 75 -------------------------------------- autoload/lsp/hover.vim | 72 ++++++++++++++++++++++++++++++++++++ autoload/lsp/lspserver.vim | 71 +++++++++++++++++++++++++----------- autoload/lsp/signature.vim | 11 +++++- autoload/lsp/util.vim | 23 ++++++++++++ 5 files changed, 153 insertions(+), 99 deletions(-) create mode 100644 autoload/lsp/hover.vim diff --git a/autoload/lsp/handlers.vim b/autoload/lsp/handlers.vim index 0501384..6d19b77 100644 --- a/autoload/lsp/handlers.vim +++ b/autoload/lsp/handlers.vim @@ -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, req: dict, reply: dict, req: dict, reply: dict): 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 = ['', @@ -270,69 +260,6 @@ def ProcessResolveReply(lspserver: dict, req: dict, reply: dict): endif enddef -# process the 'textDocument/hover' reply from the LSP server -# Result: Hover | null -def ProcessHoverReply(lspserver: dict, req: dict, reply: dict): void - if reply.result->empty() - return - endif - - var hoverText: list - 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, req: dict, reply: dict): void @@ -511,10 +438,8 @@ export def ProcessReply(lspserver: dict, req: dict, reply: dict): var lsp_reply_handlers: dict = { '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 index 0000000..ab6ab8d --- /dev/null +++ b/autoload/lsp/hover.vim @@ -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, reply: dict): void + if !util.SanitizeReply('textDocument/hover', reply) + return + endif + + var hoverText: list + 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 diff --git a/autoload/lsp/lspserver.vim b/autoload/lsp/lspserver.vim index 58d2884..c133077 100644 --- a/autoload/lsp/lspserver.vim +++ b/autoload/lsp/lspserver.vim @@ -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) 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, method: string, params: any): dict +def Rpc(lspserver: dict, method: string, params: any): dict var req = {} req.method = method req.params = {} @@ -297,6 +300,27 @@ def RpcCall(lspserver: dict, method: string, params: any): dict return {} enddef +# Send a async RPC request message to the LSP server with a callback function. +def AsyncRpc(lspserver: dict, 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, req: dict) @@ -441,7 +465,7 @@ def GetCompletion(lspserver: dict, 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, item: dict): 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): 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): 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): 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, 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, 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, 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, cmd: dict) 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, 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]), diff --git a/autoload/lsp/signature.vim b/autoload/lsp/signature.vim index 826542d..9acecb8 100644 --- a/autoload/lsp/signature.vim +++ b/autoload/lsp/signature.vim @@ -38,8 +38,15 @@ export def SignatureInit(lspserver: dict) autocmd InsertLeave call CloseCurBufSignaturePopup() enddef -# Display the symbol signature help -export def SignatureDisplay(lspserver: dict, sighelp: dict): 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, reply: dict): void + if !util.SanitizeReply('textDocument/signatureHelp', reply) + return + endif + + var sighelp: dict = reply.result if sighelp->empty() CloseSignaturePopup(lspserver) return diff --git a/autoload/lsp/util.vim b/autoload/lsp/util.vim index 679bee6..a567cbc 100644 --- a/autoload/lsp/util.vim +++ b/autoload/lsp/util.vim @@ -151,4 +151,27 @@ export def PushCursorToTagStack() }]}, 't') enddef +export def SanitizeReply(reqmsg: string, reply: dict): 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 -- 2.48.1