From acb222bdbea555f0661d43f9779f50f78c1bf1c8 Mon Sep 17 00:00:00 2001 From: Carter Green Date: Mon, 11 Apr 2022 09:43:04 -0500 Subject: [PATCH] Add command to restart LSP server Useful if LSP configuration changes or crashes --- book/src/generated/typable-cmd.md | 1 + helix-lsp/src/lib.rs | 117 +++++++++++++++++++----------- helix-term/src/commands/typed.rs | 19 ++++- helix-view/src/editor.rs | 17 +++++ 4 files changed, 109 insertions(+), 45 deletions(-) diff --git a/book/src/generated/typable-cmd.md b/book/src/generated/typable-cmd.md index bb5da3bbea50a..e3b089c406032 100644 --- a/book/src/generated/typable-cmd.md +++ b/book/src/generated/typable-cmd.md @@ -61,3 +61,4 @@ | `:tree-sitter-subtree`, `:ts-subtree` | Display tree sitter subtree under cursor, primarily for debugging queries. | | `:config-reload` | Refreshes helix's config. | | `:config-open` | Open the helix config.toml file. | +| `:lsp-restart` | Restarts the LSP server of the current buffer | diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index 7674813677646..cc101df701768 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -12,7 +12,7 @@ use futures_util::stream::select_all::SelectAll; use helix_core::syntax::LanguageConfiguration; use std::{ - collections::{hash_map::Entry, HashMap}, + collections::HashMap, sync::{ atomic::{AtomicUsize, Ordering}, Arc, @@ -313,52 +313,81 @@ impl Registry { None => return Err(Error::LspNotDefined), }; - match self.inner.entry(language_config.scope.clone()) { - Entry::Occupied(entry) => Ok(entry.get().1.clone()), - Entry::Vacant(entry) => { - // initialize a new client - let id = self.counter.fetch_add(1, Ordering::Relaxed); - let (client, incoming, initialize_notify) = Client::start( - &config.command, - &config.args, - language_config.config.clone(), - &language_config.roots, - id, - )?; - self.incoming.push(UnboundedReceiverStream::new(incoming)); - let client = Arc::new(client); - - // Initialize the client asynchronously - let _client = client.clone(); - tokio::spawn(async move { - use futures_util::TryFutureExt; - let value = _client - .capabilities - .get_or_try_init(|| { - _client - .initialize() - .map_ok(|response| response.capabilities) - }) - .await; - - if let Err(e) = value { - log::error!("failed to initialize language server: {}", e); - return; - } - - // next up, notify - _client - .notify::(lsp::InitializedParams {}) - .await - .unwrap(); + if let Some((_, client)) = self.inner.get(&language_config.scope) { + Ok(client.clone()) + } else { + let id = self.counter.fetch_add(1, Ordering::Relaxed); + let client = self.initialize_client(language_config, config, id)?; // initialize a new client + self.inner + .insert(language_config.scope.clone(), (id, client.clone())); + Ok(client) + } + } - initialize_notify.notify_one(); - }); + pub fn restart(&mut self, language_config: &LanguageConfiguration) -> Result> { + let config = language_config + .language_server + .as_ref() + .ok_or(Error::LspNotDefined)?; + let id = self + .inner + .get(&language_config.scope) + .ok_or(Error::LspNotDefined)? + .0; + let new_client = self.initialize_client(language_config, config, id)?; + let (_, client) = self + .inner + .get_mut(&language_config.scope) + .ok_or(Error::LspNotDefined)?; + *client = new_client; + + Ok(client.clone()) + } - entry.insert((id, client.clone())); - Ok(client) + fn initialize_client( + &mut self, + language_config: &LanguageConfiguration, + config: &helix_core::syntax::LanguageServerConfiguration, + id: usize, + ) -> Result> { + let (client, incoming, initialize_notify) = Client::start( + &config.command, + &config.args, + language_config.config.clone(), + &language_config.roots, + id, + )?; + self.incoming.push(UnboundedReceiverStream::new(incoming)); + let client = Arc::new(client); + + // Initialize the client asynchronously + let _client = client.clone(); + tokio::spawn(async move { + use futures_util::TryFutureExt; + let value = _client + .capabilities + .get_or_try_init(|| { + _client + .initialize() + .map_ok(|response| response.capabilities) + }) + .await; + + if let Err(e) = value { + log::error!("failed to initialize language server: {}", e); + return; } - } + + // next up, notify + _client + .notify::(lsp::InitializedParams {}) + .await + .unwrap(); + + initialize_notify.notify_one(); + }); + + Ok(client) } pub fn iter_clients(&self) -> impl Iterator> { diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index d158388fcbf52..dca822f477d43 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1067,6 +1067,16 @@ fn refresh_config( Ok(()) } +fn lsp_restart( + cx: &mut compositor::Context, + _args: &[Cow], + _event: PromptEvent, +) -> anyhow::Result<()> { + let current_document = doc!(cx.editor).id(); + cx.editor.restart_language_server(current_document); + Ok(()) +} + pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ TypableCommand { name: "quit", @@ -1495,7 +1505,14 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ fun: open_config, completer: None, }, - ]; + TypableCommand { + name: "lsp-restart", + aliases: &[], + doc: "Restarts the LSP server of the current buffer", + fun: lsp_restart, + completer: None, + }, +]; pub static TYPABLE_COMMAND_MAP: Lazy> = Lazy::new(|| { diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 76fc67138d8be..eeffb4dbbd957 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -451,6 +451,23 @@ impl Editor { Self::launch_language_server(&mut self.language_servers, doc) } + /// Restarts a language server for a given document + pub fn restart_language_server(&mut self, doc_id: DocumentId) -> Option<()> { + let doc = self.documents.get_mut(&doc_id)?; + if let Some(language) = doc.language.as_ref() { + if let Ok(client) = self.language_servers.restart(&*language).map_err(|e| { + log::error!( + "Failed to restart the LSP for `{}` {{ {} }}", + language.scope(), + e + ) + }) { + doc.set_language_server(Some(client)); + } + }; + Some(()) + } + /// Launch a language server for a given document fn launch_language_server(ls: &mut helix_lsp::Registry, doc: &mut Document) -> Option<()> { // if doc doesn't have a URL it's a scratch buffer, ignore it