From 648aee2ea27e9008a80b71da4be03a7f4a4eeda7 Mon Sep 17 00:00:00 2001 From: liam Date: Mon, 1 Jun 2026 07:40:47 -0400 Subject: [PATCH] terminal: Enable copy and paste in edit menu (#58111) Resolves https://github.com/zed-industries/zed/issues/57999 This diff fixes the macOS Edit menu showing Copy and Paste as disabled when a terminal is focused. Terminal shortcuts already worked because keybindings dispatch `terminal::Copy` and `terminal::Paste`, but the app menu validates `editor::Copy` and `editor::Paste`, which the terminal view did not expose. With this change, the terminal view handles the editor Copy and Paste actions by delegating to the existing terminal copy and paste paths. This keeps keyboard behavior unchanged while allowing menu validation and menu dispatch to work correctly for focused terminals. Release Notes: - Fixed `Copy` and `Paste` being disabled in the macOS `Edit` menu when a terminal is focused. --------- Co-authored-by: dino --- crates/terminal_view/src/terminal_view.rs | 52 +++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 92e5046cbb..905db4513d 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -854,6 +854,18 @@ impl TerminalView { cx.notify(); } + /// Specific handler for the [`editor::actions::Copy`] action in order for + /// the `Edit > Copy` menu item to not be disabled, as the app expects a + /// handler for this action in order to enable/disable the menu item. + fn editor_copy( + &mut self, + _: &editor::actions::Copy, + window: &mut Window, + cx: &mut Context, + ) { + self.copy(&Copy, window, cx); + } + ///Attempt to paste the clipboard into the terminal fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context) { let Some(clipboard) = cx.read_from_clipboard() else { @@ -876,6 +888,18 @@ impl TerminalView { } } + /// Specific handler for the [`editor::actions::Paste`] action in order for + /// the `Edit > Paste` menu item to not be disabled, as the app expects a + /// handler for this action in order to enable/disable the menu item. + fn editor_paste( + &mut self, + _: &editor::actions::Paste, + window: &mut Window, + cx: &mut Context, + ) { + self.paste(&Paste, window, cx); + } + ///Attempt to paste the clipboard text into the terminal fn paste_text(&mut self, _: &PasteText, _: &mut Window, cx: &mut Context) { let Some(clipboard) = cx.read_from_clipboard() else { @@ -1284,7 +1308,9 @@ impl Render for TerminalView { .on_action(cx.listener(TerminalView::send_text)) .on_action(cx.listener(TerminalView::send_keystroke)) .on_action(cx.listener(TerminalView::copy)) + .on_action(cx.listener(TerminalView::editor_copy)) .on_action(cx.listener(TerminalView::paste)) + .on_action(cx.listener(TerminalView::editor_paste)) .on_action(cx.listener(TerminalView::paste_text)) .on_action(cx.listener(TerminalView::clear)) .on_action(cx.listener(TerminalView::scroll_line_up)) @@ -2148,6 +2174,32 @@ mod tests { // CSI `1;2A` = cursor-up with the xterm Shift modifier (`1 + 1` for Shift). const SHIFT_UP_ESCAPE: &[u8] = b"\x1b[1;2A"; + #[gpui::test] + async fn edit_menu_copy_and_paste_are_available_when_terminal_is_focused( + cx: &mut TestAppContext, + ) { + let (project, _workspace, window_handle) = init_test_with_window(cx).await; + let (_pane, terminal, _terminal_view) = + add_display_only_terminal(&project, window_handle, true, cx); + + let mut cx = VisualTestContext::from_window(window_handle.into(), cx); + cx.update(|window, cx| { + let _ = window.draw(cx); + assert!(window.is_action_available(&editor::actions::Copy, cx)); + assert!(window.is_action_available(&editor::actions::Paste, cx)); + + cx.write_to_clipboard(gpui::ClipboardItem::new_string("foo".to_string())); + terminal.update(cx, |terminal, _| terminal.take_input_log()); + window.dispatch_action(Box::new(editor::actions::Paste), cx); + }); + cx.run_until_parked(); + + cx.update(|_, cx| { + let input_log = terminal.update(cx, |terminal, _| terminal.take_input_log()); + assert_eq!(input_log, vec![b"foo".to_vec()]); + }); + } + #[gpui::test] async fn shift_up_scrolls_history_in_normal_screen(cx: &mut TestAppContext) { let (project, _workspace, window_handle) = init_test_with_window(cx).await;