Support workspace/executeCommand
for actions' data (#26239)
Closes https://github.com/zed-industries/zed/issues/16746 Part of https://github.com/zed-extensions/deno/issues/2 Changes the action-related code so, that * `lsp::Command` as actions are supported, if server replies with them * actions with commands are filtered out based on servers' `executeCommandOptions` (https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#executeCommandOptions) — commands that are not listed won't be executed and the corresponding actions will be hidden in Zed Release Notes: - Added support of `workspace/executeCommand` for actions' data --------- Co-authored-by: Peter Tripp <petertripp@gmail.com>
This commit is contained in:
parent
97c0a0a86e
commit
af5af9d7c5
9 changed files with 217 additions and 87 deletions
|
@ -38,7 +38,7 @@ use language_model::{
|
||||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
||||||
use multi_buffer::MultiBufferRow;
|
use multi_buffer::MultiBufferRow;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use project::{CodeAction, ProjectTransaction};
|
use project::{ActionVariant, CodeAction, ProjectTransaction};
|
||||||
use prompt_store::PromptBuilder;
|
use prompt_store::PromptBuilder;
|
||||||
use rope::Rope;
|
use rope::Rope;
|
||||||
use settings::{update_settings_file, Settings, SettingsStore};
|
use settings::{update_settings_file, Settings, SettingsStore};
|
||||||
|
@ -3569,10 +3569,10 @@ impl CodeActionProvider for AssistantCodeActionProvider {
|
||||||
Task::ready(Ok(vec![CodeAction {
|
Task::ready(Ok(vec![CodeAction {
|
||||||
server_id: language::LanguageServerId(0),
|
server_id: language::LanguageServerId(0),
|
||||||
range: snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end),
|
range: snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end),
|
||||||
lsp_action: lsp::CodeAction {
|
lsp_action: ActionVariant::Action(Box::new(lsp::CodeAction {
|
||||||
title: "Fix with Assistant".into(),
|
title: "Fix with Assistant".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
})),
|
||||||
}]))
|
}]))
|
||||||
} else {
|
} else {
|
||||||
Task::ready(Ok(Vec::new()))
|
Task::ready(Ok(Vec::new()))
|
||||||
|
|
|
@ -27,6 +27,7 @@ use language::{Buffer, Point, Selection, TransactionId};
|
||||||
use language_model::{report_assistant_event, LanguageModelRegistry};
|
use language_model::{report_assistant_event, LanguageModelRegistry};
|
||||||
use multi_buffer::MultiBufferRow;
|
use multi_buffer::MultiBufferRow;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
use project::ActionVariant;
|
||||||
use project::{CodeAction, ProjectTransaction};
|
use project::{CodeAction, ProjectTransaction};
|
||||||
use prompt_store::PromptBuilder;
|
use prompt_store::PromptBuilder;
|
||||||
use settings::{Settings, SettingsStore};
|
use settings::{Settings, SettingsStore};
|
||||||
|
@ -1727,10 +1728,10 @@ impl CodeActionProvider for AssistantCodeActionProvider {
|
||||||
Task::ready(Ok(vec![CodeAction {
|
Task::ready(Ok(vec![CodeAction {
|
||||||
server_id: language::LanguageServerId(0),
|
server_id: language::LanguageServerId(0),
|
||||||
range: snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end),
|
range: snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end),
|
||||||
lsp_action: lsp::CodeAction {
|
lsp_action: ActionVariant::Action(Box::new(lsp::CodeAction {
|
||||||
title: "Fix with Assistant".into(),
|
title: "Fix with Assistant".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
})),
|
||||||
}]))
|
}]))
|
||||||
} else {
|
} else {
|
||||||
Task::ready(Ok(Vec::new()))
|
Task::ready(Ok(Vec::new()))
|
||||||
|
|
|
@ -851,7 +851,7 @@ impl CodeActionsItem {
|
||||||
|
|
||||||
pub fn label(&self) -> String {
|
pub fn label(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Self::CodeAction { action, .. } => action.lsp_action.title.clone(),
|
Self::CodeAction { action, .. } => action.lsp_action.title().to_owned(),
|
||||||
Self::Task(_, task) => task.resolved_label.clone(),
|
Self::Task(_, task) => task.resolved_label.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -984,7 +984,7 @@ impl CodeActionsMenu {
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.child(
|
.child(
|
||||||
// TASK: It would be good to make lsp_action.title a SharedString to avoid allocating here.
|
// TASK: It would be good to make lsp_action.title a SharedString to avoid allocating here.
|
||||||
action.lsp_action.title.replace("\n", ""),
|
action.lsp_action.title().replace("\n", ""),
|
||||||
)
|
)
|
||||||
.when(selected, |this| {
|
.when(selected, |this| {
|
||||||
this.text_color(colors.text_accent)
|
this.text_color(colors.text_accent)
|
||||||
|
@ -1029,7 +1029,7 @@ impl CodeActionsMenu {
|
||||||
.max_by_key(|(_, action)| match action {
|
.max_by_key(|(_, action)| match action {
|
||||||
CodeActionsItem::Task(_, task) => task.resolved_label.chars().count(),
|
CodeActionsItem::Task(_, task) => task.resolved_label.chars().count(),
|
||||||
CodeActionsItem::CodeAction { action, .. } => {
|
CodeActionsItem::CodeAction { action, .. } => {
|
||||||
action.lsp_action.title.chars().count()
|
action.lsp_action.title().chars().count()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map(|(ix, _)| ix),
|
.map(|(ix, _)| ix),
|
||||||
|
|
|
@ -679,6 +679,9 @@ impl LanguageServer {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
apply_edit: Some(true),
|
apply_edit: Some(true),
|
||||||
|
execute_command: Some(ExecuteCommandClientCapabilities {
|
||||||
|
dynamic_registration: Some(false),
|
||||||
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
text_document: Some(TextDocumentClientCapabilities {
|
text_document: Some(TextDocumentClientCapabilities {
|
||||||
|
|
|
@ -2,9 +2,10 @@ mod signature_help;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
lsp_store::{LocalLspStore, LspStore},
|
lsp_store::{LocalLspStore, LspStore},
|
||||||
CodeAction, CoreCompletion, DocumentHighlight, Hover, HoverBlock, HoverBlockKind, InlayHint,
|
ActionVariant, CodeAction, CoreCompletion, DocumentHighlight, Hover, HoverBlock,
|
||||||
InlayHintLabel, InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location,
|
HoverBlockKind, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayHintLabelPartTooltip,
|
||||||
LocationLink, MarkupContent, PrepareRenameResponse, ProjectTransaction, ResolveState,
|
InlayHintTooltip, Location, LocationLink, MarkupContent, PrepareRenameResponse,
|
||||||
|
ProjectTransaction, ResolveState,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
@ -2218,10 +2219,10 @@ impl LspCommand for GetCodeActions {
|
||||||
async fn response_from_lsp(
|
async fn response_from_lsp(
|
||||||
self,
|
self,
|
||||||
actions: Option<lsp::CodeActionResponse>,
|
actions: Option<lsp::CodeActionResponse>,
|
||||||
_: Entity<LspStore>,
|
lsp_store: Entity<LspStore>,
|
||||||
_: Entity<Buffer>,
|
_: Entity<Buffer>,
|
||||||
server_id: LanguageServerId,
|
server_id: LanguageServerId,
|
||||||
_: AsyncApp,
|
cx: AsyncApp,
|
||||||
) -> Result<Vec<CodeAction>> {
|
) -> Result<Vec<CodeAction>> {
|
||||||
let requested_kinds_set = if let Some(kinds) = self.kinds {
|
let requested_kinds_set = if let Some(kinds) = self.kinds {
|
||||||
Some(kinds.into_iter().collect::<HashSet<_>>())
|
Some(kinds.into_iter().collect::<HashSet<_>>())
|
||||||
|
@ -2229,18 +2230,47 @@ impl LspCommand for GetCodeActions {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let language_server = cx.update(|cx| {
|
||||||
|
lsp_store
|
||||||
|
.read(cx)
|
||||||
|
.language_server_for_id(server_id)
|
||||||
|
.with_context(|| {
|
||||||
|
format!("Missing the language server that just returned a response {server_id}")
|
||||||
|
})
|
||||||
|
})??;
|
||||||
|
|
||||||
|
let server_capabilities = language_server.capabilities();
|
||||||
|
let available_commands = server_capabilities
|
||||||
|
.execute_command_provider
|
||||||
|
.as_ref()
|
||||||
|
.map(|options| options.commands.as_slice())
|
||||||
|
.unwrap_or_default();
|
||||||
Ok(actions
|
Ok(actions
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|entry| {
|
.filter_map(|entry| {
|
||||||
let lsp::CodeActionOrCommand::CodeAction(lsp_action) = entry else {
|
let lsp_action = match entry {
|
||||||
return None;
|
lsp::CodeActionOrCommand::CodeAction(lsp_action) => {
|
||||||
|
if let Some(command) = lsp_action.command.as_ref() {
|
||||||
|
if !available_commands.contains(&command.command) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ActionVariant::Action(Box::new(lsp_action))
|
||||||
|
}
|
||||||
|
lsp::CodeActionOrCommand::Command(command) => {
|
||||||
|
if available_commands.contains(&command.command) {
|
||||||
|
ActionVariant::Command(command)
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some((requested_kinds, kind)) =
|
if let Some((requested_kinds, kind)) =
|
||||||
requested_kinds_set.as_ref().zip(lsp_action.kind.as_ref())
|
requested_kinds_set.as_ref().zip(lsp_action.action_kind())
|
||||||
{
|
{
|
||||||
if !requested_kinds.contains(kind) {
|
if !requested_kinds.contains(&kind) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,8 @@ use crate::{
|
||||||
toolchain_store::{EmptyToolchainStore, ToolchainStoreEvent},
|
toolchain_store::{EmptyToolchainStore, ToolchainStoreEvent},
|
||||||
worktree_store::{WorktreeStore, WorktreeStoreEvent},
|
worktree_store::{WorktreeStore, WorktreeStoreEvent},
|
||||||
yarn::YarnPathStore,
|
yarn::YarnPathStore,
|
||||||
CodeAction, Completion, CoreCompletion, Hover, InlayHint, ProjectItem as _, ProjectPath,
|
ActionVariant, CodeAction, Completion, CoreCompletion, Hover, InlayHint, ProjectItem as _,
|
||||||
ProjectTransaction, ResolveState, Symbol, ToolchainStore,
|
ProjectPath, ProjectTransaction, ResolveState, Symbol, ToolchainStore,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
@ -1666,15 +1666,21 @@ impl LocalLspStore {
|
||||||
lang_server: &LanguageServer,
|
lang_server: &LanguageServer,
|
||||||
action: &mut CodeAction,
|
action: &mut CodeAction,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
if GetCodeActions::can_resolve_actions(&lang_server.capabilities())
|
match &mut action.lsp_action {
|
||||||
&& action.lsp_action.data.is_some()
|
ActionVariant::Action(lsp_action) => {
|
||||||
&& (action.lsp_action.command.is_none() || action.lsp_action.edit.is_none())
|
if GetCodeActions::can_resolve_actions(&lang_server.capabilities())
|
||||||
{
|
&& lsp_action.data.is_some()
|
||||||
action.lsp_action = lang_server
|
&& (lsp_action.command.is_none() || lsp_action.edit.is_none())
|
||||||
.request::<lsp::request::CodeActionResolveRequest>(action.lsp_action.clone())
|
{
|
||||||
.await?;
|
*lsp_action = Box::new(
|
||||||
|
lang_server
|
||||||
|
.request::<lsp::request::CodeActionResolveRequest>(*lsp_action.clone())
|
||||||
|
.await?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ActionVariant::Command(_) => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2102,7 +2108,7 @@ impl LocalLspStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute_code_actions_on_servers(
|
async fn execute_code_actions_on_servers(
|
||||||
this: &WeakEntity<LspStore>,
|
lsp_store: &WeakEntity<LspStore>,
|
||||||
adapters_and_servers: &[(Arc<CachedLspAdapter>, Arc<LanguageServer>)],
|
adapters_and_servers: &[(Arc<CachedLspAdapter>, Arc<LanguageServer>)],
|
||||||
code_actions: Vec<lsp::CodeActionKind>,
|
code_actions: Vec<lsp::CodeActionKind>,
|
||||||
buffer: &Entity<Buffer>,
|
buffer: &Entity<Buffer>,
|
||||||
|
@ -2113,7 +2119,7 @@ impl LocalLspStore {
|
||||||
for (lsp_adapter, language_server) in adapters_and_servers.iter() {
|
for (lsp_adapter, language_server) in adapters_and_servers.iter() {
|
||||||
let code_actions = code_actions.clone();
|
let code_actions = code_actions.clone();
|
||||||
|
|
||||||
let actions = this
|
let actions = lsp_store
|
||||||
.update(cx, move |this, cx| {
|
.update(cx, move |this, cx| {
|
||||||
let request = GetCodeActions {
|
let request = GetCodeActions {
|
||||||
range: text::Anchor::MIN..text::Anchor::MAX,
|
range: text::Anchor::MIN..text::Anchor::MAX,
|
||||||
|
@ -2129,14 +2135,14 @@ impl LocalLspStore {
|
||||||
.await
|
.await
|
||||||
.context("resolving a formatting code action")?;
|
.context("resolving a formatting code action")?;
|
||||||
|
|
||||||
if let Some(edit) = action.lsp_action.edit {
|
if let Some(edit) = action.lsp_action.edit() {
|
||||||
if edit.changes.is_none() && edit.document_changes.is_none() {
|
if edit.changes.is_none() && edit.document_changes.is_none() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let new = Self::deserialize_workspace_edit(
|
let new = Self::deserialize_workspace_edit(
|
||||||
this.upgrade().ok_or_else(|| anyhow!("project dropped"))?,
|
lsp_store.upgrade().context("project dropped")?,
|
||||||
edit,
|
edit.clone(),
|
||||||
push_to_history,
|
push_to_history,
|
||||||
lsp_adapter.clone(),
|
lsp_adapter.clone(),
|
||||||
language_server.clone(),
|
language_server.clone(),
|
||||||
|
@ -2146,32 +2152,42 @@ impl LocalLspStore {
|
||||||
project_transaction.0.extend(new.0);
|
project_transaction.0.extend(new.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(command) = action.lsp_action.command {
|
if let Some(command) = action.lsp_action.command() {
|
||||||
this.update(cx, |this, _| {
|
let server_capabilities = language_server.capabilities();
|
||||||
if let LspStoreMode::Local(mode) = &mut this.mode {
|
let available_commands = server_capabilities
|
||||||
mode.last_workspace_edits_by_language_server
|
.execute_command_provider
|
||||||
.remove(&language_server.server_id());
|
.as_ref()
|
||||||
}
|
.map(|options| options.commands.as_slice())
|
||||||
})?;
|
.unwrap_or_default();
|
||||||
|
if available_commands.contains(&command.command) {
|
||||||
language_server
|
lsp_store.update(cx, |lsp_store, _| {
|
||||||
.request::<lsp::request::ExecuteCommand>(lsp::ExecuteCommandParams {
|
if let LspStoreMode::Local(mode) = &mut lsp_store.mode {
|
||||||
command: command.command,
|
|
||||||
arguments: command.arguments.unwrap_or_default(),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
this.update(cx, |this, _| {
|
|
||||||
if let LspStoreMode::Local(mode) = &mut this.mode {
|
|
||||||
project_transaction.0.extend(
|
|
||||||
mode.last_workspace_edits_by_language_server
|
mode.last_workspace_edits_by_language_server
|
||||||
.remove(&language_server.server_id())
|
.remove(&language_server.server_id());
|
||||||
.unwrap_or_default()
|
}
|
||||||
.0,
|
})?;
|
||||||
)
|
|
||||||
}
|
language_server
|
||||||
})?;
|
.request::<lsp::request::ExecuteCommand>(lsp::ExecuteCommandParams {
|
||||||
|
command: command.command.clone(),
|
||||||
|
arguments: command.arguments.clone().unwrap_or_default(),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
lsp_store.update(cx, |this, _| {
|
||||||
|
if let LspStoreMode::Local(mode) = &mut this.mode {
|
||||||
|
project_transaction.0.extend(
|
||||||
|
mode.last_workspace_edits_by_language_server
|
||||||
|
.remove(&language_server.server_id())
|
||||||
|
.unwrap_or_default()
|
||||||
|
.0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
} else {
|
||||||
|
log::warn!("Cannot execute a command {} not listed in the language server capabilities", command.command)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3896,17 +3912,17 @@ impl LspStore {
|
||||||
self.language_server_for_local_buffer(buffer, action.server_id, cx)
|
self.language_server_for_local_buffer(buffer, action.server_id, cx)
|
||||||
.map(|(adapter, server)| (adapter.clone(), server.clone()))
|
.map(|(adapter, server)| (adapter.clone(), server.clone()))
|
||||||
}) else {
|
}) else {
|
||||||
return Task::ready(Ok(Default::default()));
|
return Task::ready(Ok(ProjectTransaction::default()));
|
||||||
};
|
};
|
||||||
cx.spawn(move |this, mut cx| async move {
|
cx.spawn(move |this, mut cx| async move {
|
||||||
LocalLspStore::try_resolve_code_action(&lang_server, &mut action)
|
LocalLspStore::try_resolve_code_action(&lang_server, &mut action)
|
||||||
.await
|
.await
|
||||||
.context("resolving a code action")?;
|
.context("resolving a code action")?;
|
||||||
if let Some(edit) = action.lsp_action.edit {
|
if let Some(edit) = action.lsp_action.edit() {
|
||||||
if edit.changes.is_some() || edit.document_changes.is_some() {
|
if edit.changes.is_some() || edit.document_changes.is_some() {
|
||||||
return LocalLspStore::deserialize_workspace_edit(
|
return LocalLspStore::deserialize_workspace_edit(
|
||||||
this.upgrade().ok_or_else(|| anyhow!("no app present"))?,
|
this.upgrade().ok_or_else(|| anyhow!("no app present"))?,
|
||||||
edit,
|
edit.clone(),
|
||||||
push_to_history,
|
push_to_history,
|
||||||
lsp_adapter.clone(),
|
lsp_adapter.clone(),
|
||||||
lang_server.clone(),
|
lang_server.clone(),
|
||||||
|
@ -3916,31 +3932,41 @@ impl LspStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(command) = action.lsp_action.command {
|
if let Some(command) = action.lsp_action.command() {
|
||||||
this.update(&mut cx, |this, _| {
|
let server_capabilities = lang_server.capabilities();
|
||||||
this.as_local_mut()
|
let available_commands = server_capabilities
|
||||||
.unwrap()
|
.execute_command_provider
|
||||||
.last_workspace_edits_by_language_server
|
.as_ref()
|
||||||
.remove(&lang_server.server_id());
|
.map(|options| options.commands.as_slice())
|
||||||
})?;
|
.unwrap_or_default();
|
||||||
|
if available_commands.contains(&command.command) {
|
||||||
|
this.update(&mut cx, |this, _| {
|
||||||
|
this.as_local_mut()
|
||||||
|
.unwrap()
|
||||||
|
.last_workspace_edits_by_language_server
|
||||||
|
.remove(&lang_server.server_id());
|
||||||
|
})?;
|
||||||
|
|
||||||
let result = lang_server
|
let result = lang_server
|
||||||
.request::<lsp::request::ExecuteCommand>(lsp::ExecuteCommandParams {
|
.request::<lsp::request::ExecuteCommand>(lsp::ExecuteCommandParams {
|
||||||
command: command.command,
|
command: command.command.clone(),
|
||||||
arguments: command.arguments.unwrap_or_default(),
|
arguments: command.arguments.clone().unwrap_or_default(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
result?;
|
result?;
|
||||||
|
|
||||||
return this.update(&mut cx, |this, _| {
|
return this.update(&mut cx, |this, _| {
|
||||||
this.as_local_mut()
|
this.as_local_mut()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.last_workspace_edits_by_language_server
|
.last_workspace_edits_by_language_server
|
||||||
.remove(&lang_server.server_id())
|
.remove(&lang_server.server_id())
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
log::warn!("Cannot execute a command {} not listed in the language server capabilities", command.command);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ProjectTransaction::default())
|
Ok(ProjectTransaction::default())
|
||||||
|
@ -8158,11 +8184,23 @@ impl LspStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn serialize_code_action(action: &CodeAction) -> proto::CodeAction {
|
pub(crate) fn serialize_code_action(action: &CodeAction) -> proto::CodeAction {
|
||||||
|
let (kind, lsp_action) = match &action.lsp_action {
|
||||||
|
ActionVariant::Action(code_action) => (
|
||||||
|
proto::code_action::Kind::Action as i32,
|
||||||
|
serde_json::to_vec(code_action).unwrap(),
|
||||||
|
),
|
||||||
|
ActionVariant::Command(command) => (
|
||||||
|
proto::code_action::Kind::Command as i32,
|
||||||
|
serde_json::to_vec(command).unwrap(),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
proto::CodeAction {
|
proto::CodeAction {
|
||||||
server_id: action.server_id.0 as u64,
|
server_id: action.server_id.0 as u64,
|
||||||
start: Some(serialize_anchor(&action.range.start)),
|
start: Some(serialize_anchor(&action.range.start)),
|
||||||
end: Some(serialize_anchor(&action.range.end)),
|
end: Some(serialize_anchor(&action.range.end)),
|
||||||
lsp_action: serde_json::to_vec(&action.lsp_action).unwrap(),
|
lsp_action,
|
||||||
|
kind,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8175,7 +8213,15 @@ impl LspStore {
|
||||||
.end
|
.end
|
||||||
.and_then(deserialize_anchor)
|
.and_then(deserialize_anchor)
|
||||||
.ok_or_else(|| anyhow!("invalid end"))?;
|
.ok_or_else(|| anyhow!("invalid end"))?;
|
||||||
let lsp_action = serde_json::from_slice(&action.lsp_action)?;
|
let lsp_action = match proto::code_action::Kind::from_i32(action.kind) {
|
||||||
|
Some(proto::code_action::Kind::Action) => {
|
||||||
|
ActionVariant::Action(serde_json::from_slice(&action.lsp_action)?)
|
||||||
|
}
|
||||||
|
Some(proto::code_action::Kind::Command) => {
|
||||||
|
ActionVariant::Command(serde_json::from_slice(&action.lsp_action)?)
|
||||||
|
}
|
||||||
|
None => anyhow::bail!("Unknown action kind {}", action.kind),
|
||||||
|
};
|
||||||
Ok(CodeAction {
|
Ok(CodeAction {
|
||||||
server_id: LanguageServerId(action.server_id as usize),
|
server_id: LanguageServerId(action.server_id as usize),
|
||||||
range: start..end,
|
range: start..end,
|
||||||
|
|
|
@ -411,7 +411,48 @@ pub struct CodeAction {
|
||||||
/// The range of the buffer where this code action is applicable.
|
/// The range of the buffer where this code action is applicable.
|
||||||
pub range: Range<Anchor>,
|
pub range: Range<Anchor>,
|
||||||
/// The raw code action provided by the language server.
|
/// The raw code action provided by the language server.
|
||||||
pub lsp_action: lsp::CodeAction,
|
/// Can be either an action or a command.
|
||||||
|
pub lsp_action: ActionVariant,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An action sent back by a language server.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum ActionVariant {
|
||||||
|
/// An action with the full data, may have a command or may not.
|
||||||
|
/// May require resolving.
|
||||||
|
Action(Box<lsp::CodeAction>),
|
||||||
|
/// A command data to run as an action.
|
||||||
|
Command(lsp::Command),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActionVariant {
|
||||||
|
pub fn title(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::Action(action) => &action.title,
|
||||||
|
Self::Command(command) => &command.title,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn action_kind(&self) -> Option<lsp::CodeActionKind> {
|
||||||
|
match self {
|
||||||
|
Self::Action(action) => action.kind.clone(),
|
||||||
|
Self::Command(_) => Some(lsp::CodeActionKind::new("command")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn edit(&self) -> Option<&lsp::WorkspaceEdit> {
|
||||||
|
match self {
|
||||||
|
Self::Action(action) => action.edit.as_ref(),
|
||||||
|
Self::Command(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command(&self) -> Option<&lsp::Command> {
|
||||||
|
match self {
|
||||||
|
Self::Action(action) => action.command.as_ref(),
|
||||||
|
Self::Command(command) => Some(command),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
|
|
@ -2951,6 +2951,10 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
|
||||||
..lsp::CodeActionOptions::default()
|
..lsp::CodeActionOptions::default()
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
|
execute_command_provider: Some(lsp::ExecuteCommandOptions {
|
||||||
|
commands: vec!["_the/command".to_string()],
|
||||||
|
..lsp::ExecuteCommandOptions::default()
|
||||||
|
}),
|
||||||
..lsp::ServerCapabilities::default()
|
..lsp::ServerCapabilities::default()
|
||||||
},
|
},
|
||||||
..FakeLspAdapter::default()
|
..FakeLspAdapter::default()
|
||||||
|
@ -5372,7 +5376,7 @@ async fn test_code_actions_only_kinds(cx: &mut gpui::TestAppContext) {
|
||||||
let code_actions = code_actions_task.await.unwrap();
|
let code_actions = code_actions_task.await.unwrap();
|
||||||
assert_eq!(code_actions.len(), 1);
|
assert_eq!(code_actions.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
code_actions[0].lsp_action.kind,
|
code_actions[0].lsp_action.action_kind(),
|
||||||
Some(CodeActionKind::SOURCE_ORGANIZE_IMPORTS)
|
Some(CodeActionKind::SOURCE_ORGANIZE_IMPORTS)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5529,7 +5533,7 @@ async fn test_multiple_language_server_actions(cx: &mut gpui::TestAppContext) {
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|code_action| code_action.lsp_action.title)
|
.map(|code_action| code_action.lsp_action.title().to_owned())
|
||||||
.sorted()
|
.sorted()
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
"Should receive code actions responses from all related servers with hover capabilities"
|
"Should receive code actions responses from all related servers with hover capabilities"
|
||||||
|
|
|
@ -1286,6 +1286,11 @@ message CodeAction {
|
||||||
Anchor start = 2;
|
Anchor start = 2;
|
||||||
Anchor end = 3;
|
Anchor end = 3;
|
||||||
bytes lsp_action = 4;
|
bytes lsp_action = 4;
|
||||||
|
Kind kind = 5;
|
||||||
|
enum Kind {
|
||||||
|
Action = 0;
|
||||||
|
Command = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message ProjectTransaction {
|
message ProjectTransaction {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue