Filter language servers also by capabilities in `doc.language_servers_with_feature`

* Add `helix_lsp::client::Client::supports_feature(&self, LanguageServerFeature)`
* Extend `doc.language_servers_with_feature` to use this method as filter as well
* Add macro `language_server_with_feature!` to reduce boilerplate for non-mergeable language server requests (like goto-definition)
* Refactored most of the `find_map` code to use the either the macro or filter directly via `doc.language_servers_with_feature`
pull/2507/head
Philipp Mildenberger 2 years ago
parent 9d089c27c7
commit ff26208427

@ -4,7 +4,7 @@ use crate::{
Call, Error, OffsetEncoding, Result, 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 helix_loader::{self, VERSION_AND_GIT_HASH};
use lsp::{ use lsp::{
notification::DidChangeWorkspaceFolders, DidChangeWorkspaceFoldersParams, OneOf, notification::DidChangeWorkspaceFolders, DidChangeWorkspaceFoldersParams, OneOf,
@ -276,6 +276,93 @@ impl Client {
.expect("language server not yet initialized!") .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 { pub fn offset_encoding(&self) -> OffsetEncoding {
self.capabilities() self.capabilities()
.position_encoding .position_encoding
@ -1301,21 +1388,13 @@ impl Client {
Some(self.call::<lsp::request::CodeActionRequest>(params)) 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( pub fn rename_symbol(
&self, &self,
text_document: lsp::TextDocumentIdentifier, text_document: lsp::TextDocumentIdentifier,
position: lsp::Position, position: lsp::Position,
new_name: String, new_name: String,
) -> Option<impl Future<Output = Result<lsp::WorkspaceEdit>>> { ) -> Option<impl Future<Output = Result<lsp::WorkspaceEdit>>> {
if !self.supports_rename() { if !self.supports_feature(LanguageServerFeature::RenameSymbol) {
return None; return None;
} }

@ -3236,10 +3236,8 @@ pub mod insert {
let trigger_completion = doc let trigger_completion = doc
.language_servers_with_feature(LanguageServerFeature::Completion) .language_servers_with_feature(LanguageServerFeature::Completion)
.any(|ls| { .any(|ls| {
let capabilities = ls.capabilities();
// TODO: what if trigger is multiple chars long // 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), trigger_characters: Some(triggers),
.. ..
}) if triggers.iter().any(|trigger| trigger.contains(ch))) }) if triggers.iter().any(|trigger| trigger.contains(ch)))
@ -3252,22 +3250,20 @@ pub mod insert {
} }
fn signature_help(cx: &mut Context, ch: char) { fn signature_help(cx: &mut Context, ch: char) {
use futures_util::FutureExt;
use helix_lsp::lsp; use helix_lsp::lsp;
// if ch matches signature_help char, trigger // if ch matches signature_help char, trigger
let (view, doc) = current!(cx.editor); let doc = doc_mut!(cx.editor);
// lsp doesn't tell us when to close the signature help, so we request // TODO support multiple language servers (not just the first that is found), likely by merging UI somehow
// the help information again after common close triggers which should let Some(language_server) = doc
// 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
.language_servers_with_feature(LanguageServerFeature::SignatureHelp) .language_servers_with_feature(LanguageServerFeature::SignatureHelp)
.find_map(|ls| { .next()
let capabilities = ls.capabilities(); else {
return;
};
let capabilities = language_server.capabilities();
match capabilities { if let lsp::ServerCapabilities {
lsp::ServerCapabilities {
signature_help_provider: signature_help_provider:
Some(lsp::SignatureHelpOptions { Some(lsp::SignatureHelpOptions {
trigger_characters: Some(triggers), trigger_characters: Some(triggers),
@ -3275,28 +3271,18 @@ pub mod insert {
.. ..
}), }),
.. ..
} if triggers.iter().any(|trigger| trigger.contains(ch)) } = capabilities
|| 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 // TODO: what if trigger is multiple chars long
_ => None, 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 let Some(future) = future { if is_trigger || close_triggers.contains(&ch) {
super::signature_help_impl_with_future( super::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
cx, }
future.boxed(),
SignatureHelpInvoked::Automatic,
)
} }
} }
@ -3310,7 +3296,7 @@ pub mod insert {
Some(transaction) Some(transaction)
} }
use helix_core::{auto_pairs, syntax::LanguageServerFeature}; use helix_core::auto_pairs;
pub fn insert_char(cx: &mut Context, c: char) { pub fn insert_char(cx: &mut Context, c: char) {
let (view, doc) = current_ref!(cx.editor); let (view, doc) = current_ref!(cx.editor);
@ -4065,9 +4051,23 @@ fn format_selections(cx: &mut Context) {
.set_error("format_selections only supports a single selection for now"); .set_error("format_selections only supports a single selection for now");
return; 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) .language_servers_with_feature(LanguageServerFeature::Format)
.find_map(|language_server| { .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;
};
let offset_encoding = language_server.offset_encoding(); let offset_encoding = language_server.offset_encoding();
let ranges: Vec<lsp::Range> = doc let ranges: Vec<lsp::Range> = doc
.selection(view_id) .selection(view_id)
@ -4080,23 +4080,14 @@ fn format_selections(cx: &mut Context) {
let range = ranges[0]; let range = ranges[0];
let future = language_server.text_document_range_formatting( let future = language_server
.text_document_range_formatting(
doc.identifier(), doc.identifier(),
range, range,
lsp::FormattingOptions::default(), lsp::FormattingOptions::default(),
None, None,
)?; )
Some((future, offset_encoding)) .unwrap();
});
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 edits = tokio::task::block_in_place(|| helix_lsp::block_on(future)).unwrap_or_default(); 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 let mut futures: FuturesUnordered<_> = doc
.language_servers_with_feature(LanguageServerFeature::Completion) .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(|ls| seen_language_servers.insert(ls.id()))
.filter_map(|language_server| { .map(|language_server| {
let language_server_id = language_server.id(); let language_server_id = language_server.id();
let offset_encoding = language_server.offset_encoding(); let offset_encoding = language_server.offset_encoding();
let pos = pos_to_lsp_pos(doc.text(), cursor, 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 json = completion_request.await?;
let response: Option<lsp::CompletionResponse> = serde_json::from_value(json)?; let response: Option<lsp::CompletionResponse> = serde_json::from_value(json)?;
@ -4277,7 +4268,7 @@ pub fn completion(cx: &mut Context) {
.collect(); .collect();
anyhow::Ok(items) anyhow::Ok(items)
}) }
}) })
.collect(); .collect();

@ -45,6 +45,28 @@ use std::{
sync::Arc, 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 { impl ui::menu::Item for lsp::Location {
/// Current working directory. /// Current working directory.
type Data = PathBuf; type Data = PathBuf;
@ -361,11 +383,12 @@ pub fn symbol_picker(cx: &mut Context) {
let mut futures: FuturesUnordered<_> = doc let mut futures: FuturesUnordered<_> = doc
.language_servers_with_feature(LanguageServerFeature::DocumentSymbols) .language_servers_with_feature(LanguageServerFeature::DocumentSymbols)
.filter(|ls| seen_language_servers.insert(ls.id())) .filter(|ls| seen_language_servers.insert(ls.id()))
.filter_map(|ls| { .map(|language_server| {
let request = ls.document_symbols(doc.identifier())?; let request = language_server.document_symbols(doc.identifier()).unwrap();
Some((request, ls.offset_encoding(), doc.identifier())) let offset_encoding = language_server.offset_encoding();
}) let doc_id = doc.identifier();
.map(|(request, offset_encoding, doc_id)| async move {
async move {
let json = request.await?; let json = request.await?;
let response: Option<lsp::DocumentSymbolResponse> = serde_json::from_value(json)?; let response: Option<lsp::DocumentSymbolResponse> = serde_json::from_value(json)?;
let symbols = match response { let symbols = match response {
@ -391,6 +414,7 @@ pub fn symbol_picker(cx: &mut Context) {
} }
}; };
Ok(symbols) Ok(symbols)
}
}) })
.collect(); .collect();
let current_url = doc.url(); let current_url = doc.url();
@ -425,11 +449,14 @@ pub fn workspace_symbol_picker(cx: &mut Context) {
let mut futures: FuturesUnordered<_> = doc let mut futures: FuturesUnordered<_> = doc
.language_servers_with_feature(LanguageServerFeature::WorkspaceSymbols) .language_servers_with_feature(LanguageServerFeature::WorkspaceSymbols)
.filter(|ls| seen_language_servers.insert(ls.id())) .filter(|ls| seen_language_servers.insert(ls.id()))
.filter_map(|ls| Some((ls.workspace_symbols(pattern.clone())?, ls.offset_encoding()))) .map(|language_server| {
.map(|(request, offset_encoding)| async move { let request = language_server.workspace_symbols(pattern.clone()).unwrap();
let offset_encoding = language_server.offset_encoding();
async move {
let json = request.await?; let json = request.await?;
let response = serde_json::from_value::<Option<Vec<lsp::SymbolInformation>>>(json)? let response =
serde_json::from_value::<Option<Vec<lsp::SymbolInformation>>>(json)?
.unwrap_or_default() .unwrap_or_default()
.into_iter() .into_iter()
.map(|symbol| SymbolInformationItem { .map(|symbol| SymbolInformationItem {
@ -439,6 +466,7 @@ pub fn workspace_symbol_picker(cx: &mut Context) {
.collect(); .collect();
anyhow::Ok(response) anyhow::Ok(response)
}
}) })
.collect(); .collect();
@ -1043,11 +1071,12 @@ where
F: Future<Output = helix_lsp::Result<serde_json::Value>> + 'static + Send, F: Future<Output = helix_lsp::Result<serde_json::Value>> + 'static + Send,
{ {
let (view, doc) = current!(cx.editor); 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| { let language_server = language_server_with_feature!(cx.editor, doc, feature);
Some((request_provider(ls, pos, doc_id)?, encoding)) 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( cx.callback(
future, future,
move |editor, compositor, response: Option<lsp::GotoDefinitionResponse>| { move |editor, compositor, response: Option<lsp::GotoDefinitionResponse>| {
@ -1055,10 +1084,6 @@ where
goto_impl(editor, compositor, items, offset_encoding); goto_impl(editor, compositor, items, offset_encoding);
}, },
); );
} else {
cx.editor
.set_error("No configured language server supports {feature}");
}
} }
pub fn goto_declaration(cx: &mut Context) { pub fn goto_declaration(cx: &mut Context) {
@ -1096,21 +1121,22 @@ pub fn goto_implementation(cx: &mut Context) {
pub fn goto_reference(cx: &mut Context) { pub fn goto_reference(cx: &mut Context) {
let config = cx.editor.config(); let config = cx.editor.config();
let (view, doc) = current!(cx.editor); let (view, doc) = current!(cx.editor);
if let Some((future, offset_encoding)) = doc.run_on_first_supported_language_server(
view.id, // TODO could probably support multiple language servers,
LanguageServerFeature::GotoReference, // not sure if there's a real practical use case for this though
|ls, encoding, pos, doc_id| { let language_server =
Some(( language_server_with_feature!(cx.editor, doc, LanguageServerFeature::GotoReference);
ls.goto_reference( let offset_encoding = language_server.offset_encoding();
doc_id, let pos = doc.position(view.id, offset_encoding);
let future = language_server
.goto_reference(
doc.identifier(),
pos, pos,
config.lsp.goto_reference_include_declaration, config.lsp.goto_reference_include_declaration,
None, None,
)?, )
encoding, .unwrap();
))
},
) {
cx.callback( cx.callback(
future, future,
move |editor, compositor, response: Option<Vec<lsp::Location>>| { move |editor, compositor, response: Option<Vec<lsp::Location>>| {
@ -1118,10 +1144,6 @@ pub fn goto_reference(cx: &mut Context) {
goto_impl(editor, compositor, items, offset_encoding); goto_impl(editor, compositor, items, offset_encoding);
}, },
); );
} else {
cx.editor
.set_error("No configured language server supports goto-reference");
}
} }
#[derive(PartialEq, Eq, Clone, Copy)] #[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) language_server.text_document_signature_help(doc.identifier(), pos, None)
}); });
let future = match future { let Some(future) = future else {
Some(future) => future.boxed(),
None => {
// Do not show the message if signature help was invoked // Do not show the message if signature help was invoked
// automatically on backspace, trigger characters, etc. // automatically on backspace, trigger characters, etc.
if invoked == SignatureHelpInvoked::Manual { if invoked == SignatureHelpInvoked::Manual {
cx.editor cx.editor.set_error("No configured language server supports signature-help");
.set_error("No configured language server supports signature-help");
} }
return; 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( pub fn signature_help_impl_with_future(
@ -1272,22 +1290,14 @@ pub fn signature_help_impl_with_future(
pub fn hover(cx: &mut Context) { pub fn hover(cx: &mut Context) {
let (view, doc) = current!(cx.editor); 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 // 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()); let pos = doc.position(view.id, language_server.offset_encoding());
language_server.text_document_hover(doc.identifier(), pos, None) let future = language_server
}); .text_document_hover(doc.identifier(), pos, None)
.unwrap();
let future = match request {
Some(future) => future,
None => {
cx.editor
.set_error("No configured language server supports hover");
return;
}
};
cx.callback( cx.callback(
future, future,
@ -1381,35 +1391,27 @@ pub fn rename_symbol(cx: &mut Context) {
return; return;
} }
let (view, doc) = current!(cx.editor); let (view, doc) = current!(cx.editor);
let request = doc
let Some(language_server) = doc
.language_servers_with_feature(LanguageServerFeature::RenameSymbol) .language_servers_with_feature(LanguageServerFeature::RenameSymbol)
.find_map(|language_server| { .find(|ls| language_server_id.is_none() || Some(ls.id()) == language_server_id)
if let Some(language_server_id) = language_server_id { else {
if language_server.id() != language_server_id { cx.editor.set_error("No configured language server supports symbol renaming");
return None; return;
} };
}
let offset_encoding = language_server.offset_encoding(); let offset_encoding = language_server.offset_encoding();
let pos = doc.position(view.id, offset_encoding); let pos = doc.position(view.id, offset_encoding);
let future = language_server.rename_symbol( let future = language_server
doc.identifier(), .rename_symbol(doc.identifier(), pos, input.to_string())
pos, .unwrap();
input.to_string(),
)?;
Some((future, offset_encoding))
});
if let Some((future, offset_encoding)) = request {
match block_on(future) { match block_on(future) {
Ok(edits) => { Ok(edits) => {
let _ = apply_workspace_edit(cx.editor, offset_encoding, &edits); let _ = apply_workspace_edit(cx.editor, offset_encoding, &edits);
} }
Err(err) => cx.editor.set_error(err.to_string()), Err(err) => cx.editor.set_error(err.to_string()),
} }
} else {
cx.editor
.set_error("No configured language server supports symbol renaming");
}
}, },
) )
.with_line(prefill, editor); .with_line(prefill, editor);
@ -1417,20 +1419,28 @@ pub fn rename_symbol(cx: &mut Context) {
Box::new(prompt) 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) .language_servers_with_feature(LanguageServerFeature::RenameSymbol)
.find_map(|language_server| { .find(|ls| {
let offset_encoding = language_server.offset_encoding(); matches!(
let pos = doc.position(view.id, offset_encoding); ls.capabilities().rename_provider,
let future = language_server.prepare_rename(doc.identifier(), pos)?; Some(lsp::OneOf::Right(lsp::RenameOptions {
Some((future, offset_encoding, language_server.id())) prepare_provider: Some(true),
..
}))
)
}); });
match prepare_rename_request { if let Some(language_server) = language_server_with_prepare_rename_support {
// Language server supports textDocument/prepareRename, use it. let ls_id = language_server.id();
Some((future, offset_encoding, ls_id)) => cx.callback( 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, future,
move |editor, compositor, response: Option<lsp::PrepareRenameResponse>| { move |editor, compositor, response: Option<lsp::PrepareRenameResponse>| {
let prefill = match get_prefill_from_lsp_response(editor, offset_encoding, response) 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); compositor.push(prompt);
}, },
), );
// Language server does not support textDocument/prepareRename, fall back } else {
// to word boundary selection.
None => {
let prefill = get_prefill_from_word_boundary(cx.editor); let prefill = get_prefill_from_word_boundary(cx.editor);
let prompt = create_rename_prompt(cx.editor, prefill, None); let prompt = create_rename_prompt(cx.editor, prefill, None);
cx.push_layer(prompt); cx.push_layer(prompt);
} }
};
} }
pub fn select_references_to_symbol_under_cursor(cx: &mut Context) { pub fn select_references_to_symbol_under_cursor(cx: &mut Context) {
let (view, doc) = current!(cx.editor); let (view, doc) = current!(cx.editor);
let future_offset_encoding = doc let language_server =
.language_servers_with_feature(LanguageServerFeature::DocumentHighlight) language_server_with_feature!(cx.editor, doc, LanguageServerFeature::DocumentHighlight);
.find_map(|language_server| {
let offset_encoding = language_server.offset_encoding(); let offset_encoding = language_server.offset_encoding();
let pos = doc.position(view.id, offset_encoding); let pos = doc.position(view.id, offset_encoding);
let future = let future = language_server
language_server.text_document_document_highlight(doc.identifier(), pos, None)?; .text_document_document_highlight(doc.identifier(), pos, None)
Some((future, offset_encoding)) .unwrap();
});
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;
}
};
cx.callback( cx.callback(
future, future,
@ -1532,16 +1527,9 @@ fn compute_inlay_hints_for_view(
let view_id = view.id; let view_id = view.id;
let doc_id = view.doc; let doc_id = view.doc;
let mut language_servers = doc.language_servers_with_feature(LanguageServerFeature::InlayHints); let language_server = doc
let language_server = language_servers.find(|language_server| { .language_servers_with_feature(LanguageServerFeature::InlayHints)
matches!( .next()?;
language_server.capabilities().inlay_hint_provider,
Some(
lsp::OneOf::Left(true)
| lsp::OneOf::Right(lsp::InlayHintServerCapabilities::Options(_))
)
)
})?;
let doc_text = doc.text(); let doc_text = doc.text();
let len_lines = doc_text.len_lines(); let len_lines = doc_text.len_lines();

@ -394,14 +394,11 @@ pub mod completers {
pub fn lsp_workspace_command(editor: &Editor, input: &str) -> Vec<Completion> { pub fn lsp_workspace_command(editor: &Editor, input: &str) -> Vec<Completion> {
let matcher = Matcher::default(); let matcher = Matcher::default();
let options = match doc!(editor) let Some(options) = doc!(editor)
.language_servers_with_feature(LanguageServerFeature::WorkspaceCommand) .language_servers_with_feature(LanguageServerFeature::WorkspaceCommand)
.find_map(|ls| ls.capabilities().execute_command_provider.as_ref()) .find_map(|ls| ls.capabilities().execute_command_provider.as_ref())
{ else {
Some(options) => options,
None => {
return vec![]; return vec![];
}
}; };
let mut matches: Vec<_> = options let mut matches: Vec<_> = options

@ -580,7 +580,7 @@ where
*mut_ref = f(mem::take(mut_ref)); *mut_ref = f(mem::take(mut_ref));
} }
use helix_lsp::{lsp, Client, LanguageServerName, OffsetEncoding}; use helix_lsp::{lsp, Client, LanguageServerName};
use url::Url; use url::Url;
impl Document { impl Document {
@ -732,9 +732,9 @@ impl Document {
let text = self.text.clone(); let text = self.text.clone();
// finds first language server that supports formatting and then formats // 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) .language_servers_with_feature(LanguageServerFeature::Format)
.find_map(|language_server| { .next()?;
let offset_encoding = language_server.offset_encoding(); let offset_encoding = language_server.offset_encoding();
let request = language_server.text_document_formatting( let request = language_server.text_document_formatting(
self.identifier(), self.identifier(),
@ -745,8 +745,6 @@ impl Document {
}, },
None, None,
)?; )?;
Some((offset_encoding, request))
})?;
let fut = async move { let fut = async move {
let edits = request.await.unwrap_or_else(|e| { let edits = request.await.unwrap_or_else(|e| {
@ -1445,7 +1443,6 @@ impl Document {
self.language_servers.remove(name) self.language_servers.remove(name)
} }
// TODO filter also based on LSP capabilities?
pub fn language_servers_with_feature( pub fn language_servers_with_feature(
&self, &self,
feature: LanguageServerFeature, feature: LanguageServerFeature,
@ -1453,7 +1450,10 @@ impl Document {
self.language_config().into_iter().flat_map(move |config| { self.language_config().into_iter().flat_map(move |config| {
config.language_servers.iter().filter_map(move |features| { config.language_servers.iter().filter_map(move |features| {
let ls = &**self.language_servers.get(&features.name)?; 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) Some(ls)
} else { } else {
None None
@ -1466,23 +1466,6 @@ impl Document {
self.language_servers().any(|l| l.id() == id) 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> { pub fn diff_handle(&self) -> Option<&DiffHandle> {
self.diff_handle.as_ref() self.diff_handle.as_ref()
} }

Loading…
Cancel
Save