on master help sign in
Filter language servers also by capabilities in `doc.language_servers_with_feature`
ff26208Philipp Mildenberger
helix-lsp/src/client.rs+89−10
@@ -4,7 +4,7 @@ use crate::{
4 Call, Error, OffsetEncoding, Result,
4 Call, Error, OffsetEncoding, Result,
5};
5};
6
6
7−use helix_core::{find_workspace, path, ChangeSet, Rope};
7+use helix_core::{find_workspace, path, syntax::LanguageServerFeature, ChangeSet, Rope};
8use helix_loader::{self, VERSION_AND_GIT_HASH};
8use helix_loader::{self, VERSION_AND_GIT_HASH};
9use lsp::{
9use lsp::{
10 notification::DidChangeWorkspaceFolders, DidChangeWorkspaceFoldersParams, OneOf,
10 notification::DidChangeWorkspaceFolders, DidChangeWorkspaceFoldersParams, OneOf,
@@ -276,6 +276,93 @@ impl Client {
276 .expect("language server not yet initialized!")
276 .expect("language server not yet initialized!")
277 }
277 }
278
278
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+
279 pub fn offset_encoding(&self) -> OffsetEncoding {
366 pub fn offset_encoding(&self) -> OffsetEncoding {
280 self.capabilities()
367 self.capabilities()
281 .position_encoding
368 .position_encoding
@@ -1301,21 +1388,13 @@ impl Client {
1301 Some(self.call::<lsp::request::CodeActionRequest>(params))
1388 Some(self.call::<lsp::request::CodeActionRequest>(params))
1302 }
1389 }
1303
1390
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−
1312 pub fn rename_symbol(
1391 pub fn rename_symbol(
1313 &self,
1392 &self,
1314 text_document: lsp::TextDocumentIdentifier,
1393 text_document: lsp::TextDocumentIdentifier,
1315 position: lsp::Position,
1394 position: lsp::Position,
1316 new_name: String,
1395 new_name: String,
1317 ) -> Option<impl Future<Output = Result<lsp::WorkspaceEdit>>> {
1396 ) -> Option<impl Future<Output = Result<lsp::WorkspaceEdit>>> {
1318− if !self.supports_rename() {
1397+ if !self.supports_feature(LanguageServerFeature::RenameSymbol) {
1319 return None;
1398 return None;
1320 }
1399 }
1321
1400
helix-term/src/commands.rs+68−77
@@ -3236,10 +3236,8 @@ pub mod insert {
3236 let trigger_completion = doc
3236 let trigger_completion = doc
3237 .language_servers_with_feature(LanguageServerFeature::Completion)
3237 .language_servers_with_feature(LanguageServerFeature::Completion)
3238 .any(|ls| {
3238 .any(|ls| {
3239− let capabilities = ls.capabilities();
3240−
3241 // TODO: what if trigger is multiple chars long
3239 // 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 {
3243 trigger_characters: Some(triggers),
3241 trigger_characters: Some(triggers),
3244 ..
3242 ..
3245 }) if triggers.iter().any(|trigger| trigger.contains(ch)))
3243 }) if triggers.iter().any(|trigger| trigger.contains(ch)))
@@ -3252,51 +3250,39 @@ pub mod insert {
3252 }
3250 }
3253
3251
3254 fn signature_help(cx: &mut Context, ch: char) {
3252 fn signature_help(cx: &mut Context, ch: char) {
3255− use futures_util::FutureExt;
3256 use helix_lsp::lsp;
3253 use helix_lsp::lsp;
3257 // if ch matches signature_help char, trigger
3254 // if ch matches signature_help char, trigger
3258− let (view, doc) = current!(cx.editor);
3255+ let doc = doc_mut!(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)
3256+ // TODO support multiple language servers (not just the first that is found), likely by merging UI somehow
3264− let future = doc
3257+ let Some(language_server) = doc
3265 .language_servers_with_feature(LanguageServerFeature::SignatureHelp)
3258 .language_servers_with_feature(LanguageServerFeature::SignatureHelp)
3266− .find_map(|ls| {
3259+ .next()
3267− let capabilities = ls.capabilities();
3260+ else {
3268−
3261+ return;
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− }
3262+ };
3292− });
3293
3263
3294− if let Some(future) = future {
3264+ let capabilities = language_server.capabilities();
3295− super::signature_help_impl_with_future(
3265+
3296− cx,
3266+ if let lsp::ServerCapabilities {
3297− future.boxed(),
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) {
3298− SignatureHelpInvoked::Automatic,
3284+ super::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
3299− )
3285+ }
3300 }
3286 }
3301 }
3287 }
3302
3288
@@ -3310,7 +3296,7 @@ pub mod insert {
3310 Some(transaction)
3296 Some(transaction)
3311 }
3297 }
3312
3298
3313− use helix_core::{auto_pairs, syntax::LanguageServerFeature};
3299+ use helix_core::auto_pairs;
3314
3300
3315 pub fn insert_char(cx: &mut Context, c: char) {
3301 pub fn insert_char(cx: &mut Context, c: char) {
3316 let (view, doc) = current_ref!(cx.editor);
3302 let (view, doc) = current_ref!(cx.editor);
@@ -4065,38 +4051,43 @@ fn format_selections(cx: &mut Context) {
4065 .set_error("format_selections only supports a single selection for now");
4051 .set_error("format_selections only supports a single selection for now");
4066 return;
4052 return;
4067 }
4053 }
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
4069 .language_servers_with_feature(LanguageServerFeature::Format)
4058 .language_servers_with_feature(LanguageServerFeature::Format)
4070− .find_map(|language_server| {
4059+ .find(|ls| {
4071− let offset_encoding = language_server.offset_encoding();
4060+ matches!(
4072− let ranges: Vec<lsp::Range> = doc
4061+ ls.capabilities().document_range_formatting_provider,
4073− .selection(view_id)
4062+ Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_))
4074− .iter()
4063+ )
4075− .map(|range| range_to_lsp_range(doc.text(), *range, offset_encoding))
4064+ })
4076− .collect();
4065+ else {
4066+ cx.editor
4067+ .set_error("No configured language server does not support range formatting");
4068+ return;
4069+ };
4077
4070
4078− // TODO: handle fails
4071+ let offset_encoding = language_server.offset_encoding();
4079− // TODO: concurrent map over all ranges
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();
4080
4077
4081− let range = ranges[0];
4078+ // TODO: handle fails
4079+ // TODO: concurrent map over all ranges
4082
4080
4083− let future = language_server.text_document_range_formatting(
4084− doc.identifier(),
4085− range,
4081+ let range = ranges[0];
4086− lsp::FormattingOptions::default(),
4087− None,
4088− )?;
4089− Some((future, offset_encoding))
4090− });
4091
4082
4092− let (future, offset_encoding) = match future_offset_encoding {
4083+ let future = language_server
4093− Some(future_offset_encoding) => future_offset_encoding,
4084+ .text_document_range_formatting(
4085+ doc.identifier(),
4086+ range,
4087+ lsp::FormattingOptions::default(),
4094− None => {
4088+ None,
4095− cx.editor
4089+ )
4096− .set_error("No configured language server supports range formatting");
4090+ .unwrap();
4097− return;
4098− }
4099− };
4100
4091
4101 let edits = tokio::task::block_in_place(|| helix_lsp::block_on(future)).unwrap_or_default();
4092 let edits = tokio::task::block_in_place(|| helix_lsp::block_on(future)).unwrap_or_default();
4102
4093
@@ -4247,15 +4238,15 @@ pub fn completion(cx: &mut Context) {
4247
4238
4248 let mut futures: FuturesUnordered<_> = doc
4239 let mut futures: FuturesUnordered<_> = doc
4249 .language_servers_with_feature(LanguageServerFeature::Completion)
4240 .language_servers_with_feature(LanguageServerFeature::Completion)
4250− // TODO this should probably already been filtered in something like "language_servers_with_feature"
4251 .filter(|ls| seen_language_servers.insert(ls.id()))
4241 .filter(|ls| seen_language_servers.insert(ls.id()))
4252− .filter_map(|language_server| {
4242+ .map(|language_server| {
4253 let language_server_id = language_server.id();
4243 let language_server_id = language_server.id();
4254 let offset_encoding = language_server.offset_encoding();
4244 let offset_encoding = language_server.offset_encoding();
4255 let pos = pos_to_lsp_pos(doc.text(), cursor, offset_encoding);
4245 let pos = pos_to_lsp_pos(doc.text(), cursor, offset_encoding);
4246+ let doc_id = doc.identifier();
4256− let completion_request = language_server.completion(doc.identifier(), pos, None)?;
4247+ let completion_request = language_server.completion(doc_id, pos, None).unwrap();
4257
4248
4258− Some(async move {
4249+ async move {
4259 let json = completion_request.await?;
4250 let json = completion_request.await?;
4260 let response: Option<lsp::CompletionResponse> = serde_json::from_value(json)?;
4251 let response: Option<lsp::CompletionResponse> = serde_json::from_value(json)?;
4261
4252
@@ -4277,7 +4268,7 @@ pub fn completion(cx: &mut Context) {
4277 .collect();
4268 .collect();
4278
4269
4279 anyhow::Ok(items)
4270 anyhow::Ok(items)
4280− })
4271+ }
4281 })
4272 })
4282 .collect();
4273 .collect();
4283
4274
helix-term/src/commands/lsp.rs+172−184
@@ -45,6 +45,28 @@ use std::{
45 sync::Arc,
45 sync::Arc,
46};
46};
47
47
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+
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();
367− })
390+
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![]),
374− };
397+ };
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 {
381− symbol,
404+ symbol,
382− offset_encoding,
405+ offset_encoding,
383− })
406+ })
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)
412+ }
413+ flat_symbols
389 }
414 }
390− flat_symbols
391− }
392− };
415+ };
393− Ok(symbols)
416+ Ok(symbols)
417+ }
394 })
418 })
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?;
431−
457+
458+ let response =
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 {
436− symbol,
463+ symbol,
437− offset_encoding,
464+ offset_encoding,
438− })
465+ })
439− .collect();
466+ .collect();
440−
467+
441− anyhow::Ok(response)
468+ anyhow::Ok(response)
469+ }
442 })
470 })
443 .collect();
471 .collect();
444
472
@@ -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,
1044{
1072{
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();
1079+
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);
1056− },
1085+ },
1057− );
1086+ );
1058− } else {
1059− cx.editor
1060− .set_error("No configured language server supports {feature}");
1061− }
1062}
1087}
1063
1088
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(),
1106− pos,
1134+ pos,
1107− config.lsp.goto_reference_include_declaration,
1135+ config.lsp.goto_reference_include_declaration,
1108− None,
1136+ None,
1109− )?,
1137+ )
1110− encoding,
1138+ .unwrap();
1111− ))
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);
1112 },
1145 },
1113− ) {
1146+ );
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− }
1125}
1147}
1126
1148
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)
1146 });
1168 });
1147
1169
1148− let future = match future {
1170+ let Some(future) = future else {
1149− Some(future) => future.boxed(),
1150− None => {
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 {
1154− cx.editor
1155− .set_error("No configured language server supports signature-help");
1174+ cx.editor.set_error("No configured language server supports signature-help");
1156− }
1157− return;
1158 }
1175 }
1176+ return;
1159 };
1177 };
1160− signature_help_impl_with_future(cx, future, invoked);
1178+ signature_help_impl_with_future(cx, future.boxed(), invoked);
1161}
1179}
1162
1180
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);
1274
1292
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
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());
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();
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− };
1291
1301
1292 cx.callback(
1302 cx.callback(
1293 future,
1303 future,
@@ -1381,34 +1391,26 @@ pub fn rename_symbol(cx: &mut Context) {
1381 return;
1391 return;
1382 }
1392 }
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;
1390− }
1401+ };
1391− }
1402+
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
1395− doc.identifier(),
1396− pos,
1397− input.to_string(),
1406+ .rename_symbol(doc.identifier(), pos, input.to_string())
1398− )?;
1407+ .unwrap();
1399− Some((future, offset_encoding))
1408+
1400− });
1401−
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);
1406− }
1407− Err(err) => cx.editor.set_error(err.to_string()),
1408 }
1412 }
1409− } else {
1413+ Err(err) => cx.editor.set_error(err.to_string()),
1410− cx.editor
1411− .set_error("No configured language server supports symbol renaming");
1412 }
1414 }
1413 },
1415 },
1414 )
1416 )
@@ -1417,20 +1419,28 @@ pub fn rename_symbol(cx: &mut Context) {
1417 Box::new(prompt)
1419 Box::new(prompt)
1418 }
1420 }
1419
1421
1420− let (view, doc) = current!(cx.editor);
1422+ let (view, doc) = current_ref!(cx.editor);
1421
1423
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+ ..
1432+ }))
1433+ )
1429 });
1434 });
1430
1435
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)
1442+ .unwrap();
1433− Some((future, offset_encoding, ls_id)) => cx.callback(
1443+ cx.callback(
1434 future,
1444 future,
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) {
1446
1456
1447 compositor.push(prompt);
1457 compositor.push(prompt);
1448 },
1458 },
1449− ),
1459+ );
1450− // Language server does not support textDocument/prepareRename, fall back
1460+ } else {
1451− // to word boundary selection.
1452− None => {
1453− let prefill = get_prefill_from_word_boundary(cx.editor);
1461+ let prefill = get_prefill_from_word_boundary(cx.editor);
1454−
1455− let prompt = create_rename_prompt(cx.editor, prefill, None);
1462+ let prompt = create_rename_prompt(cx.editor, prefill, None);
1456−
1457− cx.push_layer(prompt);
1463+ cx.push_layer(prompt);
1458− }
1459− };
1464+ }
1460}
1465}
1461
1466
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();
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− };
1481
1476
1482 cx.callback(
1477 cx.callback(
1483 future,
1478 future,
@@ -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;
1534
1529
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| {
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− })?;
1532+ .next()?;
1545
1533
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();
helix-term/src/ui/mod.rs+3−6
@@ -394,14 +394,11 @@ pub mod completers {
394 pub fn lsp_workspace_command(editor: &Editor, input: &str) -> Vec<Completion> {
394 pub fn lsp_workspace_command(editor: &Editor, input: &str) -> Vec<Completion> {
395 let matcher = Matcher::default();
395 let matcher = Matcher::default();
396
396
397− let options = match doc!(editor)
397+ let Some(options) = doc!(editor)
398 .language_servers_with_feature(LanguageServerFeature::WorkspaceCommand)
398 .language_servers_with_feature(LanguageServerFeature::WorkspaceCommand)
399 .find_map(|ls| ls.capabilities().execute_command_provider.as_ref())
399 .find_map(|ls| ls.capabilities().execute_command_provider.as_ref())
400− {
400+ else {
401− Some(options) => options,
402− None => {
403− return vec![];
401+ return vec![];
404− }
405 };
402 };
406
403
407 let mut matches: Vec<_> = options
404 let mut matches: Vec<_> = options
helix-view/src/document.rs+17−34
@@ -580,7 +580,7 @@ where
580 *mut_ref = f(mem::take(mut_ref));
580 *mut_ref = f(mem::take(mut_ref));
581}
581}
582
582
583−use helix_lsp::{lsp, Client, LanguageServerName, OffsetEncoding};
583+use helix_lsp::{lsp, Client, LanguageServerName};
584use url::Url;
584use url::Url;
585
585
586impl Document {
586impl Document {
@@ -732,21 +732,19 @@ impl Document {
732
732
733 let text = self.text.clone();
733 let text = self.text.clone();
734 // finds first language server that supports formatting and then formats
734 // finds first language server that supports formatting and then formats
735− let (offset_encoding, request) = self
735+ let language_server = self
736 .language_servers_with_feature(LanguageServerFeature::Format)
736 .language_servers_with_feature(LanguageServerFeature::Format)
737− .find_map(|language_server| {
737+ .next()?;
738− let offset_encoding = language_server.offset_encoding();
738+ let offset_encoding = language_server.offset_encoding();
739− let request = language_server.text_document_formatting(
739+ let request = language_server.text_document_formatting(
740− self.identifier(),
740+ self.identifier(),
741− lsp::FormattingOptions {
741+ lsp::FormattingOptions {
742− tab_size: self.tab_width() as u32,
742+ tab_size: self.tab_width() as u32,
743− insert_spaces: matches!(self.indent_style, IndentStyle::Spaces(_)),
743+ insert_spaces: matches!(self.indent_style, IndentStyle::Spaces(_)),
744− ..Default::default()
744+ ..Default::default()
745− },
745+ },
746− None,
746+ None,
747− )?;
748− Some((offset_encoding, request))
749− })?;
747+ )?;
750
748
751 let fut = async move {
749 let fut = async move {
752 let edits = request.await.unwrap_or_else(|e| {
750 let edits = request.await.unwrap_or_else(|e| {
@@ -1445,7 +1443,6 @@ impl Document {
1445 self.language_servers.remove(name)
1443 self.language_servers.remove(name)
1446 }
1444 }
1447
1445
1448− // TODO filter also based on LSP capabilities?
1449 pub fn language_servers_with_feature(
1446 pub fn language_servers_with_feature(
1450 &self,
1447 &self,
1451 feature: LanguageServerFeature,
1448 feature: LanguageServerFeature,
@@ -1453,7 +1450,10 @@ impl Document {
1453 self.language_config().into_iter().flat_map(move |config| {
1450 self.language_config().into_iter().flat_map(move |config| {
1454 config.language_servers.iter().filter_map(move |features| {
1451 config.language_servers.iter().filter_map(move |features| {
1455 let ls = &**self.language_servers.get(&features.name)?;
1452 let ls = &**self.language_servers.get(&features.name)?;
1453+ if ls.is_initialized()
1454+ && ls.supports_feature(feature)
1456− if ls.is_initialized() && features.has_feature(feature) {
1455+ && features.has_feature(feature)
1456+ {
1457 Some(ls)
1457 Some(ls)
1458 } else {
1458 } else {
1459 None
1459 None
@@ -1466,23 +1466,6 @@ impl Document {
1466 self.language_servers().any(|l| l.id() == id)
1466 self.language_servers().any(|l| l.id() == id)
1467 }
1467 }
1468
1468
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−
1486 pub fn diff_handle(&self) -> Option<&DiffHandle> {
1469 pub fn diff_handle(&self) -> Option<&DiffHandle> {
1487 self.diff_handle.as_ref()
1470 self.diff_handle.as_ref()
1488 }
1471 }