on master help sign in
lsp: Check server provider capabilities (#3554)

Language Servers may signal that they do not support a method in the initialization result (server capabilities). We can check these when making LSP requests and hint in the status line when a method is not supported by the server. This can also prevent crashes in servers which assume that clients do not send requests for methods which are disabled in the server capabilities. There is an existing pattern the LSP client module where a method returns `Option<impl Future<Output = Result<_>>>` with `None` signaling no support in the server. This change extends this pattern to the rest of the client functions. And we log an error to the statusline for manually triggered LSP calls which return `None`.

commit9059c65a5385f6d3cc0bc7f6e3f835ae542635a5 authorMichael Davis <mcarsondavis@gmail.com> dateMon 21 Nov 2022, 20:52 (4 years ago) parent1db01ca
4 files changed+293−97
helix-lsp/src/client.rs+168−61 helix-term/src/commands.rs+19−10 helix-term/src/commands/lsp.rs+100−15 helix-term/src/ui/completion.rs+6−11
helix-lsp/src/client.rs+168−61
@@ -4,7 +4,6 @@ use crate::{
44 Call, Error, OffsetEncoding, Result,
55};
66
7−use anyhow::anyhow;
87use helix_core::{find_root, ChangeSet, Rope};
98use lsp_types as lsp;
109use serde::Deserialize;
@@ -546,16 +545,17 @@ impl Client {
546545 new_text: &Rope,
547546 changes: &ChangeSet,
548547 ) -> Option<impl Future<Output = Result<()>>> {
549− // figure out what kind of sync the server supports
550−
551548 let capabilities = self.capabilities.get().unwrap();
552549
550+ // Return early if the server does not support document sync.
553551 let sync_capabilities = match capabilities.text_document_sync {
554− Some(lsp::TextDocumentSyncCapability::Kind(kind))
555− | Some(lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
556− change: Some(kind),
557− ..
558− })) => kind,
552+ Some(
553+ lsp::TextDocumentSyncCapability::Kind(kind)
554+ | lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
555+ change: Some(kind),
556+ ..
557+ }),
558+ ) => kind,
559559 // None | SyncOptions { changes: None }
560560 _ => return None,
561561 };
@@ -631,8 +631,12 @@ impl Client {
631631 text_document: lsp::TextDocumentIdentifier,
632632 position: lsp::Position,
633633 work_done_token: Option<lsp::ProgressToken>,
634− ) -> impl Future<Output = Result<Value>> {
635− // ) -> Result<Vec<lsp::CompletionItem>> {
634+ ) -> Option<impl Future<Output = Result<Value>>> {
635+ let capabilities = self.capabilities.get().unwrap();
636+
637+ // Return early if the server does not support completion.
638+ capabilities.completion_provider.as_ref()?;
639+
636640 let params = lsp::CompletionParams {
637641 text_document_position: lsp::TextDocumentPositionParams {
638642 text_document,
@@ -647,14 +651,25 @@ impl Client {
647651 // lsp::CompletionContext { trigger_kind: , trigger_character: Some(), }
648652 };
649653
650− self.call::<lsp::request::Completion>(params)
654+ Some(self.call::<lsp::request::Completion>(params))
651655 }
652656
653657 pub fn resolve_completion_item(
654658 &self,
655659 completion_item: lsp::CompletionItem,
656− ) -> impl Future<Output = Result<Value>> {
657− self.call::<lsp::request::ResolveCompletionItem>(completion_item)
660+ ) -> Option<impl Future<Output = Result<Value>>> {
661+ let capabilities = self.capabilities.get().unwrap();
662+
663+ // Return early if the server does not support resolving completion items.
664+ match capabilities.completion_provider {
665+ Some(lsp::CompletionOptions {
666+ resolve_provider: Some(true),
667+ ..
668+ }) => (),
669+ _ => return None,
670+ }
671+
672+ Some(self.call::<lsp::request::ResolveCompletionItem>(completion_item))
658673 }
659674
660675 pub fn text_document_signature_help(
@@ -665,7 +680,7 @@ impl Client {
665680 ) -> Option<impl Future<Output = Result<Value>>> {
666681 let capabilities = self.capabilities.get().unwrap();
667682
668− // Return early if signature help is not supported
683+ // Return early if the server does not support signature help.
669684 capabilities.signature_help_provider.as_ref()?;
670685
671686 let params = lsp::SignatureHelpParams {
@@ -686,7 +701,18 @@ impl Client {
686701 text_document: lsp::TextDocumentIdentifier,
687702 position: lsp::Position,
688703 work_done_token: Option<lsp::ProgressToken>,
689− ) -> impl Future<Output = Result<Value>> {
704+ ) -> Option<impl Future<Output = Result<Value>>> {
705+ let capabilities = self.capabilities.get().unwrap();
706+
707+ // Return early if the server does not support hover.
708+ match capabilities.hover_provider {
709+ Some(
710+ lsp::HoverProviderCapability::Simple(true)
711+ | lsp::HoverProviderCapability::Options(_),
712+ ) => (),
713+ _ => return None,
714+ }
715+
690716 let params = lsp::HoverParams {
691717 text_document_position_params: lsp::TextDocumentPositionParams {
692718 text_document,
@@ -696,7 +722,7 @@ impl Client {
696722 // lsp::SignatureHelpContext
697723 };
698724
699− self.call::<lsp::request::HoverRequest>(params)
725+ Some(self.call::<lsp::request::HoverRequest>(params))
700726 }
701727
702728 // formatting
@@ -709,13 +735,11 @@ impl Client {
709735 ) -> Option<impl Future<Output = Result<Vec<lsp::TextEdit>>>> {
710736 let capabilities = self.capabilities.get().unwrap();
711737
712− // check if we're able to format
738+ // Return early if the server does not support formatting.
713739 match capabilities.document_formatting_provider {
714− Some(lsp::OneOf::Left(true)) | Some(lsp::OneOf::Right(_)) => (),
715− // None | Some(false)
740+ Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_)) => (),
716741 _ => return None,
717742 };
718− // TODO: return err::unavailable so we can fall back to tree sitter formatting
719743
720744 // merge FormattingOptions with 'config.format'
721745 let config_format = self
@@ -750,22 +774,20 @@ impl Client {
750774 })
751775 }
752776
753− pub async fn text_document_range_formatting(
777+ pub fn text_document_range_formatting(
754778 &self,
755779 text_document: lsp::TextDocumentIdentifier,
756780 range: lsp::Range,
757781 options: lsp::FormattingOptions,
758782 work_done_token: Option<lsp::ProgressToken>,
759− ) -> anyhow::Result<Vec<lsp::TextEdit>> {
783+ ) -> Option<impl Future<Output = Result<Vec<lsp::TextEdit>>>> {
760784 let capabilities = self.capabilities.get().unwrap();
761785
762− // check if we're able to format
786+ // Return early if the server does not support range formatting.
763787 match capabilities.document_range_formatting_provider {
764− Some(lsp::OneOf::Left(true)) | Some(lsp::OneOf::Right(_)) => (),
765− // None | Some(false)
766− _ => return Ok(Vec::new()),
788+ Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_)) => (),
789+ _ => return None,
767790 };
768− // TODO: return err::unavailable so we can fall back to tree sitter formatting
769791
770792 let params = lsp::DocumentRangeFormattingParams {
771793 text_document,
@@ -774,11 +796,13 @@ impl Client {
774796 work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
775797 };
776798
777− let response = self
778− .request::<lsp::request::RangeFormatting>(params)
779− .await?;
799+ let request = self.call::<lsp::request::RangeFormatting>(params);
780800
781− Ok(response.unwrap_or_default())
801+ Some(async move {
802+ let json = request.await?;
803+ let response: Option<Vec<lsp::TextEdit>> = serde_json::from_value(json)?;
804+ Ok(response.unwrap_or_default())
805+ })
782806 }
783807
784808 pub fn text_document_document_highlight(
@@ -786,7 +810,15 @@ impl Client {
786810 text_document: lsp::TextDocumentIdentifier,
787811 position: lsp::Position,
788812 work_done_token: Option<lsp::ProgressToken>,
789− ) -> impl Future<Output = Result<Value>> {
813+ ) -> Option<impl Future<Output = Result<Value>>> {
814+ let capabilities = self.capabilities.get().unwrap();
815+
816+ // Return early if the server does not support document highlight.
817+ match capabilities.document_highlight_provider {
818+ Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_)) => (),
819+ _ => return None,
820+ }
821+
790822 let params = lsp::DocumentHighlightParams {
791823 text_document_position_params: lsp::TextDocumentPositionParams {
792824 text_document,
@@ -798,7 +830,7 @@ impl Client {
798830 },
799831 };
800832
801− self.call::<lsp::request::DocumentHighlightRequest>(params)
833+ Some(self.call::<lsp::request::DocumentHighlightRequest>(params))
802834 }
803835
804836 fn goto_request<
@@ -831,8 +863,20 @@ impl Client {
831863 text_document: lsp::TextDocumentIdentifier,
832864 position: lsp::Position,
833865 work_done_token: Option<lsp::ProgressToken>,
834− ) -> impl Future<Output = Result<Value>> {
835− self.goto_request::<lsp::request::GotoDefinition>(text_document, position, work_done_token)
866+ ) -> Option<impl Future<Output = Result<Value>>> {
867+ let capabilities = self.capabilities.get().unwrap();
868+
869+ // Return early if the server does not support goto-definition.
870+ match capabilities.definition_provider {
871+ Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_)) => (),
872+ _ => return None,
873+ }
874+
875+ Some(self.goto_request::<lsp::request::GotoDefinition>(
876+ text_document,
877+ position,
878+ work_done_token,
879+ ))
836880 }
837881
838882 pub fn goto_type_definition(
@@ -840,12 +884,23 @@ impl Client {
840884 text_document: lsp::TextDocumentIdentifier,
841885 position: lsp::Position,
842886 work_done_token: Option<lsp::ProgressToken>,
843− ) -> impl Future<Output = Result<Value>> {
844− self.goto_request::<lsp::request::GotoTypeDefinition>(
887+ ) -> Option<impl Future<Output = Result<Value>>> {
888+ let capabilities = self.capabilities.get().unwrap();
889+
890+ // Return early if the server does not support goto-type-definition.
891+ match capabilities.type_definition_provider {
892+ Some(
893+ lsp::TypeDefinitionProviderCapability::Simple(true)
894+ | lsp::TypeDefinitionProviderCapability::Options(_),
895+ ) => (),
896+ _ => return None,
897+ }
898+
899+ Some(self.goto_request::<lsp::request::GotoTypeDefinition>(
845900 text_document,
846901 position,
847902 work_done_token,
848− )
903+ ))
849904 }
850905
851906 pub fn goto_implementation(
@@ -853,12 +908,23 @@ impl Client {
853908 text_document: lsp::TextDocumentIdentifier,
854909 position: lsp::Position,
855910 work_done_token: Option<lsp::ProgressToken>,
856− ) -> impl Future<Output = Result<Value>> {
857− self.goto_request::<lsp::request::GotoImplementation>(
911+ ) -> Option<impl Future<Output = Result<Value>>> {
912+ let capabilities = self.capabilities.get().unwrap();
913+
914+ // Return early if the server does not support goto-definition.
915+ match capabilities.implementation_provider {
916+ Some(
917+ lsp::ImplementationProviderCapability::Simple(true)
918+ | lsp::ImplementationProviderCapability::Options(_),
919+ ) => (),
920+ _ => return None,
921+ }
922+
923+ Some(self.goto_request::<lsp::request::GotoImplementation>(
858924 text_document,
859925 position,
860926 work_done_token,
861− )
927+ ))
862928 }
863929
864930 pub fn goto_reference(
@@ -866,7 +932,15 @@ impl Client {
866932 text_document: lsp::TextDocumentIdentifier,
867933 position: lsp::Position,
868934 work_done_token: Option<lsp::ProgressToken>,
869− ) -> impl Future<Output = Result<Value>> {
935+ ) -> Option<impl Future<Output = Result<Value>>> {
936+ let capabilities = self.capabilities.get().unwrap();
937+
938+ // Return early if the server does not support goto-reference.
939+ match capabilities.references_provider {
940+ Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_)) => (),
941+ _ => return None,
942+ }
943+
870944 let params = lsp::ReferenceParams {
871945 text_document_position: lsp::TextDocumentPositionParams {
872946 text_document,
@@ -881,31 +955,47 @@ impl Client {
881955 },
882956 };
883957
884− self.call::<lsp::request::References>(params)
958+ Some(self.call::<lsp::request::References>(params))
885959 }
886960
887961 pub fn document_symbols(
888962 &self,
889963 text_document: lsp::TextDocumentIdentifier,
890− ) -> impl Future<Output = Result<Value>> {
964+ ) -> Option<impl Future<Output = Result<Value>>> {
965+ let capabilities = self.capabilities.get().unwrap();
966+
967+ // Return early if the server does not support document symbols.
968+ match capabilities.document_symbol_provider {
969+ Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_)) => (),
970+ _ => return None,
971+ }
972+
891973 let params = lsp::DocumentSymbolParams {
892974 text_document,
893975 work_done_progress_params: lsp::WorkDoneProgressParams::default(),
894976 partial_result_params: lsp::PartialResultParams::default(),
895977 };
896978
897− self.call::<lsp::request::DocumentSymbolRequest>(params)
979+ Some(self.call::<lsp::request::DocumentSymbolRequest>(params))
898980 }
899981
900982 // empty string to get all symbols
901− pub fn workspace_symbols(&self, query: String) -> impl Future<Output = Result<Value>> {
983+ pub fn workspace_symbols(&self, query: String) -> Option<impl Future<Output = Result<Value>>> {
984+ let capabilities = self.capabilities.get().unwrap();
985+
986+ // Return early if the server does not support workspace symbols.
987+ match capabilities.workspace_symbol_provider {
988+ Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_)) => (),
989+ _ => return None,
990+ }
991+
902992 let params = lsp::WorkspaceSymbolParams {
903993 query,
904994 work_done_progress_params: lsp::WorkDoneProgressParams::default(),
905995 partial_result_params: lsp::PartialResultParams::default(),
906996 };
907997
908− self.call::<lsp::request::WorkspaceSymbol>(params)
998+ Some(self.call::<lsp::request::WorkspaceSymbol>(params))
909999 }
9101000
9111001 pub fn code_actions(
@@ -913,7 +1003,18 @@ impl Client {
9131003 text_document: lsp::TextDocumentIdentifier,
9141004 range: lsp::Range,
9151005 context: lsp::CodeActionContext,
916− ) -> impl Future<Output = Result<Value>> {
1006+ ) -> Option<impl Future<Output = Result<Value>>> {
1007+ let capabilities = self.capabilities.get().unwrap();
1008+
1009+ // Return early if the server does not support code actions.
1010+ match capabilities.code_action_provider {
1011+ Some(
1012+ lsp::CodeActionProviderCapability::Simple(true)
1013+ | lsp::CodeActionProviderCapability::Options(_),
1014+ ) => (),
1015+ _ => return None,
1016+ }
1017+
9171018 let params = lsp::CodeActionParams {
9181019 text_document,
9191020 range,
@@ -922,26 +1023,22 @@ impl Client {
9221023 partial_result_params: lsp::PartialResultParams::default(),
9231024 };
9241025
925− self.call::<lsp::request::CodeActionRequest>(params)
1026+ Some(self.call::<lsp::request::CodeActionRequest>(params))
9261027 }
9271028
928− pub async fn rename_symbol(
1029+ pub fn rename_symbol(
9291030 &self,
9301031 text_document: lsp::TextDocumentIdentifier,
9311032 position: lsp::Position,
9321033 new_name: String,
933− ) -> anyhow::Result<lsp::WorkspaceEdit> {
1034+ ) -> Option<impl Future<Output = Result<lsp::WorkspaceEdit>>> {
9341035 let capabilities = self.capabilities.get().unwrap();
9351036
936− // check if we're able to rename
1037+ // Return early if the language server does not support renaming.
9371038 match capabilities.rename_provider {
9381039 Some(lsp::OneOf::Left(true)) | Some(lsp::OneOf::Right(_)) => (),
9391040 // None | Some(false)
940− _ => {
941− log::warn!("rename_symbol failed: The server does not support rename");
942− let err = "The server does not support rename";
943− return Err(anyhow!(err));
944− }
1041+ _ => return None,
9451042 };
9461043
9471044 let params = lsp::RenameParams {
@@ -955,11 +1052,21 @@ impl Client {
9551052 },
9561053 };
9571054
958− let response = self.request::<lsp::request::Rename>(params).await?;
959− Ok(response.unwrap_or_default())
1055+ let request = self.call::<lsp::request::Rename>(params);
1056+
1057+ Some(async move {
1058+ let json = request.await?;
1059+ let response: Option<lsp::WorkspaceEdit> = serde_json::from_value(json)?;
1060+ Ok(response.unwrap_or_default())
1061+ })
9601062 }
9611063
962− pub fn command(&self, command: lsp::Command) -> impl Future<Output = Result<Value>> {
1064+ pub fn command(&self, command: lsp::Command) -> Option<impl Future<Output = Result<Value>>> {
1065+ let capabilities = self.capabilities.get().unwrap();
1066+
1067+ // Return early if the language server does not support executing commands.
1068+ capabilities.execute_command_provider.as_ref()?;
1069+
9631070 let params = lsp::ExecuteCommandParams {
9641071 command: command.command,
9651072 arguments: command.arguments.unwrap_or_default(),
@@ -968,6 +1075,6 @@ impl Client {
9681075 },
9691076 };
9701077
971− self.call::<lsp::request::ExecuteCommand>(params)
1078+ Some(self.call::<lsp::request::ExecuteCommand>(params))
9721079 }
9731080}
helix-term/src/commands.rs+19−10
@@ -3801,15 +3801,21 @@ fn format_selections(cx: &mut Context) {
38013801
38023802 let range = ranges[0];
38033803
3804− let edits = tokio::task::block_in_place(|| {
3805− helix_lsp::block_on(language_server.text_document_range_formatting(
3806− doc.identifier(),
3807− range,
3808− lsp::FormattingOptions::default(),
3809− None,
3810− ))
3811− })
3812− .unwrap_or_default();
3804+ let request = match language_server.text_document_range_formatting(
3805+ doc.identifier(),
3806+ range,
3807+ lsp::FormattingOptions::default(),
3808+ None,
3809+ ) {
3810+ Some(future) => future,
3811+ None => {
3812+ cx.editor
3813+ .set_error("Language server does not support range formatting");
3814+ return;
3815+ }
3816+ };
3817+
3818+ let edits = tokio::task::block_in_place(|| helix_lsp::block_on(request)).unwrap_or_default();
38133819
38143820 let transaction = helix_lsp::util::generate_transaction_from_edits(
38153821 doc.text(),
@@ -3953,7 +3959,10 @@ pub fn completion(cx: &mut Context) {
39533959
39543960 let pos = pos_to_lsp_pos(doc.text(), cursor, offset_encoding);
39553961
3956− let future = language_server.completion(doc.identifier(), pos, None);
3962+ let future = match language_server.completion(doc.identifier(), pos, None) {
3963+ Some(future) => future,
3964+ None => return,
3965+ };
39573966
39583967 let trigger_offset = cursor;
39593968
helix-term/src/commands/lsp.rs+100−15
@@ -333,7 +333,14 @@ pub fn symbol_picker(cx: &mut Context) {
333333 let current_url = doc.url();
334334 let offset_encoding = language_server.offset_encoding();
335335
336− let future = language_server.document_symbols(doc.identifier());
336+ let future = match language_server.document_symbols(doc.identifier()) {
337+ Some(future) => future,
338+ None => {
339+ cx.editor
340+ .set_error("Language server does not support document symbols");
341+ return;
342+ }
343+ };
337344
338345 cx.callback(
339346 future,
@@ -365,7 +372,14 @@ pub fn workspace_symbol_picker(cx: &mut Context) {
365372 let current_url = doc.url();
366373 let language_server = language_server!(cx.editor, doc);
367374 let offset_encoding = language_server.offset_encoding();
368− let future = language_server.workspace_symbols("".to_string());
375+ let future = match language_server.workspace_symbols("".to_string()) {
376+ Some(future) => future,
377+ None => {
378+ cx.editor
379+ .set_error("Language server does not support workspace symbols");
380+ return;
381+ }
382+ };
369383
370384 cx.callback(
371385 future,
@@ -493,7 +507,7 @@ pub fn code_action(cx: &mut Context) {
493507
494508 let range = range_to_lsp_range(doc.text(), selection_range, offset_encoding);
495509
496− let future = language_server.code_actions(
510+ let future = match language_server.code_actions(
497511 doc.identifier(),
498512 range,
499513 // Filter and convert overlapping diagnostics
@@ -509,7 +523,14 @@ pub fn code_action(cx: &mut Context) {
509523 .collect(),
510524 only: None,
511525 },
512− );
526+ ) {
527+ Some(future) => future,
528+ None => {
529+ cx.editor
530+ .set_error("Language server does not support code actions");
531+ return;
532+ }
533+ };
513534
514535 cx.callback(
515536 future,
@@ -617,9 +638,16 @@ pub fn execute_lsp_command(editor: &mut Editor, cmd: lsp::Command) {
617638
618639 // the command is executed on the server and communicated back
619640 // to the client asynchronously using workspace edits
620− let command_future = language_server.command(cmd);
641+ let future = match language_server.command(cmd) {
642+ Some(future) => future,
643+ None => {
644+ editor.set_error("Language server does not support executing commands");
645+ return;
646+ }
647+ };
648+
621649 tokio::spawn(async move {
622− let res = command_future.await;
650+ let res = future.await;
623651
624652 if let Err(e) = res {
625653 log::error!("execute LSP command: {}", e);
@@ -853,7 +881,14 @@ pub fn goto_definition(cx: &mut Context) {
853881
854882 let pos = doc.position(view.id, offset_encoding);
855883
856− let future = language_server.goto_definition(doc.identifier(), pos, None);
884+ let future = match language_server.goto_definition(doc.identifier(), pos, None) {
885+ Some(future) => future,
886+ None => {
887+ cx.editor
888+ .set_error("Language server does not support goto-definition");
889+ return;
890+ }
891+ };
857892
858893 cx.callback(
859894 future,
@@ -871,7 +906,14 @@ pub fn goto_type_definition(cx: &mut Context) {
871906
872907 let pos = doc.position(view.id, offset_encoding);
873908
874− let future = language_server.goto_type_definition(doc.identifier(), pos, None);
909+ let future = match language_server.goto_type_definition(doc.identifier(), pos, None) {
910+ Some(future) => future,
911+ None => {
912+ cx.editor
913+ .set_error("Language server does not support goto-type-definition");
914+ return;
915+ }
916+ };
875917
876918 cx.callback(
877919 future,
@@ -889,7 +931,14 @@ pub fn goto_implementation(cx: &mut Context) {
889931
890932 let pos = doc.position(view.id, offset_encoding);
891933
892− let future = language_server.goto_implementation(doc.identifier(), pos, None);
934+ let future = match language_server.goto_implementation(doc.identifier(), pos, None) {
935+ Some(future) => future,
936+ None => {
937+ cx.editor
938+ .set_error("Language server does not support goto-implementation");
939+ return;
940+ }
941+ };
893942
894943 cx.callback(
895944 future,
@@ -907,7 +956,14 @@ pub fn goto_reference(cx: &mut Context) {
907956
908957 let pos = doc.position(view.id, offset_encoding);
909958
910− let future = language_server.goto_reference(doc.identifier(), pos, None);
959+ let future = match language_server.goto_reference(doc.identifier(), pos, None) {
960+ Some(future) => future,
961+ None => {
962+ cx.editor
963+ .set_error("Language server does not support goto-reference");
964+ return;
965+ }
966+ };
911967
912968 cx.callback(
913969 future,
@@ -950,7 +1006,13 @@ pub fn signature_help_impl(cx: &mut Context, invoked: SignatureHelpInvoked) {
9501006
9511007 let future = match language_server.text_document_signature_help(doc.identifier(), pos, None) {
9521008 Some(f) => f,
953− None => return,
1009+ None => {
1010+ if was_manually_invoked {
1011+ cx.editor
1012+ .set_error("Language server does not support signature-help");
1013+ }
1014+ return;
1015+ }
9541016 };
9551017
9561018 cx.callback(
@@ -1051,7 +1113,14 @@ pub fn hover(cx: &mut Context) {
10511113
10521114 let pos = doc.position(view.id, offset_encoding);
10531115
1054− let future = language_server.text_document_hover(doc.identifier(), pos, None);
1116+ let future = match language_server.text_document_hover(doc.identifier(), pos, None) {
1117+ Some(future) => future,
1118+ None => {
1119+ cx.editor
1120+ .set_error("Language server does not support hover");
1121+ return;
1122+ }
1123+ };
10551124
10561125 cx.callback(
10571126 future,
@@ -1121,8 +1190,16 @@ pub fn rename_symbol(cx: &mut Context) {
11211190
11221191 let pos = doc.position(view.id, offset_encoding);
11231192
1124− let task = language_server.rename_symbol(doc.identifier(), pos, input.to_string());
1125− match block_on(task) {
1193+ let future =
1194+ match language_server.rename_symbol(doc.identifier(), pos, input.to_string()) {
1195+ Some(future) => future,
1196+ None => {
1197+ cx.editor
1198+ .set_error("Language server does not support symbol renaming");
1199+ return;
1200+ }
1201+ };
1202+ match block_on(future) {
11261203 Ok(edits) => apply_workspace_edit(cx.editor, offset_encoding, &edits),
11271204 Err(err) => cx.editor.set_error(err.to_string()),
11281205 }
@@ -1137,7 +1214,15 @@ pub fn select_references_to_symbol_under_cursor(cx: &mut Context) {
11371214
11381215 let pos = doc.position(view.id, offset_encoding);
11391216
1140− let future = language_server.text_document_document_highlight(doc.identifier(), pos, None);
1217+ let future = match language_server.text_document_document_highlight(doc.identifier(), pos, None)
1218+ {
1219+ Some(future) => future,
1220+ None => {
1221+ cx.editor
1222+ .set_error("Language server does not support document highlight");
1223+ return;
1224+ }
1225+ };
11411226
11421227 cx.callback(
11431228 future,
helix-term/src/ui/completion.rs+6−11
@@ -245,21 +245,13 @@ impl Completion {
245245 completion_item: lsp::CompletionItem,
246246 ) -> Option<CompletionItem> {
247247 let language_server = doc.language_server()?;
248− let completion_resolve_provider = language_server
249− .capabilities()
250− .completion_provider
251− .as_ref()?
252− .resolve_provider;
253− if completion_resolve_provider != Some(true) {
254− return None;
255− }
256248
257− let future = language_server.resolve_completion_item(completion_item);
249+ let future = language_server.resolve_completion_item(completion_item)?;
258250 let response = helix_lsp::block_on(future);
259251 match response {
260252 Ok(value) => serde_json::from_value(value).ok(),
261253 Err(err) => {
262− log::error!("execute LSP command: {}", err);
254+ log::error!("Failed to resolve completion item: {}", err);
263255 None
264256 }
265257 }
@@ -330,7 +322,10 @@ impl Completion {
330322 };
331323
332324 // This method should not block the compositor so we handle the response asynchronously.
333− let future = language_server.resolve_completion_item(current_item.clone());
325+ let future = match language_server.resolve_completion_item(current_item.clone()) {
326+ Some(future) => future,
327+ None => return false,
328+ };
334329
335330 cx.callback(
336331 future,