b5011067
Add visible selection design spec
a73x 2026-04-09 17:02
diff --git a/docs/superpowers/specs/2026-04-09-visible-selection-design.md b/docs/superpowers/specs/2026-04-09-visible-selection-design.md new file mode 100644 index 0000000..37ae763 --- /dev/null +++ b/docs/superpowers/specs/2026-04-09-visible-selection-design.md @@ -0,0 +1,214 @@ # Visible Selection Design ## Goal Add XTerm-style text selection for the visible terminal grid so users can drag to highlight text and press `Ctrl+Shift+C` to copy it to the Wayland clipboard. ## Scope This change covers only text currently visible in the terminal grid. Included: - Left-button drag selection across visible cells - Persistent visible highlight after mouse release - `Ctrl+Shift+C` copying the selected text to the clipboard - Clearing or replacing the current visible selection with a new drag Excluded: - Scrollback selection - Auto-copy on select - Primary selection / middle-click paste - Word-wise or line-wise selection modes - Keyboard-driven selection ## Current Context `waystty` already has: - A `ghostty-vt` backed terminal model exposed through `src/vt.zig` - Per-frame terminal snapshots via `term.snapshot()` - Row/cell instance generation in `src/main.zig` - Wayland clipboard paste support in `src/wayland.zig` `waystty` does not yet have: - Wayland pointer handling - Any selection model - Clipboard ownership for serving copied text to other clients The visible-grid-only scope is intentionally chosen because the current renderer already operates on a snapshot of the visible terminal state each frame. That gives a stable source for both highlight decisions and copy extraction without introducing scrollback traversal. ## Approach Options ### 1. UI-owned visible selection state in `main.zig` (recommended) Add a `SelectionState` alongside the main event loop. Pointer events update selection coordinates in grid space. Rendering checks whether each visible cell falls inside the active selection and swaps to selection colors. Copy walks the visible snapshot and exports the selected text through Wayland clipboard ownership. Pros: - Smallest change set - Matches existing separation where `main.zig` owns window/UI behavior - Keeps `vt.zig` focused on terminal emulation rather than GUI state - Easiest path to visible-only selection Cons: - If selection later expands into scrollback/search semantics, some logic may later move into a more dedicated module ### 2. Terminal-wrapper-owned selection in `src/vt.zig` Store selection state beside the `ghostty-vt` wrapper and expose helper APIs for rendering and extraction. Pros: - Centralizes terminal-text operations Cons: - Blurs the current boundary between VT state and window/input state - Adds API surface before the project needs it ### 3. Renderer overlay plus separate extraction pass Render selection as an independent overlay and keep copy generation in a separate traversal. Pros: - Can support more advanced visuals later Cons: - Adds rendering complexity immediately - Does not simplify copy logic - Overbuilt for the current terminal ## Recommended Design ### Architecture Selection state will live in `src/main.zig` as part of the Wayland/UI event loop. The state will store: - Whether a drag is active - The anchor cell where the drag started - The current drag cell - The last committed visible selection span, if any Selection coordinates are grid-relative `(col, row)` positions clamped to the current visible terminal dimensions. Pointer support will be added in `src/wayland.zig` and exposed similarly to the existing keyboard queue model: collect pointer button/motion events into a queue that `main.zig` drains each loop iteration. Clipboard support in `src/wayland.zig` will be extended so `waystty` can become the current clipboard source and serve a UTF-8 payload to other Wayland clients. ### Interaction Model - Left button press starts a new selection anchored at the hovered cell. - Pointer motion with the left button held updates the active selection end. - Left button release finalizes the selection and leaves it highlighted. - A click-drag over a new region replaces any old selection. - `Ctrl+Shift+C` copies the current selection if one exists. - A left click that does not create a span still resets the anchor; if press and release stay on the same cell, the resulting selection is empty and no highlight remains. This is intentionally narrower than full XTerm behavior. It provides the core visible-drag selection loop without word-select, line-select, or primary selection side effects. ### Selection Semantics The first implementation will treat selection as a linear text span over visible cells, not a rectangular block selection. Normalization rules: - If the drag end precedes the anchor in reading order, normalize the start/end positions before use. - Selection includes both start and end cells. - Rows between start and end are included fully. - On the first row, include cells from `start.col` through the row end. - On the last row, include cells from column `0` through `end.col`. This matches the usual terminal expectation for drag selection better than rectangular selection. ### Rendering Highlighting will be applied during existing cell instance generation in `src/main.zig`. For each rendered cell: - Determine whether its `(row, col)` falls inside the normalized selection span. - If selected, override fg/bg colors with dedicated selection colors. - Keep glyph placement unchanged. This avoids adding a second draw pass or renderer-specific overlay state. Initial colors: - Selection background: a light neutral accent that stays legible against the terminal theme - Selection foreground: dark text for contrast If there is already an established color constant pattern nearby, follow it there. Otherwise define a small pair of constants near the selection helpers in `main.zig`. ### Copy Extraction `Ctrl+Shift+C` will trigger selection export only when a non-empty selection exists. Copy extraction will: - Read from the same visible snapshot used for rendering - Walk selected rows in reading order - Append selected cell text into a UTF-8 buffer - Insert `\n` between selected rows - Trim trailing blank cells on each selected row before inserting row text This keeps copied text aligned with what users typically expect from terminal selections: visible text content without right-margin padding from empty cells. If a cell does not produce a printable glyph in the visible snapshot, it contributes nothing to the copied buffer. ### Resizing and Lifecycle - If the terminal grid size changes, clamp or clear the selection if it falls outside the new visible bounds. - If a snapshot update causes content changes while a selection is present, the selection remains attached to visible grid coordinates, not stable text identity. - If the window loses the selection due to a new empty click, the highlight disappears immediately. This is acceptable for the visible-only version because the selection is explicitly a UI overlay on the current grid, not a scrollback-aware logical text range. ## Components To Change ### `src/wayland.zig` Add: - Pointer object setup from the seat - Pointer event structs and queue - Surface enter/leave tracking if needed for hover validity - Clipboard source support for sending copied UTF-8 text to requesters ### `src/main.zig` Add: - Selection state types and helpers - Pointer queue handling in the main loop - Grid-coordinate mapping from surface pixels to terminal cells - Copy shortcut detection for `Ctrl+Shift+C` - Selected-text extraction from the visible snapshot - Selection-aware color override during row instance generation ### `src/vt.zig` Likely no structural change beyond possible helper exposure if snapshot cell text extraction needs a small convenience wrapper. Avoid moving selection ownership here. ## Error Handling - Clipboard ownership failures must not crash the terminal; log a warning and leave the selection intact. - Pointer events outside the surface bounds should clamp to the nearest visible cell or be ignored when no valid hover target exists. - Empty selections should not attempt clipboard export. ## Testing Strategy Add unit tests for: - Selection span normalization - Inclusion checks for cells inside/outside a span - Selected-text extraction across single-row and multi-row selections - Copy shortcut detection for `Ctrl+Shift+C` Manual verification after implementation: - Drag left-to-right within one row and copy - Drag across multiple rows and copy - Drag right-to-left and verify normalized selection - Resize after selection and verify behavior stays safe - Paste copied text into another app through the Wayland clipboard ## Risks - `ghostty-vt` render cells may require careful handling to recover copied text exactly as shown, especially around wide glyphs or style-only cells. - Wayland clipboard source handling is more involved than clipboard receive and must be wired so request lifetimes are correct. - Pointer coordinate mapping must use surface coordinates rather than buffer-scaled pixels so HiDPI behavior stays correct. ## Success Criteria The feature is complete when: - Users can drag-select visible terminal text with the left mouse button - Selected cells are visibly highlighted - `Ctrl+Shift+C` copies the highlighted visible text to the clipboard - The terminal remains stable across resize and HiDPI scale changes