@@ -570,7 +570,14 @@ impl LanguageServer {
}),
data_support: Some(true),
resolve_support: Some(CodeActionCapabilityResolveSupport {
- properties: vec!["edit".to_string(), "command".to_string()],
+ properties: vec![
+ "kind".to_string(),
+ "diagnostics".to_string(),
+ "isPreferred".to_string(),
+ "disabled".to_string(),
+ "edit".to_string(),
+ "command".to_string(),
+ ],
}),
..Default::default()
}),
@@ -40,11 +40,11 @@ use language::{
deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version,
serialize_anchor, serialize_version, split_operations,
},
- range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, Capability,
- CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff,
- Documentation, Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName,
- LocalFile, LspAdapterDelegate, OffsetRangeExt, Operation, Patch, PendingLanguageServer,
- PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
+ range_from_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, Capability, CodeAction,
+ CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Documentation,
+ Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, LocalFile,
+ LspAdapterDelegate, Operation, Patch, PendingLanguageServer, PointUtf16, TextBufferSnapshot,
+ ToOffset, ToPointUtf16, Transaction, Unclipped,
};
use log::error;
use lsp::{
@@ -4360,7 +4360,10 @@ impl Project {
})?
.await?;
- for action in actions {
+ for mut action in actions {
+ Self::try_resolve_code_action(&language_server, &mut action)
+ .await
+ .context("resolving a formatting code action")?;
if let Some(edit) = action.lsp_action.edit {
if edit.changes.is_none() && edit.document_changes.is_none() {
continue;
@@ -4380,6 +4383,7 @@ impl Project {
project_transaction.0.extend(new.0);
}
+ // TODO kb here too:
if let Some(command) = action.lsp_action.command {
project.update(&mut cx, |this, _| {
this.last_workspace_edits_by_language_server
@@ -5422,33 +5426,10 @@ impl Project {
} else {
return Task::ready(Ok(Default::default()));
};
- let range = action.range.to_point_utf16(buffer);
-
cx.spawn(move |this, mut cx| async move {
- if let Some(lsp_range) = action
- .lsp_action
- .data
- .as_mut()
- .and_then(|d| d.get_mut("codeActionParams"))
- .and_then(|d| d.get_mut("range"))
- {
- *lsp_range = serde_json::to_value(&range_to_lsp(range)).unwrap();
- action.lsp_action = lang_server
- .request::<lsp::request::CodeActionResolveRequest>(action.lsp_action)
- .await?;
- } else {
- let actions = this
- .update(&mut cx, |this, cx| {
- this.code_actions(&buffer_handle, action.range, cx)
- })?
- .await?;
- action.lsp_action = actions
- .into_iter()
- .find(|a| a.lsp_action.title == action.lsp_action.title)
- .ok_or_else(|| anyhow!("code action is outdated"))?
- .lsp_action;
- }
-
+ Self::try_resolve_code_action(&lang_server, &mut action)
+ .await
+ .context("resolving a code action")?;
if let Some(edit) = action.lsp_action.edit {
if edit.changes.is_some() || edit.document_changes.is_some() {
return Self::deserialize_workspace_edit(
@@ -8322,6 +8303,23 @@ impl Project {
})
}
+ async fn try_resolve_code_action(
+ lang_server: &LanguageServer,
+ action: &mut CodeAction,
+ ) -> anyhow::Result<()> {
+ if GetCodeActions::can_resolve_actions(&lang_server.capabilities()) {
+ if action.lsp_action.data.is_some()
+ && (action.lsp_action.command.is_none() || action.lsp_action.edit.is_none())
+ {
+ action.lsp_action = lang_server
+ .request::<lsp::request::CodeActionResolveRequest>(action.lsp_action.clone())
+ .await?;
+ }
+ }
+
+ anyhow::Ok(())
+ }
+
async fn handle_refresh_inlay_hints(
this: Model<Self>,
_: TypedEnvelope<proto::RefreshInlayHints>,
@@ -2475,8 +2475,21 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(typescript_lang());
- let mut fake_language_servers =
- language_registry.register_fake_lsp_adapter("TypeScript", Default::default());
+ let mut fake_language_servers = language_registry.register_fake_lsp_adapter(
+ "TypeScript",
+ FakeLspAdapter {
+ capabilities: lsp::ServerCapabilities {
+ code_action_provider: Some(lsp::CodeActionProviderCapability::Options(
+ lsp::CodeActionOptions {
+ resolve_provider: Some(true),
+ ..lsp::CodeActionOptions::default()
+ },
+ )),
+ ..lsp::ServerCapabilities::default()
+ },
+ ..FakeLspAdapter::default()
+ },
+ );
let buffer = project
.update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
@@ -2492,16 +2505,14 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
Ok(Some(vec![
lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
title: "The code action".into(),
- command: Some(lsp::Command {
- title: "The command".into(),
- command: "_the/command".into(),
- arguments: Some(vec![json!("the-argument")]),
- }),
- ..Default::default()
+ data: Some(serde_json::json!({
+ "command": "_the/command",
+ })),
+ ..lsp::CodeAction::default()
}),
lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
title: "two".into(),
- ..Default::default()
+ ..lsp::CodeAction::default()
}),
]))
})
@@ -2516,7 +2527,16 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
// Resolving the code action does not populate its edits. In absence of
// edits, we must execute the given command.
fake_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
- |action, _| async move { Ok(action) },
+ |mut action, _| async move {
+ if action.data.is_some() {
+ action.command = Some(lsp::Command {
+ title: "The command".into(),
+ command: "_the/command".into(),
+ arguments: Some(vec![json!("the-argument")]),
+ });
+ }
+ Ok(action)
+ },
);
// While executing the command, the language server sends the editor