@@ -45,6 +45,28 @@ use std::{
45 sync::Arc,
45 sync::Arc,
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.
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,
60+ $editor.set_status(format!(
61+ "No configured language server supports {}",
48impl ui::menu::Item for lsp::Location {
70impl ui::menu::Item for lsp::Location {
49 /// Current working directory.
71 /// Current working directory.
50 type Data = PathBuf;
72 type Data = PathBuf;
@@ -361,36 +383,38 @@ pub fn symbol_picker(cx: &mut Context) {
361 let mut futures: FuturesUnordered<_> = doc
383 let mut futures: FuturesUnordered<_> = doc
362 .language_servers_with_feature(LanguageServerFeature::DocumentSymbols)
384 .language_servers_with_feature(LanguageServerFeature::DocumentSymbols)
363 .filter(|ls| seen_language_servers.insert(ls.id()))
385 .filter(|ls| seen_language_servers.insert(ls.id()))
364− .filter_map(|ls| {
386+ .map(|language_server| {
365− let request = ls.document_symbols(doc.identifier())?;
387+ let request = language_server.document_symbols(doc.identifier()).unwrap();
388+ let offset_encoding = language_server.offset_encoding();
366− Some((request, ls.offset_encoding(), doc.identifier()))
389+ let doc_id = doc.identifier();
368− .map(|(request, offset_encoding, doc_id)| async move {
391+ async move {
369− let json = request.await?;
392+ let json = request.await?;
370− let response: Option<lsp::DocumentSymbolResponse> = serde_json::from_value(json)?;
393+ let response: Option<lsp::DocumentSymbolResponse> = serde_json::from_value(json)?;
371− let symbols = match response {
394+ let symbols = match response {
372− Some(symbols) => symbols,
395+ Some(symbols) => symbols,
373− None => return anyhow::Ok(vec![]),
396+ None => return anyhow::Ok(vec![]),
375− // lsp has two ways to represent symbols (flat/nested)
398+ // lsp has two ways to represent symbols (flat/nested)
376− // convert the nested variant to flat, so that we have a homogeneous list
399+ // convert the nested variant to flat, so that we have a homogeneous list
377− let symbols = match symbols {
400+ let symbols = match symbols {
378− lsp::DocumentSymbolResponse::Flat(symbols) => symbols
401+ lsp::DocumentSymbolResponse::Flat(symbols) => symbols
379− .into_iter()
402+ .into_iter()
380− .map(|symbol| SymbolInformationItem {
403+ .map(|symbol| SymbolInformationItem {
382− offset_encoding,
405+ offset_encoding,
384− .collect(),
407+ .collect(),
385− lsp::DocumentSymbolResponse::Nested(symbols) => {
408+ lsp::DocumentSymbolResponse::Nested(symbols) => {
386− let mut flat_symbols = Vec::new();
409+ let mut flat_symbols = Vec::new();
387− for symbol in symbols {
410+ for symbol in symbols {
388− nested_to_flat(&mut flat_symbols, &doc_id, symbol, offset_encoding)
411+ nested_to_flat(&mut flat_symbols, &doc_id, symbol, offset_encoding)
393− Ok(symbols)
416+ Ok(symbols)
395 .collect();
419 .collect();
396 let current_url = doc.url();
420 let current_url = doc.url();
@@ -425,20 +449,24 @@ pub fn workspace_symbol_picker(cx: &mut Context) {
425 let mut futures: FuturesUnordered<_> = doc
449 let mut futures: FuturesUnordered<_> = doc
426 .language_servers_with_feature(LanguageServerFeature::WorkspaceSymbols)
450 .language_servers_with_feature(LanguageServerFeature::WorkspaceSymbols)
427 .filter(|ls| seen_language_servers.insert(ls.id()))
451 .filter(|ls| seen_language_servers.insert(ls.id()))
452+ .map(|language_server| {
428− .filter_map(|ls| Some((ls.workspace_symbols(pattern.clone())?, ls.offset_encoding())))
453+ let request = language_server.workspace_symbols(pattern.clone()).unwrap();
454+ let offset_encoding = language_server.offset_encoding();
429− .map(|(request, offset_encoding)| async move {
455+ async move {
430− let json = request.await?;
456+ let json = request.await?;
432− let response = serde_json::from_value::<Option<Vec<lsp::SymbolInformation>>>(json)?
459+ serde_json::from_value::<Option<Vec<lsp::SymbolInformation>>>(json)?
433− .unwrap_or_default()
460+ .unwrap_or_default()
434− .into_iter()
461+ .into_iter()
435− .map(|symbol| SymbolInformationItem {
462+ .map(|symbol| SymbolInformationItem {
437− offset_encoding,
464+ offset_encoding,
439− .collect();
466+ .collect();
441− anyhow::Ok(response)
468+ anyhow::Ok(response)
443 .collect();
471 .collect();
@@ -1043,22 +1071,19 @@ where
1043 F: Future<Output = helix_lsp::Result<serde_json::Value>> + 'static + Send,
1071 F: Future<Output = helix_lsp::Result<serde_json::Value>> + 'static + Send,
1045 let (view, doc) = current!(cx.editor);
1073 let (view, doc) = current!(cx.editor);
1046− if let Some((future, offset_encoding)) =
1074+
1047− doc.run_on_first_supported_language_server(view.id, feature, |ls, encoding, pos, doc_id| {
1075+ let language_server = language_server_with_feature!(cx.editor, doc, feature);
1048− Some((request_provider(ls, pos, doc_id)?, encoding))
1076+ let offset_encoding = language_server.offset_encoding();
1049− })
1077+ let pos = doc.position(view.id, offset_encoding);
1050− {
1078+ let future = request_provider(language_server, pos, doc.identifier()).unwrap();
1051− cx.callback(
1080+ cx.callback(
1052− future,
1081+ future,
1053− move |editor, compositor, response: Option<lsp::GotoDefinitionResponse>| {
1082+ move |editor, compositor, response: Option<lsp::GotoDefinitionResponse>| {
1054− let items = to_locations(response);
1083+ let items = to_locations(response);
1055− goto_impl(editor, compositor, items, offset_encoding);
1084+ goto_impl(editor, compositor, items, offset_encoding);
1060− .set_error("No configured language server supports {feature}");
1064pub fn goto_declaration(cx: &mut Context) {
1089pub fn goto_declaration(cx: &mut Context) {
@@ -1096,32 +1121,29 @@ pub fn goto_implementation(cx: &mut Context) {
1096pub fn goto_reference(cx: &mut Context) {
1121pub fn goto_reference(cx: &mut Context) {
1097 let config = cx.editor.config();
1122 let config = cx.editor.config();
1098 let (view, doc) = current!(cx.editor);
1123 let (view, doc) = current!(cx.editor);
1099− if let Some((future, offset_encoding)) = doc.run_on_first_supported_language_server(
1124+
1100− view.id,
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 =
1101− LanguageServerFeature::GotoReference,
1128+ language_server_with_feature!(cx.editor, doc, LanguageServerFeature::GotoReference);
1102− |ls, encoding, pos, doc_id| {
1129+ let offset_encoding = language_server.offset_encoding();
1103− Some((
1130+ let pos = doc.position(view.id, offset_encoding);
1131+ let future = language_server
1104− ls.goto_reference(
1132+ .goto_reference(
1105− doc_id,
1133+ doc.identifier(),
1107− config.lsp.goto_reference_include_declaration,
1135+ config.lsp.goto_reference_include_declaration,
1110− encoding,
1138+ .unwrap();
1142+ move |editor, compositor, response: Option<Vec<lsp::Location>>| {
1143+ let items = response.unwrap_or_default();
1144+ goto_impl(editor, compositor, items, offset_encoding);
1116− move |editor, compositor, response: Option<Vec<lsp::Location>>| {
1117− let items = response.unwrap_or_default();
1118− goto_impl(editor, compositor, items, offset_encoding);
1123− .set_error("No configured language server supports goto-reference");
1127#[derive(PartialEq, Eq, Clone, Copy)]
1149#[derive(PartialEq, Eq, Clone, Copy)]
@@ -1145,19 +1167,15 @@ pub fn signature_help_impl(cx: &mut Context, invoked: SignatureHelpInvoked) {
1145 language_server.text_document_signature_help(doc.identifier(), pos, None)
1167 language_server.text_document_signature_help(doc.identifier(), pos, None)
1148− let future = match future {
1170+ let Some(future) = future else {
1149− Some(future) => future.boxed(),
1151− // Do not show the message if signature help was invoked
1171+ // Do not show the message if signature help was invoked
1152− // automatically on backspace, trigger characters, etc.
1172+ // automatically on backspace, trigger characters, etc.
1153− if invoked == SignatureHelpInvoked::Manual {
1173+ if invoked == SignatureHelpInvoked::Manual {
1155− .set_error("No configured language server supports signature-help");
1174+ cx.editor.set_error("No configured language server supports signature-help");
1160− signature_help_impl_with_future(cx, future, invoked);
1178+ signature_help_impl_with_future(cx, future.boxed(), invoked);
1163pub fn signature_help_impl_with_future(
1181pub fn signature_help_impl_with_future(
@@ -1272,22 +1290,14 @@ pub fn signature_help_impl_with_future(
1272pub fn hover(cx: &mut Context) {
1290pub fn hover(cx: &mut Context) {
1273 let (view, doc) = current!(cx.editor);
1291 let (view, doc) = current!(cx.editor);
1293+ // TODO support multiple language servers (merge UI somehow)
1294+ let language_server =
1295+ language_server_with_feature!(cx.editor, doc, LanguageServerFeature::Hover);
1275 // TODO: factor out a doc.position_identifier() that returns lsp::TextDocumentPositionIdentifier
1296 // TODO: factor out a doc.position_identifier() that returns lsp::TextDocumentPositionIdentifier
1277− .language_servers_with_feature(LanguageServerFeature::Hover)
1278− .find_map(|language_server| {
1279− let pos = doc.position(view.id, language_server.offset_encoding());
1297+ let pos = doc.position(view.id, language_server.offset_encoding());
1298+ let future = language_server
1280− language_server.text_document_hover(doc.identifier(), pos, None)
1299+ .text_document_hover(doc.identifier(), pos, None)
1281− });
1300+ .unwrap();
1283− let future = match request {
1284− Some(future) => future,
1287− .set_error("No configured language server supports hover");
1292 cx.callback(
1302 cx.callback(
@@ -1381,34 +1391,26 @@ pub fn rename_symbol(cx: &mut Context) {
1383 let (view, doc) = current!(cx.editor);
1393 let (view, doc) = current!(cx.editor);
1384− let request = doc
1394+
1395+ let Some(language_server) = doc
1385 .language_servers_with_feature(LanguageServerFeature::RenameSymbol)
1396 .language_servers_with_feature(LanguageServerFeature::RenameSymbol)
1386− .find_map(|language_server| {
1387− if let Some(language_server_id) = language_server_id {
1397+ .find(|ls| language_server_id.is_none() || Some(ls.id()) == language_server_id)
1388− if language_server.id() != language_server_id {
1398+ else {
1399+ cx.editor.set_error("No configured language server supports symbol renaming");
1389− return None;
1400+ return;
1392− let offset_encoding = language_server.offset_encoding();
1403+ let offset_encoding = language_server.offset_encoding();
1393− let pos = doc.position(view.id, offset_encoding);
1404+ let pos = doc.position(view.id, offset_encoding);
1394− let future = language_server.rename_symbol(
1405+ let future = language_server
1397− input.to_string(),
1406+ .rename_symbol(doc.identifier(), pos, input.to_string())
1398− )?;
1407+ .unwrap();
1399− Some((future, offset_encoding))
1408+
1402− if let Some((future, offset_encoding)) = request {
1403− match block_on(future) {
1409+ match block_on(future) {
1404− Ok(edits) => {
1410+ Ok(edits) => {
1405− let _ = apply_workspace_edit(cx.editor, offset_encoding, &edits);
1411+ let _ = apply_workspace_edit(cx.editor, offset_encoding, &edits);
1407− Err(err) => cx.editor.set_error(err.to_string()),
1409− } else {
1413+ Err(err) => cx.editor.set_error(err.to_string()),
1411− .set_error("No configured language server supports symbol renaming");
@@ -1417,20 +1419,28 @@ pub fn rename_symbol(cx: &mut Context) {
1417 Box::new(prompt)
1419 Box::new(prompt)
1420− let (view, doc) = current!(cx.editor);
1422+ let (view, doc) = current_ref!(cx.editor);
1422− let prepare_rename_request = doc
1424+ let language_server_with_prepare_rename_support = doc
1423 .language_servers_with_feature(LanguageServerFeature::RenameSymbol)
1425 .language_servers_with_feature(LanguageServerFeature::RenameSymbol)
1424− .find_map(|language_server| {
1426+ .find(|ls| {
1425− let offset_encoding = language_server.offset_encoding();
1427+ matches!(
1426− let pos = doc.position(view.id, offset_encoding);
1428+ ls.capabilities().rename_provider,
1427− let future = language_server.prepare_rename(doc.identifier(), pos)?;
1429+ Some(lsp::OneOf::Right(lsp::RenameOptions {
1428− Some((future, offset_encoding, language_server.id()))
1430+ prepare_provider: Some(true),
1431− match prepare_rename_request {
1436+ if let Some(language_server) = language_server_with_prepare_rename_support {
1432− // Language server supports textDocument/prepareRename, use it.
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)
1433− Some((future, offset_encoding, ls_id)) => cx.callback(
1443+ cx.callback(
1435 move |editor, compositor, response: Option<lsp::PrepareRenameResponse>| {
1445 move |editor, compositor, response: Option<lsp::PrepareRenameResponse>| {
1436 let prefill = match get_prefill_from_lsp_response(editor, offset_encoding, response)
1446 let prefill = match get_prefill_from_lsp_response(editor, offset_encoding, response)
@@ -1446,38 +1456,23 @@ pub fn rename_symbol(cx: &mut Context) {
1447 compositor.push(prompt);
1457 compositor.push(prompt);
1450− // Language server does not support textDocument/prepareRename, fall back
1460+ } else {
1451− // to word boundary selection.
1453− let prefill = get_prefill_from_word_boundary(cx.editor);
1461+ let prefill = get_prefill_from_word_boundary(cx.editor);
1455− let prompt = create_rename_prompt(cx.editor, prefill, None);
1462+ let prompt = create_rename_prompt(cx.editor, prefill, None);
1457− cx.push_layer(prompt);
1463+ cx.push_layer(prompt);
1462pub fn select_references_to_symbol_under_cursor(cx: &mut Context) {
1467pub fn select_references_to_symbol_under_cursor(cx: &mut Context) {
1463 let (view, doc) = current!(cx.editor);
1468 let (view, doc) = current!(cx.editor);
1464− let future_offset_encoding = doc
1469+ let language_server =
1465− .language_servers_with_feature(LanguageServerFeature::DocumentHighlight)
1470+ language_server_with_feature!(cx.editor, doc, LanguageServerFeature::DocumentHighlight);
1466− .find_map(|language_server| {
1467− let offset_encoding = language_server.offset_encoding();
1471+ let offset_encoding = language_server.offset_encoding();
1468− let pos = doc.position(view.id, offset_encoding);
1472+ let pos = doc.position(view.id, offset_encoding);
1469− let future =
1473+ let future = language_server
1470− language_server.text_document_document_highlight(doc.identifier(), pos, None)?;
1474+ .text_document_document_highlight(doc.identifier(), pos, None)
1471− Some((future, offset_encoding))
1475+ .unwrap();
1473− let (future, offset_encoding) = match future_offset_encoding {
1474− Some(future_offset_encoding) => future_offset_encoding,
1477− .set_error("No configured language server supports document-highlight");
1482 cx.callback(
1477 cx.callback(
@@ -1532,16 +1527,9 @@ fn compute_inlay_hints_for_view(
1532 let view_id = view.id;
1527 let view_id = view.id;
1533 let doc_id = view.doc;
1528 let doc_id = view.doc;
1530+ let language_server = doc
1535− let mut language_servers = doc.language_servers_with_feature(LanguageServerFeature::InlayHints);
1531+ .language_servers_with_feature(LanguageServerFeature::InlayHints)
1536− let language_server = language_servers.find(|language_server| {
1538− language_server.capabilities().inlay_hint_provider,
1540− lsp::OneOf::Left(true)
1541− | lsp::OneOf::Right(lsp::InlayHintServerCapabilities::Options(_))
1544− })?;
1532+ .next()?;
1546 let doc_text = doc.text();
1534 let doc_text = doc.text();
1547 let len_lines = doc_text.len_lines();
1535 let len_lines = doc_text.len_lines();