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 +++++++++++++++++++++++ diff --git a/autoload/lsp/handlers.vim b/autoload/lsp/handlers.vim index 0501384410d463ba6fe8272fd28f64e54735e49f..6d19b771736413422d488dbbd01e7e8a0ef75360 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 @@ -43,15 +42,6 @@ # buffer if bufwinid('LSP-Outline') != -1 lspserver.getDocSymbols(@%) endif -enddef - -# process the 'textDocument/signatureHelp' reply from the LSP server -# Result: SignatureHelp | null -def ProcessSignaturehelpReply(lspserver: 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 @@ -270,69 +260,6 @@ id->popup_show() 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): void 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 0000000000000000000000000000000000000000..ab6ab8d7e523ac6ee222101547231b4af0bb155b --- /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 58d2884e2bb45defc0031617a0f80ea68f38c66d..c1330778ecf6cf6e937e006714ce128d6f1a7b66 100644 --- a/autoload/lsp/lspserver.vim +++ b/autoload/lsp/lspserver.vim @@ -4,12 +4,15 @@ # LSP server functions # 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 @@ completion: { 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 @@ 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 @@ # interface CompletionContext 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 @@ # interface CompletionItem 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 @@ util.ErrMsg("Error: LSP server does not support signature help") 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 @@ || !lspserver.caps.hoverProvider 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 @@ # interface DocumentHighlightParams # 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 @@ # interface DocumentSymbolParams # 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 @@ endif 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 @@ # interface FoldingRangeParams # 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 @@ createResponse: function(CreateResponse, [lspserver]), 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 826542d703451ec7505ca901793defc130336491..9acecb861a8ded462674552835b8899b10af73aa 100644 --- a/autoload/lsp/signature.vim +++ b/autoload/lsp/signature.vim @@ -38,8 +38,15 @@ # close the signature popup when leaving insert mode 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 679bee60d2f61a80923c67c95c356a217e106d1d..a567cbce4ab96a734c3e2d389b8e943fac5df8c2 100644 --- a/autoload/lsp/util.vim +++ b/autoload/lsp/util.vim @@ -151,4 +151,27 @@ tagname: expand('') }]}, '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