From 0d83a1c43fbcefae5c96b53e81528553bba5a1ab Mon Sep 17 00:00:00 2001 From: Michael Lingelbach Date: Wed, 25 Nov 2020 12:07:02 -0800 Subject: [PATCH] LSP: Feature/add workspace folders (#12638) * First implementation of workspace folders * Add completion for current directory * Add tracking of workspace folders * Add workspace folder listing * Add checks on adding/removing workspaces * Add appropriate initialization options * Add documentation * Make workspaceFolders available wherever client is --- runtime/doc/lsp.txt | 13 ++++++++ runtime/lua/vim/lsp.lua | 6 +++- runtime/lua/vim/lsp/buf.lua | 55 ++++++++++++++++++++++++++++++++ runtime/lua/vim/lsp/protocol.lua | 24 ++++++++++++++ runtime/lua/vim/lsp/util.lua | 3 ++ 5 files changed, 100 insertions(+), 1 deletion(-) diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 4cab716df0..5747ba6044 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -996,6 +996,19 @@ type_definition() *vim.lsp.buf.type_definition()* Jumps to the definition of the type of the symbol under the cursor. +add_workspace_folder({path}) *vim.lsp.buf.add_workspace_folder()* + Add the folder at path to the workspace folders. If {path} is + not provided, the user will be prompted for a path using + |input()|. + +remove_workspace_folder({path}) *vim.lsp.buf.remove_workspace_folder()* + Remove the folder at path from the workspace folders. If + {path} is not provided, the user will be prompted for + a path using |input()|. + +list_workspace_folders() *vim.lsp.buf.list_workspace_folders()* + List all folders in the workspace. + workspace_symbol({query}) *vim.lsp.buf.workspace_symbol()* Lists all symbols in the current workspace in the quickfix window. diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index dacdbcfa17..92f56b2ddf 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -597,7 +597,10 @@ function lsp.start_client(config) -- -- workspace folder in the user interface. -- name -- } - workspaceFolders = nil; + workspaceFolders = {{ + uri = vim.uri_from_fname(config.root_dir); + name = string.format("%s", config.root_dir); + }}; } if config.before_init then -- TODO(ashkan) handle errors here. @@ -610,6 +613,7 @@ function lsp.start_client(config) rpc.notify('initialized', {[vim.type_idx]=vim.types.dictionary}) client.initialized = true uninitialized_clients[client_id] = nil + client.workspaceFolders = initialize_params.workspaceFolders client.server_capabilities = assert(result.capabilities, "initialize result doesn't contain capabilities") -- These are the cleaned up capabilities we use for dynamically deciding -- when to send certain events to clients. diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index fa62905c0a..a70581478b 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -238,6 +238,61 @@ function M.outgoing_calls() end) end +--- List workspace folders. +function M.list_workspace_folders() + local workspace_folders = {} + for _, client in ipairs(vim.lsp.buf_get_clients()) do + for _, folder in ipairs(client.workspaceFolders) do + table.insert(workspace_folders, folder.name) + end + end + return workspace_folders +end + +--- Add a workspace folder. +function M.add_workspace_folder(workspace_folder) + workspace_folder = workspace_folder or npcall(vfn.input, "Workspace Folder: ", vfn.expand('%:p:h')) + vim.api.nvim_command("redraw") + if not (workspace_folder and #workspace_folder > 0) then return end + if vim.fn.isdirectory(workspace_folder) == 0 then + print(workspace_folder, " is not a valid directory") + return + end + local params = util.make_workspace_params({{uri = vim.uri_from_fname(workspace_folder); name = workspace_folder}}, {{}}) + for _, client in ipairs(vim.lsp.buf_get_clients()) do + local found = false + for _, folder in ipairs(client.workspaceFolders) do + if folder.name == workspace_folder then + found = true + print(workspace_folder, "is already part of this workspace") + break + end + end + if not found then + vim.lsp.buf_notify(0, 'workspace/didChangeWorkspaceFolders', params) + table.insert(client.workspaceFolders, params.event.added[1]) + end + end +end + +--- Remove a workspace folder. +function M.remove_workspace_folder(workspace_folder) + workspace_folder = workspace_folder or npcall(vfn.input, "Workspace Folder: ", vfn.expand('%:p:h')) + vim.api.nvim_command("redraw") + if not (workspace_folder and #workspace_folder > 0) then return end + local params = util.make_workspace_params({{}}, {{uri = vim.uri_from_fname(workspace_folder); name = workspace_folder}}) + for _, client in ipairs(vim.lsp.buf_get_clients()) do + for idx, folder in ipairs(client.workspaceFolders) do + if folder.name == workspace_folder then + vim.lsp.buf_notify(0, 'workspace/didChangeWorkspaceFolders', params) + client.workspaceFolders[idx] = nil + return + end + end + end + print(workspace_folder, "is not currently part of the workspace") +end + --- Lists all symbols in the current workspace in the quickfix window. --- --- The list is filtered against {query}; if the argument is omitted from the diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 07b4e8b926..1f21bd7eb0 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -716,6 +716,7 @@ function protocol.make_client_capabilities() }; hierarchicalWorkspaceSymbolSupport = true; }; + workspaceFolders = true; applyEdit = true; }; callHierarchy = { @@ -974,6 +975,28 @@ function protocol.resolve_capabilities(server_capabilities) error("The server sent invalid implementationProvider") end + local workspace = server_capabilities.workspace + local workspace_properties = {} + if workspace == nil or workspace.workspaceFolders == nil then + -- Defaults if omitted. + workspace_properties = { + workspace_folder_properties = { + supported = false; + changeNotifications=false; + } + } + elseif type(workspace.workspaceFolders) == 'table' then + workspace_properties = { + workspace_folder_properties = { + supported = vim.F.ifnil(workspace.workspaceFolders.supported, false); + changeNotifications = vim.F.ifnil(workspace.workspaceFolders.changeNotifications, false); + + } + } + else + error("The server sent invalid workspace") + end + local signature_help_properties if server_capabilities.signatureHelpProvider == nil then signature_help_properties = { @@ -993,6 +1016,7 @@ function protocol.resolve_capabilities(server_capabilities) return vim.tbl_extend("error" , text_document_sync_properties , signature_help_properties + , workspace_properties , general_properties ) end diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 3deec6d74e..f78a36fda2 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1314,6 +1314,9 @@ function M.make_text_document_params() return { uri = vim.uri_from_bufnr(0) } end +function M.make_workspace_params(added, removed) + return { event = { added = added; removed = removed; } } +end --- Returns visual width of tabstop. --- --@see |softtabstop|