@@ -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();