autoload/lsp/lsp.vim | 9 +++++++-- autoload/lsp/lspserver.vim | 31 ++++++++++++++++++++++--------- autoload/lsp/util.vim | 41 +++++++++++++++++++++++++++++++++++++++++ doc/lsp.txt | 15 ++++++++++++++- diff --git a/autoload/lsp/lsp.vim b/autoload/lsp/lsp.vim index f4f6f013394d74a23d4aaf24fbae9fe7d6907309..020c07a3e3487a0de0f5dc315d2867a85b4025a8 100644 --- a/autoload/lsp/lsp.vim +++ b/autoload/lsp/lsp.vim @@ -371,7 +371,7 @@ if !lspserver.running if !lspInitializedOnce LspInitOnce() endif - lspserver.startServer() + lspserver.startServer(bnr) endif buf.BufLspServerSet(bnr, lspserver) @@ -438,7 +438,7 @@ endif endfor # Start the server again - lspserver.startServer() + lspserver.startServer(bufnr('')) AddBuffersToLsp(ftype) enddef @@ -514,10 +514,15 @@ if !server->has_key('workspaceConfig') server.workspaceConfig = {} endif + if !server->has_key('rootSearch') || server.rootSearch->type() != v:t_list + server.rootSearch = [] + endif + var lspserver: dict = lserver.NewLspServer(server.name, server.path, args, server.syncInit, initializationOptions, server.workspaceConfig, + server.rootSearch, customNotificationHandlers, server.debug) diff --git a/autoload/lsp/lspserver.vim b/autoload/lsp/lspserver.vim index 01df4347f6d211dc6c43d84444ebba1d77612989..dbaacea217bc3e56825bef4c72d42b2724bd4d25 100644 --- a/autoload/lsp/lspserver.vim +++ b/autoload/lsp/lspserver.vim @@ -46,7 +46,7 @@ enddef # Start a LSP server # -def StartServer(lspserver: dict): number +def StartServer(lspserver: dict, bnr: number): number if lspserver.running util.WarnMsg('LSP server for is already running') return 0 @@ -71,7 +71,7 @@ lspserver.omniCompletePending = false lspserver.completionLazyDoc = false lspserver.completionTriggerChars = [] lspserver.signaturePopup = -1 - lspserver.workspaceFolders = [getcwd()] + lspserver.workspaceFolders = [bnr->bufname()->fnamemodify(':p:h')] var job = cmd->job_start(opts) if job->job_status() == 'fail' @@ -85,7 +85,7 @@ lspserver.job = job lspserver.running = true - lspserver.initServer() + lspserver.initServer(bnr) return 0 enddef @@ -143,7 +143,7 @@ enddef # Request: 'initialize' # Param: InitializeParams -def InitServer(lspserver: dict) +def InitServer(lspserver: dict, bnr: number) # interface 'InitializeParams' var initparams: dict = {} initparams.processId = getpid() @@ -151,12 +151,23 @@ initparams.clientInfo = { name: 'Vim', version: v:versionlong->string(), } - var curdir: string = getcwd() - initparams.rootPath = curdir - initparams.rootUri = util.LspFileToUri(curdir) + + # Compute the rootpath (based on the directory of the buffer) + var bufDir = bnr->bufname()->fnamemodify(':p:h') + var rootPath = '' + var rootSearchFiles = lspserver.rootSearchFiles + if !rootSearchFiles->empty() + rootPath = util.FindNearestRootDir(bufDir, rootSearchFiles) + endif + if rootPath == '' + rootPath = bufDir + endif + var rootUri = util.LspFileToUri(rootPath) + initparams.rootPath = rootPath + initparams.rootUri = rootUri initparams.workspaceFolders = [{ - name: curdir->fnamemodify(':t'), - uri: util.LspFileToUri(curdir) + name: rootPath->fnamemodify(':t'), + uri: rootUri }] initparams.trace = 'off' initparams.capabilities = capabilities.GetClientCaps() @@ -1384,6 +1395,7 @@ export def NewLspServer(name_arg: string, path_arg: string, args: list, isSync: bool, initializationOptions: any, workspaceConfig: dict, + rootSearchFiles: list, customNotificationHandlers: dict, debug_arg: bool): dict var lspserver: dict = { @@ -1400,6 +1412,7 @@ data: '', nextID: 1, caps: {}, requests: {}, + rootSearchFiles: rootSearchFiles, omniCompletePending: false, completionTriggerChars: [], signaturePopup: -1, diff --git a/autoload/lsp/util.vim b/autoload/lsp/util.vim index a51718bb846d39b276ee7636bebb1ce82c1120aa..cb1798e610f34ad46b0a933dceb32c0c968a13cf 100644 --- a/autoload/lsp/util.vim +++ b/autoload/lsp/util.vim @@ -226,4 +226,45 @@ endfor return -1 enddef +# Find the nearest root directory containing a file or directory name from the +# list of names in 'files' starting with the directory 'startDir'. +# Based on a similar implementation in the vim-lsp plugin. +# Searches upwards starting with the directory 'startDir'. +# If a file name ends with '/' or '\', then it is a directory name, otherwise +# it is a file name. +# Returns '' if none of the file and directory names in 'files' can be found +# in one of the parent directories. +export def FindNearestRootDir(startDir: string, files: list): string + var foundDirs: dict = {} + + for file in files + if file->type() != v:t_string || file == '' + continue + endif + var isDir = file[-1 : ] ==# '/' || file[-1 : ] ==# '\' + var relPath: string + if isDir + relPath = finddir(file, $'{startDir};') + else + relPath = findfile(file, $'{startDir};') + endif + if relPath->empty() + continue + endif + var rootDir = relPath->fnamemodify(isDir ? ':p:h:h' : ':p:h') + foundDirs[rootDir] = true + endfor + if foundDirs->empty() + return '' + endif + + # Sort the directory names by length + var sortedList: list = foundDirs->keys()->sort((a, b) => { + return b->len() - a->len() + }) + + # choose the longest matching path (the nearest directory from 'startDir') + return sortedList[0] +enddef + # vim: tabstop=8 shiftwidth=2 softtabstop=2 diff --git a/doc/lsp.txt b/doc/lsp.txt index d837fc1ffcd276acfc47f611185464384d3f4e32..1aa9086e2bfa9328a1b332fdc66280583929bf00 100644 --- a/doc/lsp.txt +++ b/doc/lsp.txt @@ -2,7 +2,7 @@ *lsp.txt* Language Server Protocol (LSP) Plugin for Vim9 Author: Yegappan Lakshmanan (yegappan AT yahoo DOT com) For Vim version 9.0 and above -Last change: April 2, 2023 +Last change: April 7, 2023 ============================================================================== *lsp-license* @@ -259,6 +259,18 @@ documentation for the values that will be accepted in this notification. This configuration is also used to respond to the "workspace/configuration" request message from the language server. + *lsp-cfg-rootSearch* + rootSearch (Optional) a List of file and directory names used to + locate the root path or uri of the workspace. The + directory names in "rootSearch" must end in "/" or + "\". Each file and directory name in "rootSearch" is + searched upwards in all the parent directories. If + multiple directories are found, then the directory + closest to the directory of the current buffer is used + as the workspace root. If this parameter is not + specified or the files are not found, then the + directory of the current buffer is used as the + workspace root. Aditionally the following configurations can be made: @@ -297,6 +309,7 @@ server and to set the language server options. For example: > let lspServers = [ \ #{ + \ name: 'clangd', \ filetype: ['c', 'cpp'], \ path: '/usr/local/bin/clangd', \ args: ['--background-index']