From 71551d395b4e47804df2d8ecea99e34dbbf16157 Mon Sep 17 00:00:00 2001 From: Philipp Mildenberger Date: Mon, 23 May 2022 18:10:48 +0200 Subject: Adds support for multiple language servers per language. Language Servers are now configured in a separate table in `languages.toml`: ```toml [langauge-server.mylang-lsp] command = "mylang-lsp" args = ["--stdio"] config = { provideFormatter = true } [language-server.efm-lsp-prettier] command = "efm-langserver" [language-server.efm-lsp-prettier.config] documentFormatting = true languages = { typescript = [ { formatCommand ="prettier --stdin-filepath ${INPUT}", formatStdin = true } ] } ``` The language server for a language is configured like this (`typescript-language-server` is configured by default): ```toml [[language]] name = "typescript" language-servers = [ { name = "efm-lsp-prettier", only-features = [ "format" ] }, "typescript-language-server" ] ``` or equivalent: ```toml [[language]] name = "typescript" language-servers = [ { name = "typescript-language-server", except-features = [ "format" ] }, "efm-lsp-prettier" ] ``` Each requested LSP feature is priorized in the order of the `language-servers` array. For example the first `goto-definition` supported language server (in this case `typescript-language-server`) will be taken for the relevant LSP request (command `goto_definition`). If no `except-features` or `only-features` is given all features for the language server are enabled, as long as the language server supports these. If it doesn't the next language server which supports the feature is tried. The list of supported features are: - `format` - `goto-definition` - `goto-declaration` - `goto-type-definition` - `goto-reference` - `goto-implementation` - `signature-help` - `hover` - `document-highlight` - `completion` - `code-action` - `workspace-command` - `document-symbols` - `workspace-symbols` - `diagnostics` - `rename-symbol` - `inlay-hints` Another side-effect/difference that comes with this PR, is that only one language server instance is started if different languages use the same language server. --- helix-term/src/application.rs | 126 +++++++++++++++++++++--------------------- 1 file changed, 62 insertions(+), 64 deletions(-) (limited to 'helix-term/src/application.rs') diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index b54d6835..45f99e48 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -30,6 +30,7 @@ use crate::{ use log::{debug, error, warn}; use std::{ + collections::btree_map::Entry, io::{stdin, stdout}, path::Path, sync::Arc, @@ -564,7 +565,7 @@ impl Application { let doc = doc_mut!(self.editor, &doc_save_event.doc_id); let id = doc.id(); doc.detect_language(loader); - let _ = self.editor.refresh_language_server(id); + self.editor.refresh_language_servers(id); } // TODO: fix being overwritten by lsp @@ -662,6 +663,18 @@ impl Application { ) { use helix_lsp::{Call, MethodCall, Notification}; + macro_rules! language_server { + () => { + match self.editor.language_servers.get_by_id(server_id) { + Some(language_server) => language_server, + None => { + warn!("can't find language server with id `{}`", server_id); + return; + } + } + }; + } + match call { Call::Notification(helix_lsp::jsonrpc::Notification { method, params, .. }) => { let notification = match Notification::parse(&method, params) { @@ -677,14 +690,7 @@ impl Application { match notification { Notification::Initialized => { - let language_server = - match self.editor.language_servers.get_by_id(server_id) { - Some(language_server) => language_server, - None => { - warn!("can't find language server with id `{}`", server_id); - return; - } - }; + let language_server = language_server!(); // Trigger a workspace/didChangeConfiguration notification after initialization. // This might not be required by the spec but Neovim does this as well, so it's @@ -694,7 +700,7 @@ impl Application { } let docs = self.editor.documents().filter(|doc| { - doc.language_server().map(|server| server.id()) == Some(server_id) + doc.language_servers().iter().any(|l| l.id() == server_id) }); // trigger textDocument/didOpen for docs that are already open @@ -723,6 +729,7 @@ impl Application { return; } }; + let offset_encoding = language_server!().offset_encoding(); let doc = self.editor.document_by_path_mut(&path).filter(|doc| { if let Some(version) = params.version { if version != doc.version() { @@ -745,18 +752,11 @@ impl Application { use helix_core::diagnostic::{Diagnostic, Range, Severity::*}; use lsp::DiagnosticSeverity; - let language_server = if let Some(language_server) = doc.language_server() { - language_server - } else { - log::warn!("Discarding diagnostic because language server is not initialized: {:?}", diagnostic); - return None; - }; - // TODO: convert inside server let start = if let Some(start) = lsp_pos_to_pos( text, diagnostic.range.start, - language_server.offset_encoding(), + offset_encoding, ) { start } else { @@ -764,11 +764,9 @@ impl Application { return None; }; - let end = if let Some(end) = lsp_pos_to_pos( - text, - diagnostic.range.end, - language_server.offset_encoding(), - ) { + let end = if let Some(end) = + lsp_pos_to_pos(text, diagnostic.range.end, offset_encoding) + { end } else { log::warn!("lsp position out of bounds - {:?}", diagnostic); @@ -807,14 +805,19 @@ impl Application { None => None, }; - let tags = if let Some(ref tags) = diagnostic.tags { - let new_tags = tags.iter().filter_map(|tag| { - match *tag { - lsp::DiagnosticTag::DEPRECATED => Some(DiagnosticTag::Deprecated), - lsp::DiagnosticTag::UNNECESSARY => Some(DiagnosticTag::Unnecessary), - _ => None - } - }).collect(); + let tags = if let Some(tags) = &diagnostic.tags { + let new_tags = tags + .iter() + .filter_map(|tag| match *tag { + lsp::DiagnosticTag::DEPRECATED => { + Some(DiagnosticTag::Deprecated) + } + lsp::DiagnosticTag::UNNECESSARY => { + Some(DiagnosticTag::Unnecessary) + } + _ => None, + }) + .collect(); new_tags } else { @@ -830,11 +833,12 @@ impl Application { tags, source: diagnostic.source.clone(), data: diagnostic.data.clone(), + language_server_id: server_id, }) }) .collect(); - doc.set_diagnostics(diagnostics); + doc.replace_diagnostics(diagnostics, server_id); } // Sort diagnostics first by severity and then by line numbers. @@ -842,13 +846,26 @@ impl Application { params .diagnostics .sort_unstable_by_key(|d| (d.severity, d.range.start)); + let diagnostics = params + .diagnostics + .into_iter() + .map(|d| (d, server_id, offset_encoding)) + .collect(); // Insert the original lsp::Diagnostics here because we may have no open document // for diagnosic message and so we can't calculate the exact position. // When using them later in the diagnostics picker, we calculate them on-demand. - self.editor - .diagnostics - .insert(params.uri, params.diagnostics); + match self.editor.diagnostics.entry(params.uri) { + Entry::Occupied(o) => { + let current_diagnostics = o.into_mut(); + // there may entries of other language servers, which is why we can't overwrite the whole entry + current_diagnostics.retain(|(_, lsp_id, _)| *lsp_id != server_id); + current_diagnostics.extend(diagnostics); + } + Entry::Vacant(v) => { + v.insert(diagnostics); + } + }; } Notification::ShowMessage(params) => { log::warn!("unhandled window/showMessage: {:?}", params); @@ -950,10 +967,12 @@ impl Application { .editor .documents_mut() .filter_map(|doc| { - if doc.language_server().map(|server| server.id()) - == Some(server_id) + if doc + .language_servers() + .iter() + .any(|server| server.id() == server_id) { - doc.set_diagnostics(Vec::new()); + doc.clear_diagnostics(server_id); doc.url() } else { None @@ -1029,28 +1048,15 @@ impl Application { })) } Ok(MethodCall::WorkspaceFolders) => { - let language_server = - self.editor.language_servers.get_by_id(server_id).unwrap(); - - Ok(json!(&*language_server.workspace_folders().await)) + Ok(json!(&*language_server!().workspace_folders().await)) } Ok(MethodCall::WorkspaceConfiguration(params)) => { + let language_server = language_server!(); let result: Vec<_> = params .items .iter() - .map(|item| { - let mut config = match &item.scope_uri { - Some(scope) => { - let path = scope.to_file_path().ok()?; - let doc = self.editor.document_by_path(path)?; - doc.language_config()?.config.as_ref()? - } - None => self - .editor - .language_servers - .get_by_id(server_id)? - .config()?, - }; + .filter_map(|item| { + let mut config = language_server.config()?; if let Some(section) = item.section.as_ref() { for part in section.split('.') { config = config.get(part)?; @@ -1074,15 +1080,7 @@ impl Application { } }; - let language_server = match self.editor.language_servers.get_by_id(server_id) { - Some(language_server) => language_server, - None => { - warn!("can't find language server with id `{}`", server_id); - return; - } - }; - - tokio::spawn(language_server.reply(id, reply)); + tokio::spawn(language_server!().reply(id, reply)); } Call::Invalid { id } => log::error!("LSP invalid method call id={:?}", id), } -- cgit v1.2.3-70-g09d2 From 7d4f7eb4bda6e507e253cb276ccce254fdd51575 Mon Sep 17 00:00:00 2001 From: Philipp Mildenberger Date: Mon, 27 Mar 2023 21:05:27 +0200 Subject: Fix 'WorkspaceConfiguration' request with empty configuration section strings --- helix-term/src/application.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'helix-term/src/application.rs') diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 45f99e48..53fbe37d 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -1058,8 +1058,11 @@ impl Application { .filter_map(|item| { let mut config = language_server.config()?; if let Some(section) = item.section.as_ref() { - for part in section.split('.') { - config = config.get(part)?; + // for some reason some lsps send an empty string (observed in 'vscode-eslint-language-server') + if !section.is_empty() { + for part in section.split('.') { + config = config.get(part)?; + } } } Some(config) -- cgit v1.2.3-70-g09d2 From 44b2b401907c501aca7e3f7340730406b8c3a1fb Mon Sep 17 00:00:00 2001 From: Philipp Mildenberger Date: Thu, 16 Mar 2023 00:35:06 +0100 Subject: Fix issue with ltex-ls, filtering params is not what we want here --- helix-term/src/application.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'helix-term/src/application.rs') diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 53fbe37d..e159cb83 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -1055,7 +1055,7 @@ impl Application { let result: Vec<_> = params .items .iter() - .filter_map(|item| { + .map(|item| { let mut config = language_server.config()?; if let Some(section) = item.section.as_ref() { // for some reason some lsps send an empty string (observed in 'vscode-eslint-language-server') -- cgit v1.2.3-70-g09d2 From f9b08656f41cbb9573ffb144f5dc2e24ea764ac9 Mon Sep 17 00:00:00 2001 From: Philipp Mildenberger Date: Fri, 17 Mar 2023 15:30:49 +0100 Subject: Fix sorting issues of the editor wide diagnostics and apply diagnostics related review suggestions Co-authored-by: Pascal Kuthe --- helix-term/src/application.rs | 21 +++++++++++---------- helix-term/src/commands/lsp.rs | 17 ++++++++++------- helix-term/src/ui/statusline.rs | 2 +- helix-view/src/editor.rs | 2 +- 4 files changed, 23 insertions(+), 19 deletions(-) (limited to 'helix-term/src/application.rs') diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index e159cb83..728aa46a 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -721,7 +721,7 @@ impl Application { )); } } - Notification::PublishDiagnostics(mut params) => { + Notification::PublishDiagnostics(params) => { let path = match params.uri.to_file_path() { Ok(path) => path, Err(_) => { @@ -841,15 +841,10 @@ impl Application { doc.replace_diagnostics(diagnostics, server_id); } - // Sort diagnostics first by severity and then by line numbers. - // Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order - params - .diagnostics - .sort_unstable_by_key(|d| (d.severity, d.range.start)); - let diagnostics = params + let mut diagnostics = params .diagnostics .into_iter() - .map(|d| (d, server_id, offset_encoding)) + .map(|d| (d, server_id)) .collect(); // Insert the original lsp::Diagnostics here because we may have no open document @@ -859,10 +854,16 @@ impl Application { Entry::Occupied(o) => { let current_diagnostics = o.into_mut(); // there may entries of other language servers, which is why we can't overwrite the whole entry - current_diagnostics.retain(|(_, lsp_id, _)| *lsp_id != server_id); - current_diagnostics.extend(diagnostics); + current_diagnostics.retain(|(_, lsp_id)| *lsp_id != server_id); + current_diagnostics.append(&mut diagnostics); + // Sort diagnostics first by severity and then by line numbers. + // Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order + current_diagnostics + .sort_unstable_by_key(|(d, _)| (d.severity, d.range.start)); } Entry::Vacant(v) => { + diagnostics + .sort_unstable_by_key(|(d, _)| (d.severity, d.range.start)); v.insert(diagnostics); } }; diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs index efef1211..1a1233a9 100644 --- a/helix-term/src/commands/lsp.rs +++ b/helix-term/src/commands/lsp.rs @@ -262,7 +262,7 @@ enum DiagnosticsFormat { fn diag_picker( cx: &Context, - diagnostics: BTreeMap>, + diagnostics: BTreeMap>, current_path: Option, format: DiagnosticsFormat, ) -> FilePicker { @@ -272,12 +272,15 @@ fn diag_picker( let mut flat_diag = Vec::new(); for (url, diags) in diagnostics { flat_diag.reserve(diags.len()); - for (diag, _, offset_encoding) in diags { - flat_diag.push(PickerDiagnostic { - url: url.clone(), - diag, - offset_encoding, - }); + + for (diag, ls) in diags { + if let Some(ls) = cx.editor.language_servers.get_by_id(ls) { + flat_diag.push(PickerDiagnostic { + url: url.clone(), + diag, + offset_encoding: ls.offset_encoding(), + }); + } } } diff --git a/helix-term/src/ui/statusline.rs b/helix-term/src/ui/statusline.rs index b10e8076..60997956 100644 --- a/helix-term/src/ui/statusline.rs +++ b/helix-term/src/ui/statusline.rs @@ -266,7 +266,7 @@ where .diagnostics .values() .flatten() - .fold((0, 0), |mut counts, (diag, _, _)| { + .fold((0, 0), |mut counts, (diag, _)| { match diag.severity { Some(DiagnosticSeverity::WARNING) => counts.0 += 1, Some(DiagnosticSeverity::ERROR) | None => counts.1 += 1, diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 697d4459..2bd48af8 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -818,7 +818,7 @@ pub struct Editor { pub macro_recording: Option<(char, Vec)>, pub macro_replaying: Vec, pub language_servers: helix_lsp::Registry, - pub diagnostics: BTreeMap>, + pub diagnostics: BTreeMap>, pub diff_providers: DiffProviderRegistry, pub debugger: Option, -- cgit v1.2.3-70-g09d2 From 76b5cab52479daf25ffa0af798c1ebcf6a4f0004 Mon Sep 17 00:00:00 2001 From: Philipp Mildenberger Date: Sat, 18 Mar 2023 20:12:20 +0100 Subject: 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 --- helix-core/src/syntax.rs | 97 ++++++++++++++++++++++++++++++++-------- helix-lsp/src/lib.rs | 20 ++++----- helix-term/src/application.rs | 8 ++-- helix-term/src/commands.rs | 11 ++--- helix-term/src/commands/lsp.rs | 55 +++++++++++------------ helix-term/src/commands/typed.rs | 24 +++++----- helix-term/src/health.rs | 8 ++-- helix-term/src/ui/mod.rs | 8 ++-- helix-term/src/ui/statusline.rs | 5 +-- helix-view/src/document.rs | 52 +++++---------------- helix-view/src/editor.rs | 5 ++- 11 files changed, 155 insertions(+), 138 deletions(-) (limited to 'helix-term/src/application.rs') 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>>, // tags_config OnceCell<> https://github.com/tree-sitter/tree-sitter/pull/583 - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub language_servers: Vec, + #[serde( + default, + skip_serializing_if = "HashMap::is_empty", + serialize_with = "serialize_lang_features", + deserialize_with = "deserialize_lang_features" + )] + pub language_servers: HashMap, #[serde(skip_serializing_if = "Option::is_none")] pub indent: Option, @@ -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, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - except_features: Vec, + #[serde(default, skip_serializing_if = "HashSet::is_empty")] + only_features: HashSet, + #[serde(default, skip_serializing_if = "HashSet::is_empty")] + except_features: HashSet, name: String, }, Simple(String), } +#[derive(Debug, Default)] +pub struct LanguageServerFeatures { + 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) + } +} + +fn deserialize_lang_features<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + let raw: Vec = 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( + map: &HashMap, + serializer: S, +) -> Result +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, } -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>> { 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>> { 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 = 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) -> Vec 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::>(); @@ -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 { 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(context: &mut RenderContext, write: F) where F: Fn(&mut RenderContext, String, Option