From 9059c65a5385f6d3cc0bc7f6e3f835ae542635a5 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Mon, 21 Nov 2022 20:52:23 -0600 Subject: 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>>` 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`.--- helix-lsp/src/client.rs | 229 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 168 insertions(+), 61 deletions(-) (limited to 'helix-lsp') diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index 424cbabf..2c2c7c88 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -4,7 +4,6 @@ use crate::{ Call, Error, OffsetEncoding, Result, }; -use anyhow::anyhow; use helix_core::{find_root, ChangeSet, Rope}; use lsp_types as lsp; use serde::Deserialize; @@ -546,16 +545,17 @@ impl Client { new_text: &Rope, changes: &ChangeSet, ) -> Option>> { - // figure out what kind of sync the server supports - let capabilities = self.capabilities.get().unwrap(); + // Return early if the server does not support document sync. let sync_capabilities = match capabilities.text_document_sync { - Some(lsp::TextDocumentSyncCapability::Kind(kind)) - | Some(lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions { - change: Some(kind), - .. - })) => kind, + Some( + lsp::TextDocumentSyncCapability::Kind(kind) + | lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions { + change: Some(kind), + .. + }), + ) => kind, // None | SyncOptions { changes: None } _ => return None, }; @@ -631,8 +631,12 @@ impl Client { text_document: lsp::TextDocumentIdentifier, position: lsp::Position, work_done_token: Option, - ) -> impl Future> { - // ) -> Result> { + ) -> Option>> { + let capabilities = self.capabilities.get().unwrap(); + + // Return early if the server does not support completion. + capabilities.completion_provider.as_ref()?; + let params = lsp::CompletionParams { text_document_position: lsp::TextDocumentPositionParams { text_document, @@ -647,14 +651,25 @@ impl Client { // lsp::CompletionContext { trigger_kind: , trigger_character: Some(), } }; - self.call::(params) + Some(self.call::(params)) } pub fn resolve_completion_item( &self, completion_item: lsp::CompletionItem, - ) -> impl Future> { - self.call::(completion_item) + ) -> Option>> { + let capabilities = self.capabilities.get().unwrap(); + + // Return early if the server does not support resolving completion items. + match capabilities.completion_provider { + Some(lsp::CompletionOptions { + resolve_provider: Some(true), + .. + }) => (), + _ => return None, + } + + Some(self.call::(completion_item)) } pub fn text_document_signature_help( @@ -665,7 +680,7 @@ impl Client { ) -> Option>> { let capabilities = self.capabilities.get().unwrap(); - // Return early if signature help is not supported + // Return early if the server does not support signature help. capabilities.signature_help_provider.as_ref()?; let params = lsp::SignatureHelpParams { @@ -686,7 +701,18 @@ impl Client { text_document: lsp::TextDocumentIdentifier, position: lsp::Position, work_done_token: Option, - ) -> impl Future> { + ) -> Option>> { + let capabilities = self.capabilities.get().unwrap(); + + // Return early if the server does not support hover. + match capabilities.hover_provider { + Some( + lsp::HoverProviderCapability::Simple(true) + | lsp::HoverProviderCapability::Options(_), + ) => (), + _ => return None, + } + let params = lsp::HoverParams { text_document_position_params: lsp::TextDocumentPositionParams { text_document, @@ -696,7 +722,7 @@ impl Client { // lsp::SignatureHelpContext }; - self.call::(params) + Some(self.call::(params)) } // formatting @@ -709,13 +735,11 @@ impl Client { ) -> Option>>> { let capabilities = self.capabilities.get().unwrap(); - // check if we're able to format + // Return early if the server does not support formatting. match capabilities.document_formatting_provider { - Some(lsp::OneOf::Left(true)) | Some(lsp::OneOf::Right(_)) => (), - // None | Some(false) + Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_)) => (), _ => return None, }; - // TODO: return err::unavailable so we can fall back to tree sitter formatting // merge FormattingOptions with 'config.format' let config_format = self @@ -750,22 +774,20 @@ impl Client { }) } - pub async fn text_document_range_formatting( + pub fn text_document_range_formatting( &self, text_document: lsp::TextDocumentIdentifier, range: lsp::Range, options: lsp::FormattingOptions, work_done_token: Option, - ) -> anyhow::Result> { + ) -> Option>>> { let capabilities = self.capabilities.get().unwrap(); - // check if we're able to format + // Return early if the server does not support range formatting. match capabilities.document_range_formatting_provider { - Some(lsp::OneOf::Left(true)) | Some(lsp::OneOf::Right(_)) => (), - // None | Some(false) - _ => return Ok(Vec::new()), + Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_)) => (), + _ => return None, }; - // TODO: return err::unavailable so we can fall back to tree sitter formatting let params = lsp::DocumentRangeFormattingParams { text_document, @@ -774,11 +796,13 @@ impl Client { work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token }, }; - let response = self - .request::(params) - .await?; + let request = self.call::(params); - Ok(response.unwrap_or_default()) + Some(async move { + let json = request.await?; + let response: Option> = serde_json::from_value(json)?; + Ok(response.unwrap_or_default()) + }) } pub fn text_document_document_highlight( @@ -786,7 +810,15 @@ impl Client { text_document: lsp::TextDocumentIdentifier, position: lsp::Position, work_done_token: Option, - ) -> impl Future> { + ) -> Option>> { + let capabilities = self.capabilities.get().unwrap(); + + // Return early if the server does not support document highlight. + match capabilities.document_highlight_provider { + Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_)) => (), + _ => return None, + } + let params = lsp::DocumentHighlightParams { text_document_position_params: lsp::TextDocumentPositionParams { text_document, @@ -798,7 +830,7 @@ impl Client { }, }; - self.call::(params) + Some(self.call::(params)) } fn goto_request< @@ -831,8 +863,20 @@ impl Client { text_document: lsp::TextDocumentIdentifier, position: lsp::Position, work_done_token: Option, - ) -> impl Future> { - self.goto_request::(text_document, position, work_done_token) + ) -> Option>> { + let capabilities = self.capabilities.get().unwrap(); + + // Return early if the server does not support goto-definition. + match capabilities.definition_provider { + Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_)) => (), + _ => return None, + } + + Some(self.goto_request::( + text_document, + position, + work_done_token, + )) } pub fn goto_type_definition( @@ -840,12 +884,23 @@ impl Client { text_document: lsp::TextDocumentIdentifier, position: lsp::Position, work_done_token: Option, - ) -> impl Future> { - self.goto_request::( + ) -> Option>> { + let capabilities = self.capabilities.get().unwrap(); + + // Return early if the server does not support goto-type-definition. + match capabilities.type_definition_provider { + Some( + lsp::TypeDefinitionProviderCapability::Simple(true) + | lsp::TypeDefinitionProviderCapability::Options(_), + ) => (), + _ => return None, + } + + Some(self.goto_request::( text_document, position, work_done_token, - ) + )) } pub fn goto_implementation( @@ -853,12 +908,23 @@ impl Client { text_document: lsp::TextDocumentIdentifier, position: lsp::Position, work_done_token: Option, - ) -> impl Future> { - self.goto_request::( + ) -> Option>> { + let capabilities = self.capabilities.get().unwrap(); + + // Return early if the server does not support goto-definition. + match capabilities.implementation_provider { + Some( + lsp::ImplementationProviderCapability::Simple(true) + | lsp::ImplementationProviderCapability::Options(_), + ) => (), + _ => return None, + } + + Some(self.goto_request::( text_document, position, work_done_token, - ) + )) } pub fn goto_reference( @@ -866,7 +932,15 @@ impl Client { text_document: lsp::TextDocumentIdentifier, position: lsp::Position, work_done_token: Option, - ) -> impl Future> { + ) -> Option>> { + let capabilities = self.capabilities.get().unwrap(); + + // Return early if the server does not support goto-reference. + match capabilities.references_provider { + Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_)) => (), + _ => return None, + } + let params = lsp::ReferenceParams { text_document_position: lsp::TextDocumentPositionParams { text_document, @@ -881,31 +955,47 @@ impl Client { }, }; - self.call::(params) + Some(self.call::(params)) } pub fn document_symbols( &self, text_document: lsp::TextDocumentIdentifier, - ) -> impl Future> { + ) -> Option>> { + let capabilities = self.capabilities.get().unwrap(); + + // Return early if the server does not support document symbols. + match capabilities.document_symbol_provider { + Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_)) => (), + _ => return None, + } + let params = lsp::DocumentSymbolParams { text_document, work_done_progress_params: lsp::WorkDoneProgressParams::default(), partial_result_params: lsp::PartialResultParams::default(), }; - self.call::(params) + Some(self.call::(params)) } // empty string to get all symbols - pub fn workspace_symbols(&self, query: String) -> impl Future> { + pub fn workspace_symbols(&self, query: String) -> Option>> { + let capabilities = self.capabilities.get().unwrap(); + + // Return early if the server does not support workspace symbols. + match capabilities.workspace_symbol_provider { + Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_)) => (), + _ => return None, + } + let params = lsp::WorkspaceSymbolParams { query, work_done_progress_params: lsp::WorkDoneProgressParams::default(), partial_result_params: lsp::PartialResultParams::default(), }; - self.call::(params) + Some(self.call::(params)) } pub fn code_actions( @@ -913,7 +1003,18 @@ impl Client { text_document: lsp::TextDocumentIdentifier, range: lsp::Range, context: lsp::CodeActionContext, - ) -> impl Future> { + ) -> Option>> { + let capabilities = self.capabilities.get().unwrap(); + + // Return early if the server does not support code actions. + match capabilities.code_action_provider { + Some( + lsp::CodeActionProviderCapability::Simple(true) + | lsp::CodeActionProviderCapability::Options(_), + ) => (), + _ => return None, + } + let params = lsp::CodeActionParams { text_document, range, @@ -922,26 +1023,22 @@ impl Client { partial_result_params: lsp::PartialResultParams::default(), }; - self.call::(params) + Some(self.call::(params)) } - pub async fn rename_symbol( + pub fn rename_symbol( &self, text_document: lsp::TextDocumentIdentifier, position: lsp::Position, new_name: String, - ) -> anyhow::Result { + ) -> Option>> { let capabilities = self.capabilities.get().unwrap(); - // check if we're able to rename + // Return early if the language server does not support renaming. match capabilities.rename_provider { Some(lsp::OneOf::Left(true)) | Some(lsp::OneOf::Right(_)) => (), // None | Some(false) - _ => { - log::warn!("rename_symbol failed: The server does not support rename"); - let err = "The server does not support rename"; - return Err(anyhow!(err)); - } + _ => return None, }; let params = lsp::RenameParams { @@ -955,11 +1052,21 @@ impl Client { }, }; - let response = self.request::(params).await?; - Ok(response.unwrap_or_default()) + let request = self.call::(params); + + Some(async move { + let json = request.await?; + let response: Option = serde_json::from_value(json)?; + Ok(response.unwrap_or_default()) + }) } - pub fn command(&self, command: lsp::Command) -> impl Future> { + pub fn command(&self, command: lsp::Command) -> Option>> { + let capabilities = self.capabilities.get().unwrap(); + + // Return early if the language server does not support executing commands. + capabilities.execute_command_provider.as_ref()?; + let params = lsp::ExecuteCommandParams { command: command.command, arguments: command.arguments.unwrap_or_default(), @@ -968,6 +1075,6 @@ impl Client { }, }; - self.call::(params) + Some(self.call::(params)) } } -- cgit v1.2.3-70-g09d2