mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-16 00:47:39 +03:00
lsp: Check which code actions are supported before request (#14666)
This fixes https://github.com/zed-industries/zed/issues/13633 by not sending `source.organizeImports` to the ESLint language server anymore. Turns out that ESLint tells us through its capabilities that it doesn't support that code action kind, but we ignored that. What this code does is to check whether a given server supports specific code action kinds. It does this in two places: 1. When constructing the request: we now filter down the list of requested `kinds`, in case we can do so. If we can't filter down the list, we keep the previous behavior of sending the `language_server.code_action_kinds()` 2. Before sending the request: we now check whether the server even supports sending the request. This fixes the issue by only sending actions to servers that support it. I tested this with various language servers and setups and everything still works (or works better). But of course there are a ton of different combinations of language servers and code actions and file types, so I couldn't test them all. Release Notes: - Fix ESLint language server adding comments on save if the `source.organizeImports` code action was used on save. Zed now filters out code actions sent to the language servers by checking whether they are supported first. ([#13633](https://github.com/zed-industries/zed/issues/13633)).
This commit is contained in:
parent
49effeb7ba
commit
22a2cc6950
@ -310,6 +310,13 @@ impl EsLintLspAdapter {
|
|||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
impl LspAdapter for EsLintLspAdapter {
|
impl LspAdapter for EsLintLspAdapter {
|
||||||
|
fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
|
||||||
|
Some(vec![
|
||||||
|
CodeActionKind::QUICKFIX,
|
||||||
|
CodeActionKind::new("source.fixAll.eslint"),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
async fn workspace_configuration(
|
async fn workspace_configuration(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||||
|
@ -209,6 +209,14 @@ impl<F: Future> LspRequestFuture<F::Output> for LspRequest<F> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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<Vec<CodeActionKind>>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Experimental: Informs the end user about the state of the server
|
/// 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)
|
/// [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()
|
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)) {
|
pub fn update_capabilities(&self, update: impl FnOnce(&mut ServerCapabilities)) {
|
||||||
update(self.capabilities.write().deref_mut());
|
update(self.capabilities.write().deref_mut());
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ use anyhow::{anyhow, Context, Result};
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use client::proto::{self, PeerId};
|
use client::proto::{self, PeerId};
|
||||||
use clock::Global;
|
use clock::Global;
|
||||||
|
use collections::HashSet;
|
||||||
use futures::future;
|
use futures::future;
|
||||||
use gpui::{AppContext, AsyncAppContext, Model};
|
use gpui::{AppContext, AsyncAppContext, Model};
|
||||||
use language::{
|
use language::{
|
||||||
@ -19,9 +20,10 @@ use language::{
|
|||||||
OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, Unclipped,
|
OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, Unclipped,
|
||||||
};
|
};
|
||||||
use lsp::{
|
use lsp::{
|
||||||
CompletionContext, CompletionListItemDefaultsEditRange, CompletionTriggerKind,
|
AdapterServerCapabilities, CodeActionKind, CodeActionOptions, CompletionContext,
|
||||||
DocumentHighlightKind, LanguageServer, LanguageServerId, LinkedEditingRangeServerCapabilities,
|
CompletionListItemDefaultsEditRange, CompletionTriggerKind, DocumentHighlightKind,
|
||||||
OneOf, ServerCapabilities,
|
LanguageServer, LanguageServerId, LinkedEditingRangeServerCapabilities, OneOf,
|
||||||
|
ServerCapabilities,
|
||||||
};
|
};
|
||||||
use signature_help::{lsp_to_proto_signature, proto_to_lsp_signature};
|
use signature_help::{lsp_to_proto_signature, proto_to_lsp_signature};
|
||||||
use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
|
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 LspRequest: 'static + Send + lsp::request::Request;
|
||||||
type ProtoRequest: 'static + Send + proto::RequestMessage;
|
type ProtoRequest: 'static + Send + proto::RequestMessage;
|
||||||
|
|
||||||
fn check_capabilities(&self, _: &lsp::ServerCapabilities) -> bool {
|
fn check_capabilities(&self, _: AdapterServerCapabilities) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,8 +175,8 @@ impl LspCommand for PrepareRename {
|
|||||||
type LspRequest = lsp::request::PrepareRenameRequest;
|
type LspRequest = lsp::request::PrepareRenameRequest;
|
||||||
type ProtoRequest = proto::PrepareRename;
|
type ProtoRequest = proto::PrepareRename;
|
||||||
|
|
||||||
fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool {
|
fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
|
||||||
if let Some(lsp::OneOf::Right(rename)) = &capabilities.rename_provider {
|
if let Some(lsp::OneOf::Right(rename)) = &capabilities.server_capabilities.rename_provider {
|
||||||
rename.prepare_provider == Some(true)
|
rename.prepare_provider == Some(true)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
@ -611,8 +613,8 @@ impl LspCommand for GetTypeDefinition {
|
|||||||
type LspRequest = lsp::request::GotoTypeDefinition;
|
type LspRequest = lsp::request::GotoTypeDefinition;
|
||||||
type ProtoRequest = proto::GetTypeDefinition;
|
type ProtoRequest = proto::GetTypeDefinition;
|
||||||
|
|
||||||
fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool {
|
fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
|
||||||
match &capabilities.type_definition_provider {
|
match &capabilities.server_capabilities.type_definition_provider {
|
||||||
None => false,
|
None => false,
|
||||||
Some(lsp::TypeDefinitionProviderCapability::Simple(false)) => false,
|
Some(lsp::TypeDefinitionProviderCapability::Simple(false)) => false,
|
||||||
_ => true,
|
_ => true,
|
||||||
@ -914,8 +916,8 @@ impl LspCommand for GetReferences {
|
|||||||
return Some("Finding references...".to_owned());
|
return Some("Finding references...".to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool {
|
fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
|
||||||
match &capabilities.references_provider {
|
match &capabilities.server_capabilities.references_provider {
|
||||||
Some(OneOf::Left(has_support)) => *has_support,
|
Some(OneOf::Left(has_support)) => *has_support,
|
||||||
Some(OneOf::Right(_)) => true,
|
Some(OneOf::Right(_)) => true,
|
||||||
None => false,
|
None => false,
|
||||||
@ -1085,8 +1087,11 @@ impl LspCommand for GetDocumentHighlights {
|
|||||||
type LspRequest = lsp::request::DocumentHighlightRequest;
|
type LspRequest = lsp::request::DocumentHighlightRequest;
|
||||||
type ProtoRequest = proto::GetDocumentHighlights;
|
type ProtoRequest = proto::GetDocumentHighlights;
|
||||||
|
|
||||||
fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool {
|
fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
|
||||||
capabilities.document_highlight_provider.is_some()
|
capabilities
|
||||||
|
.server_capabilities
|
||||||
|
.document_highlight_provider
|
||||||
|
.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_lsp(
|
fn to_lsp(
|
||||||
@ -1236,8 +1241,11 @@ impl LspCommand for GetSignatureHelp {
|
|||||||
type LspRequest = lsp::SignatureHelpRequest;
|
type LspRequest = lsp::SignatureHelpRequest;
|
||||||
type ProtoRequest = proto::GetSignatureHelp;
|
type ProtoRequest = proto::GetSignatureHelp;
|
||||||
|
|
||||||
fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool {
|
fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
|
||||||
capabilities.signature_help_provider.is_some()
|
capabilities
|
||||||
|
.server_capabilities
|
||||||
|
.signature_help_provider
|
||||||
|
.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_lsp(
|
fn to_lsp(
|
||||||
@ -1860,11 +1868,25 @@ impl LspCommand for GetCodeActions {
|
|||||||
type LspRequest = lsp::request::CodeActionRequest;
|
type LspRequest = lsp::request::CodeActionRequest;
|
||||||
type ProtoRequest = proto::GetCodeActions;
|
type ProtoRequest = proto::GetCodeActions;
|
||||||
|
|
||||||
fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool {
|
fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
|
||||||
match &capabilities.code_action_provider {
|
match &capabilities.server_capabilities.code_action_provider {
|
||||||
None => false,
|
None => false,
|
||||||
Some(lsp::CodeActionProviderCapability::Simple(false)) => 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::<HashSet<_>>();
|
||||||
|
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())
|
.map(|entry| entry.to_lsp_diagnostic_stub())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
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::<HashSet<_>>();
|
||||||
|
|
||||||
|
let filtered = requested
|
||||||
|
.iter()
|
||||||
|
.filter(|kind| server_supported.contains(kind))
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
Some(filtered)
|
||||||
|
} else {
|
||||||
|
Some(requested.clone())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
supported
|
||||||
|
};
|
||||||
|
|
||||||
lsp::CodeActionParams {
|
lsp::CodeActionParams {
|
||||||
text_document: lsp::TextDocumentIdentifier::new(
|
text_document: lsp::TextDocumentIdentifier::new(
|
||||||
lsp::Url::from_file_path(path).unwrap(),
|
lsp::Url::from_file_path(path).unwrap(),
|
||||||
@ -1890,10 +1932,7 @@ impl LspCommand for GetCodeActions {
|
|||||||
partial_result_params: Default::default(),
|
partial_result_params: Default::default(),
|
||||||
context: lsp::CodeActionContext {
|
context: lsp::CodeActionContext {
|
||||||
diagnostics: relevant_diagnostics,
|
diagnostics: relevant_diagnostics,
|
||||||
only: self
|
only,
|
||||||
.kinds
|
|
||||||
.clone()
|
|
||||||
.or_else(|| language_server.code_action_kinds()),
|
|
||||||
..lsp::CodeActionContext::default()
|
..lsp::CodeActionContext::default()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -2001,6 +2040,18 @@ impl LspCommand for GetCodeActions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl GetCodeActions {
|
impl GetCodeActions {
|
||||||
|
fn supported_code_action_kinds(
|
||||||
|
capabilities: AdapterServerCapabilities,
|
||||||
|
) -> Option<Vec<CodeActionKind>> {
|
||||||
|
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 {
|
pub fn can_resolve_actions(capabilities: &ServerCapabilities) -> bool {
|
||||||
capabilities
|
capabilities
|
||||||
.code_action_provider
|
.code_action_provider
|
||||||
@ -2030,9 +2081,10 @@ impl LspCommand for OnTypeFormatting {
|
|||||||
type LspRequest = lsp::request::OnTypeFormatting;
|
type LspRequest = lsp::request::OnTypeFormatting;
|
||||||
type ProtoRequest = proto::OnTypeFormatting;
|
type ProtoRequest = proto::OnTypeFormatting;
|
||||||
|
|
||||||
fn check_capabilities(&self, server_capabilities: &lsp::ServerCapabilities) -> bool {
|
fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
|
||||||
let Some(on_type_formatting_options) =
|
let Some(on_type_formatting_options) = &capabilities
|
||||||
&server_capabilities.document_on_type_formatting_provider
|
.server_capabilities
|
||||||
|
.document_on_type_formatting_provider
|
||||||
else {
|
else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
@ -2536,8 +2588,9 @@ impl LspCommand for InlayHints {
|
|||||||
type LspRequest = lsp::InlayHintRequest;
|
type LspRequest = lsp::InlayHintRequest;
|
||||||
type ProtoRequest = proto::InlayHints;
|
type ProtoRequest = proto::InlayHints;
|
||||||
|
|
||||||
fn check_capabilities(&self, server_capabilities: &lsp::ServerCapabilities) -> bool {
|
fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
|
||||||
let Some(inlay_hint_provider) = &server_capabilities.inlay_hint_provider else {
|
let Some(inlay_hint_provider) = &capabilities.server_capabilities.inlay_hint_provider
|
||||||
|
else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
match inlay_hint_provider {
|
match inlay_hint_provider {
|
||||||
@ -2693,8 +2746,10 @@ impl LspCommand for LinkedEditingRange {
|
|||||||
type LspRequest = lsp::request::LinkedEditingRange;
|
type LspRequest = lsp::request::LinkedEditingRange;
|
||||||
type ProtoRequest = proto::LinkedEditingRange;
|
type ProtoRequest = proto::LinkedEditingRange;
|
||||||
|
|
||||||
fn check_capabilities(&self, server_capabilities: &lsp::ServerCapabilities) -> bool {
|
fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
|
||||||
let Some(linked_editing_options) = &server_capabilities.linked_editing_range_provider
|
let Some(linked_editing_options) = &capabilities
|
||||||
|
.server_capabilities
|
||||||
|
.linked_editing_range_provider
|
||||||
else {
|
else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
@ -7355,7 +7355,7 @@ impl Project {
|
|||||||
let lsp_params = request.to_lsp(&file.abs_path(cx), buffer, &language_server, cx);
|
let lsp_params = request.to_lsp(&file.abs_path(cx), buffer, &language_server, cx);
|
||||||
let status = request.status();
|
let status = request.status();
|
||||||
return cx.spawn(move |this, cx| async move {
|
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());
|
return Ok(Default::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user