aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--helix-lsp/src/client.rs99
-rw-r--r--helix-term/src/commands.rs145
-rw-r--r--helix-term/src/commands/lsp.rs356
-rw-r--r--helix-term/src/ui/mod.rs9
-rw-r--r--helix-view/src/document.rs51
5 files changed, 349 insertions, 311 deletions
diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs
index 65c6954d..b1a73247 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::<lsp::request::CodeActionRequest>(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<impl Future<Output = Result<lsp::WorkspaceEdit>>> {
- 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 d602eaa2..749b0ecf 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<lsp::Range> = 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<lsp::Range> = 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<lsp::CompletionResponse> = 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 6a024bed..15f8d93d 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 <feature>" 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<lsp::DocumentSymbolResponse> = 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<lsp::DocumentSymbolResponse> = 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::<Option<Vec<lsp::SymbolInformation>>>(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::<Option<Vec<lsp::SymbolInformation>>>(json)?
+ .unwrap_or_default()
+ .into_iter()
+ .map(|symbol| SymbolInformationItem {
+ symbol,
+ offset_encoding,
+ })
+ .collect();
+
+ anyhow::Ok(response)
+ }
})
.collect();
@@ -1043,22 +1071,19 @@ where
F: Future<Output = helix_lsp::Result<serde_json::Value>> + '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<lsp::GotoDefinitionResponse>| {
- 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<lsp::GotoDefinitionResponse>| {
+ 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<Vec<lsp::Location>>| {
+ let items = response.unwrap_or_default();
+ goto_impl(editor, compositor, items, offset_encoding);
},
- ) {
- cx.callback(
- future,
- move |editor, compositor, response: Option<Vec<lsp::Location>>| {
- 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<lsp::PrepareRenameResponse>| {
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 6f7ed174..ec328ec5 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<Completion> {
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 bc81567e..f2f373aa 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<T, P>(
- &self,
- view_id: ViewId,
- feature: LanguageServerFeature,
- request_provider: P,
- ) -> Option<T>
- where
- P: Fn(&Client, OffsetEncoding, lsp::Position, lsp::TextDocumentIdentifier) -> Option<T>,
- {
- 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()
}