From 783ec4456dc6aea5780a36c975038505110d1288 Mon Sep 17 00:00:00 2001 From: loadingalias <138315197+loadingalias@users.noreply.github.com> Date: Thu, 19 Mar 2026 11:59:52 -0400 Subject: [PATCH] project: Don't send context.only for generic code action requests (#50979) Closes #48917 ## Summary - Stop sending `context.only` for generic `textDocument/codeAction` requests. - Keep explicit kind filtered requests unchanged. - Add regression coverage for generic code action requests so actions like `source.addTest` remain visible. ## Root Cause `GetCodeActions::to_lsp` populated `context.only` even when the caller requested all code actions (`kinds == None`). That turned the normal code actions menu into a filtered request. With `gopls`, this filtered out `source.addTest`, so `Add test for ...` never appeared. ## Verification - `cargo fmt --all -- --check` - `./script/clippy -p project` - `cargo nextest run -p project --no-fail-fast --no-tests=warn` - `cargo test -p editor editor_tests::test_organize_imports_manual_trigger -- --exact` - `cargo test -p editor editor_tests::test_context_menus_hide_hover_popover -- --exact` ## Manual Testing - Repro'd the protocol level behavior against `gopls`: unfiltered requests return `source.addTest`, filtered requests excluding it do not. - Opened `zed_guild/testing_projects/act/pkg/artifactcache/handler.go` - Triggered `Show Code Actions` on `StartHandler` - Confirmed `Add test for StartHandler` appears Release Notes: - Fixed Go `gopls` code actions so `Add test for ...` appears in the generic code actions menu. Co-authored-by: Kirill Bulatov --- crates/project/src/lsp_command.rs | 9 +- .../tests/integration/project_tests.rs | 86 +++++++++++++++++++ 2 files changed, 90 insertions(+), 5 deletions(-) diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index ebc5ea038e0726384bc7d677f6fc6aa8ce87661e..c5cd568fb88c0c1de66adb99bb47f96508a6df04 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -2636,11 +2636,10 @@ impl LspCommand for GetCodeActions { relevant_diagnostics.push(entry.to_lsp_diagnostic_stub()?); } - 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 { + if let Some(supported_kinds) = + Self::supported_code_action_kinds(language_server.adapter_server_capabilities()) + { let filtered = requested .iter() .filter(|requested_kind| { @@ -2655,7 +2654,7 @@ impl LspCommand for GetCodeActions { Some(requested.clone()) } } else { - supported + None }; Ok(lsp::CodeActionParams { diff --git a/crates/project/tests/integration/project_tests.rs b/crates/project/tests/integration/project_tests.rs index 657d03a75f153b4c9c1ddb299e258e378b789b2f..087ecc8060543e5bd1c05eaac9848b96681d6473 100644 --- a/crates/project/tests/integration/project_tests.rs +++ b/crates/project/tests/integration/project_tests.rs @@ -7755,6 +7755,92 @@ async fn test_code_actions_only_kinds(cx: &mut gpui::TestAppContext) { ); } +#[gpui::test] +async fn test_code_actions_without_requested_kinds_do_not_send_only_filter( + cx: &mut gpui::TestAppContext, +) { + init_test(cx); + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree( + path!("/dir"), + json!({ + "a.ts": "a", + }), + ) + .await; + + let project = Project::test(fs, [path!("/dir").as_ref()], cx).await; + + 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( + "TypeScript", + FakeLspAdapter { + capabilities: lsp::ServerCapabilities { + code_action_provider: Some(lsp::CodeActionProviderCapability::Options( + lsp::CodeActionOptions { + code_action_kinds: Some(vec![ + CodeActionKind::SOURCE_ORGANIZE_IMPORTS, + "source.doc".into(), + ]), + ..lsp::CodeActionOptions::default() + }, + )), + ..lsp::ServerCapabilities::default() + }, + ..FakeLspAdapter::default() + }, + ); + + let (buffer, _handle) = project + .update(cx, |p, cx| { + p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx) + }) + .await + .unwrap(); + cx.executor().run_until_parked(); + + let fake_server = fake_language_servers + .next() + .await + .expect("failed to get the language server"); + + let mut request_handled = fake_server.set_request_handler::< + lsp::request::CodeActionRequest, + _, + _, + >(move |params, _| async move { + assert_eq!( + params.context.only, None, + "Code action requests without explicit kind filters should not send `context.only`" + ); + Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction( + lsp::CodeAction { + title: "Add test".to_string(), + kind: Some("source.addTest".into()), + ..lsp::CodeAction::default() + }, + )])) + }); + + let code_actions_task = project.update(cx, |project, cx| { + project.code_actions(&buffer, 0..buffer.read(cx).len(), None, cx) + }); + + let () = request_handled + .next() + .await + .expect("The code action request should have been triggered"); + + let code_actions = code_actions_task.await.unwrap().unwrap(); + assert_eq!(code_actions.len(), 1); + assert_eq!( + code_actions[0].lsp_action.action_kind(), + Some("source.addTest".into()) + ); +} + #[gpui::test] async fn test_multiple_language_server_actions(cx: &mut gpui::TestAppContext) { init_test(cx);