diff options
author | Philipp Mildenberger | 2023-03-18 19:12:20 +0000 |
---|---|---|
committer | Philipp Mildenberger | 2023-05-18 19:48:32 +0000 |
commit | 76b5cab52479daf25ffa0af798c1ebcf6a4f0004 (patch) | |
tree | af2dc03114a05cb458518006ef16c3a74a28b097 | |
parent | 0637691eb1fb7e2055fc04a0209be94906c2bd1a (diff) |
Refactored doc.language_servers and doc.language_servers_with_feature to return an iterator and refactor LanguageServerFeature handling to a HashMap (language server name maps to features)
Co-authored-by: Pascal Kuthe <pascal.kuthe@semimod.de>
-rw-r--r-- | helix-core/src/syntax.rs | 97 | ||||
-rw-r--r-- | helix-lsp/src/lib.rs | 20 | ||||
-rw-r--r-- | helix-term/src/application.rs | 8 | ||||
-rw-r--r-- | helix-term/src/commands.rs | 11 | ||||
-rw-r--r-- | helix-term/src/commands/lsp.rs | 55 | ||||
-rw-r--r-- | helix-term/src/commands/typed.rs | 24 | ||||
-rw-r--r-- | helix-term/src/health.rs | 8 | ||||
-rw-r--r-- | helix-term/src/ui/mod.rs | 8 | ||||
-rw-r--r-- | helix-term/src/ui/statusline.rs | 5 | ||||
-rw-r--r-- | helix-view/src/document.rs | 52 | ||||
-rw-r--r-- | helix-view/src/editor.rs | 5 |
11 files changed, 155 insertions, 138 deletions
diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index f45a38cc..a4e6d990 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -16,7 +16,7 @@ use slotmap::{DefaultKey as LayerId, HopSlotMap}; use std::{ borrow::Cow, cell::RefCell, - collections::{HashMap, VecDeque}, + collections::{HashMap, HashSet, VecDeque}, fmt::{self, Display}, hash::{Hash, Hasher}, mem::{replace, transmute}, @@ -26,7 +26,7 @@ use std::{ }; use once_cell::sync::{Lazy, OnceCell}; -use serde::{Deserialize, Serialize}; +use serde::{ser::SerializeSeq, Deserialize, Serialize}; use helix_loader::grammar::{get_language, load_runtime_file}; @@ -110,8 +110,13 @@ pub struct LanguageConfiguration { #[serde(skip)] pub(crate) highlight_config: OnceCell<Option<Arc<HighlightConfiguration>>>, // tags_config OnceCell<> https://github.com/tree-sitter/tree-sitter/pull/583 - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub language_servers: Vec<LanguageServerFeatureConfiguration>, + #[serde( + default, + skip_serializing_if = "HashMap::is_empty", + serialize_with = "serialize_lang_features", + deserialize_with = "deserialize_lang_features" + )] + pub language_servers: HashMap<String, LanguageServerFeatures>, #[serde(skip_serializing_if = "Option::is_none")] pub indent: Option<IndentationConfiguration>, @@ -211,7 +216,7 @@ impl<'de> Deserialize<'de> for FileType { } } -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] #[serde(rename_all = "kebab-case")] pub enum LanguageServerFeature { Format, @@ -261,18 +266,81 @@ impl Display for LanguageServerFeature { #[derive(Debug, Serialize, Deserialize)] #[serde(untagged, rename_all = "kebab-case", deny_unknown_fields)] -pub enum LanguageServerFeatureConfiguration { +enum LanguageServerFeatureConfiguration { #[serde(rename_all = "kebab-case")] Features { - #[serde(default, skip_serializing_if = "Vec::is_empty")] - only_features: Vec<LanguageServerFeature>, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - except_features: Vec<LanguageServerFeature>, + #[serde(default, skip_serializing_if = "HashSet::is_empty")] + only_features: HashSet<LanguageServerFeature>, + #[serde(default, skip_serializing_if = "HashSet::is_empty")] + except_features: HashSet<LanguageServerFeature>, name: String, }, Simple(String), } +#[derive(Debug, Default)] +pub struct LanguageServerFeatures { + pub only: HashSet<LanguageServerFeature>, + pub excluded: HashSet<LanguageServerFeature>, +} + +impl LanguageServerFeatures { + pub fn has_feature(&self, feature: LanguageServerFeature) -> bool { + self.only.is_empty() || self.only.contains(&feature) && !self.excluded.contains(&feature) + } +} + +fn deserialize_lang_features<'de, D>( + deserializer: D, +) -> Result<HashMap<String, LanguageServerFeatures>, D::Error> +where + D: serde::Deserializer<'de>, +{ + let raw: Vec<LanguageServerFeatureConfiguration> = Deserialize::deserialize(deserializer)?; + let res = raw + .into_iter() + .map(|config| match config { + LanguageServerFeatureConfiguration::Simple(name) => { + (name, LanguageServerFeatures::default()) + } + LanguageServerFeatureConfiguration::Features { + only_features, + except_features, + name, + } => ( + name, + LanguageServerFeatures { + only: only_features, + excluded: except_features, + }, + ), + }) + .collect(); + Ok(res) +} +fn serialize_lang_features<S>( + map: &HashMap<String, LanguageServerFeatures>, + serializer: S, +) -> Result<S::Ok, S::Error> +where + S: serde::Serializer, +{ + let mut serializer = serializer.serialize_seq(Some(map.len()))?; + for (name, features) in map { + let features = if features.only.is_empty() && features.excluded.is_empty() { + LanguageServerFeatureConfiguration::Simple(name.to_owned()) + } else { + LanguageServerFeatureConfiguration::Features { + only_features: features.only.clone(), + except_features: features.excluded.clone(), + name: name.to_owned(), + } + }; + serializer.serialize_element(&features)?; + } + serializer.end() +} + #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct LanguageServerConfiguration { @@ -650,15 +718,6 @@ pub struct SoftWrap { pub wrap_at_text_width: Option<bool>, } -impl LanguageServerFeatureConfiguration { - pub fn name(&self) -> &String { - match self { - LanguageServerFeatureConfiguration::Simple(name) => name, - LanguageServerFeatureConfiguration::Features { name, .. } => name, - } - } -} - // Expose loader as Lazy<> global since it's always static? #[derive(Debug)] diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index 12e63255..ba0c3fee 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -689,12 +689,10 @@ impl Registry { ) -> Result<Vec<Arc<Client>>> { language_config .language_servers - .iter() - .filter_map(|config| { - let name = config.name().clone(); - + .keys() + .filter_map(|name| { #[allow(clippy::map_entry)] - if self.inner.contains_key(&name) { + if self.inner.contains_key(name) { let client = match self.start_client( name.clone(), language_config, @@ -705,7 +703,10 @@ impl Registry { Ok(client) => client, error => return Some(error), }; - let old_clients = self.inner.insert(name, vec![client.clone()]).unwrap(); + let old_clients = self + .inner + .insert(name.clone(), vec![client.clone()]) + .unwrap(); // TODO what if there are different language servers for different workspaces, // I think the language servers will be stopped without being restarted, which is not intended @@ -742,9 +743,8 @@ impl Registry { ) -> Result<Vec<Arc<Client>>> { language_config .language_servers - .iter() - .map(|features| { - let name = features.name(); + .keys() + .map(|name| { if let Some(clients) = self.inner.get_mut(name) { if let Some((_, client)) = clients.iter_mut().enumerate().find(|(i, client)| { client.try_add_doc(&language_config.roots, root_dirs, doc_path, *i == 0) @@ -759,7 +759,7 @@ impl Registry { root_dirs, enable_snippets, )?; - let clients = self.inner.entry(features.name().clone()).or_default(); + let clients = self.inner.entry(name.clone()).or_default(); clients.push(client.clone()); Ok(client) }) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 728aa46a..83473179 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -699,9 +699,10 @@ impl Application { tokio::spawn(language_server.did_change_configuration(config.clone())); } - let docs = self.editor.documents().filter(|doc| { - doc.language_servers().iter().any(|l| l.id() == server_id) - }); + let docs = self + .editor + .documents() + .filter(|doc| doc.language_servers().any(|l| l.id() == server_id)); // trigger textDocument/didOpen for docs that are already open for doc in docs { @@ -970,7 +971,6 @@ impl Application { .filter_map(|doc| { if doc .language_servers() - .iter() .any(|server| server.id() == server_id) { doc.clear_diagnostics(server_id); diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 14a68490..060c9d83 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3235,7 +3235,6 @@ pub mod insert { let doc = doc_mut!(cx.editor); let trigger_completion = doc .language_servers_with_feature(LanguageServerFeature::Completion) - .iter() .any(|ls| { let capabilities = ls.capabilities(); @@ -3264,7 +3263,6 @@ pub mod insert { // TODO support multiple language servers (not just the first that is found) let future = doc .language_servers_with_feature(LanguageServerFeature::SignatureHelp) - .iter() .find_map(|ls| { let capabilities = ls.capabilities(); @@ -4067,10 +4065,8 @@ fn format_selections(cx: &mut Context) { .set_error("format_selections only supports a single selection for now"); return; } - - let (future, offset_encoding) = match doc + let future_offset_encoding = doc .language_servers_with_feature(LanguageServerFeature::Format) - .iter() .find_map(|language_server| { let offset_encoding = language_server.offset_encoding(); let ranges: Vec<lsp::Range> = doc @@ -4091,7 +4087,9 @@ fn format_selections(cx: &mut Context) { None, )?; Some((future, offset_encoding)) - }) { + }); + + let (future, offset_encoding) = match future_offset_encoding { Some(future_offset_encoding) => future_offset_encoding, None => { cx.editor @@ -4247,7 +4245,6 @@ pub fn completion(cx: &mut Context) { let mut futures: FuturesUnordered<_> = doc .language_servers_with_feature(LanguageServerFeature::Completion) - .iter() // TODO this should probably already been filtered in something like "language_servers_with_feature" .filter_map(|language_server| { let language_server_id = language_server.id(); diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs index 25a54aba..6553ce16 100644 --- a/helix-term/src/commands/lsp.rs +++ b/helix-term/src/commands/lsp.rs @@ -353,7 +353,6 @@ pub fn symbol_picker(cx: &mut Context) { let mut futures: FuturesUnordered<_> = doc .language_servers_with_feature(LanguageServerFeature::DocumentSymbols) - .iter() .filter_map(|ls| { let request = ls.document_symbols(doc.identifier())?; Some((request, ls.offset_encoding(), doc.identifier())) @@ -420,7 +419,6 @@ pub fn workspace_symbol_picker(cx: &mut Context) { let doc = doc!(editor); let mut futures: FuturesUnordered<_> = doc .language_servers_with_feature(LanguageServerFeature::WorkspaceSymbols) - .iter() .filter_map(|ls| Some((ls.workspace_symbols(pattern.clone())?, ls.offset_encoding()))) .map(|(request, offset_encoding)| async move { let json = request.await?; @@ -581,7 +579,6 @@ pub fn code_action(cx: &mut Context) { let mut futures: FuturesUnordered<_> = doc .language_servers_with_feature(LanguageServerFeature::CodeAction) - .iter() // TODO this should probably already been filtered in something like "language_servers_with_feature" .filter_map(|language_server| { let offset_encoding = language_server.offset_encoding(); @@ -1034,15 +1031,15 @@ fn to_locations(definitions: Option<lsp::GotoDefinitionResponse>) -> Vec<lsp::Lo // TODO find a way to reduce boilerplate of all the goto functions, without unnecessary complexity... pub fn goto_declaration(cx: &mut Context) { let (view, doc) = current!(cx.editor); - let (future, offset_encoding) = match doc + let future_offset_encoding = doc .language_servers_with_feature(LanguageServerFeature::GotoDeclaration) - .iter() .find_map(|language_server| { let offset_encoding = language_server.offset_encoding(); let pos = doc.position(view.id, offset_encoding); let future = language_server.goto_declaration(doc.identifier(), pos, None)?; Some((future, offset_encoding)) - }) { + }); + let (future, offset_encoding) = match future_offset_encoding { Some(future_offset_encoding) => future_offset_encoding, None => { cx.editor @@ -1062,15 +1059,15 @@ pub fn goto_declaration(cx: &mut Context) { pub fn goto_definition(cx: &mut Context) { let (view, doc) = current!(cx.editor); - let (future, offset_encoding) = match doc + let future_offset_encoding = doc .language_servers_with_feature(LanguageServerFeature::GotoDefinition) - .iter() .find_map(|language_server| { let offset_encoding = language_server.offset_encoding(); let pos = doc.position(view.id, offset_encoding); let future = language_server.goto_definition(doc.identifier(), pos, None)?; Some((future, offset_encoding)) - }) { + }); + let (future, offset_encoding) = match future_offset_encoding { Some(future_offset_encoding) => future_offset_encoding, None => { cx.editor @@ -1090,15 +1087,15 @@ pub fn goto_definition(cx: &mut Context) { pub fn goto_type_definition(cx: &mut Context) { let (view, doc) = current!(cx.editor); - let (future, offset_encoding) = match doc + let future_offset_encoding = doc .language_servers_with_feature(LanguageServerFeature::GotoTypeDefinition) - .iter() .find_map(|language_server| { let offset_encoding = language_server.offset_encoding(); let pos = doc.position(view.id, offset_encoding); let future = language_server.goto_type_definition(doc.identifier(), pos, None)?; Some((future, offset_encoding)) - }) { + }); + let (future, offset_encoding) = match future_offset_encoding { Some(future_offset_encoding) => future_offset_encoding, None => { cx.editor @@ -1118,15 +1115,15 @@ pub fn goto_type_definition(cx: &mut Context) { pub fn goto_implementation(cx: &mut Context) { let (view, doc) = current!(cx.editor); - let (future, offset_encoding) = match doc + let future_offset_encoding = doc .language_servers_with_feature(LanguageServerFeature::GotoImplementation) - .iter() .find_map(|language_server| { let offset_encoding = language_server.offset_encoding(); let pos = doc.position(view.id, offset_encoding); let future = language_server.goto_implementation(doc.identifier(), pos, None)?; Some((future, offset_encoding)) - }) { + }); + let (future, offset_encoding) = match future_offset_encoding { Some(future_offset_encoding) => future_offset_encoding, None => { cx.editor @@ -1147,9 +1144,8 @@ pub fn goto_implementation(cx: &mut Context) { pub fn goto_reference(cx: &mut Context) { let config = cx.editor.config(); let (view, doc) = current!(cx.editor); - let (future, offset_encoding) = match doc + let future_offset_encoding = doc .language_servers_with_feature(LanguageServerFeature::GotoReference) - .iter() .find_map(|language_server| { let offset_encoding = language_server.offset_encoding(); let pos = doc.position(view.id, offset_encoding); @@ -1160,7 +1156,8 @@ pub fn goto_reference(cx: &mut Context) { None, )?; Some((future, offset_encoding)) - }) { + }); + let (future, offset_encoding) = match future_offset_encoding { Some(future_offset_encoding) => future_offset_encoding, None => { cx.editor @@ -1192,13 +1189,14 @@ pub fn signature_help_impl(cx: &mut Context, invoked: SignatureHelpInvoked) { let (view, doc) = current!(cx.editor); // TODO merge multiple language server signature help into one instead of just taking the first language server that supports it - let future = match doc + let future = doc .language_servers_with_feature(LanguageServerFeature::SignatureHelp) - .iter() .find_map(|language_server| { let pos = doc.position(view.id, language_server.offset_encoding()); language_server.text_document_signature_help(doc.identifier(), pos, None) - }) { + }); + + let future = match future { Some(future) => future.boxed(), None => { // Do not show the message if signature help was invoked @@ -1328,7 +1326,6 @@ pub fn hover(cx: &mut Context) { // TODO: factor out a doc.position_identifier() that returns lsp::TextDocumentPositionIdentifier let request = doc .language_servers_with_feature(LanguageServerFeature::Hover) - .iter() .find_map(|language_server| { let pos = doc.position(view.id, language_server.offset_encoding()); language_server.text_document_hover(doc.identifier(), pos, None) @@ -1436,7 +1433,6 @@ pub fn rename_symbol(cx: &mut Context) { let (view, doc) = current!(cx.editor); let request = doc .language_servers_with_feature(LanguageServerFeature::RenameSymbol) - .iter() .find_map(|language_server| { if let Some(language_server_id) = language_server_id { if language_server.id() != language_server_id { @@ -1475,7 +1471,6 @@ pub fn rename_symbol(cx: &mut Context) { let prepare_rename_request = doc .language_servers_with_feature(LanguageServerFeature::RenameSymbol) - .iter() .find_map(|language_server| { let offset_encoding = language_server.offset_encoding(); let pos = doc.position(view.id, offset_encoding); @@ -1516,17 +1511,17 @@ pub fn rename_symbol(cx: &mut Context) { pub fn select_references_to_symbol_under_cursor(cx: &mut Context) { let (view, doc) = current!(cx.editor); - let (future, offset_encoding) = match doc + let future_offset_encoding = doc .language_servers_with_feature(LanguageServerFeature::DocumentHighlight) - .iter() .find_map(|language_server| { let offset_encoding = language_server.offset_encoding(); let pos = doc.position(view.id, offset_encoding); let future = language_server.text_document_document_highlight(doc.identifier(), pos, None)?; Some((future, offset_encoding)) - }) { - Some(future) => future, + }); + let (future, offset_encoding) = match future_offset_encoding { + Some(future_offset_encoding) => future_offset_encoding, None => { cx.editor .set_error("No language server supports document-highlight"); @@ -1587,8 +1582,8 @@ fn compute_inlay_hints_for_view( let view_id = view.id; let doc_id = view.doc; - let language_servers = doc.language_servers_with_feature(LanguageServerFeature::InlayHints); - let language_server = language_servers.iter().find(|language_server| { + let mut language_servers = doc.language_servers_with_feature(LanguageServerFeature::InlayHints); + let language_server = language_servers.find(|language_server| { matches!( language_server.capabilities().inlay_hint_provider, Some( diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index b78de772..38058ed5 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1330,14 +1330,16 @@ fn lsp_workspace_command( return Ok(()); } let doc = doc!(cx.editor); - let language_servers = - doc.language_servers_with_feature(LanguageServerFeature::WorkspaceCommand); - let (language_server_id, options) = match language_servers.iter().find_map(|ls| { - ls.capabilities() - .execute_command_provider - .as_ref() - .map(|options| (ls.id(), options)) - }) { + let id_options = doc + .language_servers_with_feature(LanguageServerFeature::WorkspaceCommand) + .find_map(|ls| { + ls.capabilities() + .execute_command_provider + .as_ref() + .map(|options| (ls.id(), options)) + }); + + let (language_server_id, options) = match id_options { Some(id_options) => id_options, None => { cx.editor.set_status( @@ -1346,6 +1348,7 @@ fn lsp_workspace_command( return Ok(()); } }; + if args.is_empty() { let commands = options .commands @@ -1445,7 +1448,6 @@ fn lsp_stop( // I'm not sure if this is really what we want let ls_shutdown_names = doc .language_servers() - .iter() .map(|ls| ls.name()) .collect::<Vec<_>>(); @@ -1459,7 +1461,6 @@ fn lsp_stop( .filter_map(|doc| { let doc_active_ls_ids: Vec<_> = doc .language_servers() - .iter() .filter(|ls| !ls_shutdown_names.contains(&ls.name())) .map(|ls| ls.id()) .collect(); @@ -1472,7 +1473,7 @@ fn lsp_stop( .map(Clone::clone) .collect(); - if active_clients.len() != doc.language_servers().len() { + if active_clients.len() != doc.language_servers().count() { Some((doc.id(), active_clients)) } else { None @@ -1485,7 +1486,6 @@ fn lsp_stop( let stopped_clients: Vec<_> = doc .language_servers() - .iter() .filter(|ls| { !active_clients .iter() diff --git a/helix-term/src/health.rs b/helix-term/src/health.rs index 6b9f8517..5b22ea55 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.first().and_then(|lsp| { + let lsp = lang.language_servers.keys().next().and_then(|ls_name| { syn_loader_conf .language_server - .get(lsp.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.first().and_then(|lsp| { + lang.language_servers.keys().next().and_then(|ls_name| { syn_loader_conf .language_server - .get(lsp.name()) + .get(ls_name) .map(|config| config.command.clone()) }), )?; diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 118836c0..6f7ed174 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -394,13 +394,11 @@ pub mod completers { pub fn lsp_workspace_command(editor: &Editor, input: &str) -> Vec<Completion> { let matcher = Matcher::default(); - let language_servers = - doc!(editor).language_servers_with_feature(LanguageServerFeature::WorkspaceCommand); - let options = match language_servers - .into_iter() + let options = match doc!(editor) + .language_servers_with_feature(LanguageServerFeature::WorkspaceCommand) .find_map(|ls| ls.capabilities().execute_command_provider.as_ref()) { - Some(id_options) => id_options, + Some(options) => options, None => { return vec![]; } diff --git a/helix-term/src/ui/statusline.rs b/helix-term/src/ui/statusline.rs index 60997956..4aa64634 100644 --- a/helix-term/src/ui/statusline.rs +++ b/helix-term/src/ui/statusline.rs @@ -202,11 +202,10 @@ fn render_lsp_spinner<F>(context: &mut RenderContext, write: F) where F: Fn(&mut RenderContext, String, Option<Style>) + Copy, { - let language_servers = context.doc.language_servers(); + let language_server = context.doc.language_servers().next(); write( context, - language_servers - .first() + language_server .and_then(|srv| { context .spinners diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 37ddc2b6..4b075293 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -6,7 +6,7 @@ use futures_util::FutureExt; use helix_core::auto_pairs::AutoPairs; use helix_core::doc_formatter::TextFormat; use helix_core::encoding::Encoding; -use helix_core::syntax::{Highlight, LanguageServerFeature, LanguageServerFeatureConfiguration}; +use helix_core::syntax::{Highlight, LanguageServerFeature}; use helix_core::text_annotations::{InlineAnnotation, TextAnnotations}; use helix_core::Range; use helix_vcs::{DiffHandle, DiffProviderRegistry}; @@ -734,7 +734,6 @@ impl Document { // finds first language server that supports formatting and then formats let (offset_encoding, request) = self .language_servers_with_feature(LanguageServerFeature::Format) - .iter() .find_map(|language_server| { let offset_encoding = language_server.offset_encoding(); let request = language_server.text_document_formatting( @@ -1437,54 +1436,24 @@ impl Document { self.version } - /// Language servers that have been initialized. - pub fn language_servers(&self) -> Vec<&helix_lsp::Client> { + pub fn language_servers(&self) -> impl Iterator<Item = &helix_lsp::Client> { self.language_servers .iter() .filter_map(|l| if l.is_initialized() { Some(&**l) } else { None }) - .collect() } // TODO filter also based on LSP capabilities? pub fn language_servers_with_feature( &self, feature: LanguageServerFeature, - ) -> Vec<&helix_lsp::Client> { - let language_servers = self.language_servers(); - - let language_config = match self.language_config() { - Some(language_config) => language_config, - None => return Vec::new(), - }; - - // O(n^2) but since language_servers will be of very small length, - // I don't see the necessity to optimize - language_config - .language_servers - .iter() - .filter_map(|c| match c { - LanguageServerFeatureConfiguration::Simple(name) => language_servers - .iter() - .find(|ls| ls.name() == name) - .copied(), - LanguageServerFeatureConfiguration::Features { - only_features, - except_features, - name, - } => { - if (only_features.is_empty() || only_features.contains(&feature)) - && !except_features.contains(&feature) - { - language_servers - .iter() - .find(|ls| ls.name() == name) - .copied() - } else { - None - } - } - }) - .collect() + ) -> impl Iterator<Item = &helix_lsp::Client> { + 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) + }) + }) } pub fn diff_handle(&self) -> Option<&DiffHandle> { @@ -1610,7 +1579,6 @@ impl Document { pub fn shown_diagnostics(&self) -> impl Iterator<Item = &Diagnostic> + DoubleEndedIterator { let ls_ids: HashSet<_> = self .language_servers_with_feature(LanguageServerFeature::Diagnostics) - .iter() .map(|ls| ls.id()) .collect(); self.diagnostics diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 366cc01c..bca97815 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 @@ -1129,7 +1129,8 @@ impl Editor { if let Some(language_servers) = language_servers { // only spawn new lang servers if the servers aren't the same - let doc_language_servers = doc.language_servers(); + // TODO simplify? + let doc_language_servers = doc.language_servers().collect::<Vec<_>>(); let spawn_new_servers = language_servers.len() != doc_language_servers.len() || language_servers .iter() |