agent_ui: Add right-click context menu to the thread view (#45440)

zchira and Danilo Leal created

Closes #23158

Release Notes:

- Added a right-click context menu for the thread view in the agent
panel.

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>

Change summary

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

Detailed changes

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 =

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()
                 }
             }

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;