Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add command to restart LSP server #2105

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions book/src/generated/typable-cmd.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,4 @@
| `:config-reload` | Refreshes helix's config. |
| `:config-open` | Open the helix config.toml file. |
| `:pipe` | Pipe each selection to the shell command. |
| `: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 @@ -343,52 +343,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)
}
}
Comment on lines +346 to +355
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the change here? This was deliberate since we don't want to try reinitializing the client if we already tried starting it and failed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the functionality of get should be unchanged; I pulled out initialize_client into its own function so that I could also use it in restart. I switched from using self.inner.entry to self.inner.get to avoid multiple mutable borrows.


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
17 changes: 17 additions & 0 deletions helix-term/src/commands/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1077,6 +1077,16 @@ fn pipe(
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 @@ -1512,6 +1522,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
fun: pipe,
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>> =
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 @@ -475,6 +475,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