# 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 面。 --- ## 1. 覆盖度自评(截至本文档) 按用途粗估: - **「跑通真实终端会话」核心用途**:约 50–60% - **xterm.js 完整 API / 特性面**:约 15–20% 差距集中在**输入完整性**、**API/生态**、**可配置性**,而非「能不能用」。 ### 已实现(有代码 + 单元测试) | 能力 | 位置 | |---|---| | 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 滚轮 → 方向键翻页 | `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` | --- ## 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. **选区(较复杂,下一步)** - 因为渲染是 DOM(每行一个 `
`、每段一个 ``),可优先尝试**浏览器原生选区**:给可见行允许 `user-select: text`,监听 `copy` 事件,从 `window.getSelection()` 取文本。 - 但虚拟滚动只渲染可见窗口,跨窗口选区会断 → 需要把「逻辑行号 → 文本」的映射暴露给选区逻辑,从 `TerminalCore` 的 `history_rows + screen_rows` 直接取纯文本(已有 `row` → 文本的能力,测试里 `row_text` 即是)。 - MVP 可只支持**可见范围内选区 + 复制**,跨滚动选区列为后续。 3. **快捷键**:Ctrl-Shift-C / Ctrl-Shift-V(终端惯例,避免和 Ctrl-C 中断冲突)。 **风险**:选区与虚拟滚动的交互是主要难点;建议 MVP 限定可见区,先把粘贴 + 可见区复制跑通。 --- ### P1 — 完整功能键 + application-cursor 模式 **现状**:只映射了方向键、Ctrl-C/D/L、Enter/Tab/Esc/Backspace。缺 F1–F12、Home/End/Insert/Delete、PageUp/PageDown、大量 Ctrl/Alt 组合。 **计划实现**: 1. 扩展 `map_key_to_terminal_input`,补齐: - `F1`–`F12` → `ESC O P/Q/R/S`(F1–4)、`ESC[15~`…(F5–12) - `Home`/`End` → `ESC[H` / `ESC[F`(或 `ESC[1~` / `ESC[4~`) - `Insert`/`Delete` → `ESC[2~` / `ESC[3~` - `PageUp`/`PageDown` → `ESC[5~` / `ESC[6~` - `Ctrl+A..Z` → 控制字符 `0x01..0x1A`(通用化,不再只 C/D/L) - `Alt+` → 前缀 `ESC` + 字符 2. **application-cursor 模式**:vt100 暴露 `screen().application_cursor()`。为真时,方向键要发 **SS3 形式 `ESC O A/B/C/D`** 而非 `ESC [ A/B/C/D`。`handle_keydown` 和 `handle_wheel`(alt-screen 滚轮翻页)都要据此切换序列。 - 这能修掉「某些 vim 配置 / 全屏程序里方向键行为怪异」的潜在问题。 **风险**:低。纯查表扩展 + 一个模式判断。建议把按键表抽成数据驱动并加单元测试覆盖。 --- ### P2 — 鼠标上报(mouse reporting) **现状**:没有。点击 / 拖拽位置不会发给应用,vim 鼠标、tmux 选区、btm 点击都用不了。 **计划实现**: 1. vt100 已解析鼠标模式:`screen().mouse_protocol_mode()`(None/Press/PressRelease/ButtonMotion/AnyMotion)和 `mouse_protocol_encoding()`(Default/Utf8/Sgr)。 2. 在 `terminal-screen` 上监听 `on:mousedown` / `on:mouseup` / `on:mousemove`: - 把像素坐标换算成终端 **行列**(用已测量的 cell 宽高 + 容器 rect)。 - 按当前 encoding 编码: - **SGR**(最常用):`ESC[`** 的标准方案(xterm.js 也是这套): - 一个 1×1 透明、跟随光标的 `textarea` 承接 IME。 - 监听 `compositionstart` / `compositionupdate` / `compositionend`。 - 组合进行中不发字节;`compositionend` 时把最终文本一次性作为 Input 发送。 - 普通 `input` 事件(非组合)也走这个 textarea,替代部分 `keydown` 逐字符逻辑。 2. 光标定位:把隐藏 textarea 定位到终端光标处,让候选框出现在正确位置。 **风险**:中高。事件模型要从「keydown 逐键」迁一部分到「textarea + composition」,需保证不回归现有按键。建议**先把 P1 功能键做完再动这块**,避免两套输入逻辑互相打架。 --- ## 3. 其余缺口(暂不排期,记录备查) xterm.js 还有这些,本项目暂列「按需再做」: - **链接检测**(点 URL 打开)、**搜索 / 查找**(Ctrl-F 高亮) - **光标样式**:块 / 竖线 / 下划线 + 闪烁(目前固定块状反色) - **Canvas / WebGL 渲染器**:当前是 DOM 渲染,够用但不如 Canvas 快;超大终端 + 高刷新率场景才需要 - **sixel / 图片协议**、**字形连字控制** - **Unicode 11/15 宽度表**:目前依赖 vt100 的宽度判断,极端 emoji / 新字符可能不准 - **无障碍**(屏幕阅读器 live region) - **公开 API / 插件系统 / 配置项**:当前是项目内定制组件,无对外 API --- ## 4. 建议推进顺序 ``` P0 复制粘贴(先粘贴→可见区复制) ← 最先,体验提升最大 ↓ P1 完整功能键 + application-cursor ↓ P2 鼠标上报 ↓ P3 中文 IME(放在功能键之后,避免输入逻辑冲突) ``` 每一项都应延续现有做法:**纯逻辑抽成可单测的函数**(如 `map_key_to_terminal_input`、`wheel_delta_to_rows`、`virtual_window`),用 `cargo test -p app --lib terminal::` 锁定,浏览器只做交互验证。