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
This commit is contained in:
Michael Lingelbach 2020-11-25 12:07:02 -08:00 committed by GitHub
parent 2d5dd85eef
commit 0d83a1c43f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 100 additions and 1 deletions

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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|