diff --git a/crates/languages/src/typescript.rs b/crates/languages/src/typescript.rs index 0217c03358..6be2d5d82d 100644 --- a/crates/languages/src/typescript.rs +++ b/crates/languages/src/typescript.rs @@ -310,6 +310,13 @@ impl EsLintLspAdapter { #[async_trait(?Send)] impl LspAdapter for EsLintLspAdapter { + fn code_action_kinds(&self) -> Option> { + Some(vec![ + CodeActionKind::QUICKFIX, + CodeActionKind::new("source.fixAll.eslint"), + ]) + } + async fn workspace_configuration( self: Arc, delegate: &Arc, diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 7957079f51..d3cce5dbe3 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -209,6 +209,14 @@ impl LspRequestFuture for LspRequest { } } +/// Combined capabilities of the server and the adapter. +pub struct AdapterServerCapabilities { + // Reported capabilities by the server + pub server_capabilities: ServerCapabilities, + // List of code actions supported by the LspAdapter matching the server + pub code_action_kinds: Option>, +} + /// Experimental: Informs the end user about the state of the server /// /// [Rust Analyzer Specification](https://github.com/rust-lang/rust-analyzer/blob/master/docs/dev/lsp-extensions.md#server-status) @@ -916,6 +924,15 @@ impl LanguageServer { self.capabilities.read().clone() } + /// Get the reported capabilities of the running language server and + /// what we know on the client/adapter-side of its capabilities. + pub fn adapter_server_capabilities(&self) -> AdapterServerCapabilities { + AdapterServerCapabilities { + server_capabilities: self.capabilities(), + code_action_kinds: self.code_action_kinds(), + } + } + pub fn update_capabilities(&self, update: impl FnOnce(&mut ServerCapabilities)) { update(self.capabilities.write().deref_mut()); } diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 645ebf6af6..ac4aadc568 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -9,6 +9,7 @@ use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::proto::{self, PeerId}; use clock::Global; +use collections::HashSet; use futures::future; use gpui::{AppContext, AsyncAppContext, Model}; use language::{ @@ -19,9 +20,10 @@ use language::{ OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, Unclipped, }; use lsp::{ - CompletionContext, CompletionListItemDefaultsEditRange, CompletionTriggerKind, - DocumentHighlightKind, LanguageServer, LanguageServerId, LinkedEditingRangeServerCapabilities, - OneOf, ServerCapabilities, + AdapterServerCapabilities, CodeActionKind, CodeActionOptions, CompletionContext, + CompletionListItemDefaultsEditRange, CompletionTriggerKind, DocumentHighlightKind, + LanguageServer, LanguageServerId, LinkedEditingRangeServerCapabilities, OneOf, + ServerCapabilities, }; use signature_help::{lsp_to_proto_signature, proto_to_lsp_signature}; use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc}; @@ -48,7 +50,7 @@ pub trait LspCommand: 'static + Sized + Send { type LspRequest: 'static + Send + lsp::request::Request; type ProtoRequest: 'static + Send + proto::RequestMessage; - fn check_capabilities(&self, _: &lsp::ServerCapabilities) -> bool { + fn check_capabilities(&self, _: AdapterServerCapabilities) -> bool { true } @@ -173,8 +175,8 @@ impl LspCommand for PrepareRename { type LspRequest = lsp::request::PrepareRenameRequest; type ProtoRequest = proto::PrepareRename; - fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool { - if let Some(lsp::OneOf::Right(rename)) = &capabilities.rename_provider { + fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool { + if let Some(lsp::OneOf::Right(rename)) = &capabilities.server_capabilities.rename_provider { rename.prepare_provider == Some(true) } else { false @@ -611,8 +613,8 @@ impl LspCommand for GetTypeDefinition { type LspRequest = lsp::request::GotoTypeDefinition; type ProtoRequest = proto::GetTypeDefinition; - fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool { - match &capabilities.type_definition_provider { + fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool { + match &capabilities.server_capabilities.type_definition_provider { None => false, Some(lsp::TypeDefinitionProviderCapability::Simple(false)) => false, _ => true, @@ -914,8 +916,8 @@ impl LspCommand for GetReferences { return Some("Finding references...".to_owned()); } - fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool { - match &capabilities.references_provider { + fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool { + match &capabilities.server_capabilities.references_provider { Some(OneOf::Left(has_support)) => *has_support, Some(OneOf::Right(_)) => true, None => false, @@ -1085,8 +1087,11 @@ impl LspCommand for GetDocumentHighlights { type LspRequest = lsp::request::DocumentHighlightRequest; type ProtoRequest = proto::GetDocumentHighlights; - fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool { - capabilities.document_highlight_provider.is_some() + fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool { + capabilities + .server_capabilities + .document_highlight_provider + .is_some() } fn to_lsp( @@ -1236,8 +1241,11 @@ impl LspCommand for GetSignatureHelp { type LspRequest = lsp::SignatureHelpRequest; type ProtoRequest = proto::GetSignatureHelp; - fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool { - capabilities.signature_help_provider.is_some() + fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool { + capabilities + .server_capabilities + .signature_help_provider + .is_some() } fn to_lsp( @@ -1860,11 +1868,25 @@ impl LspCommand for GetCodeActions { type LspRequest = lsp::request::CodeActionRequest; type ProtoRequest = proto::GetCodeActions; - fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool { - match &capabilities.code_action_provider { + fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool { + match &capabilities.server_capabilities.code_action_provider { None => false, Some(lsp::CodeActionProviderCapability::Simple(false)) => false, - _ => true, + _ => { + // If we do know that we want specific code actions AND we know that + // the server only supports specific code actions, then we want to filter + // down to the ones that are supported. + if let Some((requested, supported)) = self + .kinds + .as_ref() + .zip(Self::supported_code_action_kinds(capabilities)) + { + let server_supported = supported.into_iter().collect::>(); + requested.iter().any(|kind| server_supported.contains(kind)) + } else { + true + } + } } } @@ -1881,6 +1903,26 @@ impl LspCommand for GetCodeActions { .map(|entry| entry.to_lsp_diagnostic_stub()) .collect::>(); + let supported = + Self::supported_code_action_kinds(language_server.adapter_server_capabilities()); + + let only = if let Some(requested) = &self.kinds { + if let Some(supported_kinds) = supported { + let server_supported = supported_kinds.into_iter().collect::>(); + + let filtered = requested + .iter() + .filter(|kind| server_supported.contains(kind)) + .cloned() + .collect(); + Some(filtered) + } else { + Some(requested.clone()) + } + } else { + supported + }; + lsp::CodeActionParams { text_document: lsp::TextDocumentIdentifier::new( lsp::Url::from_file_path(path).unwrap(), @@ -1890,10 +1932,7 @@ impl LspCommand for GetCodeActions { partial_result_params: Default::default(), context: lsp::CodeActionContext { diagnostics: relevant_diagnostics, - only: self - .kinds - .clone() - .or_else(|| language_server.code_action_kinds()), + only, ..lsp::CodeActionContext::default() }, } @@ -2001,6 +2040,18 @@ impl LspCommand for GetCodeActions { } impl GetCodeActions { + fn supported_code_action_kinds( + capabilities: AdapterServerCapabilities, + ) -> Option> { + match capabilities.server_capabilities.code_action_provider { + Some(lsp::CodeActionProviderCapability::Options(CodeActionOptions { + code_action_kinds: Some(supported_action_kinds), + .. + })) => Some(supported_action_kinds.clone()), + _ => capabilities.code_action_kinds, + } + } + pub fn can_resolve_actions(capabilities: &ServerCapabilities) -> bool { capabilities .code_action_provider @@ -2030,9 +2081,10 @@ impl LspCommand for OnTypeFormatting { type LspRequest = lsp::request::OnTypeFormatting; type ProtoRequest = proto::OnTypeFormatting; - fn check_capabilities(&self, server_capabilities: &lsp::ServerCapabilities) -> bool { - let Some(on_type_formatting_options) = - &server_capabilities.document_on_type_formatting_provider + fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool { + let Some(on_type_formatting_options) = &capabilities + .server_capabilities + .document_on_type_formatting_provider else { return false; }; @@ -2536,8 +2588,9 @@ impl LspCommand for InlayHints { type LspRequest = lsp::InlayHintRequest; type ProtoRequest = proto::InlayHints; - fn check_capabilities(&self, server_capabilities: &lsp::ServerCapabilities) -> bool { - let Some(inlay_hint_provider) = &server_capabilities.inlay_hint_provider else { + fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool { + let Some(inlay_hint_provider) = &capabilities.server_capabilities.inlay_hint_provider + else { return false; }; match inlay_hint_provider { @@ -2693,8 +2746,10 @@ impl LspCommand for LinkedEditingRange { type LspRequest = lsp::request::LinkedEditingRange; type ProtoRequest = proto::LinkedEditingRange; - fn check_capabilities(&self, server_capabilities: &lsp::ServerCapabilities) -> bool { - let Some(linked_editing_options) = &server_capabilities.linked_editing_range_provider + fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool { + let Some(linked_editing_options) = &capabilities + .server_capabilities + .linked_editing_range_provider else { return false; }; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index ef00e9b5ee..e5c223aa38 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -7355,7 +7355,7 @@ impl Project { let lsp_params = request.to_lsp(&file.abs_path(cx), buffer, &language_server, cx); let status = request.status(); return cx.spawn(move |this, cx| async move { - if !request.check_capabilities(&language_server.capabilities()) { + if !request.check_capabilities(language_server.adapter_server_capabilities()) { return Ok(Default::default()); }