diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index ce26a1bc..3fa7994d 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -112,11 +112,11 @@ pub struct LanguageConfiguration { // tags_config OnceCell<> https://github.com/tree-sitter/tree-sitter/pull/583 #[serde( default, - skip_serializing_if = "HashMap::is_empty", + skip_serializing_if = "Vec::is_empty", serialize_with = "serialize_lang_features", deserialize_with = "deserialize_lang_features" )] - pub language_servers: HashMap, + pub language_servers: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub indent: Option, @@ -282,19 +282,20 @@ enum LanguageServerFeatureConfiguration { #[derive(Debug, Default)] pub struct LanguageServerFeatures { + pub name: String, pub only: HashSet, pub excluded: HashSet, } impl LanguageServerFeatures { pub fn has_feature(&self, feature: LanguageServerFeature) -> bool { - self.only.is_empty() || self.only.contains(&feature) && !self.excluded.contains(&feature) + (self.only.is_empty() || self.only.contains(&feature)) && !self.excluded.contains(&feature) } } fn deserialize_lang_features<'de, D>( deserializer: D, -) -> Result, D::Error> +) -> Result, D::Error> where D: serde::Deserializer<'de>, { @@ -302,40 +303,39 @@ where let res = raw .into_iter() .map(|config| match config { - LanguageServerFeatureConfiguration::Simple(name) => { - (name, LanguageServerFeatures::default()) - } + LanguageServerFeatureConfiguration::Simple(name) => LanguageServerFeatures { + name, + ..Default::default() + }, LanguageServerFeatureConfiguration::Features { only_features, except_features, name, - } => ( + } => LanguageServerFeatures { name, - LanguageServerFeatures { - only: only_features, - excluded: except_features, - }, - ), + only: only_features, + excluded: except_features, + }, }) .collect(); Ok(res) } fn serialize_lang_features( - map: &HashMap, + map: &Vec, serializer: S, ) -> Result where S: serde::Serializer, { let mut serializer = serializer.serialize_seq(Some(map.len()))?; - for (name, features) in map { + for features in map { let features = if features.only.is_empty() && features.excluded.is_empty() { - LanguageServerFeatureConfiguration::Simple(name.to_owned()) + LanguageServerFeatureConfiguration::Simple(features.name.to_owned()) } else { LanguageServerFeatureConfiguration::Features { only_features: features.only.clone(), except_features: features.excluded.clone(), - name: name.to_owned(), + name: features.name.to_owned(), } }; serializer.serialize_element(&features)?; diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index ba0c3fee..6b4bb430 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -12,7 +12,7 @@ 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; @@ -26,7 +26,7 @@ use thiserror::Error; use tokio_stream::wrappers::UnboundedReceiverStream; pub type Result = core::result::Result; -type LanguageServerName = String; +pub type LanguageServerName = String; #[derive(Error, Debug)] pub enum Error { @@ -689,9 +689,9 @@ impl Registry { ) -> Result>> { language_config .language_servers - .keys() - .filter_map(|name| { - #[allow(clippy::map_entry)] + .iter() + .filter_map(|LanguageServerFeatures { name, .. }| { + // #[allow(clippy::map_entry)] if self.inner.contains_key(name) { let client = match self.start_client( name.clone(), @@ -740,17 +740,20 @@ impl Registry { doc_path: Option<&std::path::PathBuf>, root_dirs: &[PathBuf], enable_snippets: bool, - ) -> Result>> { + ) -> Result>> { language_config .language_servers - .keys() - .map(|name| { + .iter() + .map(|LanguageServerFeatures { name, .. }| { if let Some(clients) = self.inner.get_mut(name) { + // clients.find( + 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(client.clone()); + return Ok((name.to_owned(), client.clone())); } + // return Ok((name.clone(), clients.clone())); } let client = self.start_client( name.clone(), @@ -761,7 +764,7 @@ impl Registry { )?; let clients = self.inner.entry(name.clone()).or_default(); clients.push(client.clone()); - Ok(client) + Ok((name.clone(), client)) }) .collect() } diff --git a/helix-term/src/health.rs b/helix-term/src/health.rs index 5b22ea55..8f921877 100644 --- a/helix-term/src/health.rs +++ b/helix-term/src/health.rs @@ -194,10 +194,10 @@ pub fn languages_all() -> std::io::Result<()> { // TODO multiple language servers (check binary for each supported language server, not just the first) - let lsp = lang.language_servers.keys().next().and_then(|ls_name| { + let lsp = lang.language_servers.first().and_then(|ls| { syn_loader_conf .language_server - .get(ls_name) + .get(&ls.name) .map(|config| config.command.clone()) }); check_binary(lsp); @@ -271,10 +271,10 @@ pub fn language(lang_str: String) -> std::io::Result<()> { // TODO multiple language servers probe_protocol( "language server", - lang.language_servers.keys().next().and_then(|ls_name| { + lang.language_servers.first().and_then(|ls| { syn_loader_conf .language_server - .get(ls_name) + .get(&ls.name) .map(|config| config.command.clone()) }), )?; diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 49eb13a0..27f5d279 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -180,7 +180,7 @@ pub struct Document { pub(crate) modified_since_accessed: bool, diagnostics: Vec, - language_servers: Vec>, + pub(crate) language_servers: HashMap>, diff_handle: Option, version_control_head: Option>>>, @@ -580,7 +580,7 @@ where *mut_ref = f(mem::take(mut_ref)); } -use helix_lsp::{lsp, Client, OffsetEncoding}; +use helix_lsp::{lsp, Client, LanguageServerName, OffsetEncoding}; use url::Url; impl Document { @@ -616,7 +616,7 @@ impl Document { last_saved_time: SystemTime::now(), last_saved_revision: 0, modified_since_accessed: false, - language_servers: Vec::new(), + language_servers: HashMap::new(), diff_handle: None, config, version_control_head: None, @@ -850,7 +850,7 @@ impl Document { text: text.clone(), }; - for language_server in language_servers { + for (_, language_server) in language_servers { if !language_server.is_initialized() { return Ok(event); } @@ -1006,11 +1006,6 @@ impl Document { Ok(()) } - /// Set the LSP. - pub fn set_language_servers(&mut self, language_servers: Vec>) { - self.language_servers = language_servers; - } - /// Select text within the [`Document`]. pub fn set_selection(&mut self, view_id: ViewId, selection: Selection) { // TODO: use a transaction? @@ -1437,16 +1432,17 @@ impl Document { } pub fn language_servers(&self) -> impl Iterator { - self.language_servers - .iter() - .filter_map(|l| if l.is_initialized() { Some(&**l) } else { None }) + self.language_servers.values().filter_map(|l| { + if l.is_initialized() { + Some(&**l) + } else { + None + } + }) } pub fn remove_language_server_by_name(&mut self, name: &str) -> Option> { - match self.language_servers.iter().position(|l| l.name() == name) { - Some(index) => Some(self.language_servers.remove(index)), - None => None, - } + self.language_servers.remove(name) } // TODO filter also based on LSP capabilities? @@ -1454,12 +1450,15 @@ impl Document { &self, feature: LanguageServerFeature, ) -> impl Iterator { - self.language_servers().filter(move |server| { - self.language_config() - .and_then(|config| config.language_servers.get(server.name())) - .map_or(false, |server_features| { - server_features.has_feature(feature) - }) + self.language_config().into_iter().flat_map(move |config| { + config.language_servers.iter().filter_map(move |features| { + let ls = &**self.language_servers.get(&features.name)?; + if ls.is_initialized() && features.has_feature(feature) { + Some(ls) + } else { + None + } + }) }) } @@ -1610,7 +1609,10 @@ impl Document { .find(|ls| ls.id() == d.language_server_id) .and_then(|ls| { let config = self.language_config()?; - let features = config.language_servers.get(ls.name())?; + let features = config + .language_servers + .iter() + .find(|features| features.name == ls.name())?; Some(features.has_feature(LanguageServerFeature::Diagnostics)) }) == Some(true) diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index bca97815..ca2144fd 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -689,7 +689,7 @@ pub struct WhitespaceCharacters { impl Default for WhitespaceCharacters { fn default() -> Self { Self { - space: '·', // U+00B7 + space: '·', // U+00B7 nbsp: '⍽', // U+237D tab: '→', // U+2192 newline: '⏎', // U+23CE @@ -1103,9 +1103,9 @@ impl Editor { if !self.config().lsp.enable { return None; } - // if doc doesn't have a URL it's a scratch buffer, ignore it - let doc = self.document(doc_id)?; + let doc = self.documents.get_mut(&doc_id)?; + let doc_url = doc.url()?; let (lang, path) = (doc.language.clone(), doc.path().cloned()); let config = doc.config.load(); let root_dirs = &config.workspace_lsp_roots; @@ -1124,37 +1124,37 @@ impl Editor { .ok() }); - let doc = self.document_mut(doc_id)?; - let doc_url = doc.url()?; - if let Some(language_servers) = language_servers { - // only spawn new lang servers if the servers aren't the same - // TODO simplify? - let doc_language_servers = doc.language_servers().collect::>(); - let spawn_new_servers = language_servers.len() != doc_language_servers.len() - || language_servers - .iter() - .zip(doc_language_servers.iter()) - .any(|(l, dl)| l.id() != dl.id()); - if spawn_new_servers { - for doc_language_server in doc_language_servers { - tokio::spawn(doc_language_server.text_document_did_close(doc.identifier())); - } + let language_id = doc.language_id().map(ToOwned::to_owned).unwrap_or_default(); - let language_id = doc.language_id().map(ToOwned::to_owned).unwrap_or_default(); + // only spawn new language servers if the servers aren't the same - for language_server in &language_servers { - // TODO: this now races with on_init code if the init happens too quickly - tokio::spawn(language_server.text_document_did_open( - doc_url.clone(), - doc.version(), - doc.text(), - language_id.clone(), - )); - } + let doc_language_servers_not_in_registry = + doc.language_servers.iter().filter(|(name, doc_ls)| { + !language_servers.contains_key(*name) + || language_servers[*name].id() != doc_ls.id() + }); - doc.set_language_servers(language_servers); + for (_, language_server) in doc_language_servers_not_in_registry { + tokio::spawn(language_server.text_document_did_close(doc.identifier())); } + + let language_servers_not_in_doc = language_servers.iter().filter(|(name, ls)| { + !doc.language_servers.contains_key(*name) + || doc.language_servers[*name].id() != ls.id() + }); + + for (_, language_server) in language_servers_not_in_doc { + // TODO: this now races with on_init code if the init happens too quickly + tokio::spawn(language_server.text_document_did_open( + doc_url.clone(), + doc.version(), + doc.text(), + language_id.clone(), + )); + } + + doc.language_servers = language_servers; } Some(()) }