From 1bde1c24c024170e6043ffb265ba8efde7b5d25e Mon Sep 17 00:00:00 2001 From: Yegappan Lakshmanan Date: Mon, 3 Apr 2023 19:49:56 -0700 Subject: [PATCH] Use server specific log and error files. Add optional name and debug options to the LSP server configuration. Use server specific debug flag for logging. --- README.md | 10 +++++++ autoload/lsp/handlers.vim | 2 +- autoload/lsp/hover.vim | 10 +++---- autoload/lsp/lsp.vim | 39 +++++++++++++++++--------- autoload/lsp/lspserver.vim | 46 +++++++++++++++++++++++-------- autoload/lsp/util.vim | 56 ++++++++++++++------------------------ doc/lsp.txt | 49 +++++++++++++++++++++++---------- 7 files changed, 131 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index d5d428a..ebb1726 100644 --- a/README.md +++ b/README.md @@ -54,11 +54,13 @@ function! TypeScriptCustomNotificationHandler(lspserver, reply) abort endfunction let lspServers = [ \ #{ + \ name: 'clangd', \ filetype: ['c', 'cpp'], \ path: '/usr/local/bin/clangd', \ args: ['--background-index'] \ }, \ #{ + \ name: 'typescriptlang', \ filetype: ['javascript', 'typescript'], \ path: '/usr/local/bin/typescript-language-server', \ args: ['--stdio'], @@ -67,38 +69,45 @@ let lspServers = [ \ } \ }, \ #{ + \ name: 'bashlang', \ filetype: 'sh', \ path: '/usr/local/bin/bash-language-server', \ args: ['start'] \ }, \ #{ + \ name: 'vimlang', \ filetype: 'vim', \ path: '/usr/local/bin/vim-language-server', \ args: ['--stdio'] \ }, \ #{ + \ name: 'golang', \ filetype: ['go', 'gomod'], \ path: '/usr/local/bin/gopls', \ args: ['serve'], \ syncInit: v:true \ }, \ #{ + \ name: 'rustlang', \ filetype: ['rust'], \ path: '/usr/local/bin/rust-analyzer', \ args: [], \ syncInit: v:true \ }, \ #{ + \ name: 'pylang', \ filetype: ['python'], \ path: '/usr/local/bin/pyls', \ args: [] \ }, \ #{ + \ name: 'fortranls', \ filetype: ['fortran'], \ path: '/usr/local/bin/fortls', \ args: ['--nthreads=1', '--use_signature_help', '--hover_signature'] \ }, \ #{ + \ name: 'phplang', \ filetype: ['php'], \ path: '/usr/local/bin/intelephense', \ args: ['--stdio'], @@ -129,6 +138,7 @@ If you used [vim-plug](https://github.com/junegunn/vim-plug) to install the LSP ```viml let lspServers = [ \ #{ + \ name: 'clang', \ filetype: ['c', 'cpp'], \ path: '/usr/local/bin/clangd', \ args: ['--background-index'] diff --git a/autoload/lsp/handlers.vim b/autoload/lsp/handlers.vim index cbeab06..c2479e8 100644 --- a/autoload/lsp/handlers.vim +++ b/autoload/lsp/handlers.vim @@ -50,7 +50,7 @@ def ProcessLogMsgNotif(lspserver: dict, reply: dict) mtype = msgType[reply.params.type] endif - util.TraceLog(false, $'{strftime("%m/%d/%y %T")}: [{mtype}]: {reply.params.message}') + lspserver.traceLog($'{strftime("%m/%d/%y %T")}: [{mtype}]: {reply.params.message}') enddef # process unsupported notification messages diff --git a/autoload/lsp/hover.vim b/autoload/lsp/hover.vim index b5dc50f..c90a4b3 100644 --- a/autoload/lsp/hover.vim +++ b/autoload/lsp/hover.vim @@ -6,7 +6,7 @@ import './util.vim' import './options.vim' as opt # Util used to compute the hoverText from textDocument/hover reply -def GetHoverText(hoverResult: any): list +def GetHoverText(lspserver: dict, hoverResult: any): list if hoverResult->empty() return ['', ''] endif @@ -22,8 +22,7 @@ def GetHoverText(hoverResult: any): list return [hoverResult.contents.value->split("\n"), 'lspgfm'] endif - util.TraceLog( - true, + lspserver.errorLog( $'{strftime("%m/%d/%y %T")}: Unsupported hover contents kind ({hoverResult.contents.kind})' ) return ['', ''] @@ -65,8 +64,7 @@ def GetHoverText(hoverResult: any): list return [hoverText, 'lspgfm'] endif - util.TraceLog( - true, + lspserver.errorLog( $'{strftime("%m/%d/%y %T")}: Unsupported hover reply ({hoverResult})' ) return ['', ''] @@ -75,7 +73,7 @@ enddef # process the 'textDocument/hover' reply from the LSP server # Result: Hover | null export def HoverReply(lspserver: dict, hoverResult: any): void - var [ hoverText, hoverKind ] = GetHoverText(hoverResult) + var [hoverText, hoverKind] = GetHoverText(lspserver, hoverResult) # Nothing to show if hoverText->empty() diff --git a/autoload/lsp/lsp.vim b/autoload/lsp/lsp.vim index a9ea16a..65accd3 100644 --- a/autoload/lsp/lsp.vim +++ b/autoload/lsp/lsp.vim @@ -60,15 +60,21 @@ export def ServerDebug(arg: string) return endif + var lspserver: dict = buf.CurbufGetServer() + if lspserver->empty() + return + endif + if arg ==? 'on' - util.ClearTraceLogs() - util.ServerTrace(true) + util.ClearTraceLogs(lspserver.logfile) + util.ClearTraceLogs(lspserver.errfile) + lspserver.debug = true elseif arg ==? 'off' - util.ServerTrace(false) + lspserver.debug = false elseif arg ==? 'messages' - util.ServerMessagesShow(false) + util.ServerMessagesShow(lspserver.logfile) else - util.ServerMessagesShow(true) + util.ServerMessagesShow(lspserver.errfile) endif enddef @@ -471,8 +477,6 @@ export def AddServer(serverList: list>) return endif args = server.args - else - endif var initializationOptions: dict = {} @@ -494,18 +498,27 @@ export def AddServer(serverList: list>) server.syncInit = v:false endif - var lspserver: dict = lserver.NewLspServer(server.path, - args, - server.syncInit, + if !server->has_key('name') || server.name->type() != v:t_string + || server.name == '' + # Use the executable name (without the extension) as the language server + # name. + server.name = server.path->fnamemodify(':t:r') + endif + + if !server->has_key('debug') || server.debug->type() != v:t_bool + server.debug = false + endif + + var lspserver: dict = lserver.NewLspServer(server.name, server.path, + args, server.syncInit, initializationOptions, - customNotificationHandlers) + customNotificationHandlers, + server.debug) var ftypes = server.filetype if ftypes->type() == v:t_string - lspserver.name = ftypes->substitute('\w\+', '\L\u\0', '') AddServerForFiltype(lspserver, ftypes, server.omnicompl) elseif ftypes->type() == v:t_list - lspserver.name = ftypes[0]->substitute('\w\+', '\L\u\0', '') for ftype in ftypes AddServerForFiltype(lspserver, ftype, server.omnicompl) endfor diff --git a/autoload/lsp/lspserver.vim b/autoload/lsp/lspserver.vim index b93d885..2a64da1 100644 --- a/autoload/lsp/lspserver.vim +++ b/autoload/lsp/lspserver.vim @@ -26,14 +26,14 @@ import './inlayhints.vim' # LSP server standard output handler def Output_cb(lspserver: dict, chan: channel, msg: any): void - util.TraceLog(false, $'{strftime("%m/%d/%y %T")}: Received {msg->string()}') + lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Received {msg->string()}') lspserver.data = msg lspserver.processMessages() enddef # LSP server error output handler -def Error_cb(lspserver: dict, chan: channel, emsg: string,): void - util.TraceLog(true, emsg) +def Error_cb(lspserver: dict, chan: channel, emsg: string): void + lspserver.errorLog(emsg) enddef # LSP server exit callback @@ -222,6 +222,20 @@ def SetTrace(lspserver: dict, traceVal: string) lspserver.sendNotification('$/setTrace', params) enddef +# Log a debug message to the LSP server debug file +def TraceLog(lspserver: dict, msg: string) + if lspserver.debug + util.TraceLog(lspserver.logfile, false, msg) + endif +enddef + +# Log an error message to the LSP server error file +def ErrorLog(lspserver: dict, errmsg: string) + if lspserver.debug + util.TraceLog(lspserver.errfile, true, errmsg) + endif +enddef + # Return the next id for a LSP server request message def NextReqID(lspserver: dict): number var id = lspserver.nextID @@ -290,7 +304,7 @@ def SendMessage(lspserver: dict, content: dict): void endif job->ch_sendexpr(content) if content->has_key('id') - util.TraceLog(false, $'{strftime("%m/%d/%y %T")}: Sent {content->string()}') + lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Sent {content->string()}') endif enddef @@ -315,12 +329,12 @@ def Rpc(lspserver: dict, method: string, params: any, handleError: bool = t return {} endif - util.TraceLog(false, $'{strftime("%m/%d/%y %T")}: Sent {req->string()}') + lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Sent {req->string()}') # Do the synchronous RPC call var reply = job->ch_evalexpr(req) - util.TraceLog(false, $'{strftime("%m/%d/%y %T")}: Received {reply->string()}') + lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Received {reply->string()}') if reply->has_key('result') # successful reply @@ -342,7 +356,7 @@ enddef # LSP server asynchronous RPC callback def AsyncRpcCb(lspserver: dict, method: string, RpcCb: func, chan: channel, reply: dict) - util.TraceLog(false, $'{strftime("%m/%d/%y %T")}: Received {reply->string()}') + lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Received {reply->string()}') if reply->empty() return @@ -382,7 +396,7 @@ def AsyncRpc(lspserver: dict, method: string, params: any, Cbfunc: func): n return -1 endif - util.TraceLog(false, $'{strftime("%m/%d/%y %T")}: Sent {req->string()}') + lspserver.traceLog($'{strftime("%m/%d/%y %T")}: Sent {req->string()}') # Do the asynchronous RPC call var Fn = function('AsyncRpcCb', [lspserver, method, Cbfunc]) @@ -1336,9 +1350,13 @@ def TagFunc(lspserver: dict, pat: string, flags: string, info: dict): return symbol.TagFunc(lspserver, taglocations, pat) enddef -export def NewLspServer(path: string, args: list, isSync: bool, initializationOptions: any, customNotificationHandlers: dict): dict +export def NewLspServer(name_arg: string, path_arg: string, args: list, + isSync: bool, initializationOptions: any, + customNotificationHandlers: dict, + debug_arg: bool): dict var lspserver: dict = { - path: path, + name: name_arg, + path: path_arg, args: args, syncInit: isSync, initializationOptions: initializationOptions, @@ -1361,8 +1379,12 @@ export def NewLspServer(path: string, args: list, isSync: bool, initiali peekSymbolPopup: -1, peekSymbolFilePopup: -1, callHierarchyType: '', - selection: {} + selection: {}, + debug: debug_arg } + lspserver.logfile = $'lsp-{lspserver.name}.log' + lspserver.errfile = $'lsp-{lspserver.name}.err' + # Add the LSP server functions lspserver->extend({ startServer: function(StartServer, [lspserver]), @@ -1371,6 +1393,8 @@ export def NewLspServer(path: string, args: list, isSync: bool, initiali shutdownServer: function(ShutdownServer, [lspserver]), exitServer: function(ExitServer, [lspserver]), setTrace: function(SetTrace, [lspserver]), + traceLog: function(TraceLog, [lspserver]), + errorLog: function(ErrorLog, [lspserver]), nextReqID: function(NextReqID, [lspserver]), createRequest: function(CreateRequest, [lspserver]), createResponse: function(CreateResponse, [lspserver]), diff --git a/autoload/lsp/util.vim b/autoload/lsp/util.vim index d2c9e60..a51718b 100644 --- a/autoload/lsp/util.vim +++ b/autoload/lsp/util.vim @@ -21,55 +21,39 @@ if has('unix') else lsp_log_dir = $TEMP .. '\\' endif -var lsp_server_trace: bool = false - -# Enable or disable LSP server trace messages -export def ServerTrace(trace_enable: bool) - lsp_server_trace = trace_enable -enddef # Log a message from the LSP server. stderr is true for logging messages # from the standard error and false for stdout. -export def TraceLog(stderr: bool, msg: string) - if !lsp_server_trace - return - endif +export def TraceLog(fname: string, stderr: bool, msg: string) if stderr - writefile(msg->split("\n"), $'{lsp_log_dir}lsp-server.err', 'a') + writefile(msg->split("\n"), $'{lsp_log_dir}{fname}', 'a') else - writefile([msg], $'{lsp_log_dir}lsp-server.out', 'a') + writefile([msg], $'{lsp_log_dir}{fname}', 'a') endif enddef # Empty out the LSP server trace logs -export def ClearTraceLogs() - if !lsp_server_trace - return - endif - writefile([], $'{lsp_log_dir}lsp-server.out') - writefile([], $'{lsp_log_dir}lsp-server.err') +export def ClearTraceLogs(fname: string) + writefile([], fname) enddef -# Open the LSP server debug messages file. If errors is true, then open the -# error messages file. -export def ServerMessagesShow(errors: bool = false) - var fname: string - if errors - fname = $'{lsp_log_dir}lsp-server.err' - else - fname = $'{lsp_log_dir}lsp-server.out' +# Open the LSP server debug messages file. +export def ServerMessagesShow(fname: string) + var fullname = $'{lsp_log_dir}{fname}' + if !filereadable(fullname) + WarnMsg($'File {fullname} is not found') + return endif - if filereadable(fname) - var wid = fname->bufwinid() - if wid == -1 - exe $'split {fname}' - else - win_gotoid(wid) - endif - setlocal autoread - setlocal nomodified - setlocal nomodifiable + var wid = fullname->bufwinid() + if wid == -1 + exe $'split {fullname}' + else + win_gotoid(wid) endif + setlocal autoread + setlocal bufhidden=wipe + setlocal nomodified + setlocal nomodifiable enddef # Parse a LSP Location or LocationLink type and return a List with two items. diff --git a/doc/lsp.txt b/doc/lsp.txt index bef44bb..2869ded 100644 --- a/doc/lsp.txt +++ b/doc/lsp.txt @@ -145,7 +145,7 @@ The following commands are provided: ============================================================================== 4. Configuration *lsp-configuration* - *LspAddServer* *g:LspAddServer* + *LspAddServer()* *g:LspAddServer()* To use the plugin features with a particular file type(s), you need to first register a language server for that file type(s). @@ -160,11 +160,13 @@ LSP plugin, the steps are described later in this section: > let lspServers = [ \ #{ + \ name: 'typescriptls', \ filetype: ['javascript', 'typescript'], \ path: '/usr/local/bin/typescript-language-server', \ args: ['--stdio'] \ }, \ #{ + \ name: 'pythonls', \ filetype: 'python', \ path: '/usr/local/bin/pyls', \ args: ['--check-parent-process', '-v'] @@ -181,33 +183,39 @@ Shell script, Vim script and PHP file types: > let lspServers = [ \ #{ + \ name: 'clangd', \ filetype: ['c', 'cpp'], \ path: '/usr/local/bin/clangd', \ args: ['--background-index'] \ }, \ #{ + \ name: 'golang', \ filetype: ['go', 'gomod', 'gohtmltmpl', 'gotexttmpl'], \ path: '/path/to/.go/bin/gopls', \ args: [], \ syncInit: v:true, \ }, \ #{ + \ name: 'rustls', \ filetype: ['rust'], \ path: '/path/to/.cargo/bin/rust-analyzer', \ args: [], \ syncInit: v:true, \ }, \ #{ + \ name: 'bashls', \ filetype: 'sh', \ path: '/usr/local/bin/bash-language-server', \ args: ['start'] \ }, \ #{ + \ name: 'vimls', \ filetype: ['vim'], \ path: '/usr/local/bin/vim-language-server', \ args: ['--stdio'] \ }, \ #{ + \ name: 'phpls', \ filetype: ['php'], \ path: '/usr/local/bin/intelephense', \ args: ['--stdio'], @@ -221,6 +229,9 @@ Shell script, Vim script and PHP file types: > < To add a language server, the following information is needed: + *lsp-cfg-name* + name (Optional) name of the language server. Can by any + string. Used in LSP messages and log files. *lsp-cfg-path* path complete path to the language server executable (without any arguments). @@ -239,10 +250,16 @@ To add a language server, the following information is needed: or useful for initialization. Those can be provided in this dictionary and if present will be transmitted to the lsp server. + *lsp-cfg-debug* + debug (Optional) log the messages printed by this language + server in stdout and stderr to a file. Useful for + debugging a language server. By default the + messages are not logged. See |lsp-debug| for more + information. Aditionally the following configurations can be made: - *lsp-cfg-customNotificationHandlers* + *lsp-cfg-customNotificationHandlers* customNotificationHandlers (Optional) some lsp servers (e.g. typescript-language-server) will send additional @@ -702,10 +719,11 @@ can map these commands to keys and make it easier to invoke them. and standard error. By default, the language server messages are not logged. On a Unix-like system, when enabled, these - messages are logged to the /tmp/lsp-server.out and - /tmp/lsp-server.err file respectively. On MS-Windows, - the %TEMP%/lsp-server.out and %TEMP%/lsp-server.err% - files are used. See |lsp-debug| for more information. + messages are logged to the /tmp/lsp-.log + and /tmp/lsp-.err file respectively. On + MS-Windows, the %TEMP%/lsp-.log and + %TEMP%/lsp-.err% files are used. See + |lsp-debug| for more information. *:LspServerRestart* :LspServerRestart Restart (stop and then start) the language server for @@ -1090,22 +1108,25 @@ or > 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: > +enables the logging of the messages from the language server for the current +buffer: > :LspServerDebug on < This command also clears the log files. The following command disables the -logging of the messages: > +logging of the messages from the language server for the current buffer: > :LspServerDebug off < -By default, the messages are not logged. +By default, the messages are not logged. Another method to enable the debug +is to set the "debug" field to v:true when adding a language server +using |LspAddServer()|. The messages printed by the language 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. +lsp-.log file and the messages printed in the stderr are logged +to the lsp-.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 following command opens the file containing the messages printed by the language server in the stdout: > @@ -1129,7 +1150,7 @@ language server documentation for information about this. You can include these options when registering the language 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: > +use the :LspServerTrace command to set the server trace value: > :LspServerTrace { off | messages | verbose } < -- 2.48.1