# 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 面。 > > **性能目标**:终端默认渲染器已切到 WebGL glyph atlas / per-cell quad,保留 Canvas 2D 与 DOM renderer 作为回退。Canvas 2D 解决了大部分 DOM 节点膨胀、长输出重排和高频输出卡顿问题;WebGL 路径现在按字形 atlas 缓存并批量绘制单元格 quad。 --- ## 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-screen(vim / 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` | | 完整键表(F1–F12/Home/End/PageUp/Down/Ctrl A–Z/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_*` | | WebGL glyph atlas 默认渲染器(per-cell quad、DPR glyph atlas、背景/underline/cursor backdrop、Canvas/DOM 回退) | `component.rs::Renderer` / `render_webgl` / `GlyphAtlas` / `render_canvas` / `canvas_render_plan` | --- ## 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`) - F1–F4 SS3(`ESC O P/Q/R/S`)、F5–F12 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` 纯函数) - **SGR(1006)**:`ESC[` 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` 单测,确保打印字符被正确交给 textarea;24 个 terminal 测试全过,clippy 干净。 **已知限制**: - 主屏幕 scrollback 滚动后,光标可能位于可见区域下方,IME 候选框位置会对应到光标逻辑位置(可能偏下)。IME 主要用于当前输入, scrolled-back 场景输入较少见。 - 目前不支持 textarea 自动滚动延伸选区(和 P0 同一限制)。 --- ### P4 — Canvas / WebGL 渲染器(性能目标)✅ 已完成 **现状**:WebGL glyph atlas / per-cell quad 已作为默认渲染器;Canvas 2D 与 DOM renderer 保留为回退路径。长输出、输入后 live/history 稳定性、IME textarea buffer 坐标、DPR backing store、虚拟滚动 sizer、selection overlay、cursor overlay 均已验证。 **已实现**: 1. **WebGL glyph atlas 默认渲染器** ✅ - `Renderer::WebGl` 默认,`Renderer::Canvas` / `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 背景由 Canvas backdrop 绘制后作为底层 texture 合成;cursor 前景文字仍进 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 策略。 2. keyboard-only selection / copy。 3. high contrast / minimum contrast ratio。 4. reduced motion 与焦点可见性。 ### P8 — Unicode / 字形精度 **计划实现**: 1. emoji / ZWJ / variation selector 的 grapheme cluster 宽度策略。 2. CJK ambiguous width 配置。 3. ligature 可选支持。 4. fallback font metrics 校准。 ### P9 — WebGL Renderer 工程化深化 **计划实现**: 1. WebGL context loss/recovery 与 atlas 重建。 2. 渲染性能 benchmark 和自动截图 / pixel smoke test。 3. Canvas 2D / WebGL runtime switch。 4. 与 Canvas 2D 做 benchmark 对比,按 viewport size / output rate 自动选择 renderer。 --- ## 4. 建议推进顺序 ``` P0 复制粘贴(先粘贴→可见区复制) ← 最先,体验提升最大 ↓ P1 完整功能键 + application-cursor ↓ P2 鼠标上报 ↓ P3 中文 IME(放在功能键之后,避免输入逻辑冲突) ↓ P4 Canvas / WebGL 渲染器(满足大终端 + 高刷新率性能目标) ↓ P5 API / 配置面 ↓ P6 addon parity(fit/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::` 锁定,浏览器只做交互验证。