Files
rustui-playground/TERMINAL_ROADMAP.md
zhangheng 3603321dea feat(terminal): add Unicode precision — CJK ambiguous width, emoji ZWJ, ligatures
- Add grapheme_width() with emoji ZWJ sequence detection (width 2)
  and comprehensive is_cjk_ambiguous() per UAX #11
- Add TerminalFontOptions::{ligatures, cjk_ambiguous_wide} options
- Add is_emoji_presentation() covering major emoji Unicode blocks
- Update default font stack with CJK and emoji fallback families
- Add --terminal-font-ligatures CSS variable via font-variant-ligatures
- Add 8 unit tests for grapheme_width covering ASCII, CJK, emoji,
  ZWJ sequences, combining chars, and variation selectors
- Mark P8 complete in roadmap
2026-06-25 11:37:17 +08:00

317 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Terminal Roadmap — 对照 xterm.js 的追平计划
本文档记录 `base-path-demo` 浏览器终端**当前已实现的能力**、**相比 xterm.js 仍缺失的功能**,以及每一项**计划的实现方式与优先级**,供后续开发接手。
> 定位提醒:本项目目标不是复刻 xterm.js 这个通用 JS 终端库,而是按 [TERMINAL_HANDOFF.md](TERMINAL_HANDOFF.md) 的方向,用 `Axum + portable-pty`(后端)+ `Leptos + Rust/WASM`(前端)沉淀一套**高性能、可复用的 Rust 终端组件**。因此追平的重点是「让真实终端会话好用且性能好」,而非铺平 xterm.js 的全部 API 面。
>
> **性能目标**:终端默认渲染器已回切到 Canvas 2D保留 WebGL glyph atlas / per-cell quad 与 DOM renderer 作为可切换路径。最新基准显示 Canvas 2D 在 10k 行输出、200×80 / 240×100 viewport 下吞吐和 frame time 都明显优于当前 WebGL 实现WebGL 继续作为优化回归目标保留。
---
## 1. 覆盖度自评(截至本文档)
按用途粗估:
- **「跑通真实终端会话」核心用途**:约 **85%**
- **xterm.js 完整 API / 特性面**:约 **40%**
差距集中在 **API/插件生态、accessibility、搜索/链接、Unicode 深度精度、运行时可配置性、工程化性能基准**,而非「能不能用」。
### 已实现(有代码 + 单元测试)
| 能力 | 位置 |
|---|---|
| PTY 会话 + WebSocket 双向流 | `server/src/terminal/{pty_session,ws}.rs` |
| ANSI 解析vt100 crate颜色 / 粗体 / 斜体 / 下划线 / 反色、CSI 光标控制、擦除、滚动区 | `app/src/terminal/core.rs` |
| 256 色 + RGB 真彩 | `core.rs::indexed_color` / `resolve_*` |
| alt-screenvim / btm / htop含历史隔离 | `core.rs::process` |
| scrollback + 虚拟滚动(万行不卡,绝对定位) | `core.rs::collect_window` / `component.rs::virtual_window` |
| 宽字符 / 组合字符网格对齐 | `core.rs` `TerminalSegment.cols` |
| resize / reflow | `core.rs::resize` |
| `clear`(含 `ESC[3J` 清 scrollback | `core.rs::contains_erase_scrollback` |
| alt-screen 滚轮 → 方向键翻页rAF 合并多次 wheel 事件,单次滑动最多一屏) | `component.rs::handle_wheel` |
| 基础按键Ctrl-C/D/L、方向键、Enter/Tab/Esc/Backspace | `component.rs::map_key_to_terminal_input` |
| 完整键表F1F12/Home/End/PageUp/Down/Ctrl AZ/Alt-key | `component.rs::key_to_bytes` |
| application-cursor 模式DECCKM方向键切 SS3 | `component.rs::cursor_seq` + `TerminalCore::application_cursor` |
| 粘贴Ctrl-V / Ctrl-Shift-V / 右键菜单,含 bracketed paste | `component.rs::handle_paste` / `prepare_paste` |
| 文本选区 + 复制自建模型Ctrl-Shift-C / 选区存在时 Ctrl+C跨滚动 | `component.rs` 鼠标处理 / `core.rs::selection_text` |
| 鼠标上报vim/htop/tmux 鼠标SGR/Default/UTF-8 编码,滚轮) | `component.rs` 鼠标处理 / `encode_mouse_report` |
| 中文 / IME 输入(拼音/日文/韩文,隐藏 textarea + composition 事件) | `component.rs::handle_input` / `handle_composition_*` |
| Canvas 2D 默认渲染器 + WebGL glyph atlas 可选路径per-cell quad、DPR glyph atlas、背景/underline/cursor backdrop、DOM 回退) | `component.rs::Renderer` / `render_canvas` / `canvas_render_plan` / `render_webgl` / `GlyphAtlas` |
---
## 2. 追平项(按优先级)
### P0 — 复制 / 粘贴 + 文本选区 ⭐ ✅ 已完成
**现状**:粘贴、选区、复制**全部完成并验证通过**。
**已实现**
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. **选区 + 复制**自建选区模型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**当选区存在时 Ctrl+C 优先复制**(与 VS Code / Windows Terminal 一致),无选区时才发送 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 粘贴。
**已知后续项(非阻塞)**:拖拽到视窗边缘时**没有自动滚动延伸选区**(需先滚到位再选);行内 wide+组合字符混排的列切片为近似。
---
### P1 — 完整功能键 + application-cursor 模式 ✅ 已完成
**现状**:完整键表已实现并验证通过(`key_to_bytes_table` 单测覆盖)。
**已实现**
1. **完整键表**`component.rs::key_to_bytes`
- F1F4 SS3`ESC O P/Q/R/S`、F5F12 CSI `~`(含 xterm 真实的 16/22 跳号)。
- Home/End、Insert/Delete、PageUp/PageDown、Enter/Tab/Esc/Backspace。
- Ctrl A..Z → 0x01..0x1a;以及 `@[\\]^_` 变体(→ 0x00/0x1b/0x1c/0x1d/0x1e/0x1f
- Alt+char → `ESC char`(标准 meta-as-ESC 编码)。
2. **application-cursor 模式DECCKM**`cursor_seq(byte, app_cursor)` 助手;为真时方向键 / Home / End 发 SS3`ESC O X`),否则 CSI`ESC [ X`)。`handle_keydown``handle_wheel` 都据此切换。
3. **浏览器快捷键仲裁**`handle_keydown` 对 bare `Ctrl+字母`(无 Shift/Alt/Meta调用 `preventDefault()`,阻止浏览器吃掉 `Ctrl-R` 刷新、`Ctrl-F` 查找、`Ctrl-A` 全选等。`Ctrl+Shift` 组合(如 `Ctrl-Shift-C` 复制)和 `Ctrl+Alt`/`Ctrl+Meta` 组合保留给浏览器/系统。
**浏览器预留键(硬限制,无法拦截)**`Ctrl-W`(关标签)、`Ctrl-T`(新标签)、`Ctrl-N`(新窗口)从 Chrome 4 起被浏览器保留,`preventDefault()` 对它们无效,浏览器在 `keydown` 到达页面前就消费了。这是所有网页终端的共同限制xterm.js 同样如此),只能在浏览器之外解决(系统全局快捷键 / 扩展 / Electron 包装。readline 退格删词可用 `Alt+Backspace` 替代 `Ctrl-W`
4. **测试**`key_to_bytes_table` 单测覆盖整套键表23 个 terminal 测试全过、clippy 干净。
**有意调整的行为**(出于浏览器终端的可用性,与原生 xterm 不同):
- `Ctrl-C` (0x03) → 无选区时发 SIGINT 中断前台进程;**有选区时优先复制**,不发送 0x03。
- `Ctrl-Z` (0x1a) 与 `Ctrl-\` (0x1c) → 被前端过滤为 `None`,不发送到 PTY。原因是这两个键在默认 `stty` 下会触发 SIGTSTP / SIGQUIT在浏览器标签页里极易误触导致前台进程被挂起或杀掉需要这两个信号的用户可用 shell 内建命令(如 `kill -STOP` / `kill -QUIT`)替代。
---
### P2 — 鼠标上报mouse reporting ✅ 已完成
**现状**:已实现并验证。`vim`/`htop`/`btm`/`tmux` 的鼠标交互、tmux 选区、vim 滚轮均可以工作。
**已实现**
1. **vt100 mouse-mode accessors**`core.rs::mouse_protocol_mode` / `mouse_protocol_encoding`)—— 暴露 `None/Press/PressRelease/ButtonMotion/AnyMotion``Default/Utf8/Sgr`
2. **事件监听与坐标换算**`component.rs`
- `on:mousedown` / `on:mouseup` / `on:mousemove` 复用现有的 DOM 事件。
- `pixel_to_grid``(clientX, clientY)` 换算成 0-based 可见网格 `(row, col)`(注意:与选区用的 `point_to_cell` 不同,这里**不加 scroll_top**,因为鼠标报告坐标是相对于应用正在绘制的当前屏幕,而非 scrollback 缓冲区)。
3. **编码**`encode_mouse_report` 纯函数)
- **SGR1006**`ESC[<b;col;row;M`(按下/移动)/ `ESC[<b;col;row;m`释放。ASCII-only任意屏幕尺寸都正确。
- **Default / UTF-8**`ESC[M` + 三字节(各 +32。坐标 ≤94 时完全正确并 ASCII-safe更大坐标被钳位到 94大多数终端 likewise现代应用都走 SGR
- **滚轮**:垂直/水平滚轮映射为按钮 64/65/66/67 的 `Press` 事件。
4. **模式仲裁**
- 应用开启 mouse reporting`mode != None`)时,鼠标事件优先发给应用;否则走本地选区。
- 按住 **Shift** 点击可绕过鼠标上报、强制本地选区xterm 行为)。
- 鼠标移动按网格单元去重,避免每像素都发报告。
5. **测试**`encode_mouse_report``pixel_to_grid``with_mouse_modifiers``mouse_button_base` 均有单测23 个 terminal 测试全过clippy 干净。
**已知限制**
- 主屏幕(非 alt-screen滚动后鼠标报告坐标仍相对于可见网格顶部因为开启鼠标上报的应用通常运行在 alt-screen这覆盖主要使用场景。
- 与浏览器 `Ctrl+W/T/N` 类似,浏览器可能保留某些鼠标手势(如右键菜单、拖拽文本),这些在网页终端内无法 100% 拦截。应用内鼠标操作应正常。
---
### P3 — 中文 / IME 输入 ✅ 已完成
**现状**:已实现。拼音、日文、韩文等 IME 组合输入可以正常工作;普通英文/数字输入也迁到了 `textarea``input` 事件,确保和 IME 走同一条路径。
**已实现**
1. **隐藏 `<textarea>` IME 载体**`component.rs` view
- 绝对定位、透明、无边框、大小跟随终端光标单元格。
- 通过 `tabindex="0"` 成为焦点目标;`terminal-screen` 改用 `:focus-within` 显示焦点环。
2. **事件模型迁移**
- `on:input`:普通字符/数字/空格输入,读取 textarea value 后清空并发送。
- `on:compositionstart` / `on:compositionend`IME 组合期间不发字节,`compositionend` 时发送最终文本。
- `on:keydown`仍处理方向键、Enter、Esc、Backspace、F 键、Ctrl 组合等**非打印键**。
- `on:paste`:从 `terminal-screen` 移到 textarea确保 focus 在 textarea 时右键/菜单粘贴也能触发。
3. **焦点管理**
- 点击 terminal-screen、WebSocket 连接成功、窗口 resize 后都聚焦 IME textarea。
4. **测试**:新增 `is_regular_text_input_detects_printable_keys` 单测,确保打印字符被正确交给 textarea24 个 terminal 测试全过clippy 干净。
**已知限制**
- 主屏幕 scrollback 滚动后光标可能位于可见区域下方IME 候选框位置会对应到光标逻辑位置可能偏下。IME 主要用于当前输入, scrolled-back 场景输入较少见。
- 目前不支持 textarea 自动滚动延伸选区(和 P0 同一限制)。
---
### P4 — Canvas / WebGL 渲染器(性能目标)✅ 已完成
**现状**Canvas 2D 已作为默认渲染器WebGL glyph atlas / per-cell quad 与 DOM renderer 保留为可切换路径。长输出、输入后 live/history 稳定性、IME textarea buffer 坐标、DPR backing store、虚拟滚动 sizer、selection overlay、cursor overlay 均已验证。2026-06-24 benchmark 显示 Canvas 2D 在当前实现下优于 WebGL因此默认策略先以 Canvas 为准。
**已实现**
1. **Canvas 2D 默认渲染器 + WebGL 可选路径**
- `Renderer::Canvas` 默认,`Renderer::WebGl` / `Renderer::Dom` 作为可切换回归路径。
- WebGL2 初始化 shader/program/vertex buffer/atlas texture/fallback texture。
- `GlyphAtlas` 按 grapheme + bold/italic + cell width 缓存字形ASCII、CJK wide 字符、组合字符按 `unicode-segmentation` / `unicode-width` 拆成 cell glyph。
- 文本通过 per-cell quad 批量绘制,颜色作为 vertex attribute小字号不再走整屏纹理二次采样。
- 背景、underline、cursor 背景在 WebGL 正常路径中直接绘制为纯色 quadcursor 前景文字仍进 atlas。
- atlas 溢出或遇到不可拆分复杂段时自动回退整屏 Canvas texture避免真实终端内容白屏。
- WebGL2 不可用时自动落回 Canvas 2D。
2. **Canvas 2D 渲染器**
- 保留现有 `TerminalCore` 数据模型,渲染层切换为 Canvas。
- 一屏/一窗口 `fillText` / `fillRect` 绘制,避免每个 segment 都生成 DOM 节点。
- DPR backing store 与 CSS 尺寸分离,避免高 DPI 模糊。
- `CanvasRenderPlan` 统一计算 backing store、CSS size、canvas top、content offset。
- `CanvasDomState` 缓存 DOM 尺寸状态,避免每帧无意义重设 backing store。
- 保留 `terminal-virtual-sizer` 维持完整 scroll geometry`cat` 后 live 模式稳定贴底。
- selection overlay 保持 DOM 绝对定位canvas 只负责文本/背景/光标。
- IME textarea 改用 buffer 坐标定位,长 scrollback 后输入不会触发 live/history 抖动。
3. **测试**
- `canvas_render_plan``canvas_fill_style``terminal_font``canvas_viewport_top_px``ime_cursor_cell``webgl_quad_vertices``webgl_shader_sources``GlyphAtlas``split_segment_glyphs``push_quad` 均有单测。
- `cargo test -p app --lib terminal::` 当前 38 个 terminal 测试通过。
**后续深化(进入新一轮 xterm.js 对标计划)**
1. WebGL context loss/recovery 与 atlas 重建。
2. 渲染性能基准:固定脚本覆盖 10k/50k 行输出、200×80、240×100、持续输出 FPS / frame time。
3. 浏览器截图 + canvas/WebGL pixel smoke test 固化到工程化脚本。
4. emoji / ZWJ / variation selector / ligature 的 advanced shaping 策略。
---
### P5 — xterm.js API / 配置面(新一轮)✅ 已完成
**目标**:把当前 `TerminalPanel` 从页面内组件推进成可复用终端组件。
**已实现**
1. `TerminalOptions`
- cols/rows、scrollback、renderer、font family/size/line-height、theme、cursor style/blink、title/subtitle、show_header。
- 支持 runtime options signal 更新字体、行高、theme 同步到 DOM / Canvas / WebGL glyph atlas / 虚拟滚动计算。
- `TerminalCore::new_with_scrollback` 让 scrollback 容量进入 parser 层,而不只是 UI 参数。
2. `TerminalHandle`
- `write``paste_text``clear``focus``resize``scroll_to_bottom``copy_selection`
3. 事件回调 ✅
- `on_data``on_resize``on_selection_change``on_title_change``on_render`
4. 测试 ✅
- 新增 options 解析 / CSS 变量 / 行高参数化 / cursor shape / cursor glyph color 相关单测。
- `cargo test -p app --lib terminal::` 当前 41 个 terminal 测试通过。
5. 第一轮模块拆分 ✅
- `keyboard.rs`paste normalization、key mapping、printable key 判断。
- `mouse.rs`wheel rows、mouse report 编码、visible grid mapping。
- `selection.rs`selection model、selection rect 计算。
- `ime.rs`IME cursor buffer 坐标和 hidden textarea helpers。
**后续进入 P6**
1. `TerminalHandle` 增强:`fit()`、serialize/search/link addon 接口。
2. 继续把 WebGL / Canvas renderer 从 `component.rs` 拆成专门模块。
### P6 — xterm.js addon parity搜索 / 链接 / 序列化 / fit
**当前进度**
1. **Fit addon 等价能力**
-`TerminalHandle::fit()`,根据容器内容区重新测量 rows/cols更新 core viewport 并同步 PTY resize。
2. **Search**
-`TerminalHandle::search_next/search_previous/clear_search`
- ✅ literal search 跨 `history + screen`,支持 wrap搜索结果通过 DOM overlay 高亮WebGL/Canvas/DOM renderer 共用。
3. **Serialize**
-`TerminalHandle::serialize_text()``TerminalCore::buffer_text()`,导出当前 retained buffer 文本。
-`TerminalHandle::serialize_ansi()``TerminalCore::buffer_ansi()`,按当前 row/style 模型导出 ANSI-styled snapshot。
4. **Web links / OSC 8**
- ✅ URL 自动识别、点击打开WebGL/Canvas/DOM renderer 共用链接 hitbox overlay。
- ✅ OSC 8 hyperlink 解析:支持 BEL / ST 终止,使用 cell-grid sidecar 记录链接,覆盖写入、常见 CSI 光标移动、清行/清屏、滚屏进 history 后仍能正确映射到链接 hitbox overlay。
- 后续可继续扩展非常规控制序列覆盖面,但 P6 addon parity 的核心能力已完成。
### P7 — Accessibility / 可访问性 ✅ 已完成
**已实现**
1. **Screen reader buffer / aria-live**
- 隐藏 `div[aria-live="polite"][role="status"]` 实时推送可见终端文本。
- 提取可见行文本(去尾空、过滤空行),每 500ms 防抖更新,避免大量输出时淹没屏幕阅读器。
- `role="application"` + `aria-label="Terminal"` + `aria-roledescription="terminal emulator"` 标注终端区域。
2. **Keyboard-only selection / copy** ✅(已在 P0 完成)
- `Ctrl-Shift-C` 复制;选区存在时 `Ctrl-C` 优先复制。
- 所有键盘处理通过 IME textarea 的 `on:keydown` 统一入口。
3. **High contrast / minimum contrast ratio**
- 监听 `prefers-contrast: more` 媒体查询,自动切换 `Canvas` / `CanvasText` 系统色。
- CSS `@media (prefers-contrast: more)` 覆盖终端前景/背景/边框。
4. **Reduced motion 与焦点可见性**
- 监听 `prefers-reduced-motion: reduce` 媒体查询,自动禁用光标闪烁动画和过渡。
- JS 侧同步设置 `--terminal-reduce-motion:reduce` CSS 变量Effect 内联动 `prefers_reduced_motion` 信号。
- `terminal-screen:focus-within` 已有可见焦点环(`box-shadow: 0 0 0 3px rgb(56 189 248 / 15%)`)。
### P8 — Unicode / 字形精度 ✅ 已完成
**已实现**
1. **Emoji / ZWJ / variation selector 宽度策略**
- `grapheme_width()` 函数ZWJ emoji 序列(如家族 emoji检测在标准宽度计算之前整体返回宽度 2。
- `is_emoji_presentation()` 覆盖 Emoticons、Misc Symbols、Supplemental Symbols、Transport、Dingbats 等 Unicode 区块。
- ZWJ 非 emoji 序列返回 1combining marks 返回 1variation selectors 被吸收不额外占列。
- 8 个单元测试覆盖 ASCII、CJK、emoji、ZWJ、combining、variation selector。
2. **CJK ambiguous width 配置**
- `TerminalFontOptions::cjk_ambiguous_wide` 选项(默认 `true`,遵循 CJK 终端惯例)。
- `is_cjk_ambiguous()` 按 UAX #11 完整列举 Ambiguous East Asian Width 字符。
- 开启时 ○、△ 等符号按宽度 2 渲染;关闭时按宽度 1。
3. **Ligature 可选支持**
- `TerminalFontOptions::ligatures` 选项(默认 `false`,终端通常不需要连字)。
- `--terminal-font-ligatures` CSS 变量联动 `font-variant-ligatures`
4. **Fallback font 校准**
- 默认字体栈新增 Noto Sans CJK SC、Microsoft YaHei、PingFang SCCJK和 Apple Color Emoji、Segoe UI Emoji、Noto Color Emojiemoji
### P9 — WebGL Renderer 工程化深化
**计划实现**
1. **WebGL context loss/recovery 与 atlas 重建**
- 监听 `webglcontextlost` / `webglcontextrestored`lost 时阻止默认行为并清空 `WebGlRendererState`
- context lost 或 WebGL 初始化失败时显示独立 Canvas 2D fallback surface避免同一 canvas 上 WebGL lost 后无法获取 2D context 导致白屏。
- restore 后清空旧 state 并触发重绘,下一帧重建 shader/program/buffer/texture/glyph atlas。
- context lifecycle listener 由 `WebGlContextLifecycle` 持有,组件卸载或 renderer 切换时自动移除。
2. **渲染性能 benchmark 和自动截图 / pixel smoke test**
- `scripts/terminal-bench.mjs` 连接已启动的 `/rustui/terminal`,不负责启动/关闭服务。
- 固定覆盖 `200x80``240x100` viewport支持 `--lines``--viewport``--output` 参数。
- 记录行输出耗时、rows/sec、requestAnimationFrame frame time 摘要、`window.__terminalBench` render probe。
- 截图 smoke 默认走 Playwright screenshot PNG 非空/熵检查Canvas 2D fallback 可读时额外做像素采样。
3. **Canvas 2D / WebGL runtime switch**
- `/rustui/terminal` 页面提供 WebGL / Canvas / DOM runtime 切换。
- 支持 `?renderer=webgl|canvas|dom` 初始化,便于浏览器回归和 benchmark 固定模式。
- `scripts/terminal-bench.mjs --renderer webgl|canvas|dom` 复用同一入口跑对比,并将截图按 renderer 命名。
4. **与 Canvas 2D 做 benchmark 对比,按 viewport size / output rate 自动选择 renderer**
- 2026-06-24 基线10k 行输出:
- `200x80`WebGL `155.27 rows/sec`frame p95 `283.3ms`Canvas `311.67 rows/sec`frame p95 `116.7ms`
- `240x100`WebGL `92.48 rows/sec`frame p95 `399.9ms`Canvas `157.65 rows/sec`frame p95 `116.8ms`
- 两种 renderer 的 pixel smoke 均通过Canvas 可直接读取 canvas 像素WebGL 走 screenshot smoke。
- 当前策略:默认 CanvasWebGL 继续通过页面切换、`?renderer=webgl``--renderer webgl` 保留优化回归入口。
5. **WebGL renderer profiling / 慢路径拆解**
- WebGL render path 记录阶段耗时DOM/canvas 同步、backdrop 绘制、glyph collect、backdrop texture pass、glyph atlas、atlas upload、vertex build/upload、draw call。
- 记录计数cells、atlas entries、atlas insertions/resets、full-canvas fallback 次数、glyph draw failed 次数。
- `window.__terminalBench.webglFrames` 暴露每帧采样,`scripts/terminal-bench.mjs` 汇总为 `rendererStats`,用于判断慢点是整屏 backdrop texture、atlas、vertex buffer还是 fallback。
6. **WebGL backdrop texture pass 拆除**
- 正常 WebGL 路径不再每帧把 Canvas backdrop 上传为整屏 texture背景、underline、cursor 背景改为 1×1 白色纹理 + 纯色 quad 批量绘制。
- `backdropTextureMs` 只在复杂 glyph / atlas 失败等 full-canvas fallback 路径产生,用于继续观察 fallback 是否被真实内容触发。
- 页面切换、`?renderer=webgl``--renderer webgl` 保留,下一轮基准可直接对比这次优化后 WebGL 是否追近 Canvas。
7. **WebGL glyph collect cache**
- WebGL renderer state 增加可见行 glyph 拆分缓存,相同行内容 / 样式 / theme 在滚动和连续输出时复用 grapheme 拆分结果。
- `GlyphKey.text` 改为 `Rc<str>`,缓存命中后 materialize 行 glyph cell 时避免反复复制 glyph 字符串。
- benchmark `rendererStats` 新增 `rowCacheHits` / `rowCacheMisses`,用于判断 `collectMs` 是否被 cache 命中率拉低。
8. **WebGL row vertex template cache**
- atlas 覆盖后按行缓存 glyph vertex template后续帧只按当前 row y 偏移 materialize减少每帧 atlas lookup / UV 计算 / quad 构建。
- benchmark `rendererStats` 新增 `vertexCacheHits` / `vertexCacheMisses`,用于判断 `vertexBuildMs` 是否主要来自 cache miss。
---
## 4. 建议推进顺序
```
P0 复制粘贴(先粘贴→可见区复制) ← 最先,体验提升最大
P1 完整功能键 + application-cursor
P2 鼠标上报
P3 中文 IME(放在功能键之后,避免输入逻辑冲突)
P4 Canvas / WebGL 渲染器(满足大终端 + 高刷新率性能目标)
P5 API / 配置面
P6 addon parityfit/search/serialize/web-links
P7 accessibility
P8 Unicode / 字形精度
P9 WebGL renderer 工程化深化
```
每一项都应延续现有做法:**纯逻辑抽成可单测的函数**(如 `map_key_to_terminal_input``wheel_delta_to_rows``virtual_window`),用 `cargo test -p app --lib terminal::` 锁定,浏览器只做交互验证。