mirror of
https://github.com/neovim/neovim.git
synced 2026-04-24 08:15:41 +00:00
feat(lsp): builtin :lsp command
Problem: - Despite [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig) claims to be a "data-only" plugin, in fact it still provides some user-facing commands because they haven't been upstreamed to Nvim. Solution: - Upstream `:LspRestart`, `:LspStart` and `:LspStop` commands as `:lsp restart`, `:lsp start` and `:lsp stop` respectively. Co-authored-by: glepnir <glephunter@gmail.com>
This commit is contained in:
committed by
Justin M. Keyes
parent
89d26d61d2
commit
63abb1a88f
@@ -1420,6 +1420,7 @@ Tag Command Action ~
|
|||||||
|:lpfile| :lpf[ile] go to last location in previous file
|
|:lpfile| :lpf[ile] go to last location in previous file
|
||||||
|:lrewind| :lr[ewind] go to the specified location, default first one
|
|:lrewind| :lr[ewind] go to the specified location, default first one
|
||||||
|:ls| :ls list all buffers
|
|:ls| :ls list all buffers
|
||||||
|
|:lsp| :lsp language server protocol
|
||||||
|:ltag| :lt[ag] jump to tag and add matching tags to the
|
|:ltag| :lt[ag] jump to tag and add matching tags to the
|
||||||
location list
|
location list
|
||||||
|:lunmap| :lu[nmap] like ":unmap!" but includes Lang-Arg mode
|
|:lunmap| :lu[nmap] like ":unmap!" but includes Lang-Arg mode
|
||||||
|
|||||||
@@ -116,6 +116,22 @@ To remove or override BUFFER-LOCAL defaults, define a |LspAttach| handler: >lua
|
|||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
<
|
<
|
||||||
|
|
||||||
|
COMMANDS *:lsp*
|
||||||
|
|
||||||
|
:lsp restart {names} *:lsp-restart*
|
||||||
|
Restarts the given language servers. If no names are given, all active
|
||||||
|
servers are restarted.
|
||||||
|
|
||||||
|
:lsp start {names} *:lsp-start*
|
||||||
|
Starts the given language servers. If no names are given, all configured
|
||||||
|
with |vim.lsp.config()| with filetype matching the current buffer are
|
||||||
|
started.
|
||||||
|
|
||||||
|
:lsp stop {names} *:lsp-stop*
|
||||||
|
Stops and disables the given language servers. If no names are given, all
|
||||||
|
servers on the current buffer are stopped.
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
CONFIG *lsp-config*
|
CONFIG *lsp-config*
|
||||||
|
|
||||||
@@ -1023,12 +1039,6 @@ enable({name}, {enable}) *vim.lsp.enable()*
|
|||||||
vim.lsp.enable({'lua_ls', 'pyright'})
|
vim.lsp.enable({'lua_ls', 'pyright'})
|
||||||
<
|
<
|
||||||
|
|
||||||
Example: *lsp-restart* Passing `false` stops and detaches the client(s).
|
|
||||||
Thus you can "restart" LSP by disabling and re-enabling a given config: >lua
|
|
||||||
vim.lsp.enable('clangd', false)
|
|
||||||
vim.lsp.enable('clangd', true)
|
|
||||||
<
|
|
||||||
|
|
||||||
Example: To dynamically decide whether LSP is activated, define a
|
Example: To dynamically decide whether LSP is activated, define a
|
||||||
|lsp-root_dir()| function which calls `on_dir()` only when you want that
|
|lsp-root_dir()| function which calls `on_dir()` only when you want that
|
||||||
config to activate: >lua
|
config to activate: >lua
|
||||||
|
|||||||
@@ -291,6 +291,7 @@ LSP
|
|||||||
• Support for `workspace/diagnostic/refresh`:
|
• Support for `workspace/diagnostic/refresh`:
|
||||||
https://microsoft.github.io/language-server-protocol/specification/#diagnostic_refresh
|
https://microsoft.github.io/language-server-protocol/specification/#diagnostic_refresh
|
||||||
- Support for dynamic registration for `textDocument/diagnostic`
|
- Support for dynamic registration for `textDocument/diagnostic`
|
||||||
|
• |:lsp| command to restart/stop LSP servers.
|
||||||
|
|
||||||
LUA
|
LUA
|
||||||
|
|
||||||
|
|||||||
@@ -307,6 +307,7 @@ Commands:
|
|||||||
- |:EditQuery|
|
- |:EditQuery|
|
||||||
- |:Inspect|
|
- |:Inspect|
|
||||||
- |:InspectTree|
|
- |:InspectTree|
|
||||||
|
- |:lsp|
|
||||||
- |:Man| is available by default, with many improvements such as completion
|
- |:Man| is available by default, with many improvements such as completion
|
||||||
- |:match| can be invoked before highlight group is defined
|
- |:match| can be invoked before highlight group is defined
|
||||||
- |:restart|
|
- |:restart|
|
||||||
|
|||||||
@@ -528,14 +528,6 @@ end
|
|||||||
--- vim.lsp.enable({'lua_ls', 'pyright'})
|
--- vim.lsp.enable({'lua_ls', 'pyright'})
|
||||||
--- ```
|
--- ```
|
||||||
---
|
---
|
||||||
--- Example: [lsp-restart]() Passing `false` stops and detaches the client(s). Thus you can
|
|
||||||
--- "restart" LSP by disabling and re-enabling a given config:
|
|
||||||
---
|
|
||||||
--- ```lua
|
|
||||||
--- vim.lsp.enable('clangd', false)
|
|
||||||
--- vim.lsp.enable('clangd', true)
|
|
||||||
--- ```
|
|
||||||
---
|
|
||||||
--- Example: To _dynamically_ decide whether LSP is activated, define a |lsp-root_dir()| function
|
--- Example: To _dynamically_ decide whether LSP is activated, define a |lsp-root_dir()| function
|
||||||
--- which calls `on_dir()` only when you want that config to activate:
|
--- which calls `on_dir()` only when you want that config to activate:
|
||||||
---
|
---
|
||||||
|
|||||||
142
runtime/lua/vim/lsp/_cmd.lua
Normal file
142
runtime/lua/vim/lsp/_cmd.lua
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
local lsp = vim.lsp
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
--- @param filter? vim.lsp.get_clients.Filter
|
||||||
|
--- @return string[]
|
||||||
|
local function get_client_names(filter)
|
||||||
|
return vim
|
||||||
|
.iter(lsp.get_clients(filter))
|
||||||
|
:map(function(client)
|
||||||
|
return client.name
|
||||||
|
end)
|
||||||
|
:filter(function(name)
|
||||||
|
return vim.lsp.config[name] ~= nil
|
||||||
|
end)
|
||||||
|
:totable()
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return string[]
|
||||||
|
local function get_config_names()
|
||||||
|
local config_names = vim
|
||||||
|
.iter(vim.api.nvim_get_runtime_file('lsp/*.lua', true))
|
||||||
|
---@param path string
|
||||||
|
:map(function(path)
|
||||||
|
local file_name = path:match('[^/]*.lua$')
|
||||||
|
return file_name:sub(0, #file_name - 4)
|
||||||
|
end)
|
||||||
|
:totable()
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: invisible
|
||||||
|
vim.list_extend(config_names, vim.tbl_keys(vim.lsp.config._configs))
|
||||||
|
return vim.list.unique(config_names)
|
||||||
|
end
|
||||||
|
|
||||||
|
local complete_args = {
|
||||||
|
start = get_config_names,
|
||||||
|
stop = get_client_names,
|
||||||
|
restart = get_client_names,
|
||||||
|
}
|
||||||
|
|
||||||
|
local function ex_lsp_start(servers)
|
||||||
|
-- Default to enabling all servers matching the filetype of the current buffer.
|
||||||
|
-- This assumes that they've been explicitly configured through `vim.lsp.config`,
|
||||||
|
-- otherwise they won't be present in the private `vim.lsp.config._configs` table.
|
||||||
|
if #servers == 0 then
|
||||||
|
local filetype = vim.bo.filetype
|
||||||
|
---@diagnostic disable-next-line: invisible
|
||||||
|
for name, _ in pairs(vim.lsp.config._configs) do
|
||||||
|
local filetypes = vim.lsp.config[name].filetypes
|
||||||
|
if filetypes and vim.tbl_contains(filetypes, filetype) then
|
||||||
|
table.insert(servers, name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.lsp.enable(servers)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param clients string[]
|
||||||
|
local function ex_lsp_stop(clients)
|
||||||
|
-- Default to disabling all servers on current buffer
|
||||||
|
if #clients == 0 then
|
||||||
|
clients = get_client_names { bufnr = vim.api.nvim_get_current_buf() }
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, name in ipairs(clients) do
|
||||||
|
if vim.lsp.config[name] == nil then
|
||||||
|
vim.notify(("Invalid server name '%s'"):format(name))
|
||||||
|
else
|
||||||
|
vim.lsp.enable(name, false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param clients string[]
|
||||||
|
local function ex_lsp_restart(clients)
|
||||||
|
-- Default to restarting all active servers
|
||||||
|
if #clients == 0 then
|
||||||
|
clients = get_client_names()
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, name in ipairs(clients) do
|
||||||
|
if vim.lsp.config[name] == nil then
|
||||||
|
vim.notify(("Invalid server name '%s'"):format(name))
|
||||||
|
else
|
||||||
|
vim.lsp.enable(name, false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local timer = assert(vim.uv.new_timer())
|
||||||
|
timer:start(500, 0, function()
|
||||||
|
for _, name in ipairs(clients) do
|
||||||
|
vim.schedule_wrap(function(x)
|
||||||
|
vim.lsp.enable(x)
|
||||||
|
end)(name)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local actions = {
|
||||||
|
start = ex_lsp_start,
|
||||||
|
restart = ex_lsp_restart,
|
||||||
|
stop = ex_lsp_stop,
|
||||||
|
}
|
||||||
|
|
||||||
|
local available_subcmds = vim.tbl_keys(actions)
|
||||||
|
|
||||||
|
--- Use for `:lsp {subcmd} {clients}` command
|
||||||
|
---@param args string
|
||||||
|
M._ex_lsp = function(args)
|
||||||
|
local fargs = vim.api.nvim_parse_cmd('lsp ' .. args, {}).args
|
||||||
|
if not fargs then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local subcmd = fargs[1]
|
||||||
|
if not vim.list_contains(available_subcmds, subcmd) then
|
||||||
|
vim.notify(("Invalid subcommand '%s'"):format(subcmd), vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local clients = { unpack(fargs, 2) }
|
||||||
|
|
||||||
|
actions[subcmd](clients)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Completion logic for `:lsp` command
|
||||||
|
--- @param line string content of the current command line
|
||||||
|
--- @return string[] list of completions
|
||||||
|
function M._ex_lsp_complete(line)
|
||||||
|
local splited = vim.split(line, '%s+')
|
||||||
|
if #splited == 2 then
|
||||||
|
return available_subcmds
|
||||||
|
else
|
||||||
|
local subcmd = splited[2]
|
||||||
|
---@param n string
|
||||||
|
return vim.tbl_map(function(n)
|
||||||
|
return vim.fn.escape(n, [[" |]])
|
||||||
|
end, complete_args[subcmd]())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -1301,6 +1301,7 @@ char *addstar(char *fname, size_t len, int context)
|
|||||||
|| ((context == EXPAND_TAGS_LISTFILES || context == EXPAND_TAGS)
|
|| ((context == EXPAND_TAGS_LISTFILES || context == EXPAND_TAGS)
|
||||||
&& fname[0] == '/')
|
&& fname[0] == '/')
|
||||||
|| context == EXPAND_CHECKHEALTH
|
|| context == EXPAND_CHECKHEALTH
|
||||||
|
|| context == EXPAND_LSP
|
||||||
|| context == EXPAND_LUA) {
|
|| context == EXPAND_LUA) {
|
||||||
retval = xstrnsave(fname, len);
|
retval = xstrnsave(fname, len);
|
||||||
} else {
|
} else {
|
||||||
@@ -2311,6 +2312,10 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
|
|||||||
xp->xp_context = EXPAND_CHECKHEALTH;
|
xp->xp_context = EXPAND_CHECKHEALTH;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case CMD_lsp:
|
||||||
|
xp->xp_context = EXPAND_LSP;
|
||||||
|
break;
|
||||||
|
|
||||||
case CMD_retab:
|
case CMD_retab:
|
||||||
xp->xp_context = EXPAND_RETAB;
|
xp->xp_context = EXPAND_RETAB;
|
||||||
xp->xp_pattern = (char *)arg;
|
xp->xp_pattern = (char *)arg;
|
||||||
@@ -2849,6 +2854,43 @@ static char *get_healthcheck_names(expand_T *xp FUNC_ATTR_UNUSED, int idx)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Completion for |:lsp| command.
|
||||||
|
///
|
||||||
|
/// Given to ExpandGeneric() to obtain `:lsp` completion.
|
||||||
|
/// @param[in] idx Index of the item.
|
||||||
|
/// @param[in] xp Not used.
|
||||||
|
static char *get_lsp_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx)
|
||||||
|
{
|
||||||
|
static Object names = OBJECT_INIT;
|
||||||
|
static char *last_xp_line = NULL;
|
||||||
|
static unsigned last_gen = 0;
|
||||||
|
|
||||||
|
if (last_xp_line == NULL || strcmp(last_xp_line,
|
||||||
|
xp->xp_line) != 0
|
||||||
|
|| last_gen != get_cmdline_last_prompt_id()) {
|
||||||
|
xfree(last_xp_line);
|
||||||
|
last_xp_line = xstrdup(xp->xp_line);
|
||||||
|
MAXSIZE_TEMP_ARRAY(args, 1);
|
||||||
|
Error err = ERROR_INIT;
|
||||||
|
|
||||||
|
ADD_C(args, CSTR_AS_OBJ(xp->xp_line));
|
||||||
|
// Build the current command line as a Lua string argument
|
||||||
|
Object res = NLUA_EXEC_STATIC("return require'vim.lsp._cmd'._ex_lsp_complete(...)", args,
|
||||||
|
kRetObject, NULL,
|
||||||
|
&err);
|
||||||
|
api_clear_error(&err);
|
||||||
|
api_free_object(names);
|
||||||
|
names = res;
|
||||||
|
last_gen = get_cmdline_last_prompt_id();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (names.type == kObjectTypeArray && idx < (int)names.data.array.size
|
||||||
|
&& names.data.array.items[idx].type == kObjectTypeString) {
|
||||||
|
return names.data.array.items[idx].data.string.data;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/// Do the expansion based on xp->xp_context and "rmp".
|
/// Do the expansion based on xp->xp_context and "rmp".
|
||||||
static int ExpandOther(char *pat, expand_T *xp, regmatch_T *rmp, char ***matches, int *numMatches)
|
static int ExpandOther(char *pat, expand_T *xp, regmatch_T *rmp, char ***matches, int *numMatches)
|
||||||
{
|
{
|
||||||
@@ -2891,6 +2933,7 @@ static int ExpandOther(char *pat, expand_T *xp, regmatch_T *rmp, char ***matches
|
|||||||
{ EXPAND_SCRIPTNAMES, get_scriptnames_arg, true, false },
|
{ EXPAND_SCRIPTNAMES, get_scriptnames_arg, true, false },
|
||||||
{ EXPAND_RETAB, get_retab_arg, true, true },
|
{ EXPAND_RETAB, get_retab_arg, true, true },
|
||||||
{ EXPAND_CHECKHEALTH, get_healthcheck_names, true, false },
|
{ EXPAND_CHECKHEALTH, get_healthcheck_names, true, false },
|
||||||
|
[32] = { EXPAND_LSP, get_lsp_arg, true, false },
|
||||||
};
|
};
|
||||||
int ret = FAIL;
|
int ret = FAIL;
|
||||||
|
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ enum {
|
|||||||
EXPAND_RETAB,
|
EXPAND_RETAB,
|
||||||
EXPAND_CHECKHEALTH,
|
EXPAND_CHECKHEALTH,
|
||||||
EXPAND_LUA,
|
EXPAND_LUA,
|
||||||
|
EXPAND_LSP,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Type used by ExpandGeneric()
|
/// Type used by ExpandGeneric()
|
||||||
|
|||||||
@@ -1670,6 +1670,12 @@ M.cmds = {
|
|||||||
addr_type = 'ADDR_NONE',
|
addr_type = 'ADDR_NONE',
|
||||||
func = 'buflist_list',
|
func = 'buflist_list',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
command = 'lsp',
|
||||||
|
flags = bit.bor(NEEDARG, EXTRA, TRLBAR),
|
||||||
|
addr_type = 'ADDR_NONE',
|
||||||
|
func = 'ex_lsp',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
command = 'move',
|
command = 'move',
|
||||||
flags = bit.bor(RANGE, WHOLEFOLD, EXTRA, TRLBAR, CMDWIN, LOCK_OK, MODIFY),
|
flags = bit.bor(RANGE, WHOLEFOLD, EXTRA, TRLBAR, CMDWIN, LOCK_OK, MODIFY),
|
||||||
|
|||||||
@@ -8048,6 +8048,18 @@ static void ex_terminal(exarg_T *eap)
|
|||||||
do_cmdline_cmd(ex_cmd);
|
do_cmdline_cmd(ex_cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ":lsp {subcmd} {clients}"
|
||||||
|
static void ex_lsp(exarg_T *eap)
|
||||||
|
{
|
||||||
|
Error err = ERROR_INIT;
|
||||||
|
MAXSIZE_TEMP_ARRAY(args, 1);
|
||||||
|
|
||||||
|
ADD_C(args, CSTR_AS_OBJ(eap->arg));
|
||||||
|
|
||||||
|
NLUA_EXEC_STATIC("require'vim.lsp._cmd'._ex_lsp(...)", args, kRetNilBool, NULL, &err);
|
||||||
|
api_clear_error(&err);
|
||||||
|
}
|
||||||
|
|
||||||
/// ":fclose"
|
/// ":fclose"
|
||||||
static void ex_fclose(exarg_T *eap)
|
static void ex_fclose(exarg_T *eap)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1289,7 +1289,7 @@ func Test_cmdline_complete_various()
|
|||||||
|
|
||||||
" completion for a command with a trailing command
|
" completion for a command with a trailing command
|
||||||
call feedkeys(":ls | ls\<C-A>\<C-B>\"\<CR>", 'xt')
|
call feedkeys(":ls | ls\<C-A>\<C-B>\"\<CR>", 'xt')
|
||||||
call assert_equal("\"ls | ls", @:)
|
call assert_equal("\"ls | ls lsp", @:)
|
||||||
|
|
||||||
" completion for a command with an CTRL-V escaped argument
|
" completion for a command with an CTRL-V escaped argument
|
||||||
call feedkeys(":ls \<C-V>\<C-V>a\<C-A>\<C-B>\"\<CR>", 'xt')
|
call feedkeys(":ls \<C-V>\<C-V>a\<C-A>\<C-B>\"\<CR>", 'xt')
|
||||||
|
|||||||
Reference in New Issue
Block a user