aboutsummaryrefslogtreecommitdiff
path: root/helix-lsp/src/lib.rs
diff options
context:
space:
mode:
authorBlaž Hrastnik2023-05-19 00:39:35 +0000
committerGitHub2023-05-19 00:39:35 +0000
commit53f47bc47771c94dab51626ca025be28e62eba0c (patch)
treec8f5c59d40d1ecde227c209f898cc7afd6da5477 /helix-lsp/src/lib.rs
parent7f5940be80eaa3aec7903903072b7108f41dd97b (diff)
parent2a512f7c487f0a707a7eb158e24bd478433bcd91 (diff)
Merge pull request #2507 from Philipp-M/multiple-language-servers
Add support for multiple language servers per language
Diffstat (limited to 'helix-lsp/src/lib.rs')
-rw-r--r--helix-lsp/src/lib.rs210
1 files changed, 114 insertions, 96 deletions
diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs
index 31ee1d75..d053dbf9 100644
--- a/helix-lsp/src/lib.rs
+++ b/helix-lsp/src/lib.rs
@@ -12,24 +12,21 @@ pub use lsp_types as lsp;
use futures_util::stream::select_all::SelectAll;
use helix_core::{
path,
- syntax::{LanguageConfiguration, LanguageServerConfiguration},
+ syntax::{LanguageConfiguration, LanguageServerConfiguration, LanguageServerFeatures},
};
use tokio::sync::mpsc::UnboundedReceiver;
use std::{
- collections::{hash_map::Entry, HashMap},
+ collections::HashMap,
path::{Path, PathBuf},
- sync::{
- atomic::{AtomicUsize, Ordering},
- Arc,
- },
+ sync::Arc,
};
use thiserror::Error;
use tokio_stream::wrappers::UnboundedReceiverStream;
pub type Result<T> = core::result::Result<T, Error>;
-type LanguageId = String;
+pub type LanguageServerName = String;
#[derive(Error, Debug)]
pub enum Error {
@@ -49,7 +46,7 @@ pub enum Error {
Other(#[from] anyhow::Error),
}
-#[derive(Clone, Copy, Debug, Default)]
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum OffsetEncoding {
/// UTF-8 code units aka bytes
Utf8,
@@ -624,23 +621,18 @@ impl Notification {
#[derive(Debug)]
pub struct Registry {
- inner: HashMap<LanguageId, Vec<(usize, Arc<Client>)>>,
-
- counter: AtomicUsize,
+ inner: HashMap<LanguageServerName, Vec<Arc<Client>>>,
+ syn_loader: Arc<helix_core::syntax::Loader>,
+ counter: usize,
pub incoming: SelectAll<UnboundedReceiverStream<(usize, Call)>>,
}
-impl Default for Registry {
- fn default() -> Self {
- Self::new()
- }
-}
-
impl Registry {
- pub fn new() -> Self {
+ pub fn new(syn_loader: Arc<helix_core::syntax::Loader>) -> Self {
Self {
inner: HashMap::new(),
- counter: AtomicUsize::new(0),
+ syn_loader,
+ counter: 0,
incoming: SelectAll::new(),
}
}
@@ -649,65 +641,92 @@ impl Registry {
self.inner
.values()
.flatten()
- .find(|(client_id, _)| client_id == &id)
- .map(|(_, client)| client.as_ref())
+ .find(|client| client.id() == id)
+ .map(|client| &**client)
}
pub fn remove_by_id(&mut self, id: usize) {
- self.inner.retain(|_, clients| {
- clients.retain(|&(client_id, _)| client_id != id);
- !clients.is_empty()
- })
+ self.inner.retain(|_, language_servers| {
+ language_servers.retain(|ls| id != ls.id());
+ !language_servers.is_empty()
+ });
}
+ fn start_client(
+ &mut self,
+ name: String,
+ ls_config: &LanguageConfiguration,
+ doc_path: Option<&std::path::PathBuf>,
+ root_dirs: &[PathBuf],
+ enable_snippets: bool,
+ ) -> Result<Arc<Client>> {
+ let config = self
+ .syn_loader
+ .language_server_configs()
+ .get(&name)
+ .ok_or_else(|| anyhow::anyhow!("Language server '{name}' not defined"))?;
+ let id = self.counter;
+ self.counter += 1;
+ let NewClient(client, incoming) = start_client(
+ id,
+ name,
+ ls_config,
+ config,
+ doc_path,
+ root_dirs,
+ enable_snippets,
+ )?;
+ self.incoming.push(UnboundedReceiverStream::new(incoming));
+ Ok(client)
+ }
+
+ /// 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,
+ /// as it could be that language servers of these documents were stopped by this method.
+ /// See helix_view::editor::Editor::refresh_language_servers
pub fn restart(
&mut self,
language_config: &LanguageConfiguration,
doc_path: Option<&std::path::PathBuf>,
root_dirs: &[PathBuf],
enable_snippets: bool,
- ) -> Result<Option<Arc<Client>>> {
- let config = match &language_config.language_server {
- Some(config) => config,
- None => return Ok(None),
- };
-
- let scope = language_config.scope.clone();
-
- match self.inner.entry(scope) {
- Entry::Vacant(_) => Ok(None),
- Entry::Occupied(mut entry) => {
- // initialize a new client
- let id = self.counter.fetch_add(1, Ordering::Relaxed);
-
- let NewClientResult(client, incoming) = start_client(
- id,
- language_config,
- config,
- doc_path,
- root_dirs,
- enable_snippets,
- )?;
- self.incoming.push(UnboundedReceiverStream::new(incoming));
-
- let old_clients = entry.insert(vec![(id, client.clone())]);
-
- for (_, old_client) in old_clients {
- tokio::spawn(async move {
- let _ = old_client.force_shutdown().await;
- });
+ ) -> Result<Vec<Arc<Client>>> {
+ language_config
+ .language_servers
+ .iter()
+ .filter_map(|LanguageServerFeatures { name, .. }| {
+ if self.inner.contains_key(name) {
+ let client = match self.start_client(
+ name.clone(),
+ language_config,
+ doc_path,
+ root_dirs,
+ enable_snippets,
+ ) {
+ Ok(client) => client,
+ error => return Some(error),
+ };
+ let old_clients = self
+ .inner
+ .insert(name.clone(), vec![client.clone()])
+ .unwrap();
+
+ for old_client in old_clients {
+ tokio::spawn(async move {
+ let _ = old_client.force_shutdown().await;
+ });
+ }
+
+ Some(Ok(client))
+ } else {
+ None
}
-
- Ok(Some(client))
- }
- }
+ })
+ .collect()
}
- pub fn stop(&mut self, language_config: &LanguageConfiguration) {
- let scope = language_config.scope.clone();
-
- if let Some(clients) = self.inner.remove(&scope) {
- for (_, client) in clients {
+ pub fn stop(&mut self, name: &str) {
+ if let Some(clients) = self.inner.remove(name) {
+ for client in clients {
tokio::spawn(async move {
let _ = client.force_shutdown().await;
});
@@ -721,37 +740,34 @@ impl Registry {
doc_path: Option<&std::path::PathBuf>,
root_dirs: &[PathBuf],
enable_snippets: bool,
- ) -> Result<Option<Arc<Client>>> {
- let config = match &language_config.language_server {
- Some(config) => config,
- None => return Ok(None),
- };
-
- let clients = self.inner.entry(language_config.scope.clone()).or_default();
- // check if we already have a client for this documents root that we can reuse
- if let Some((_, client)) = clients.iter_mut().enumerate().find(|(i, (_, client))| {
- client.try_add_doc(&language_config.roots, root_dirs, doc_path, *i == 0)
- }) {
- return Ok(Some(client.1.clone()));
- }
- // initialize a new client
- let id = self.counter.fetch_add(1, Ordering::Relaxed);
-
- let NewClientResult(client, incoming) = start_client(
- id,
- language_config,
- config,
- doc_path,
- root_dirs,
- enable_snippets,
- )?;
- clients.push((id, client.clone()));
- self.incoming.push(UnboundedReceiverStream::new(incoming));
- Ok(Some(client))
+ ) -> Result<HashMap<LanguageServerName, Arc<Client>>> {
+ language_config
+ .language_servers
+ .iter()
+ .map(|LanguageServerFeatures { name, .. }| {
+ if let Some(clients) = self.inner.get(name) {
+ if let Some((_, client)) = clients.iter().enumerate().find(|(i, client)| {
+ client.try_add_doc(&language_config.roots, root_dirs, doc_path, *i == 0)
+ }) {
+ return Ok((name.to_owned(), client.clone()));
+ }
+ }
+ let client = self.start_client(
+ name.clone(),
+ language_config,
+ doc_path,
+ root_dirs,
+ enable_snippets,
+ )?;
+ let clients = self.inner.entry(name.clone()).or_default();
+ clients.push(client.clone());
+ Ok((name.clone(), client))
+ })
+ .collect()
}
pub fn iter_clients(&self) -> impl Iterator<Item = &Arc<Client>> {
- self.inner.values().flatten().map(|(_, client)| client)
+ self.inner.values().flatten()
}
}
@@ -833,26 +849,28 @@ impl LspProgressMap {
}
}
-struct NewClientResult(Arc<Client>, UnboundedReceiver<(usize, Call)>);
+struct NewClient(Arc<Client>, UnboundedReceiver<(usize, Call)>);
/// start_client takes both a LanguageConfiguration and a LanguageServerConfiguration to ensure that
/// it is only called when it makes sense.
fn start_client(
id: usize,
+ name: String,
config: &LanguageConfiguration,
ls_config: &LanguageServerConfiguration,
doc_path: Option<&std::path::PathBuf>,
root_dirs: &[PathBuf],
enable_snippets: bool,
-) -> Result<NewClientResult> {
+) -> Result<NewClient> {
let (client, incoming, initialize_notify) = Client::start(
&ls_config.command,
&ls_config.args,
- config.config.clone(),
+ ls_config.config.clone(),
ls_config.environment.clone(),
&config.roots,
config.workspace_lsp_roots.as_deref().unwrap_or(root_dirs),
id,
+ name,
ls_config.timeout,
doc_path,
)?;
@@ -886,7 +904,7 @@ fn start_client(
initialize_notify.notify_one();
});
- Ok(NewClientResult(client, incoming))
+ Ok(NewClient(client, incoming))
}
/// Find an LSP workspace of a file using the following mechanism: