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 <dinojoaocosta@gmail.com>
This commit is contained in:
liamanddino authored and GitHub committed 2026-06-01 11:40:47 +00:00
1 parent a03e3a56db
commit 648aee2ea2
1 file changed
+52
+52
View File
@@ -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>,
) {
self.copy(&Copy, window, cx);
}
///Attempt to paste the clipboard into the terminal
fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
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>,
) {
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<Self>) {
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;