From 6bc433ed43f0bc49268e7338d4bec72367141d82 Mon Sep 17 00:00:00 2001 From: zchira Date: Tue, 23 Dec 2025 16:09:46 +0100 Subject: [PATCH] agent_ui: Add right-click context menu to the thread view (#45440) Closes #23158 Release Notes: - Added a right-click context menu for the thread view in the agent panel. --------- Co-authored-by: Danilo Leal --- crates/agent_ui/src/acp/message_editor.rs | 17 ++++++++++++++++- crates/agent_ui/src/acp/thread_view.rs | 21 ++++++++++++++++----- crates/editor/src/mouse_context_menu.rs | 10 +++++----- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/crates/agent_ui/src/acp/message_editor.rs b/crates/agent_ui/src/acp/message_editor.rs index 6bed82accf876aaaba0668d366216c3a965ad8cb..d2c9cf9cb430793d788ed8cb61ecaa01e8f989a8 100644 --- a/crates/agent_ui/src/acp/message_editor.rs +++ b/crates/agent_ui/src/acp/message_editor.rs @@ -31,7 +31,7 @@ use rope::Point; use settings::Settings; use std::{cell::RefCell, fmt::Write, rc::Rc, sync::Arc}; use theme::ThemeSettings; -use ui::prelude::*; +use ui::{ContextMenu, prelude::*}; use util::{ResultExt, debug_panic}; use workspace::{CollaboratorId, Workspace}; use zed_actions::agent::{Chat, PasteRaw}; @@ -132,6 +132,21 @@ impl MessageEditor { placement: Some(ContextMenuPlacement::Above), }); editor.register_addon(MessageEditorAddon::new()); + + editor.set_custom_context_menu(|editor, _point, window, cx| { + let has_selection = editor.has_non_empty_selection(&editor.display_snapshot(cx)); + + Some(ContextMenu::build(window, cx, |menu, _, _| { + menu.action("Cut", Box::new(editor::actions::Cut)) + .action_disabled_when( + !has_selection, + "Copy", + Box::new(editor::actions::Copy), + ) + .action("Paste", Box::new(editor::actions::Paste)) + })) + }); + editor }); let mention_set = diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index 709217fe9f8e532aafa8ac8426473c6c5dacb93d..21790a8f30846af573a9884d40be16aa584122ef 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -47,8 +47,9 @@ use terminal_view::terminal_panel::TerminalPanel; use text::Anchor; use theme::{AgentFontSize, ThemeSettings}; use ui::{ - Callout, CommonAnimationExt, Disclosure, Divider, DividerColor, ElevationIndex, KeyBinding, - PopoverMenuHandle, SpinnerLabel, TintColor, Tooltip, WithScrollbar, prelude::*, + Callout, CommonAnimationExt, ContextMenu, Disclosure, Divider, DividerColor, ElevationIndex, + KeyBinding, PopoverMenuHandle, SpinnerLabel, TintColor, Tooltip, WithScrollbar, prelude::*, + right_click_menu, }; use util::{ResultExt, size::format_file_size, time::duration_alt_display}; use workspace::{CollaboratorId, NewTerminal, Workspace}; @@ -2038,7 +2039,7 @@ impl AcpThreadView { } }) .text_xs() - .child(editor.clone().into_any_element()), + .child(editor.clone().into_any_element()) ) .when(editor_focus, |this| { let base_container = h_flex() @@ -2154,7 +2155,6 @@ impl AcpThreadView { if this_is_blank { return None; } - Some( self.render_thinking_block( entry_ix, @@ -2180,7 +2180,18 @@ impl AcpThreadView { .when(is_last, |this| this.pb_4()) .w_full() .text_ui(cx) - .child(message_body) + .child( + right_click_menu(format!("agent_context_menu-{}", entry_ix)) + .trigger(move |_, _, _| message_body) + .menu(move |window, cx| { + let focus = window.focused(cx); + ContextMenu::build(window, cx, move |menu, _, _cx| { + menu.action("Copy", Box::new(markdown::CopyAsMarkdown)) + .when_some(focus, |menu, focus| menu.context(focus)) + }) + }) + .into_any_element(), + ) .into_any() } } diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs index 7314991bd5e4842f395383888a87b4e2db7e0a0c..1eaaff416ed2415ae147bac361261dc8a3b8bf06 100644 --- a/crates/editor/src/mouse_context_menu.rs +++ b/crates/editor/src/mouse_context_menu.rs @@ -164,11 +164,6 @@ pub fn deploy_context_menu( window.focus(&editor.focus_handle(cx), cx); } - // Don't show context menu for inline editors - if !editor.mode().is_full() { - return; - } - let display_map = editor.display_snapshot(cx); let source_anchor = display_map.display_point_to_anchor(point, text::Bias::Right); let context_menu = if let Some(custom) = editor.custom_context_menu.take() { @@ -179,6 +174,11 @@ pub fn deploy_context_menu( }; menu } else { + // Don't show context menu for inline editors (only applies to default menu) + if !editor.mode().is_full() { + return; + } + // Don't show the context menu if there isn't a project associated with this editor let Some(project) = editor.project.clone() else { return;