on master help sign in
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`

commitff262084271492bba239dbc2e5788be3c4d5a4e5 authorPhilipp Mildenberger <philipp@mildenberger.me> dateMon 20 Mar 2023, 17:44 (4 years ago) parent9d089c2
5 files changed+349−311
helix-lsp/src/client.rs+89−10 helix-term/src/commands.rs+68−77 helix-term/src/commands/lsp.rs+172−184 helix-term/src/ui/mod.rs+3−6 helix-view/src/document.rs+17−34
helix-lsp/src/client.rs+89−10
@@ -4,7 +4,7 @@ use crate::{
44 Call, Error, OffsetEncoding, Result,
55};
66
7−use helix_core::{find_workspace, path, ChangeSet, Rope};
7+use helix_core::{find_workspace, path, syntax::LanguageServerFeature, ChangeSet, Rope};
88use helix_loader::{self, VERSION_AND_GIT_HASH};
99use lsp::{
1010 notification::DidChangeWorkspaceFolders, DidChangeWorkspaceFoldersParams, OneOf,
@@ -276,6 +276,93 @@ impl Client {
276276 .expect("language server not yet initialized!")
277277 }
278278
279+ #[inline] // TODO inline?
280+ pub fn supports_feature(&self, feature: LanguageServerFeature) -> bool {
281+ let capabilities = match self.capabilities.get() {
282+ Some(capabilities) => capabilities,
283+ None => return false, // not initialized, TODO unwrap/expect instead?
284+ };
285+ match feature {
286+ LanguageServerFeature::Format => matches!(
287+ capabilities.document_formatting_provider,
288+ Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_))
289+ ),
290+ LanguageServerFeature::GotoDeclaration => matches!(
291+ capabilities.declaration_provider,
292+ Some(
293+ lsp::DeclarationCapability::Simple(true)
294+ | lsp::DeclarationCapability::RegistrationOptions(_)
295+ | lsp::DeclarationCapability::Options(_),
296+ )
297+ ),
298+ LanguageServerFeature::GotoDefinition => matches!(
299+ capabilities.definition_provider,
300+ Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_))
301+ ),
302+ LanguageServerFeature::GotoTypeDefinition => matches!(
303+ capabilities.type_definition_provider,
304+ Some(
305+ lsp::TypeDefinitionProviderCapability::Simple(true)
306+ | lsp::TypeDefinitionProviderCapability::Options(_),
307+ )
308+ ),
309+ LanguageServerFeature::GotoReference => matches!(
310+ capabilities.references_provider,
311+ Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_))
312+ ),
313+ LanguageServerFeature::GotoImplementation => matches!(
314+ capabilities.implementation_provider,
315+ Some(
316+ lsp::ImplementationProviderCapability::Simple(true)
317+ | lsp::ImplementationProviderCapability::Options(_),
318+ )
319+ ),
320+ LanguageServerFeature::SignatureHelp => capabilities.signature_help_provider.is_some(),
321+ LanguageServerFeature::Hover => matches!(
322+ capabilities.hover_provider,
323+ Some(
324+ lsp::HoverProviderCapability::Simple(true)
325+ | lsp::HoverProviderCapability::Options(_),
326+ )
327+ ),
328+ LanguageServerFeature::DocumentHighlight => matches!(
329+ capabilities.document_highlight_provider,
330+ Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_))
331+ ),
332+ LanguageServerFeature::Completion => capabilities.completion_provider.is_some(),
333+ LanguageServerFeature::CodeAction => matches!(
334+ capabilities.code_action_provider,
335+ Some(
336+ lsp::CodeActionProviderCapability::Simple(true)
337+ | lsp::CodeActionProviderCapability::Options(_),
338+ )
339+ ),
340+ LanguageServerFeature::WorkspaceCommand => {
341+ capabilities.execute_command_provider.is_some()
342+ }
343+ LanguageServerFeature::DocumentSymbols => matches!(
344+ capabilities.document_symbol_provider,
345+ Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_))
346+ ),
347+ LanguageServerFeature::WorkspaceSymbols => matches!(
348+ capabilities.workspace_symbol_provider,
349+ Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_))
350+ ),
351+ LanguageServerFeature::Diagnostics => true, // there's no extra server capability
352+ LanguageServerFeature::RenameSymbol => matches!(
353+ capabilities.rename_provider,
354+ Some(lsp::OneOf::Left(true)) | Some(lsp::OneOf::Right(_))
355+ ),
356+ LanguageServerFeature::InlayHints => matches!(
357+ capabilities.inlay_hint_provider,
358+ Some(
359+ lsp::OneOf::Left(true)
360+ | lsp::OneOf::Right(lsp::InlayHintServerCapabilities::Options(_))
361+ )
362+ ),
363+ }
364+ }
365+
279366 pub fn offset_encoding(&self) -> OffsetEncoding {
280367 self.capabilities()
281368 .position_encoding
@@ -1301,21 +1388,13 @@ impl Client {
13011388 Some(self.call::<lsp::request::CodeActionRequest>(params))
13021389 }
13031390
1304− pub fn supports_rename(&self) -> bool {
1305− let capabilities = self.capabilities.get().unwrap();
1306− matches!(
1307− capabilities.rename_provider,
1308− Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_))
1309− )
1310− }
1311−
13121391 pub fn rename_symbol(
13131392 &self,
13141393 text_document: lsp::TextDocumentIdentifier,
13151394 position: lsp::Position,
13161395 new_name: String,
13171396 ) -> Option<impl Future<Output = Result<lsp::WorkspaceEdit>>> {
1318− if !self.supports_rename() {
1397+ if !self.supports_feature(LanguageServerFeature::RenameSymbol) {
13191398 return None;
13201399 }
13211400
helix-term/src/commands.rs+68−77
@@ -3236,10 +3236,8 @@ pub mod insert {
32363236 let trigger_completion = doc
32373237 .language_servers_with_feature(LanguageServerFeature::Completion)
32383238 .any(|ls| {
3239− let capabilities = ls.capabilities();
3240−
32413239 // TODO: what if trigger is multiple chars long
3242− matches!(&capabilities.completion_provider, Some(lsp::CompletionOptions {
3240+ matches!(&ls.capabilities().completion_provider, Some(lsp::CompletionOptions {
32433241 trigger_characters: Some(triggers),
32443242 ..
32453243 }) if triggers.iter().any(|trigger| trigger.contains(ch)))
@@ -3252,51 +3250,39 @@ pub mod insert {
32523250 }
32533251
32543252 fn signature_help(cx: &mut Context, ch: char) {
3255− use futures_util::FutureExt;
32563253 use helix_lsp::lsp;
32573254 // if ch matches signature_help char, trigger
3258− let (view, doc) = current!(cx.editor);
3259− // lsp doesn't tell us when to close the signature help, so we request
3260− // the help information again after common close triggers which should
3261− // return None, which in turn closes the popup.
3262− let close_triggers = &[')', ';', '.'];
3263− // TODO support multiple language servers (not just the first that is found)
3264− let future = doc
3255+ let doc = doc_mut!(cx.editor);
3256+ // TODO support multiple language servers (not just the first that is found), likely by merging UI somehow
3257+ let Some(language_server) = doc
32653258 .language_servers_with_feature(LanguageServerFeature::SignatureHelp)
3266− .find_map(|ls| {
3267− let capabilities = ls.capabilities();
3268−
3269− match capabilities {
3270− lsp::ServerCapabilities {
3271− signature_help_provider:
3272− Some(lsp::SignatureHelpOptions {
3273− trigger_characters: Some(triggers),
3274− // TODO: retrigger_characters
3275− ..
3276− }),
3277− ..
3278− } if triggers.iter().any(|trigger| trigger.contains(ch))
3279− || close_triggers.contains(&ch) =>
3280− {
3281− let pos = doc.position(view.id, ls.offset_encoding());
3282− ls.text_document_signature_help(doc.identifier(), pos, None)
3283− }
3284− _ if close_triggers.contains(&ch) => ls.text_document_signature_help(
3285− doc.identifier(),
3286− doc.position(view.id, ls.offset_encoding()),
3287− None,
3288− ),
3289− // TODO: what if trigger is multiple chars long
3290− _ => None,
3291− }
3292− });
3259+ .next()
3260+ else {
3261+ return;
3262+ };
32933263
3294− if let Some(future) = future {
3295− super::signature_help_impl_with_future(
3296− cx,
3297− future.boxed(),
3298− SignatureHelpInvoked::Automatic,
3299− )
3264+ let capabilities = language_server.capabilities();
3265+
3266+ if let lsp::ServerCapabilities {
3267+ signature_help_provider:
3268+ Some(lsp::SignatureHelpOptions {
3269+ trigger_characters: Some(triggers),
3270+ // TODO: retrigger_characters
3271+ ..
3272+ }),
3273+ ..
3274+ } = capabilities
3275+ {
3276+ // TODO: what if trigger is multiple chars long
3277+ let is_trigger = triggers.iter().any(|trigger| trigger.contains(ch));
3278+ // lsp doesn't tell us when to close the signature help, so we request
3279+ // the help information again after common close triggers which should
3280+ // return None, which in turn closes the popup.
3281+ let close_triggers = &[')', ';', '.'];
3282+
3283+ if is_trigger || close_triggers.contains(&ch) {
3284+ super::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
3285+ }
33003286 }
33013287 }
33023288
@@ -3310,7 +3296,7 @@ pub mod insert {
33103296 Some(transaction)
33113297 }
33123298
3313− use helix_core::{auto_pairs, syntax::LanguageServerFeature};
3299+ use helix_core::auto_pairs;
33143300
33153301 pub fn insert_char(cx: &mut Context, c: char) {
33163302 let (view, doc) = current_ref!(cx.editor);
@@ -4065,38 +4051,43 @@ fn format_selections(cx: &mut Context) {
40654051 .set_error("format_selections only supports a single selection for now");
40664052 return;
40674053 }
4068− let future_offset_encoding = doc
4054+
4055+ // TODO extra LanguageServerFeature::FormatSelections?
4056+ // maybe such that LanguageServerFeature::Format contains it as well
4057+ let Some(language_server) = doc
40694058 .language_servers_with_feature(LanguageServerFeature::Format)
4070− .find_map(|language_server| {
4071− let offset_encoding = language_server.offset_encoding();
4072− let ranges: Vec<lsp::Range> = doc
4073− .selection(view_id)
4074− .iter()
4075− .map(|range| range_to_lsp_range(doc.text(), *range, offset_encoding))
4076− .collect();
4059+ .find(|ls| {
4060+ matches!(
4061+ ls.capabilities().document_range_formatting_provider,
4062+ Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_))
4063+ )
4064+ })
4065+ else {
4066+ cx.editor
4067+ .set_error("No configured language server does not support range formatting");
4068+ return;
4069+ };
40774070
4078− // TODO: handle fails
4079− // TODO: concurrent map over all ranges
4071+ let offset_encoding = language_server.offset_encoding();
4072+ let ranges: Vec<lsp::Range> = doc
4073+ .selection(view_id)
4074+ .iter()
4075+ .map(|range| range_to_lsp_range(doc.text(), *range, offset_encoding))
4076+ .collect();
40804077
4081− let range = ranges[0];
4078+ // TODO: handle fails
4079+ // TODO: concurrent map over all ranges
40824080
4083− let future = language_server.text_document_range_formatting(
4084− doc.identifier(),
4085− range,
4086− lsp::FormattingOptions::default(),
4087− None,
4088− )?;
4089− Some((future, offset_encoding))
4090− });
4081+ let range = ranges[0];
40914082
4092− let (future, offset_encoding) = match future_offset_encoding {
4093− Some(future_offset_encoding) => future_offset_encoding,
4094− None => {
4095− cx.editor
4096− .set_error("No configured language server supports range formatting");
4097− return;
4098− }
4099− };
4083+ let future = language_server
4084+ .text_document_range_formatting(
4085+ doc.identifier(),
4086+ range,
4087+ lsp::FormattingOptions::default(),
4088+ None,
4089+ )
4090+ .unwrap();
41004091
41014092 let edits = tokio::task::block_in_place(|| helix_lsp::block_on(future)).unwrap_or_default();
41024093
@@ -4247,15 +4238,15 @@ pub fn completion(cx: &mut Context) {
42474238
42484239 let mut futures: FuturesUnordered<_> = doc
42494240 .language_servers_with_feature(LanguageServerFeature::Completion)
4250− // TODO this should probably already been filtered in something like "language_servers_with_feature"
42514241 .filter(|ls| seen_language_servers.insert(ls.id()))
4252− .filter_map(|language_server| {
4242+ .map(|language_server| {
42534243 let language_server_id = language_server.id();
42544244 let offset_encoding = language_server.offset_encoding();
42554245 let pos = pos_to_lsp_pos(doc.text(), cursor, offset_encoding);
4256− let completion_request = language_server.completion(doc.identifier(), pos, None)?;
4246+ let doc_id = doc.identifier();
4247+ let completion_request = language_server.completion(doc_id, pos, None).unwrap();
42574248
4258− Some(async move {
4249+ async move {
42594250 let json = completion_request.await?;
42604251 let response: Option<lsp::CompletionResponse> = serde_json::from_value(json)?;
42614252
@@ -4277,7 +4268,7 @@ pub fn completion(cx: &mut Context) {
42774268 .collect();
42784269
42794270 anyhow::Ok(items)
4280− })
4271+ }
42814272 })
42824273 .collect();
42834274
helix-term/src/commands/lsp.rs+172−184
@@ -45,6 +45,28 @@ use std::{
4545 sync::Arc,
4646};
4747
48+/// Gets the first language server that is attached to a document which supports a specific feature.
49+/// If there is no configured language server that supports the feature, this displays a status message.
50+/// Using this macro in a context where the editor automatically queries the LSP
51+/// (instead of when the user explicitly does so via a keybind like `gd`)
52+/// will spam the "No configured language server supports <feature>" status message confusingly.
53+#[macro_export]
54+macro_rules! language_server_with_feature {
55+ ($editor:expr, $doc:expr, $feature:expr) => {{
56+ let language_server = $doc.language_servers_with_feature($feature).next();
57+ match language_server {
58+ Some(language_server) => language_server,
59+ None => {
60+ $editor.set_status(format!(
61+ "No configured language server supports {}",
62+ $feature
63+ ));
64+ return;
65+ }
66+ }
67+ }};
68+}
69+
4870impl ui::menu::Item for lsp::Location {
4971 /// Current working directory.
5072 type Data = PathBuf;
@@ -361,36 +383,38 @@ pub fn symbol_picker(cx: &mut Context) {
361383 let mut futures: FuturesUnordered<_> = doc
362384 .language_servers_with_feature(LanguageServerFeature::DocumentSymbols)
363385 .filter(|ls| seen_language_servers.insert(ls.id()))
364− .filter_map(|ls| {
365− let request = ls.document_symbols(doc.identifier())?;
366− Some((request, ls.offset_encoding(), doc.identifier()))
367− })
368− .map(|(request, offset_encoding, doc_id)| async move {
369− let json = request.await?;
370− let response: Option<lsp::DocumentSymbolResponse> = serde_json::from_value(json)?;
371− let symbols = match response {
372− Some(symbols) => symbols,
373− None => return anyhow::Ok(vec![]),
374− };
375− // lsp has two ways to represent symbols (flat/nested)
376− // convert the nested variant to flat, so that we have a homogeneous list
377− let symbols = match symbols {
378− lsp::DocumentSymbolResponse::Flat(symbols) => symbols
379− .into_iter()
380− .map(|symbol| SymbolInformationItem {
381− symbol,
382− offset_encoding,
383− })
384− .collect(),
385− lsp::DocumentSymbolResponse::Nested(symbols) => {
386− let mut flat_symbols = Vec::new();
387− for symbol in symbols {
388− nested_to_flat(&mut flat_symbols, &doc_id, symbol, offset_encoding)
386+ .map(|language_server| {
387+ let request = language_server.document_symbols(doc.identifier()).unwrap();
388+ let offset_encoding = language_server.offset_encoding();
389+ let doc_id = doc.identifier();
390+
391+ async move {
392+ let json = request.await?;
393+ let response: Option<lsp::DocumentSymbolResponse> = serde_json::from_value(json)?;
394+ let symbols = match response {
395+ Some(symbols) => symbols,
396+ None => return anyhow::Ok(vec![]),
397+ };
398+ // lsp has two ways to represent symbols (flat/nested)
399+ // convert the nested variant to flat, so that we have a homogeneous list
400+ let symbols = match symbols {
401+ lsp::DocumentSymbolResponse::Flat(symbols) => symbols
402+ .into_iter()
403+ .map(|symbol| SymbolInformationItem {
404+ symbol,
405+ offset_encoding,
406+ })
407+ .collect(),
408+ lsp::DocumentSymbolResponse::Nested(symbols) => {
409+ let mut flat_symbols = Vec::new();
410+ for symbol in symbols {
411+ nested_to_flat(&mut flat_symbols, &doc_id, symbol, offset_encoding)
412+ }
413+ flat_symbols
389414 }
390− flat_symbols
391− }
392− };
393− Ok(symbols)
415+ };
416+ Ok(symbols)
417+ }
394418 })
395419 .collect();
396420 let current_url = doc.url();
@@ -425,20 +449,24 @@ pub fn workspace_symbol_picker(cx: &mut Context) {
425449 let mut futures: FuturesUnordered<_> = doc
426450 .language_servers_with_feature(LanguageServerFeature::WorkspaceSymbols)
427451 .filter(|ls| seen_language_servers.insert(ls.id()))
428− .filter_map(|ls| Some((ls.workspace_symbols(pattern.clone())?, ls.offset_encoding())))
429− .map(|(request, offset_encoding)| async move {
430− let json = request.await?;
431−
432− let response = serde_json::from_value::<Option<Vec<lsp::SymbolInformation>>>(json)?
433− .unwrap_or_default()
434− .into_iter()
435− .map(|symbol| SymbolInformationItem {
436− symbol,
437− offset_encoding,
438− })
439− .collect();
440−
441− anyhow::Ok(response)
452+ .map(|language_server| {
453+ let request = language_server.workspace_symbols(pattern.clone()).unwrap();
454+ let offset_encoding = language_server.offset_encoding();
455+ async move {
456+ let json = request.await?;
457+
458+ let response =
459+ serde_json::from_value::<Option<Vec<lsp::SymbolInformation>>>(json)?
460+ .unwrap_or_default()
461+ .into_iter()
462+ .map(|symbol| SymbolInformationItem {
463+ symbol,
464+ offset_encoding,
465+ })
466+ .collect();
467+
468+ anyhow::Ok(response)
469+ }
442470 })
443471 .collect();
444472
@@ -1043,22 +1071,19 @@ where
10431071 F: Future<Output = helix_lsp::Result<serde_json::Value>> + 'static + Send,
10441072{
10451073 let (view, doc) = current!(cx.editor);
1046− if let Some((future, offset_encoding)) =
1047− doc.run_on_first_supported_language_server(view.id, feature, |ls, encoding, pos, doc_id| {
1048− Some((request_provider(ls, pos, doc_id)?, encoding))
1049− })
1050− {
1051− cx.callback(
1052− future,
1053− move |editor, compositor, response: Option<lsp::GotoDefinitionResponse>| {
1054− let items = to_locations(response);
1055− goto_impl(editor, compositor, items, offset_encoding);
1056− },
1057− );
1058− } else {
1059− cx.editor
1060− .set_error("No configured language server supports {feature}");
1061− }
1074+
1075+ let language_server = language_server_with_feature!(cx.editor, doc, feature);
1076+ let offset_encoding = language_server.offset_encoding();
1077+ let pos = doc.position(view.id, offset_encoding);
1078+ let future = request_provider(language_server, pos, doc.identifier()).unwrap();
1079+
1080+ cx.callback(
1081+ future,
1082+ move |editor, compositor, response: Option<lsp::GotoDefinitionResponse>| {
1083+ let items = to_locations(response);
1084+ goto_impl(editor, compositor, items, offset_encoding);
1085+ },
1086+ );
10621087}
10631088
10641089pub fn goto_declaration(cx: &mut Context) {
@@ -1096,32 +1121,29 @@ pub fn goto_implementation(cx: &mut Context) {
10961121pub fn goto_reference(cx: &mut Context) {
10971122 let config = cx.editor.config();
10981123 let (view, doc) = current!(cx.editor);
1099− if let Some((future, offset_encoding)) = doc.run_on_first_supported_language_server(
1100− view.id,
1101− LanguageServerFeature::GotoReference,
1102− |ls, encoding, pos, doc_id| {
1103− Some((
1104− ls.goto_reference(
1105− doc_id,
1106− pos,
1107− config.lsp.goto_reference_include_declaration,
1108− None,
1109− )?,
1110− encoding,
1111− ))
1124+
1125+ // TODO could probably support multiple language servers,
1126+ // not sure if there's a real practical use case for this though
1127+ let language_server =
1128+ language_server_with_feature!(cx.editor, doc, LanguageServerFeature::GotoReference);
1129+ let offset_encoding = language_server.offset_encoding();
1130+ let pos = doc.position(view.id, offset_encoding);
1131+ let future = language_server
1132+ .goto_reference(
1133+ doc.identifier(),
1134+ pos,
1135+ config.lsp.goto_reference_include_declaration,
1136+ None,
1137+ )
1138+ .unwrap();
1139+
1140+ cx.callback(
1141+ future,
1142+ move |editor, compositor, response: Option<Vec<lsp::Location>>| {
1143+ let items = response.unwrap_or_default();
1144+ goto_impl(editor, compositor, items, offset_encoding);
11121145 },
1113− ) {
1114− cx.callback(
1115− future,
1116− move |editor, compositor, response: Option<Vec<lsp::Location>>| {
1117− let items = response.unwrap_or_default();
1118− goto_impl(editor, compositor, items, offset_encoding);
1119− },
1120− );
1121− } else {
1122− cx.editor
1123− .set_error("No configured language server supports goto-reference");
1124− }
1146+ );
11251147}
11261148
11271149#[derive(PartialEq, Eq, Clone, Copy)]
@@ -1145,19 +1167,15 @@ pub fn signature_help_impl(cx: &mut Context, invoked: SignatureHelpInvoked) {
11451167 language_server.text_document_signature_help(doc.identifier(), pos, None)
11461168 });
11471169
1148− let future = match future {
1149− Some(future) => future.boxed(),
1150− None => {
1151− // Do not show the message if signature help was invoked
1152− // automatically on backspace, trigger characters, etc.
1153− if invoked == SignatureHelpInvoked::Manual {
1154− cx.editor
1155− .set_error("No configured language server supports signature-help");
1156− }
1157− return;
1170+ let Some(future) = future else {
1171+ // Do not show the message if signature help was invoked
1172+ // automatically on backspace, trigger characters, etc.
1173+ if invoked == SignatureHelpInvoked::Manual {
1174+ cx.editor.set_error("No configured language server supports signature-help");
11581175 }
1176+ return;
11591177 };
1160− signature_help_impl_with_future(cx, future, invoked);
1178+ signature_help_impl_with_future(cx, future.boxed(), invoked);
11611179}
11621180
11631181pub fn signature_help_impl_with_future(
@@ -1272,22 +1290,14 @@ pub fn signature_help_impl_with_future(
12721290pub fn hover(cx: &mut Context) {
12731291 let (view, doc) = current!(cx.editor);
12741292
1293+ // TODO support multiple language servers (merge UI somehow)
1294+ let language_server =
1295+ language_server_with_feature!(cx.editor, doc, LanguageServerFeature::Hover);
12751296 // TODO: factor out a doc.position_identifier() that returns lsp::TextDocumentPositionIdentifier
1276− let request = doc
1277− .language_servers_with_feature(LanguageServerFeature::Hover)
1278− .find_map(|language_server| {
1279− let pos = doc.position(view.id, language_server.offset_encoding());
1280− language_server.text_document_hover(doc.identifier(), pos, None)
1281− });
1282−
1283− let future = match request {
1284− Some(future) => future,
1285− None => {
1286− cx.editor
1287− .set_error("No configured language server supports hover");
1288− return;
1289− }
1290− };
1297+ let pos = doc.position(view.id, language_server.offset_encoding());
1298+ let future = language_server
1299+ .text_document_hover(doc.identifier(), pos, None)
1300+ .unwrap();
12911301
12921302 cx.callback(
12931303 future,
@@ -1381,34 +1391,26 @@ pub fn rename_symbol(cx: &mut Context) {
13811391 return;
13821392 }
13831393 let (view, doc) = current!(cx.editor);
1384− let request = doc
1394+
1395+ let Some(language_server) = doc
13851396 .language_servers_with_feature(LanguageServerFeature::RenameSymbol)
1386− .find_map(|language_server| {
1387− if let Some(language_server_id) = language_server_id {
1388− if language_server.id() != language_server_id {
1389− return None;
1390− }
1391− }
1392− let offset_encoding = language_server.offset_encoding();
1393− let pos = doc.position(view.id, offset_encoding);
1394− let future = language_server.rename_symbol(
1395− doc.identifier(),
1396− pos,
1397− input.to_string(),
1398− )?;
1399− Some((future, offset_encoding))
1400− });
1401−
1402− if let Some((future, offset_encoding)) = request {
1403− match block_on(future) {
1404− Ok(edits) => {
1405− let _ = apply_workspace_edit(cx.editor, offset_encoding, &edits);
1406− }
1407− Err(err) => cx.editor.set_error(err.to_string()),
1397+ .find(|ls| language_server_id.is_none() || Some(ls.id()) == language_server_id)
1398+ else {
1399+ cx.editor.set_error("No configured language server supports symbol renaming");
1400+ return;
1401+ };
1402+
1403+ let offset_encoding = language_server.offset_encoding();
1404+ let pos = doc.position(view.id, offset_encoding);
1405+ let future = language_server
1406+ .rename_symbol(doc.identifier(), pos, input.to_string())
1407+ .unwrap();
1408+
1409+ match block_on(future) {
1410+ Ok(edits) => {
1411+ let _ = apply_workspace_edit(cx.editor, offset_encoding, &edits);
14081412 }
1409− } else {
1410− cx.editor
1411− .set_error("No configured language server supports symbol renaming");
1413+ Err(err) => cx.editor.set_error(err.to_string()),
14121414 }
14131415 },
14141416 )
@@ -1417,20 +1419,28 @@ pub fn rename_symbol(cx: &mut Context) {
14171419 Box::new(prompt)
14181420 }
14191421
1420− let (view, doc) = current!(cx.editor);
1422+ let (view, doc) = current_ref!(cx.editor);
14211423
1422− let prepare_rename_request = doc
1424+ let language_server_with_prepare_rename_support = doc
14231425 .language_servers_with_feature(LanguageServerFeature::RenameSymbol)
1424− .find_map(|language_server| {
1425− let offset_encoding = language_server.offset_encoding();
1426− let pos = doc.position(view.id, offset_encoding);
1427− let future = language_server.prepare_rename(doc.identifier(), pos)?;
1428− Some((future, offset_encoding, language_server.id()))
1426+ .find(|ls| {
1427+ matches!(
1428+ ls.capabilities().rename_provider,
1429+ Some(lsp::OneOf::Right(lsp::RenameOptions {
1430+ prepare_provider: Some(true),
1431+ ..
1432+ }))
1433+ )
14291434 });
14301435
1431− match prepare_rename_request {
1432− // Language server supports textDocument/prepareRename, use it.
1433− Some((future, offset_encoding, ls_id)) => cx.callback(
1436+ if let Some(language_server) = language_server_with_prepare_rename_support {
1437+ let ls_id = language_server.id();
1438+ let offset_encoding = language_server.offset_encoding();
1439+ let pos = doc.position(view.id, offset_encoding);
1440+ let future = language_server
1441+ .prepare_rename(doc.identifier(), pos)
1442+ .unwrap();
1443+ cx.callback(
14341444 future,
14351445 move |editor, compositor, response: Option<lsp::PrepareRenameResponse>| {
14361446 let prefill = match get_prefill_from_lsp_response(editor, offset_encoding, response)
@@ -1446,38 +1456,23 @@ pub fn rename_symbol(cx: &mut Context) {
14461456
14471457 compositor.push(prompt);
14481458 },
1449− ),
1450− // Language server does not support textDocument/prepareRename, fall back
1451− // to word boundary selection.
1452− None => {
1453− let prefill = get_prefill_from_word_boundary(cx.editor);
1454−
1455− let prompt = create_rename_prompt(cx.editor, prefill, None);
1456−
1457− cx.push_layer(prompt);
1458− }
1459− };
1459+ );
1460+ } else {
1461+ let prefill = get_prefill_from_word_boundary(cx.editor);
1462+ let prompt = create_rename_prompt(cx.editor, prefill, None);
1463+ cx.push_layer(prompt);
1464+ }
14601465}
14611466
14621467pub fn select_references_to_symbol_under_cursor(cx: &mut Context) {
14631468 let (view, doc) = current!(cx.editor);
1464− let future_offset_encoding = doc
1465− .language_servers_with_feature(LanguageServerFeature::DocumentHighlight)
1466− .find_map(|language_server| {
1467− let offset_encoding = language_server.offset_encoding();
1468− let pos = doc.position(view.id, offset_encoding);
1469− let future =
1470− language_server.text_document_document_highlight(doc.identifier(), pos, None)?;
1471− Some((future, offset_encoding))
1472− });
1473− let (future, offset_encoding) = match future_offset_encoding {
1474− Some(future_offset_encoding) => future_offset_encoding,
1475− None => {
1476− cx.editor
1477− .set_error("No configured language server supports document-highlight");
1478− return;
1479− }
1480− };
1469+ let language_server =
1470+ language_server_with_feature!(cx.editor, doc, LanguageServerFeature::DocumentHighlight);
1471+ let offset_encoding = language_server.offset_encoding();
1472+ let pos = doc.position(view.id, offset_encoding);
1473+ let future = language_server
1474+ .text_document_document_highlight(doc.identifier(), pos, None)
1475+ .unwrap();
14811476
14821477 cx.callback(
14831478 future,
@@ -1532,16 +1527,9 @@ fn compute_inlay_hints_for_view(
15321527 let view_id = view.id;
15331528 let doc_id = view.doc;
15341529
1535− let mut language_servers = doc.language_servers_with_feature(LanguageServerFeature::InlayHints);
1536− let language_server = language_servers.find(|language_server| {
1537− matches!(
1538− language_server.capabilities().inlay_hint_provider,
1539− Some(
1540− lsp::OneOf::Left(true)
1541− | lsp::OneOf::Right(lsp::InlayHintServerCapabilities::Options(_))
1542− )
1543− )
1544− })?;
1530+ let language_server = doc
1531+ .language_servers_with_feature(LanguageServerFeature::InlayHints)
1532+ .next()?;
15451533
15461534 let doc_text = doc.text();
15471535 let len_lines = doc_text.len_lines();
helix-term/src/ui/mod.rs+3−6
@@ -394,14 +394,11 @@ pub mod completers {
394394 pub fn lsp_workspace_command(editor: &Editor, input: &str) -> Vec<Completion> {
395395 let matcher = Matcher::default();
396396
397− let options = match doc!(editor)
397+ let Some(options) = doc!(editor)
398398 .language_servers_with_feature(LanguageServerFeature::WorkspaceCommand)
399399 .find_map(|ls| ls.capabilities().execute_command_provider.as_ref())
400− {
401− Some(options) => options,
402− None => {
403− return vec![];
404− }
400+ else {
401+ return vec![];
405402 };
406403
407404 let mut matches: Vec<_> = options
helix-view/src/document.rs+17−34
@@ -580,7 +580,7 @@ where
580580 *mut_ref = f(mem::take(mut_ref));
581581}
582582
583−use helix_lsp::{lsp, Client, LanguageServerName, OffsetEncoding};
583+use helix_lsp::{lsp, Client, LanguageServerName};
584584use url::Url;
585585
586586impl Document {
@@ -732,21 +732,19 @@ impl Document {
732732
733733 let text = self.text.clone();
734734 // finds first language server that supports formatting and then formats
735− let (offset_encoding, request) = self
735+ let language_server = self
736736 .language_servers_with_feature(LanguageServerFeature::Format)
737− .find_map(|language_server| {
738− let offset_encoding = language_server.offset_encoding();
739− let request = language_server.text_document_formatting(
740− self.identifier(),
741− lsp::FormattingOptions {
742− tab_size: self.tab_width() as u32,
743− insert_spaces: matches!(self.indent_style, IndentStyle::Spaces(_)),
744− ..Default::default()
745− },
746− None,
747− )?;
748− Some((offset_encoding, request))
749− })?;
737+ .next()?;
738+ let offset_encoding = language_server.offset_encoding();
739+ let request = language_server.text_document_formatting(
740+ self.identifier(),
741+ lsp::FormattingOptions {
742+ tab_size: self.tab_width() as u32,
743+ insert_spaces: matches!(self.indent_style, IndentStyle::Spaces(_)),
744+ ..Default::default()
745+ },
746+ None,
747+ )?;
750748
751749 let fut = async move {
752750 let edits = request.await.unwrap_or_else(|e| {
@@ -1445,7 +1443,6 @@ impl Document {
14451443 self.language_servers.remove(name)
14461444 }
14471445
1448− // TODO filter also based on LSP capabilities?
14491446 pub fn language_servers_with_feature(
14501447 &self,
14511448 feature: LanguageServerFeature,
@@ -1453,7 +1450,10 @@ impl Document {
14531450 self.language_config().into_iter().flat_map(move |config| {
14541451 config.language_servers.iter().filter_map(move |features| {
14551452 let ls = &**self.language_servers.get(&features.name)?;
1456− if ls.is_initialized() && features.has_feature(feature) {
1453+ if ls.is_initialized()
1454+ && ls.supports_feature(feature)
1455+ && features.has_feature(feature)
1456+ {
14571457 Some(ls)
14581458 } else {
14591459 None
@@ -1466,23 +1466,6 @@ impl Document {
14661466 self.language_servers().any(|l| l.id() == id)
14671467 }
14681468
1469− pub fn run_on_first_supported_language_server<T, P>(
1470− &self,
1471− view_id: ViewId,
1472− feature: LanguageServerFeature,
1473− request_provider: P,
1474− ) -> Option<T>
1475− where
1476− P: Fn(&Client, OffsetEncoding, lsp::Position, lsp::TextDocumentIdentifier) -> Option<T>,
1477− {
1478− self.language_servers_with_feature(feature)
1479− .find_map(|language_server| {
1480− let offset_encoding = language_server.offset_encoding();
1481− let pos = self.position(view_id, offset_encoding);
1482− request_provider(language_server, offset_encoding, pos, self.identifier())
1483− })
1484− }
1485−
14861469 pub fn diff_handle(&self) -> Option<&DiffHandle> {
14871470 self.diff_handle.as_ref()
14881471 }