From 2e9b4fe596e43da859bf6d23b68c6b9559f94dc0 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 8 Sep 2026 11:21:20 +0000 Subject: [PATCH] Unify copy actions for agent panel and markdown (#63884) Before, it was not possible at all to copy the text from the agent panel, only its markdown. The PR breaks the existing ctrl/cmd-c binding there by using the same approach markdown preview does: copy as text by default, allow to copy as markdown via the context menu. Before: https://github.com/user-attachments/assets/2014f363-5167-4700-be86-9c0774d7e4fd After: https://github.com/user-attachments/assets/1dad253c-7a3b-4ca7-99c8-e8df8b3c3fd6 Release Notes: - Changed agent panel to copy text with cmd/ctrl-c, as markdown preview does. `markdown::CopyAsMarkdown` action is available still and can be bound if needed. --- assets/keymaps/default-linux.json | 8 --- assets/keymaps/default-macos.json | 7 -- assets/keymaps/default-windows.json | 7 -- .../src/conversation_view/thread_view.rs | 66 ++++++++++++------- docs/src/ai/agent-panel.md | 3 +- 5 files changed, 45 insertions(+), 46 deletions(-) diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index 553b9a2a6b..37ad4da639 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -226,14 +226,6 @@ "ctrl-shift-tab": ["agents_sidebar::ToggleThreadSwitcher", { "select_last": true }], }, }, - { - "context": "AgentPanel > Markdown", - "bindings": { - "copy": "markdown::CopyAsMarkdown", - "ctrl-insert": "markdown::CopyAsMarkdown", - "ctrl-c": "markdown::CopyAsMarkdown", - }, - }, { "context": "AgentFeedbackMessageEditor > Editor", "bindings": { diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 4b5dd97951..75776e0955 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -264,13 +264,6 @@ "ctrl-shift-tab": ["agents_sidebar::ToggleThreadSwitcher", { "select_last": true }], }, }, - { - "context": "AgentPanel > Markdown", - "use_key_equivalents": true, - "bindings": { - "cmd-c": "markdown::CopyAsMarkdown", - }, - }, { "context": "AgentFeedbackMessageEditor > Editor", "use_key_equivalents": true, diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json index 3752597005..c7747268ef 100644 --- a/assets/keymaps/default-windows.json +++ b/assets/keymaps/default-windows.json @@ -228,13 +228,6 @@ "ctrl-shift-tab": ["agents_sidebar::ToggleThreadSwitcher", { "select_last": true }], }, }, - { - "context": "AgentPanel > Markdown", - "use_key_equivalents": true, - "bindings": { - "ctrl-c": "markdown::CopyAsMarkdown", - }, - }, { "context": "AgentFeedbackMessageEditor > Editor", "use_key_equivalents": true, diff --git a/crates/agent_ui/src/conversation_view/thread_view.rs b/crates/agent_ui/src/conversation_view/thread_view.rs index 0bd96811ca..26187c7045 100644 --- a/crates/agent_ui/src/conversation_view/thread_view.rs +++ b/crates/agent_ui/src/conversation_view/thread_view.rs @@ -7566,29 +7566,36 @@ impl ThreadView { }, ); - let has_selection = chunks - .map(|chunks| { - chunks.iter().any(|chunk| { - let md = match chunk { - AssistantMessageChunk::Message { block, .. } => { - block.markdown() - } - AssistantMessageChunk::Thought { block, .. } => { - block.markdown() - } - }; - md.map_or(false, |m| m.read(cx).has_selection()) - }) - }) - .unwrap_or(false); - let context_menu_link = chunks.and_then(|chunks| { chunks.iter().find_map(|chunk| { - let md = match chunk { + let markdown = match chunk { AssistantMessageChunk::Message { block, .. } => block.markdown(), AssistantMessageChunk::Thought { block, .. } => block.markdown(), }; - md.and_then(|m| m.read(cx).context_menu_link().cloned()) + markdown + .and_then(|markdown| markdown.read(cx).context_menu_link().cloned()) + }) + }); + let selected_text = chunks.and_then(|chunks| { + chunks.iter().find_map(|chunk| { + let markdown = match chunk { + AssistantMessageChunk::Message { block, .. } => block.markdown(), + AssistantMessageChunk::Thought { block, .. } => block.markdown(), + }; + markdown.and_then(|markdown| { + markdown.read(cx).context_menu_selected_text().cloned() + }) + }) + }); + let selected_markdown = chunks.and_then(|chunks| { + chunks.iter().find_map(|chunk| { + let markdown = match chunk { + AssistantMessageChunk::Message { block, .. } => block.markdown(), + AssistantMessageChunk::Thought { block, .. } => block.markdown(), + }; + markdown.and_then(|markdown| { + markdown.read(cx).context_menu_selected_markdown().cloned() + }) }) }); @@ -7649,11 +7656,24 @@ impl ThreadView { }) .separator() }) - .action_disabled_when( - !has_selection, - "Copy Selection", - Box::new(markdown::CopyAsMarkdown), - ) + .when_some(selected_text, |menu, selected_text| { + menu.entry("Copy", Some(Box::new(markdown::Copy)), move |_, cx| { + cx.write_to_clipboard(ClipboardItem::new_string( + selected_text.to_string(), + )); + }) + }) + .when_some(selected_markdown, |menu, selected_markdown| { + menu.entry( + "Copy as Markdown", + Some(Box::new(markdown::CopyAsMarkdown)), + move |_, cx| { + cx.write_to_clipboard(ClipboardItem::new_string( + selected_markdown.to_string(), + )); + }, + ) + }) .item(copy_this_agent_response) .separator() .item(scroll_item) diff --git a/docs/src/ai/agent-panel.md b/docs/src/ai/agent-panel.md index a26189de21..d659523f59 100644 --- a/docs/src/ai/agent-panel.md +++ b/docs/src/ai/agent-panel.md @@ -79,7 +79,8 @@ The checkpoint button appears even if you interrupt the thread midway through an Right-click on any agent response in the thread view to access a context menu with the following actions: -- **Copy Selection**: Copies the currently selected text as Markdown (available when text is selected). +- **Copy**: Copies the currently selected text as plain text (available when text is selected). +- **Copy as Markdown**: Copies the currently selected text as Markdown (available when text is selected). - **Copy This Agent Response**: Copies the full text of the agent response you right-clicked on. - **Scroll to Top / Scroll to Bottom**: Scrolls to the beginning or end of the thread, depending on your current position. - **Open Thread as Markdown**: Opens the entire thread as a Markdown file in a new tab.