Skip to content

[Bug]: terminal Cmd+C copies blankness from an empty IME textarea #7677

Description

@sethwebster

Before submitting

  • I searched existing issues and did not find a duplicate of this diagnosis.
  • I included enough detail to reproduce or investigate the problem.

User-facing report of the same symptom: #6173. This issue is the root-cause writeup.

Area

apps/web (desktop wraps this surface; Electron's Edit menu is the macOS trigger)

Steps to reproduce

  1. Open the integrated terminal in the desktop app on macOS.
  2. Select some terminal text (single line or multiline).
  3. Press Cmd+C, or use Edit → Copy.
  4. Paste into another app.

Expected behavior

The selected terminal text is on the clipboard.

Actual behavior

The clipboard is blank (multiline) or unchanged (short selections). Right-click Copy is also unreliable. Matches #6173.

Impact

Blocks work completely

Version or commit

Regressed in 1add47b32 (fix(web): add copying terminal selection with ctrl+c in the web app / #5638). Still present on main @ beab6886f.

Environment

macOS desktop app. Web is the same Ghostty canvas path; Electron { role: "editMenu" } makes Mac worse.

Root cause

The Ghostty terminal is a canvas plus a 1×1 empty IME textarea. There is no DOM selection of the visible text.

#5638 stopped calling writeTextToClipboard(getSelection()) on Cmd+C. It instead:

  1. Leaves the keydown default alive so a native copy event can fire.
  2. On that event, always preventDefault(), clipboardData?.setData(...), and cancels the deferred navigator.clipboard.writeText fallback.
  3. Does not put the Ghostty selection into the textarea first.

Two things then write emptiness:

  • Chromium/Electron copy of the focused empty textarea.
  • On macOS, the application menu { role: "editMenu" } Copy accelerator (webContents.copy() / getSelectedText()), which often delivers a copy event with no clipboardData and then writes the empty DOM selection after renderer JS.

Because the handler claims the gesture even when clipboardData is null, writeText never runs, and the OS clipboard ends up blank.

Ghostty formatting is not the bug. ghostty_terminal_selection_format_buf on a cell-drag selection of hello\nworld returns hello\nworld. hasSelection() / getSelection() already use that string. The text is available; copy throws it away.

Analysis

Copy shortcut path (apps/web/src/terminal/ghostty/surface.ts):

  • isTerminalCopyShortcut + hasSelection() → schedule clipboard.writeText(selection) on a microtask, do not preventDefault.
  • input copy listener: if hasSelection(), preventDefault, optional setData, bump copyShortcutToken (this cancels the microtask).

Desktop Edit menu (apps/desktop/src/window/DesktopApplicationMenu.ts):

  • { role: "editMenu" } includes Copy with Cmd+C.
  • Renderer keydown.preventDefault does not stop that accelerator on macOS.
  • The accelerator copies whatever DOM selection exists. The IME textarea is empty.

Why multiline vs short looked different in #6173: a native empty copy sometimes clears the clipboard (writes "") and sometimes is a no-op (previous contents remain). Both are the empty-textarea path, not a Ghostty format split.

Right-click Copy in the custom terminal menu is a separate seam (writeTextToClipboard after await contextMenu.show). The Cmd+C blankness is the empty-textarea / cancelled-fallback race above.

Solution

  1. Before native copy runs, park the Ghostty selection in the hidden textarea and select() it, so Electron Edit → Copy and a DOM copy of the focused field have real text.
  2. Only preventDefault + cancel writeText when clipboardData exists and setData("text/plain", selection) actually ran. A copy event with no clipboardData must leave the fallback alive and must not suppress the default copy of the primed textarea.
  3. Clear the primed textarea on the next non-copy key, composition start, and clearSelection(), so IME is not composing over leftover copy text.

Keep clipboard.writeText as the WebKit/no-copy-event path. Do not go back to racing an empty textarea against the Edit menu.

Tests to lock it:

  • applyTerminalCopyEvent: missing clipboardData → do not claim the fallback.
  • primeTerminalCopyInput: textarea value and selection cover the Ghostty string.
  • ABI: cell-drag selection via screen grid refs still formats with Ghostty copy semantics.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions