feat(terminal): text selection + copy (custom model)
Drag to select terminal text and copy it with Ctrl-Shift-C. Built as a
custom selection model (xterm.js-style) rather than native selection,
because the virtual scroll only keeps ~60 rows in the DOM and rebuilds
them every frame — native selection would be destroyed and couldn't span
scrolled-out rows.
core.rs:
- selection_text(start, end) extracts a (row,col)→(row,col) range straight
from history_rows + screen_cache, so copy is correct across the whole
buffer regardless of what is currently rendered
- TerminalRow::text_in_cols slices a row by grid columns (using the
segment `cols` widths), trimming trailing whitespace
component.rs:
- Selection { anchor, head } in logical buffer coords; mousedown/move/up
drag updates it; point_to_cell maps pixels→cell (padding + scrollTop)
- selection_rects computes per-row highlight rects clipped to the rendered
window; rendered as an overlay inside the translated rows layer so the
highlight follows scrolling without splitting text segments
- cell_size signal kept in sync with viewport measurement
- Ctrl-Shift-C copies via navigator.clipboard.write_text; Ctrl-C is left
untouched (still SIGINT); typing clears the selection
style: .terminal-selection overlay, user-select:none on the grid, rows
above the highlight via z-index.
web-sys: add DomRect, MouseEvent, Navigator, Clipboard.
Adds point_to_cell / selection_rects / selection_text unit tests (16
terminal tests total). Marks P0 done in TERMINAL_ROADMAP.md.
Known follow-up: no auto-scroll when dragging past the viewport edge
(scroll first, then select); wide+combining column slicing is approximate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -30,28 +30,31 @@
|
||||
| alt-screen 滚轮 → 方向键翻页 | `component.rs::handle_wheel` |
|
||||
| 基础按键:Ctrl-C/D/L、方向键、Enter/Tab/Esc/Backspace | `component.rs::map_key_to_terminal_input` |
|
||||
| 粘贴(Ctrl-V / 浏览器菜单,含 bracketed paste) | `component.rs::handle_paste` / `prepare_paste` |
|
||||
| 文本选区 + 复制(自建模型,Ctrl-Shift-C,跨滚动) | `component.rs` 鼠标处理 / `core.rs::selection_text` |
|
||||
|
||||
---
|
||||
|
||||
## 2. 追平项(按优先级)
|
||||
|
||||
### P0 — 复制 / 粘贴 + 文本选区 ⭐ 投入产出比最高
|
||||
### P0 — 复制 / 粘贴 + 文本选区 ⭐ ✅ 已完成
|
||||
|
||||
**现状**:粘贴**已完成**(见下);选区 / 复制尚未做。
|
||||
**现状**:粘贴、选区、复制**全部完成并验证通过**。
|
||||
|
||||
**计划实现**:
|
||||
1. ~~**粘贴(先做,最简单)**~~ ✅ **已完成**
|
||||
**已实现**:
|
||||
1. **粘贴** ✅
|
||||
- `on:paste`(`ClipboardEvent`)读 `clipboard_data().get_data("text")`,归一换行为 `\r` 后作为 `Input` 发送。
|
||||
- bracketed paste:应用开启时(`core.bracketed_paste()`)包进 `ESC[200~ … ESC[201~`。
|
||||
- 实现于 `component.rs::handle_paste` + 纯函数 `prepare_paste`(有单元测试)。
|
||||
- 右键不拦截,走浏览器默认菜单(避免依赖剪贴板读权限);Ctrl-V 为主路径。
|
||||
2. **选区(较复杂,下一步)**
|
||||
- 因为渲染是 DOM(每行一个 `<div>`、每段一个 `<span>`),可优先尝试**浏览器原生选区**:给可见行允许 `user-select: text`,监听 `copy` 事件,从 `window.getSelection()` 取文本。
|
||||
- 但虚拟滚动只渲染可见窗口,跨窗口选区会断 → 需要把「逻辑行号 → 文本」的映射暴露给选区逻辑,从 `TerminalCore` 的 `history_rows + screen_rows` 直接取纯文本(已有 `row` → 文本的能力,测试里 `row_text` 即是)。
|
||||
- MVP 可只支持**可见范围内选区 + 复制**,跨滚动选区列为后续。
|
||||
3. **快捷键**:Ctrl-Shift-C / Ctrl-Shift-V(终端惯例,避免和 Ctrl-C 中断冲突)。
|
||||
- 实现于 `component.rs::handle_paste` + 纯函数 `prepare_paste`。
|
||||
- 右键走浏览器默认菜单(避免依赖剪贴板读权限);Ctrl-V 为主路径。
|
||||
2. **选区 + 复制** ✅(自建选区模型,xterm.js 风格)
|
||||
- 选区按**逻辑行列坐标**跟踪(`Selection { anchor, head }`),鼠标 `mousedown/move/up` 拖拽更新;`point_to_cell` 做像素→单元格换算(含 padding/scroll)。
|
||||
- 高亮 overlay:`selection_rects` 算每行矩形,渲染在虚拟滚动层内(共享 translateY,跟随滚动),不拆分文本段。
|
||||
- 复制取 `TerminalCore::selection_text(start, end)` —— 直接从 `history_rows + screen_cache` 数据模型按行列取文本,**跨滚动、输出流动时都正确**(不依赖 DOM)。
|
||||
- **Ctrl-Shift-C** 复制(Ctrl-C 保持 SIGINT);打字清除选区。
|
||||
- 实现于 `component.rs`(`Selection`/`selection_rects`/`point_to_cell`/鼠标处理)+ `core.rs::selection_text` + `core.rs::TerminalRow::text_in_cols`,均有单元测试。
|
||||
3. **快捷键**:Ctrl-Shift-C 复制 / Ctrl-V 粘贴。
|
||||
|
||||
**风险**:选区与虚拟滚动的交互是主要难点;建议 MVP 限定可见区,先把粘贴 + 可见区复制跑通。
|
||||
**已知后续项(非阻塞)**:拖拽到视窗边缘时**没有自动滚动延伸选区**(需先滚到位再选);行内 wide+组合字符混排的列切片为近似。
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user