diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index caefa57c869d39a9c8e9a693c1d0d6eebee1766a..1f072e8d804ea26bde05afada77533c64017fa96 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -541,7 +541,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() }), diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 59e958d0820b2f15c8b2c294994f46c86fe26967..16a0908ec23afc82eb0ea223f3532b87c22d2012 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1818,6 +1818,19 @@ impl LspCommand for GetCodeActions { } } +impl GetCodeActions { + pub fn can_resolve_actions(capabilities: &ServerCapabilities) -> bool { + capabilities + .code_action_provider + .as_ref() + .and_then(|options| match options { + lsp::CodeActionProviderCapability::Simple(_is_supported) => None, + lsp::CodeActionProviderCapability::Options(options) => options.resolve_provider, + }) + .unwrap_or(false) + } +} + #[async_trait(?Send)] impl LspCommand for OnTypeFormatting { type Response = Option; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index af142d4ad6e9de7e635da38b6ce82e82b3167819..db156e9ca3128f306597714cfc39639b8676fc6e 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -35,11 +35,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::{ @@ -4235,7 +4235,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; @@ -4255,6 +4258,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 @@ -5296,33 +5300,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::(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( @@ -8143,6 +8124,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::(action.lsp_action.clone()) + .await?; + } + } + + anyhow::Ok(()) + } + async fn handle_refresh_inlay_hints( this: Model, _: TypedEnvelope, diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index b222913325121c2e04698c094ea8061ed6cc4ba3..db704d9c23351e3edfef1defceba84b48ca360b2 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -2564,7 +2564,20 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) { }, None, ); - let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await; + let mut fake_language_servers = language + .set_fake_lsp_adapter(Arc::new(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() + })) + .await; let fs = FakeFs::new(cx.executor()); fs.insert_tree( @@ -2591,16 +2604,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() }), ])) }) @@ -2615,7 +2626,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::( - |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