Read language servers from config in :lsp-restart

`:lsp-stop` should consider only the set of active language servers for
a document. `:lsp-restart` though may be used to start up a language
server that crashed or was manually stopped, so it needs to consider the
language servers in config instead.

This change inlines the `valid_lang_servers` function into `:lsp-stop`
and `:lsp-restart` and changes `:lsp-restart` to check the doc's config
rather than active language servers. `:lsp-restart` now also does not
need to clone the language server names as strings since it borrows from
the config and arguments rather than `Document`. The completer has also
been split into two - one matching active language servers, used by
`:lsp-stop`, and the other matching configured language servers, used by
`:lsp-restart`.

This also removes the part of `:lsp-restart` which bailed if a language
server failed to be restarted (for example because it is not installed).
There might be multiple language servers configured for a language and
only one installed. In that case the `:lsp-restart` should be considered
successful even if not all servers could be started. Bailing prevented
any language servers which could start from being attached to the
document. Instead errors are collected and emitted at the end.
This commit is contained in:
Michael Davis 2025-02-20 11:40:56 -05:00
parent 6304e7b2a7
commit 3d7e2730e7
No known key found for this signature in database
2 changed files with 77 additions and 36 deletions

View file

@ -1513,31 +1513,6 @@ fn lsp_workspace_command(
Ok(())
}
/// Returns all language servers used by the current document if no servers are supplied
/// If servers are supplied, do a check to make sure that all of the servers exist
fn valid_lang_servers(doc: &Document, servers: &[Cow<str>]) -> anyhow::Result<Vec<String>> {
let valid_ls_names = doc
.language_servers()
.map(|ls| ls.name().to_string())
.collect();
if servers.is_empty() {
Ok(valid_ls_names)
} else {
let (valid, invalid): (Vec<_>, Vec<_>) = servers
.iter()
.map(|m| m.to_string())
.partition(|ls| valid_ls_names.contains(ls));
if !invalid.is_empty() {
let s = if invalid.len() == 1 { "" } else { "s" };
bail!("Unknown language server{s}: {}", invalid.join(", "));
};
Ok(valid)
}
}
fn lsp_restart(
cx: &mut compositor::Context,
args: &[Cow<str>],
@ -1553,10 +1528,29 @@ fn lsp_restart(
.language_config()
.context("LSP not defined for the current document")?;
let ls_restart_names = valid_lang_servers(doc, args)?;
let language_servers: Vec<_> = config
.language_servers
.iter()
.map(|ls| ls.name.as_str())
.collect();
let language_servers = if args.is_empty() {
language_servers
} else {
let (valid, invalid): (Vec<_>, Vec<_>) = args
.iter()
.map(|arg| arg.as_ref())
.partition(|name| language_servers.contains(name));
if !invalid.is_empty() {
let s = if invalid.len() == 1 { "" } else { "s" };
bail!("Unknown language server{s}: {}", invalid.join(", "));
}
valid
};
for server in ls_restart_names.iter() {
cx.editor
let mut errors = Vec::new();
for server in language_servers.iter() {
match cx
.editor
.language_servers
.restart_server(
server,
@ -1565,7 +1559,15 @@ fn lsp_restart(
&editor_config.workspace_lsp_roots,
editor_config.lsp.snippets,
)
.transpose()?;
.transpose()
{
// Ignore the executable-not-found error unless the server was explicitly requested
// in the arguments.
Err(helix_lsp::Error::ExecutableNotFound(_))
if !args.iter().any(|arg| arg == server) => {}
Err(err) => errors.push(err.to_string()),
_ => (),
}
}
// This collect is needed because refresh_language_server would need to re-borrow editor.
@ -1575,7 +1577,7 @@ fn lsp_restart(
.filter_map(|doc| match doc.language_config() {
Some(config)
if config.language_servers.iter().any(|ls| {
ls_restart_names
language_servers
.iter()
.any(|restarted_ls| restarted_ls == &ls.name)
}) =>
@ -1590,7 +1592,14 @@ fn lsp_restart(
cx.editor.refresh_language_servers(document_id);
}
Ok(())
if errors.is_empty() {
Ok(())
} else {
Err(anyhow::anyhow!(
"Error restarting language servers: {}",
errors.join(", ")
))
}
}
fn lsp_stop(
@ -1603,9 +1612,25 @@ fn lsp_stop(
}
let doc = doc!(cx.editor);
let ls_shutdown_names = valid_lang_servers(doc, args)?;
let language_servers: Vec<_> = doc
.language_servers()
.map(|ls| ls.name().to_string())
.collect();
let language_servers = if args.is_empty() {
language_servers
} else {
let (valid, invalid): (Vec<_>, Vec<_>) = args
.iter()
.map(|arg| arg.to_string())
.partition(|name| language_servers.contains(name));
if !invalid.is_empty() {
let s = if invalid.len() == 1 { "" } else { "s" };
bail!("Unknown language server{s}: {}", invalid.join(", "));
}
valid
};
for ls_name in &ls_shutdown_names {
for ls_name in &language_servers {
cx.editor.language_servers.stop(ls_name);
for doc in cx.editor.documents_mut() {
@ -2983,14 +3008,14 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
aliases: &[],
doc: "Restarts the given language servers, or all language servers that are used by the current file if no arguments are supplied",
fun: lsp_restart,
signature: CommandSignature::all(completers::language_servers),
signature: CommandSignature::all(completers::configured_language_servers),
},
TypableCommand {
name: "lsp-stop",
aliases: &[],
doc: "Stops the given language servers, or all language servers that are used by the current file if no arguments are supplied",
fun: lsp_stop,
signature: CommandSignature::all(completers::language_servers),
signature: CommandSignature::all(completers::active_language_servers),
},
TypableCommand {
name: "tree-sitter-scopes",

View file

@ -410,7 +410,8 @@ pub mod completers {
}
}
pub fn language_servers(editor: &Editor, input: &str) -> Vec<Completion> {
/// Completes names of language servers which are running for the current document.
pub fn active_language_servers(editor: &Editor, input: &str) -> Vec<Completion> {
let language_servers = doc!(editor).language_servers().map(|ls| ls.name());
fuzzy_match(input, language_servers, false)
@ -419,6 +420,21 @@ pub mod completers {
.collect()
}
/// Completes names of language servers which are configured for the language of the current
/// document.
pub fn configured_language_servers(editor: &Editor, input: &str) -> Vec<Completion> {
let language_servers = doc!(editor)
.language_config()
.into_iter()
.flat_map(|config| &config.language_servers)
.map(|ls| ls.name.as_str());
fuzzy_match(input, language_servers, false)
.into_iter()
.map(|(name, _)| ((0..), Span::raw(name.to_string())))
.collect()
}
pub fn setting(_editor: &Editor, input: &str) -> Vec<Completion> {
static KEYS: Lazy<Vec<String>> = Lazy::new(|| {
let mut keys = Vec::new();