Skip to content

Commit

Permalink
feat: restart few times LSP if exited
Browse files Browse the repository at this point in the history
  • Loading branch information
l4l committed May 1, 2024
1 parent d8701bf commit 6df3039
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 34 deletions.
12 changes: 11 additions & 1 deletion helix-lsp/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use parking_lot::Mutex;
use serde::Deserialize;
use serde_json::Value;
use std::sync::{
atomic::{AtomicU64, Ordering},
atomic::{AtomicU64, AtomicU8, Ordering},
Arc,
};
use std::{collections::HashMap, path::PathBuf};
Expand Down Expand Up @@ -60,6 +60,7 @@ pub struct Client {
initialize_notify: Arc<Notify>,
/// workspace folders added while the server is still initializing
req_timeout: u64,
restarts_left: AtomicU8,
}

impl Client {
Expand Down Expand Up @@ -229,6 +230,7 @@ impl Client {
root_uri,
workspace_folders: Mutex::new(workspace_folders),
initialize_notify: initialize_notify.clone(),
restarts_left: AtomicU8::new(2),
};

Ok((client, server_rx, initialize_notify))
Expand All @@ -242,6 +244,14 @@ impl Client {
self.id
}

pub fn set_restarts_left(&self, x: u8) {
self.restarts_left.store(x, Ordering::Relaxed);
}

pub fn restarts_left(&self) -> u8 {
self.restarts_left.load(Ordering::Relaxed)
}

fn next_request_id(&self) -> jsonrpc::Id {
let id = self.request_counter.fetch_add(1, Ordering::Relaxed);
jsonrpc::Id::Num(id)
Expand Down
69 changes: 37 additions & 32 deletions helix-lsp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -675,10 +675,10 @@ impl Registry {
self.inner.get(id)
}

pub fn remove_by_id(&mut self, id: LanguageServerId) {
pub fn remove_by_id(&mut self, id: LanguageServerId) -> Option<Arc<Client>> {
let Some(client) = self.inner.remove(id) else {
log::error!("client was already removed");
return
return None;
};
self.file_event_handler.remove_client(id);
let instances = self
Expand All @@ -689,22 +689,23 @@ impl Registry {
if instances.is_empty() {
self.inner_by_name.remove(client.name());
}
Some(client)
}

fn start_client(
pub fn start(
&mut self,
name: String,
ls_config: &LanguageConfiguration,
doc_path: Option<&std::path::PathBuf>,
root_dirs: &[PathBuf],
enable_snippets: bool,
) -> Result<Arc<Client>, StartupError> {
) -> Result<Option<Arc<Client>>, Error> {
let syn_loader = self.syn_loader.load();
let config = syn_loader
.language_server_configs()
.get(&name)
.ok_or_else(|| anyhow::anyhow!("Language server '{name}' not defined"))?;
let id = self.inner.try_insert_with_key(|id| {
match self.inner.try_insert_with_key(|id| {
start_client(
id,
name,
Expand All @@ -718,8 +719,11 @@ impl Registry {
self.incoming.push(UnboundedReceiverStream::new(client.1));
client.0
})
})?;
Ok(self.inner[id].clone())
}) {
Ok(id) => Ok(Some(self.inner[id].clone())),
Err(StartupError::NoRequiredRootFound) => Ok(None),
Err(StartupError::Error(err)) => Err(err),
}
}

/// If this method is called, all documents that have a reference to language servers used by the language config have to refresh their language servers,
Expand All @@ -745,16 +749,18 @@ impl Registry {
});
}
}
let client = match self.start_client(
name.clone(),
language_config,
doc_path,
root_dirs,
enable_snippets,
) {
let client = match self
.start(
name.clone(),
language_config,
doc_path,
root_dirs,
enable_snippets,
)
.transpose()?
{
Ok(client) => client,
Err(StartupError::NoRequiredRootFound) => return None,
Err(StartupError::Error(err)) => return Some(Err(err)),
Err(err) => return Some(Err(err)),
};
self.inner_by_name
.insert(name.to_owned(), vec![client.clone()]);
Expand Down Expand Up @@ -792,23 +798,22 @@ impl Registry {
return Some((name.to_owned(), Ok(client.clone())));
}
}
match self.start_client(
name.clone(),
language_config,
doc_path,
root_dirs,
enable_snippets,
) {
Ok(client) => {
self.inner_by_name
.entry(name.to_owned())
.or_default()
.push(client.clone());
Some((name.clone(), Ok(client)))
}
Err(StartupError::NoRequiredRootFound) => None,
Err(StartupError::Error(err)) => Some((name.to_owned(), Err(err))),
let client = self
.start(
name.clone(),
language_config,
doc_path,
root_dirs,
enable_snippets,
)
.transpose()?;
if let Ok(client) = &client {
self.inner_by_name
.entry(name.to_owned())
.or_default()
.push(client.clone());
}
Some((name.to_owned(), client))
},
)
}
Expand Down
46 changes: 45 additions & 1 deletion helix-term/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -935,7 +935,51 @@ impl Application {
}

// Remove the language server from the registry.
self.editor.language_servers.remove_by_id(server_id);
let client = self.editor.language_servers.remove_by_id(server_id);

if let Some(client) = client {
let name = client.name().to_owned();
let restarts = client.restarts_left();
if let Some(restarts) = restarts.checked_sub(1) {
let job = async move {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
Ok(crate::job::Callback::Editor(Box::new(
move |editor: &mut Editor| {
let editor_config = &editor.config();
let (_, doc) = {
let view = view_mut!(editor);
let id = view.doc;
let doc = doc_mut!(editor, &id);
(view, doc)
};
let doc_path = doc.path();
let Some(lang_config) = doc.language_config() else {
log::warn!("at LSP restart config is missing");
return;
};

match editor.language_servers.start(
name,
lang_config,
doc_path,
&editor_config.workspace_lsp_roots,
editor_config.lsp.snippets,
) {
Ok(Some(client)) => {
client.set_restarts_left(restarts)
}
Ok(None) => {}
Err(err) => {
log::warn!("failed to restart LSP: {:?}", err);
}
}
},
)))
};

self.jobs.callback(job);
}
}
}
}
}
Expand Down

0 comments on commit 6df3039

Please sign in to comment.