diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index 65c6954df..b1a73247a 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -4,7 +4,7 @@ use crate::{ Call, Error, OffsetEncoding, Result, }; -use helix_core::{find_workspace, path, ChangeSet, Rope}; +use helix_core::{find_workspace, path, syntax::LanguageServerFeature, ChangeSet, Rope}; use helix_loader::{self, VERSION_AND_GIT_HASH}; use lsp::{ notification::DidChangeWorkspaceFolders, DidChangeWorkspaceFoldersParams, OneOf, @@ -276,6 +276,93 @@ impl Client { .expect("language server not yet initialized!") } + #[inline] // TODO inline? + pub fn supports_feature(&self, feature: LanguageServerFeature) -> bool { + let capabilities = match self.capabilities.get() { + Some(capabilities) => capabilities, + None => return false, // not initialized, TODO unwrap/expect instead? + }; + match feature { + LanguageServerFeature::Format => matches!( + capabilities.document_formatting_provider, + Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_)) + ), + LanguageServerFeature::GotoDeclaration => matches!( + capabilities.declaration_provider, + Some( + lsp::DeclarationCapability::Simple(true) + | lsp::DeclarationCapability::RegistrationOptions(_) + | lsp::DeclarationCapability::Options(_), + ) + ), + LanguageServerFeature::GotoDefinition => matches!( + capabilities.definition_provider, + Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_)) + ), + LanguageServerFeature::GotoTypeDefinition => matches!( + capabilities.type_definition_provider, + Some( + lsp::TypeDefinitionProviderCapability::Simple(true) + | lsp::TypeDefinitionProviderCapability::Options(_), + ) + ), + LanguageServerFeature::GotoReference => matches!( + capabilities.references_provider, + Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_)) + ), + LanguageServerFeature::GotoImplementation => matches!( + capabilities.implementation_provider, + Some( + lsp::ImplementationProviderCapability::Simple(true) + | lsp::ImplementationProviderCapability::Options(_), + ) + ), + LanguageServerFeature::SignatureHelp => capabilities.signature_help_provider.is_some(), + LanguageServerFeature::Hover => matches!( + capabilities.hover_provider, + Some( + lsp::HoverProviderCapability::Simple(true) + | lsp::HoverProviderCapability::Options(_), + ) + ), + LanguageServerFeature::DocumentHighlight => matches!( + capabilities.document_highlight_provider, + Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_)) + ), + LanguageServerFeature::Completion => capabilities.completion_provider.is_some(), + LanguageServerFeature::CodeAction => matches!( + capabilities.code_action_provider, + Some( + lsp::CodeActionProviderCapability::Simple(true) + | lsp::CodeActionProviderCapability::Options(_), + ) + ), + LanguageServerFeature::WorkspaceCommand => { + capabilities.execute_command_provider.is_some() + } + LanguageServerFeature::DocumentSymbols => matches!( + capabilities.document_symbol_provider, + Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_)) + ), + LanguageServerFeature::WorkspaceSymbols => matches!( + capabilities.workspace_symbol_provider, + Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_)) + ), + LanguageServerFeature::Diagnostics => true, // there's no extra server capability + LanguageServerFeature::RenameSymbol => matches!( + capabilities.rename_provider, + Some(lsp::OneOf::Left(true)) | Some(lsp::OneOf::Right(_)) + ), + LanguageServerFeature::InlayHints => matches!( + capabilities.inlay_hint_provider, + Some( + lsp::OneOf::Left(true) + | lsp::OneOf::Right(lsp::InlayHintServerCapabilities::Options(_)) + ) + ), + } + } + pub fn offset_encoding(&self) -> OffsetEncoding { self.capabilities() .position_encoding @@ -1301,21 +1388,13 @@ impl Client { Some(self.call::(params)) } - pub fn supports_rename(&self) -> bool { - let capabilities = self.capabilities.get().unwrap(); - matches!( - capabilities.rename_provider, - Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_)) - ) - } - pub fn rename_symbol( &self, text_document: lsp::TextDocumentIdentifier, position: lsp::Position, new_name: String, ) -> Option>> { - if !self.supports_rename() { + if !self.supports_feature(LanguageServerFeature::RenameSymbol) { return None; } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index d602eaa20..749b0ecf8 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3236,10 +3236,8 @@ pub mod insert { let trigger_completion = doc .language_servers_with_feature(LanguageServerFeature::Completion) .any(|ls| { - let capabilities = ls.capabilities(); - // TODO: what if trigger is multiple chars long - matches!(&capabilities.completion_provider, Some(lsp::CompletionOptions { + matches!(&ls.capabilities().completion_provider, Some(lsp::CompletionOptions { trigger_characters: Some(triggers), .. }) if triggers.iter().any(|trigger| trigger.contains(ch))) @@ -3252,51 +3250,39 @@ pub mod insert { } fn signature_help(cx: &mut Context, ch: char) { - use futures_util::FutureExt; use helix_lsp::lsp; // if ch matches signature_help char, trigger - let (view, doc) = current!(cx.editor); - // lsp doesn't tell us when to close the signature help, so we request - // the help information again after common close triggers which should - // return None, which in turn closes the popup. - let close_triggers = &[')', ';', '.']; - // TODO support multiple language servers (not just the first that is found) - let future = doc + let doc = doc_mut!(cx.editor); + // TODO support multiple language servers (not just the first that is found), likely by merging UI somehow + let Some(language_server) = doc .language_servers_with_feature(LanguageServerFeature::SignatureHelp) - .find_map(|ls| { - let capabilities = ls.capabilities(); - - match capabilities { - lsp::ServerCapabilities { - signature_help_provider: - Some(lsp::SignatureHelpOptions { - trigger_characters: Some(triggers), - // TODO: retrigger_characters - .. - }), - .. - } if triggers.iter().any(|trigger| trigger.contains(ch)) - || close_triggers.contains(&ch) => - { - let pos = doc.position(view.id, ls.offset_encoding()); - ls.text_document_signature_help(doc.identifier(), pos, None) - } - _ if close_triggers.contains(&ch) => ls.text_document_signature_help( - doc.identifier(), - doc.position(view.id, ls.offset_encoding()), - None, - ), - // TODO: what if trigger is multiple chars long - _ => None, - } - }); + .next() + else { + return; + }; - if let Some(future) = future { - super::signature_help_impl_with_future( - cx, - future.boxed(), - SignatureHelpInvoked::Automatic, - ) + let capabilities = language_server.capabilities(); + + if let lsp::ServerCapabilities { + signature_help_provider: + Some(lsp::SignatureHelpOptions { + trigger_characters: Some(triggers), + // TODO: retrigger_characters + .. + }), + .. + } = capabilities + { + // TODO: what if trigger is multiple chars long + let is_trigger = triggers.iter().any(|trigger| trigger.contains(ch)); + // lsp doesn't tell us when to close the signature help, so we request + // the help information again after common close triggers which should + // return None, which in turn closes the popup. + let close_triggers = &[')', ';', '.']; + + if is_trigger || close_triggers.contains(&ch) { + super::signature_help_impl(cx, SignatureHelpInvoked::Automatic); + } } } @@ -3310,7 +3296,7 @@ pub mod insert { Some(transaction) } - use helix_core::{auto_pairs, syntax::LanguageServerFeature}; + use helix_core::auto_pairs; pub fn insert_char(cx: &mut Context, c: char) { let (view, doc) = current_ref!(cx.editor); @@ -4065,38 +4051,43 @@ fn format_selections(cx: &mut Context) { .set_error("format_selections only supports a single selection for now"); return; } - let future_offset_encoding = doc + + // TODO extra LanguageServerFeature::FormatSelections? + // maybe such that LanguageServerFeature::Format contains it as well + let Some(language_server) = doc .language_servers_with_feature(LanguageServerFeature::Format) - .find_map(|language_server| { - let offset_encoding = language_server.offset_encoding(); - let ranges: Vec = doc - .selection(view_id) - .iter() - .map(|range| range_to_lsp_range(doc.text(), *range, offset_encoding)) - .collect(); + .find(|ls| { + matches!( + ls.capabilities().document_range_formatting_provider, + Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_)) + ) + }) + else { + cx.editor + .set_error("No configured language server does not support range formatting"); + return; + }; - // TODO: handle fails - // TODO: concurrent map over all ranges + let offset_encoding = language_server.offset_encoding(); + let ranges: Vec = doc + .selection(view_id) + .iter() + .map(|range| range_to_lsp_range(doc.text(), *range, offset_encoding)) + .collect(); - let range = ranges[0]; + // TODO: handle fails + // TODO: concurrent map over all ranges - let future = language_server.text_document_range_formatting( - doc.identifier(), - range, - lsp::FormattingOptions::default(), - None, - )?; - Some((future, offset_encoding)) - }); + let range = ranges[0]; - let (future, offset_encoding) = match future_offset_encoding { - Some(future_offset_encoding) => future_offset_encoding, - None => { - cx.editor - .set_error("No configured language server supports range formatting"); - return; - } - }; + let future = language_server + .text_document_range_formatting( + doc.identifier(), + range, + lsp::FormattingOptions::default(), + None, + ) + .unwrap(); let edits = tokio::task::block_in_place(|| helix_lsp::block_on(future)).unwrap_or_default(); @@ -4247,15 +4238,15 @@ pub fn completion(cx: &mut Context) { let mut futures: FuturesUnordered<_> = doc .language_servers_with_feature(LanguageServerFeature::Completion) - // TODO this should probably already been filtered in something like "language_servers_with_feature" .filter(|ls| seen_language_servers.insert(ls.id())) - .filter_map(|language_server| { + .map(|language_server| { let language_server_id = language_server.id(); let offset_encoding = language_server.offset_encoding(); let pos = pos_to_lsp_pos(doc.text(), cursor, offset_encoding); - let completion_request = language_server.completion(doc.identifier(), pos, None)?; + let doc_id = doc.identifier(); + let completion_request = language_server.completion(doc_id, pos, None).unwrap(); - Some(async move { + async move { let json = completion_request.await?; let response: Option = serde_json::from_value(json)?; @@ -4277,7 +4268,7 @@ pub fn completion(cx: &mut Context) { .collect(); anyhow::Ok(items) - }) + } }) .collect(); diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs index 6a024beda..15f8d93d8 100644 --- a/helix-term/src/commands/lsp.rs +++ b/helix-term/src/commands/lsp.rs @@ -45,6 +45,28 @@ use std::{ sync::Arc, }; +/// Gets the first language server that is attached to a document which supports a specific feature. +/// If there is no configured language server that supports the feature, this displays a status message. +/// Using this macro in a context where the editor automatically queries the LSP +/// (instead of when the user explicitly does so via a keybind like `gd`) +/// will spam the "No configured language server supports " status message confusingly. +#[macro_export] +macro_rules! language_server_with_feature { + ($editor:expr, $doc:expr, $feature:expr) => {{ + let language_server = $doc.language_servers_with_feature($feature).next(); + match language_server { + Some(language_server) => language_server, + None => { + $editor.set_status(format!( + "No configured language server supports {}", + $feature + )); + return; + } + } + }}; +} + impl ui::menu::Item for lsp::Location { /// Current working directory. type Data = PathBuf; @@ -361,36 +383,38 @@ pub fn symbol_picker(cx: &mut Context) { let mut futures: FuturesUnordered<_> = doc .language_servers_with_feature(LanguageServerFeature::DocumentSymbols) .filter(|ls| seen_language_servers.insert(ls.id())) - .filter_map(|ls| { - let request = ls.document_symbols(doc.identifier())?; - Some((request, ls.offset_encoding(), doc.identifier())) - }) - .map(|(request, offset_encoding, doc_id)| async move { - let json = request.await?; - let response: Option = serde_json::from_value(json)?; - let symbols = match response { - Some(symbols) => symbols, - None => return anyhow::Ok(vec![]), - }; - // lsp has two ways to represent symbols (flat/nested) - // convert the nested variant to flat, so that we have a homogeneous list - let symbols = match symbols { - lsp::DocumentSymbolResponse::Flat(symbols) => symbols - .into_iter() - .map(|symbol| SymbolInformationItem { - symbol, - offset_encoding, - }) - .collect(), - lsp::DocumentSymbolResponse::Nested(symbols) => { - let mut flat_symbols = Vec::new(); - for symbol in symbols { - nested_to_flat(&mut flat_symbols, &doc_id, symbol, offset_encoding) + .map(|language_server| { + let request = language_server.document_symbols(doc.identifier()).unwrap(); + let offset_encoding = language_server.offset_encoding(); + let doc_id = doc.identifier(); + + async move { + let json = request.await?; + let response: Option = serde_json::from_value(json)?; + let symbols = match response { + Some(symbols) => symbols, + None => return anyhow::Ok(vec![]), + }; + // lsp has two ways to represent symbols (flat/nested) + // convert the nested variant to flat, so that we have a homogeneous list + let symbols = match symbols { + lsp::DocumentSymbolResponse::Flat(symbols) => symbols + .into_iter() + .map(|symbol| SymbolInformationItem { + symbol, + offset_encoding, + }) + .collect(), + lsp::DocumentSymbolResponse::Nested(symbols) => { + let mut flat_symbols = Vec::new(); + for symbol in symbols { + nested_to_flat(&mut flat_symbols, &doc_id, symbol, offset_encoding) + } + flat_symbols } - flat_symbols - } - }; - Ok(symbols) + }; + Ok(symbols) + } }) .collect(); let current_url = doc.url(); @@ -425,20 +449,24 @@ pub fn workspace_symbol_picker(cx: &mut Context) { let mut futures: FuturesUnordered<_> = doc .language_servers_with_feature(LanguageServerFeature::WorkspaceSymbols) .filter(|ls| seen_language_servers.insert(ls.id())) - .filter_map(|ls| Some((ls.workspace_symbols(pattern.clone())?, ls.offset_encoding()))) - .map(|(request, offset_encoding)| async move { - let json = request.await?; - - let response = serde_json::from_value::>>(json)? - .unwrap_or_default() - .into_iter() - .map(|symbol| SymbolInformationItem { - symbol, - offset_encoding, - }) - .collect(); - - anyhow::Ok(response) + .map(|language_server| { + let request = language_server.workspace_symbols(pattern.clone()).unwrap(); + let offset_encoding = language_server.offset_encoding(); + async move { + let json = request.await?; + + let response = + serde_json::from_value::>>(json)? + .unwrap_or_default() + .into_iter() + .map(|symbol| SymbolInformationItem { + symbol, + offset_encoding, + }) + .collect(); + + anyhow::Ok(response) + } }) .collect(); @@ -1043,22 +1071,19 @@ where F: Future> + 'static + Send, { let (view, doc) = current!(cx.editor); - if let Some((future, offset_encoding)) = - doc.run_on_first_supported_language_server(view.id, feature, |ls, encoding, pos, doc_id| { - Some((request_provider(ls, pos, doc_id)?, encoding)) - }) - { - cx.callback( - future, - move |editor, compositor, response: Option| { - let items = to_locations(response); - goto_impl(editor, compositor, items, offset_encoding); - }, - ); - } else { - cx.editor - .set_error("No configured language server supports {feature}"); - } + + let language_server = language_server_with_feature!(cx.editor, doc, feature); + let offset_encoding = language_server.offset_encoding(); + let pos = doc.position(view.id, offset_encoding); + let future = request_provider(language_server, pos, doc.identifier()).unwrap(); + + cx.callback( + future, + move |editor, compositor, response: Option| { + let items = to_locations(response); + goto_impl(editor, compositor, items, offset_encoding); + }, + ); } pub fn goto_declaration(cx: &mut Context) { @@ -1096,32 +1121,29 @@ 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); - if let Some((future, offset_encoding)) = doc.run_on_first_supported_language_server( - view.id, - LanguageServerFeature::GotoReference, - |ls, encoding, pos, doc_id| { - Some(( - ls.goto_reference( - doc_id, - pos, - config.lsp.goto_reference_include_declaration, - None, - )?, - encoding, - )) + + // TODO could probably support multiple language servers, + // not sure if there's a real practical use case for this though + let language_server = + language_server_with_feature!(cx.editor, doc, LanguageServerFeature::GotoReference); + let offset_encoding = language_server.offset_encoding(); + let pos = doc.position(view.id, offset_encoding); + let future = language_server + .goto_reference( + doc.identifier(), + pos, + config.lsp.goto_reference_include_declaration, + None, + ) + .unwrap(); + + cx.callback( + future, + move |editor, compositor, response: Option>| { + let items = response.unwrap_or_default(); + goto_impl(editor, compositor, items, offset_encoding); }, - ) { - cx.callback( - future, - move |editor, compositor, response: Option>| { - let items = response.unwrap_or_default(); - goto_impl(editor, compositor, items, offset_encoding); - }, - ); - } else { - cx.editor - .set_error("No configured language server supports goto-reference"); - } + ); } #[derive(PartialEq, Eq, Clone, Copy)] @@ -1145,19 +1167,15 @@ pub fn signature_help_impl(cx: &mut Context, invoked: SignatureHelpInvoked) { 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 - // automatically on backspace, trigger characters, etc. - if invoked == SignatureHelpInvoked::Manual { - cx.editor - .set_error("No configured language server supports signature-help"); - } - return; + let Some(future) = future else { + // Do not show the message if signature help was invoked + // automatically on backspace, trigger characters, etc. + if invoked == SignatureHelpInvoked::Manual { + cx.editor.set_error("No configured language server supports signature-help"); } + return; }; - signature_help_impl_with_future(cx, future, invoked); + signature_help_impl_with_future(cx, future.boxed(), invoked); } pub fn signature_help_impl_with_future( @@ -1272,22 +1290,14 @@ pub fn signature_help_impl_with_future( pub fn hover(cx: &mut Context) { let (view, doc) = current!(cx.editor); + // TODO support multiple language servers (merge UI somehow) + let language_server = + language_server_with_feature!(cx.editor, doc, LanguageServerFeature::Hover); // TODO: factor out a doc.position_identifier() that returns lsp::TextDocumentPositionIdentifier - let request = doc - .language_servers_with_feature(LanguageServerFeature::Hover) - .find_map(|language_server| { - let pos = doc.position(view.id, language_server.offset_encoding()); - language_server.text_document_hover(doc.identifier(), pos, None) - }); - - let future = match request { - Some(future) => future, - None => { - cx.editor - .set_error("No configured language server supports hover"); - return; - } - }; + let pos = doc.position(view.id, language_server.offset_encoding()); + let future = language_server + .text_document_hover(doc.identifier(), pos, None) + .unwrap(); cx.callback( future, @@ -1381,34 +1391,26 @@ pub fn rename_symbol(cx: &mut Context) { return; } let (view, doc) = current!(cx.editor); - let request = doc + + let Some(language_server) = doc .language_servers_with_feature(LanguageServerFeature::RenameSymbol) - .find_map(|language_server| { - if let Some(language_server_id) = language_server_id { - if language_server.id() != language_server_id { - return None; - } - } - let offset_encoding = language_server.offset_encoding(); - let pos = doc.position(view.id, offset_encoding); - let future = language_server.rename_symbol( - doc.identifier(), - pos, - input.to_string(), - )?; - Some((future, offset_encoding)) - }); - - if let Some((future, offset_encoding)) = request { - match block_on(future) { - Ok(edits) => { - let _ = apply_workspace_edit(cx.editor, offset_encoding, &edits); - } - Err(err) => cx.editor.set_error(err.to_string()), + .find(|ls| language_server_id.is_none() || Some(ls.id()) == language_server_id) + else { + cx.editor.set_error("No configured language server supports symbol renaming"); + return; + }; + + let offset_encoding = language_server.offset_encoding(); + let pos = doc.position(view.id, offset_encoding); + let future = language_server + .rename_symbol(doc.identifier(), pos, input.to_string()) + .unwrap(); + + match block_on(future) { + Ok(edits) => { + let _ = apply_workspace_edit(cx.editor, offset_encoding, &edits); } - } else { - cx.editor - .set_error("No configured language server supports symbol renaming"); + Err(err) => cx.editor.set_error(err.to_string()), } }, ) @@ -1417,20 +1419,28 @@ pub fn rename_symbol(cx: &mut Context) { Box::new(prompt) } - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); - let prepare_rename_request = doc + let language_server_with_prepare_rename_support = doc .language_servers_with_feature(LanguageServerFeature::RenameSymbol) - .find_map(|language_server| { - let offset_encoding = language_server.offset_encoding(); - let pos = doc.position(view.id, offset_encoding); - let future = language_server.prepare_rename(doc.identifier(), pos)?; - Some((future, offset_encoding, language_server.id())) + .find(|ls| { + matches!( + ls.capabilities().rename_provider, + Some(lsp::OneOf::Right(lsp::RenameOptions { + prepare_provider: Some(true), + .. + })) + ) }); - match prepare_rename_request { - // Language server supports textDocument/prepareRename, use it. - Some((future, offset_encoding, ls_id)) => cx.callback( + if let Some(language_server) = language_server_with_prepare_rename_support { + let ls_id = language_server.id(); + let offset_encoding = language_server.offset_encoding(); + let pos = doc.position(view.id, offset_encoding); + let future = language_server + .prepare_rename(doc.identifier(), pos) + .unwrap(); + cx.callback( future, move |editor, compositor, response: Option| { let prefill = match get_prefill_from_lsp_response(editor, offset_encoding, response) @@ -1446,38 +1456,23 @@ pub fn rename_symbol(cx: &mut Context) { compositor.push(prompt); }, - ), - // Language server does not support textDocument/prepareRename, fall back - // to word boundary selection. - None => { - let prefill = get_prefill_from_word_boundary(cx.editor); - - let prompt = create_rename_prompt(cx.editor, prefill, None); - - cx.push_layer(prompt); - } - }; + ); + } else { + let prefill = get_prefill_from_word_boundary(cx.editor); + let prompt = create_rename_prompt(cx.editor, prefill, None); + cx.push_layer(prompt); + } } pub fn select_references_to_symbol_under_cursor(cx: &mut Context) { let (view, doc) = current!(cx.editor); - let future_offset_encoding = doc - .language_servers_with_feature(LanguageServerFeature::DocumentHighlight) - .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)) - }); - let (future, offset_encoding) = match future_offset_encoding { - Some(future_offset_encoding) => future_offset_encoding, - None => { - cx.editor - .set_error("No configured language server supports document-highlight"); - return; - } - }; + let language_server = + language_server_with_feature!(cx.editor, doc, LanguageServerFeature::DocumentHighlight); + 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) + .unwrap(); cx.callback( future, @@ -1532,16 +1527,9 @@ fn compute_inlay_hints_for_view( let view_id = view.id; let doc_id = view.doc; - 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( - lsp::OneOf::Left(true) - | lsp::OneOf::Right(lsp::InlayHintServerCapabilities::Options(_)) - ) - ) - })?; + let language_server = doc + .language_servers_with_feature(LanguageServerFeature::InlayHints) + .next()?; let doc_text = doc.text(); let len_lines = doc_text.len_lines(); diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 6f7ed1748..ec328ec55 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -394,14 +394,11 @@ pub mod completers { pub fn lsp_workspace_command(editor: &Editor, input: &str) -> Vec { let matcher = Matcher::default(); - let options = match doc!(editor) + let Some(options) = doc!(editor) .language_servers_with_feature(LanguageServerFeature::WorkspaceCommand) .find_map(|ls| ls.capabilities().execute_command_provider.as_ref()) - { - Some(options) => options, - None => { - return vec![]; - } + else { + return vec![]; }; let mut matches: Vec<_> = options diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index bc81567ef..f2f373aac 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -580,7 +580,7 @@ where *mut_ref = f(mem::take(mut_ref)); } -use helix_lsp::{lsp, Client, LanguageServerName, OffsetEncoding}; +use helix_lsp::{lsp, Client, LanguageServerName}; use url::Url; impl Document { @@ -732,21 +732,19 @@ impl Document { let text = self.text.clone(); // finds first language server that supports formatting and then formats - let (offset_encoding, request) = self + let language_server = self .language_servers_with_feature(LanguageServerFeature::Format) - .find_map(|language_server| { - let offset_encoding = language_server.offset_encoding(); - let request = language_server.text_document_formatting( - self.identifier(), - lsp::FormattingOptions { - tab_size: self.tab_width() as u32, - insert_spaces: matches!(self.indent_style, IndentStyle::Spaces(_)), - ..Default::default() - }, - None, - )?; - Some((offset_encoding, request)) - })?; + .next()?; + let offset_encoding = language_server.offset_encoding(); + let request = language_server.text_document_formatting( + self.identifier(), + lsp::FormattingOptions { + tab_size: self.tab_width() as u32, + insert_spaces: matches!(self.indent_style, IndentStyle::Spaces(_)), + ..Default::default() + }, + None, + )?; let fut = async move { let edits = request.await.unwrap_or_else(|e| { @@ -1445,7 +1443,6 @@ impl Document { self.language_servers.remove(name) } - // TODO filter also based on LSP capabilities? pub fn language_servers_with_feature( &self, feature: LanguageServerFeature, @@ -1453,7 +1450,10 @@ impl Document { 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) { + if ls.is_initialized() + && ls.supports_feature(feature) + && features.has_feature(feature) + { Some(ls) } else { None @@ -1466,23 +1466,6 @@ impl Document { self.language_servers().any(|l| l.id() == id) } - pub fn run_on_first_supported_language_server( - &self, - view_id: ViewId, - feature: LanguageServerFeature, - request_provider: P, - ) -> Option - where - P: Fn(&Client, OffsetEncoding, lsp::Position, lsp::TextDocumentIdentifier) -> Option, - { - self.language_servers_with_feature(feature) - .find_map(|language_server| { - let offset_encoding = language_server.offset_encoding(); - let pos = self.position(view_id, offset_encoding); - request_provider(language_server, offset_encoding, pos, self.identifier()) - }) - } - pub fn diff_handle(&self) -> Option<&DiffHandle> { self.diff_handle.as_ref() }