project: Don't send context.only for generic code action requests (#50979)

loadingalias and Kirill Bulatov created

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 <kirill@zed.dev>

Change summary

crates/project/src/lsp_command.rs                 |  9 
crates/project/tests/integration/project_tests.rs | 86 +++++++++++++++++
2 files changed, 90 insertions(+), 5 deletions(-)

Detailed changes

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 {

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);