autoload/lsp/hover.vim | 36 ++++++++++++++++++------------------ autoload/lsp/lsp.vim | 18 ++++++++++++++---- autoload/lsp/lspserver.vim | 69 +++++++++++++++++++++++++++++++++++++---------------- autoload/lsp/signature.vim | 7 +------ autoload/lsp/util.vim | 27 ++------------------------- doc/lsp.txt | 32 ++++++++++++++++++++++++++++++++ plugin/lsp.vim | 17 ++++++++++++----- diff --git a/autoload/lsp/hover.vim b/autoload/lsp/hover.vim index ab6ab8d7e523ac6ee222101547231b4af0bb155b..7119aab51a429f49459d68cb3bc9fcc63995165b 100644 --- a/autoload/lsp/hover.vim +++ b/autoload/lsp/hover.vim @@ -7,50 +7,50 @@ 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) +export def HoverReply(lspserver: dict, hoverResult: any): void + if hoverResult->empty() return endif var hoverText: list var hoverKind: string - if reply.result.contents->type() == v:t_dict - if reply.result.contents->has_key('kind') + if hoverResult.contents->type() == v:t_dict + if hoverResult.contents->has_key('kind') # MarkupContent - if reply.result.contents.kind == 'plaintext' - hoverText = reply.result.contents.value->split("\n") + if hoverResult.contents.kind == 'plaintext' + hoverText = hoverResult.contents.value->split("\n") hoverKind = 'text' - elseif reply.result.contents.kind == 'markdown' - hoverText = reply.result.contents.value->split("\n") + elseif hoverResult.contents.kind == 'markdown' + hoverText = hoverResult.contents.value->split("\n") hoverKind = 'markdown' else - util.ErrMsg($'Error: Unsupported hover contents type ({reply.result.contents.kind})') + util.ErrMsg($'Error: Unsupported hover contents type ({hoverResult.contents.kind})') return endif - elseif reply.result.contents->has_key('value') + elseif hoverResult.contents->has_key('value') # MarkedString - hoverText = reply.result.contents.value->split("\n") + hoverText = hoverResult.contents.value->split("\n") else - util.ErrMsg($'Error: Unsupported hover contents ({reply.result.contents})') + util.ErrMsg($'Error: Unsupported hover contents ({hoverResult.contents})') return endif - elseif reply.result.contents->type() == v:t_list + elseif hoverResult.contents->type() == v:t_list # interface MarkedString[] - for e in reply.result.contents + for e in hoverResult.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() + elseif hoverResult.contents->type() == v:t_string + if hoverResult.contents->empty() return endif - hoverText->extend(reply.result.contents->split("\n")) + hoverText->extend(hoverResult.contents->split("\n")) else - util.ErrMsg($'Error: Unsupported hover contents ({reply.result.contents})') + util.ErrMsg($'Error: Unsupported hover contents ({hoverResult.contents})') return endif diff --git a/autoload/lsp/lsp.vim b/autoload/lsp/lsp.vim index 23ae46a7aa2181279af9141b41e518fae8d1c801..373ff050e6298dcaf713b1c7881f68df2b70cb4d 100644 --- a/autoload/lsp/lsp.vim +++ b/autoload/lsp/lsp.vim @@ -92,9 +92,19 @@ def LspOmniComplSet(ftype: string, enabled: bool) ftypeOmniCtrlMap->extend({[ftype]: enabled}) enddef -export def EnableServerTrace() - util.ClearTraceLogs() - util.ServerTrace(true) +# Enable/disable the logging of the language server protocol messages +export def ServerDebug(arg: string) + if arg !=? 'on' && arg !=? 'off' + util.ErrMsg($'Error: Invalid argument ("{arg}") for LSP server debug') + return + endif + + if arg ==? 'on' + util.ClearTraceLogs() + util.ServerTrace(true) + else + util.ServerTrace(false) + endif enddef # Show information about all the LSP servers @@ -491,7 +501,7 @@ enddef # set the LSP server trace level for the current buffer # Params: SetTraceParams -export def SetTraceServer(traceVal: string) +export def ServerTraceSet(traceVal: string) if ['off', 'messages', 'verbose']->index(traceVal) == -1 util.ErrMsg($'Error: Unsupported LSP server trace value {traceVal}') return diff --git a/autoload/lsp/lspserver.vim b/autoload/lsp/lspserver.vim index 0f8287851544fa3f3dd3ad97d5a5295fe7ac5a18..6ef13f63d0685cc40d8c73d6366b5209b0f2e846 100644 --- a/autoload/lsp/lspserver.vim +++ b/autoload/lsp/lspserver.vim @@ -17,7 +17,7 @@ import './callhierarchy.vim' as callhier # LSP server standard output handler def Output_cb(lspserver: dict, chan: channel, msg: any): void - util.TraceLog(false, $'Received: {msg->string()}') + util.TraceLog(false, $'Received [{strftime("%m/%d/%y %T")}]: {msg->string()}') lspserver.data = msg lspserver.processMessages() enddef @@ -99,7 +99,7 @@ completion: { completionItem: { documentationFormat: ['plaintext', 'markdown'], resolveSupport: {properties: ['detail', 'documentation']}, - snippetSupport: true + snippetSupport: false }, completionItemKind: {valueSet: range(1, 25)} }, @@ -265,7 +265,7 @@ return endif ch->ch_sendexpr(content) if content->has_key('id') - util.TraceLog(false, $'Sent: {content->string()}') + util.TraceLog(false, $'Sent [{strftime("%m/%d/%y %T")}]: {content->string()}') endif enddef @@ -283,7 +283,12 @@ # LSP server has exited return {} endif + util.TraceLog(false, $'Sent [{strftime("%m/%d/%y %T")}]: {req->string()}') + + # Do the synchronous RPC call var reply = ch->ch_evalexpr(req) + + util.TraceLog(false, $'Received [{strftime("%m/%d/%y %T")}]: {reply->string()}') if reply->has_key('result') # successful reply @@ -303,7 +308,31 @@ return {} enddef +# LSP server asynchronous RPC callback +def AsyncRpcCb(lspserver: dict, method: string, RpcCb: func, chan: channel, reply: dict) + util.TraceLog(false, $'Received [{strftime("%m/%d/%y %T")}]: {reply->string()}') + + if reply->empty() + return + 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 + util.ErrMsg($'Error(LSP): request {method} failed ({emsg})') + return + endif + + RpcCb(lspserver, reply.result) +enddef + # Send a async RPC request message to the LSP server with a callback function. +# Returns the LSP message id. This id can be used to cancel the RPC request +# (if needed). Returns -1 on error. def AsyncRpc(lspserver: dict, method: string, params: any, Cbfunc: func): number var req = {} req.method = method @@ -316,7 +345,20 @@ # LSP server has exited return -1 endif - var reply = ch->ch_sendexpr(req, {callback: Cbfunc}) + util.TraceLog(false, $'Sent [{strftime("%m/%d/%y %T")}]: {req->string()}') + + # Do the asynchronous RPC call + var Fn = function('AsyncRpcCb', [lspserver, method, Cbfunc]) + + var reply: dict + if get(g:, 'LSPTest') + # When running LSP tests, make this a synchronous RPC call + reply = Rpc(lspserver, method, params) + Fn(test_null_channel(), reply) + else + # Otherwise, make an asynchronous RPC call + reply = ch->ch_sendexpr(req, {callback: Fn}) + endif if reply->empty() return -1 endif @@ -643,14 +685,8 @@ # interface SignatureHelpParams # interface TextDocumentPositionParams var params = GetLspTextDocPosition() - if get(g:, 'LSPTest') - # When running LSP tests, make this a synchronous call - var reply = lspserver.rpc('textDocument/signatureHelp', params) - signature.SignatureHelp(lspserver, 0, reply) - else - lspserver.rpc_a('textDocument/signatureHelp', params, - function(signature.SignatureHelp, [lspserver])) - endif + lspserver.rpc_a('textDocument/signatureHelp', params, + signature.SignatureHelp) enddef def DidSaveFile(lspserver: dict, bnr: number): void @@ -682,14 +718,7 @@ # interface HoverParams # interface TextDocumentPositionParams var params = GetLspTextDocPosition() - if get(g:, 'LSPTest') - # When running LSP tests, make this a synchronous call - var reply = lspserver.rpc('textDocument/hover', params) - hover.HoverReply(lspserver, 0, reply) - else - lspserver.rpc_a('textDocument/hover', params, - function(hover.HoverReply, [lspserver])) - endif + lspserver.rpc_a('textDocument/hover', params, hover.HoverReply) enddef # Request: "textDocument/references" diff --git a/autoload/lsp/signature.vim b/autoload/lsp/signature.vim index 9acecb861a8ded462674552835b8899b10af73aa..c5c0c84a4a67060f05934a27b6e3eccfb7e54c30 100644 --- a/autoload/lsp/signature.vim +++ b/autoload/lsp/signature.vim @@ -41,12 +41,7 @@ # 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 +export def SignatureHelp(lspserver: dict, sighelp: any): void if sighelp->empty() CloseSignaturePopup(lspserver) return diff --git a/autoload/lsp/util.vim b/autoload/lsp/util.vim index a567cbce4ab96a734c3e2d389b8e943fac5df8c2..25701e38bcb7c3a254dd3e31ac1dc702d39dde69 100644 --- a/autoload/lsp/util.vim +++ b/autoload/lsp/util.vim @@ -35,9 +35,9 @@ if !lsp_server_trace return endif if stderr - writefile(split(msg, "\n"), $'{lsp_log_dir}lsp_server.err', 'a') + writefile(msg->split("\n"), $'{lsp_log_dir}lsp_server.err', 'a') else - writefile(split(msg, "\n"), $'{lsp_log_dir}lsp_server.out', 'a') + writefile([msg], $'{lsp_log_dir}lsp_server.out', 'a') endif enddef @@ -149,29 +149,6 @@ from: getpos('.'), matchnr: 1, 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 diff --git a/doc/lsp.txt b/doc/lsp.txt index c8379644a9181432f1e1e7493bf4b50c018236c5..71bbdc6b67a87a7ecd54239865b9c5ee88a87aac 100644 --- a/doc/lsp.txt +++ b/doc/lsp.txt @@ -711,4 +711,36 @@ diagnostics are received from the language server. This is invoked after the LSP client has processed the diagnostics. +============================================================================== +10. 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 +enables the logging of the messages: > + + :LspServerDebug on +< +This command also clears the log files. The following command disables the +logging of the messages: > + + :LspServerDebug off +< +By default, the messages are not logged. + +The messages printed by the LSP server in the stdout are logged to the +lsp_server.out file and the messages printed in the stderr are logged to the +lsp_server.err file. On a Unix-like system, these files are created in the +/tmp directory. On MS-Windows, these files are created in the %TEMP% +directory. + +The language servers typically support command line options to enable debug +messages and to increase the verbosity of the messages. You can refer to the +language server documentation for information about this. You can include +these options when registering the LSP server with this plugin. + +If a language server supports the "$/logTrace" LSP notification, then you can +use the :LspServerTrace command to set the trace value: > + + :LspServerTrace { off | messages | verbose } +< vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/plugin/lsp.vim b/plugin/lsp.vim index 635328cd72260813a5f229edaf3b7656fe7bc7a8..c9a15a7c29661c1db71e8b79319d6a713dcfa01b 100644 --- a/plugin/lsp.vim +++ b/plugin/lsp.vim @@ -14,10 +14,6 @@ def g:LspOptionsSet(opts: dict) options.OptionsSet(opts) enddef -def g:LspServerTraceEnable() - lsp.EnableServerTrace() -enddef - def g:LspAddServer(serverList: list>) lsp.AddServer(serverList) enddef @@ -40,6 +36,16 @@ return filter(l, (_, val) => val =~ arglead) endif enddef +# Command line completion function for the LspSetTrace command. +def LspServerDebugComplete(arglead: string, cmdline: string, cursorpos: number): list + var l = ['off', 'on'] + if arglead->empty() + return l + else + return filter(l, (_, val) => val =~ arglead) + endif +enddef + augroup LSPAutoCmds au! autocmd BufNewFile,BufReadPost * lsp.AddFile(expand('')->str2nr()) @@ -59,7 +65,8 @@ # LSP commands command! -nargs=0 -bar LspShowServers lsp.ShowServers() command! -nargs=0 -bar LspShowServerCapabilities lsp.ShowServerCapabilities() command! -nargs=0 -bar LspServerRestart lsp.RestartServer() -command! -nargs=1 -complete=customlist,LspServerTraceComplete -bar LspSetTrace lsp.SetTraceServer() +command! -nargs=1 -complete=customlist,LspServerTraceComplete -bar LspServerTrace lsp.ServerTraceSet() +command! -nargs=1 -complete=customlist,LspServerDebugComplete -bar LspServerDebug lsp.ServerDebug() command! -nargs=0 -bar LspGotoDefinition lsp.GotoDefinition(v:false) command! -nargs=0 -bar LspGotoDeclaration lsp.GotoDeclaration(v:false) command! -nargs=0 -bar LspGotoTypeDef lsp.GotoTypedef(v:false)