Skip to content

Commit

Permalink
Add command to restart LSP server
Browse files Browse the repository at this point in the history
Useful if LSP configuration changes or crashes
  • Loading branch information
threecgreen committed Apr 14, 2022
1 parent 764adbd commit acb222b
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 45 deletions.
1 change: 1 addition & 0 deletions book/src/generated/typable-cmd.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
117 changes: 73 additions & 44 deletions helix-lsp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<initialized>
_client
.notify::<lsp::notification::Initialized>(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<Arc<Client>> {
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<Arc<Client>> {
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<initialized>
_client
.notify::<lsp::notification::Initialized>(lsp::InitializedParams {})
.await
.unwrap();

initialize_notify.notify_one();
});

Ok(client)
}

pub fn iter_clients(&self) -> impl Iterator<Item = &Arc<Client>> {
Expand Down
19 changes: 18 additions & 1 deletion helix-term/src/commands/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1067,6 +1067,16 @@ fn refresh_config(
Ok(())
}

fn lsp_restart(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_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",
Expand Down Expand Up @@ -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<HashMap<&'static str, &'static TypableCommand>> =
Lazy::new(|| {
Expand Down
17 changes: 17 additions & 0 deletions helix-view/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit acb222b

Please sign in to comment.