Files
terminal-lab/terminal/xterm-standalone-lab-plan-2026-03-01.md
douboer@gmail.com 3b7c1d558a first commit
2026-03-03 13:23:14 +08:00

731 lines
29 KiB
Markdown
Raw Permalink 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.

# xterm 移植独立实验项目实施方案2026-03-01
## 1. 目标与范围
本方案用于落地一个独立实验项目验证“xterm.js 基线能力”与“原生 textarea 方案”的迁移可行性。
该项目仅用于测试,不替换现有 Web/小程序主链路。
规范基线:
1. 渲染与输入语义以 `xterm.js@5.3.0` 源码为准。
2. 文档中的换行、回显、光标、粘贴规则均以该版本源码行为校正。
3. 若未来升级 xterm 版本,需先做“规范差异审计”再修改本方案。
目标:
1. 目录独立:实验代码只在 `labs/xterm-standalone/`
2. 依赖可复用:允许复用现有依赖版本策略和工具链版本。
3. 构建/运行可复用:命令约定复用 `dev/build/test/lint/typecheck`
4. 业务零耦合:不复用现有业务模块逻辑,不修改现有状态机与会话实现。
5. 可删除无副作用:删除实验目录和小程序入口后,主项目行为不变。
## 2. 非目标
1. 不替换 `apps/web` 现有终端实现。
2. 不修改 `apps/gateway` 的业务协议和转发逻辑。
3. 不改 `packages/shared` 的现有模型定义和状态机实现。
4. 不将实验代码接入生产发布链路。
## 3. 独立性与复用规则
### 3.1 必须满足
1. 实验代码不得 `import` 现有业务模块(`apps/*``packages/*` 的业务实现)。
2. 小程序仅新增“入口级改动”(页面按钮 + web-view 跳转)。
3. 实验项目失败、下线、删除,不影响主流程连接与发布。
4. 禁止在实验项目中运行时依赖 `@xterm/*`xterm 仅作为源码语义参考,不作为可执行依赖。
### 3.2 允许复用
1. 依赖版本策略可复用(例如与根工程保持同主版本)。
2. 工程工具可复用TypeScript、Vite、ESLint、Vitest 配置风格)。
3. 运行方式可复用(统一命令命名,不要求命令实现完全一致)。
4. 主题 token 可复用现有 Web/小程序的成熟定义(颜色变量、语义色分层、主题切换策略),但实现代码必须保留在实验目录内。
## 4. 目录规划(实施级)
```text
labs/xterm-standalone/
README.md
package.json
src/
main.ts
app/App.vue
pages/TerminalLabPage.vue
components/
TerminalToolbar.vue
TerminalViewport.vue
TerminalInputBar.vue
TerminalTouchTools.vue
core/
session/sessionMachine.ts
session/sessionTypes.ts
transport/terminalTransport.ts
transport/gatewayTransport.ts
renderer/rendererAdapter.ts
renderer/compatRenderer.ts
renderer/textareaRenderer.ts
input/inputBridge.ts
input/imeController.ts
layout/sizeCalculator.ts
layout/cursorMath.ts
buffer/outputBuffer.ts
sanitize/terminalSanitizer.ts
stores/useTerminalLabStore.ts
styles/terminal-lab.css
```
## 5. 总体架构与数据流
```text
Transport(stdout/stderr) -> Sanitizer -> OutputBuffer -> RendererAdapter(compat|textarea) -> View
UserInput(key/ime/paste/touch) -> InputBridge -> xterm-compatible input mapping -> Transport(stdin)
LayoutResize -> sizeCalculator(cols/rows) -> Transport(resize) + Renderer.resize
```
关键原则:
1. 传输层不感知渲染器类型。
2. 输入处理统一走 `InputBridge`,避免各组件各自发送 stdin。
3. 渲染器通过统一接口实现,便于模式切换和 A/B 对比。
4. 输入映射遵循 xterm 规则:`Enter -> CR`,粘贴文本行结束归一到 `CR`
5. 输出渲染遵循 xterm 控制字符纪律:`CR``LF` 分离语义,`convertEol` 默认 `false`
6. 原生响应优先:软键盘、选区、滚动由原生 `textarea` 通道主导xterm 仅负责渲染与终端语义,不得接管系统输入响应。
7. 事件主权清晰:键盘/鼠标事件先由应用层做区域过滤与路由,再决定是否交给 xterm。
## 6. 状态机规划
## 6.1 会话状态机(连接生命周期)
状态集合(与现有模型对齐):
1. `idle`
2. `connecting`
3. `auth_pending`
4. `connected`
5. `reconnecting`
6. `disconnected`
7. `error`
合法迁移(必须在 `sessionMachine.ts` 中强校验):
1. `idle -> connecting | disconnected`
2. `connecting -> auth_pending | error | disconnected`
3. `auth_pending -> connected | error | disconnected`
4. `connected -> reconnecting | disconnected | error`
5. `reconnecting -> connected | error | disconnected`
6. `disconnected -> connecting | idle`
7. `error -> connecting | disconnected`
触发事件:
1. `connect_request`
2. `socket_open`
3. `connected_frame`
4. `stdout_first_packet`
5. `disconnect_frame/ws_close`
6. `error_frame/ws_error`
7. `manual_disconnect`
8. `auto_reconnect_timeout`
## 6.2 输入状态机IME/普通输入)
状态:
1. `input_idle`
2. `input_composing`
3. `input_commit_pending`
规则:
1. `compositionstart` -> `input_composing`
2. `compositionend` -> 进入 `input_commit_pending`,提交非 ASCII 候选
3. `beforeinput/input` 非组合态可触发 ASCII 兜底发送
4. 超时守卫(如 1.8s)未收到 `compositionend` 时自动恢复到 `input_idle`
## 6.3 触摸焦点状态机(移动端)
动作集合:
1. `BLUR_ONLY`
2. `FOCUS_KEYBOARD`
3. `PASS_NATIVE`
4. `PASS_SCROLL`
决策优先级:
1. `hasSelectionStart || hasSelectionEnd` -> `PASS_NATIVE`
2. `scrollLike` -> `PASS_SCROLL`
3. `moved` -> `PASS_NATIVE`
4. `!inBand` -> `BLUR_ONLY`
5. `inBand` -> `FOCUS_KEYBOARD`
硬约束(新增):
1. 只有 `FOCUS_KEYBOARD` 动作允许触发原生键盘弹出。
2. `FOCUS_KEYBOARD` 必须满足“光标附近行”条件(`inBand=true`),禁止在非光标邻近区域弹键盘。
3. `PASS_NATIVE/PASS_SCROLL` 禁止 `preventDefault`,确保原生选区和滚动不被破坏。
4. 禁止在终端根容器上全局拦截 `touchstart/touchmove/click`;仅允许在明确手势分支下做最小拦截。
5. 移动端 `compat` 仅承担回显渲染,输入焦点必须由原生 `textarea` 锚点持有。
6. 禁止在非用户手势路径下调用 `terminal.focus()` 触发软键盘。
## 6.4 渲染模式状态机
状态:
1. `renderer_compat`
2. `renderer_textarea`
切换事件:
1. `switch_to_compat`
2. `switch_to_textarea`
切换过程必须原子化:
1. `oldRenderer.dispose()`
2. `newRenderer.mount(container)`
3. `replay buffer`
4. `apply size/theme`
5. `focus restore`(仅桌面自动聚焦)
## 6.5 换行模式状态xterm 对齐)
状态:
1. `convertEol=false`(默认)
2. `convertEol=true`(由 `CSI 20 h` 打开)
规则:
1. `LF/VT/FF` 进入 `lineFeed()`:始终 `y+1`,并在触底时滚屏。
2. `convertEol=true` 时,`lineFeed()` 额外执行 `x=0``false` 时保留列位置。
3. `CR` 仅执行 `x=0`,不改变 `y`
4. `NEL``ESC E` / `C1.NEL`)等价 `x=0 + index()`(下移一行并在必要时滚屏)。
5. 右边界写入是否自动折行由 `DECAWM(wraparound)` 控制。
6. 自动折行触发条件按 xterm 口径:字符写入将越过末列时触发换行;换行行需标记 `isWrapped=true`
7. 显式 `LF` 后目标行应清除 `isWrapped`,避免把硬换行误判为软换行。
## 6.6 屏幕缓冲状态(主屏/备用屏,强制)
状态:
1. `buffer_normal`(主屏)
2. `buffer_alternate`(备用屏)
切换规则xterm 对齐):
1. `CSI ? 47 h` / `CSI ? 1047 h`:切换到备用屏。
2. `CSI ? 47 l` / `CSI ? 1047 l`:返回主屏。
3. `CSI ? 1049 h`:保存光标状态并切换到备用屏。
4. `CSI ? 1049 l`:返回主屏并恢复保存光标状态。
5. 对齐 `xterm.js@5.3.0` 现实行为:`1049` 不强制清空备用屏历史(按源码注释口径处理)。
实现约束:
1. `TerminalCore` 必须同时维护 `normalBuffer``alternateBuffer`,仅一个为 `activeBuffer`
2. 两个缓冲区的 `cursor``scroll region``isWrapped` 状态独立维护,禁止互相污染。
3. 切屏仅切换活动缓冲区引用,不得重建对象导致历史丢失。
4. `1049` 路径必须包含 `saveCursor/restoreCursor`,并保证退出全屏后提示符位置正确。
5. 该能力只影响输出缓冲与光标状态,不改变“原生输入主通道”与事件归属策略。
## 7. 组件职责与接口
## 7.1 `TerminalLabPage.vue`
职责:
1. 页面编排、模式切换、连接/断开、状态展示。
2. 组装 `store + renderer + inputBridge`
## 7.2 `TerminalViewport.vue`
职责:
1. 只负责承载渲染容器(不直连 transport
2. 提供挂载点给 `RendererAdapter`
## 7.3 `TerminalInputBar.vue`
职责:
1. 文本输入框、发送按钮、粘贴、快捷键入口。
2. 通过 `InputBridge.send()` 发送,不直接访问 transport。
## 7.4 `TerminalToolbar.vue`
职责:
1. 展示连接状态、延迟、渲染模式。
2. 触发 `connect/disconnect/clear/switchRenderer`
## 7.5 `TerminalTouchTools.vue`
职责:
1. 方向键、Enter、Ctrl+C、Tab、Paste。
2. 输出标准控制序列(如 `ESC[A`)。
## 8. 传输与协议实现要点
## 8.1 `TerminalTransport` 抽象(必须)
接口:
1. `connect(params)`
2. `send(data, meta?)`
3. `resize(cols, rows)`
4. `disconnect(reason?)`
5. `on(listener)`
6. `getState()`
## 8.2 网关帧协议(实验项目内按同协议实现)
入站:
1. `init`
2. `stdin`(含可选 `meta: { source, txnId }`
3. `resize`
4. `control(ping/pong/disconnect)`
出站:
1. `stdout`
2. `stderr`
3. `control(connected/disconnect/pong)`
4. `error`
## 9. 渲染管线设计
## 9.1 输出处理链
1. 收到 `stdout/stderr`
2. 执行 `sanitizeTerminalOutput`(过滤同步更新模式等控制序列噪音)。
3. 追加到 `OutputBuffer`(条目上限 + 字节上限双阈值)。
4. `outputRevision++`
5. 渲染器增量写入;必要时重放。
## 9.2 缓冲策略(必须)
1. `maxEntries` 下限保护(建议 >= 200
2. `maxBytes` 下限保护(建议 >= 64KB
3. 先按条目裁剪,再按字节裁剪。
4. 至少保留最新一条,避免全清空闪烁。
## 9.3 兼容渲染规则(自研)
1. 使用 `write()` 增量写入,不用 `writeln()` 改写换行语义。
2. `convertEol` 默认值必须为 `false`(与 xterm 默认一致)。
3. 显式 `LF` 不等于 `CRLF``CR``LF` 分别处理,禁止业务层自行合并。
4. `fit` 后同步 `resize(cols, rows)`
## 9.4 textarea 渲染规则
1. 采用“只读显示区 + 输入区”或“单 textarea 双模式”方案(二选一,建议前者)。
2. 输出区按文本流追加,必要时使用 `requestAnimationFrame` 批量刷新。
3. 默认自动滚到底;用户手动上滚时暂停自动跟随。
4. 不允许使用“仅按字符串估算”的粗略光标算法作为最终实现。
5. 必须模拟 xterm 的 C0/C1 换行语义:`CR``LF``VT``FF``NEL`
6. 必须显式维护软换行标记(等价 `isWrapped`),区分“自动折行”与“显式换行”。
## 9.5 精准光标内核(强制)
为满足“回显后光标位置必须精准计算”,实验项目必须引入统一终端状态内核(`TerminalCore`),并将其作为两种渲染模式的唯一真相源。
实现要求:
1. `stdout/stderr` 必须先写入 `TerminalCore`,由内核解析控制序列并更新光标、屏幕缓冲、滚动区域状态。
2. `compat` 模式与 `textarea` 模式都必须消费同一份 `TerminalCore` 状态快照。
3. 禁止直接通过 `selectionStart + 文本长度` 推断“终端光标”,该方法仅可用于“输入框本地插入点”,不能代表远端终端光标。
4. `TerminalCore` 至少要正确处理本项目目标程序涉及的序列族:`CR/LF/VT/FF/NEL/BS/TAB``CSI A/B/C/D/H/f/J/K/m``SM/RM(含 Ps=20)``DECSET/DECRST(含 DECAWM)`
5. `TerminalCore` 必须维护以下状态:`cursorX/cursorY``baseY``viewportY``scrollTop/scrollBottom``convertEol``wraparound``line.isWrapped`
6. 每次输出处理后,必须产出 `cursor: { x, y, globalRow }``viewport` 快照,供触摸激活带与滚动定位复用。
## 9.6 颜色渲染规范xterm 对齐,强制)
目标:
1. 颜色与文本属性语义必须对齐 `xterm.js@5.3.0``SGR(CSI ... m)` 行为。
2. `compat` 模式与 `textarea` 模式必须共享同一套属性状态机,避免模式切换后颜色跳变。
实现边界:
1. 必须支持基础 SGR 属性:`0/1/2/3/4/7/8/9/21/22/23/24/25/27/28/29/53/55`
2. 必须支持前景/背景标准色:`30-37``40-47`
3. 必须支持高亮色:`90-97``100-107`
4. 必须支持扩展色:
- 前景 `38`
- 背景 `48`
- 下划线颜色 `58`
5. 扩展色子模式必须支持:
- `;5;INDEX`256 色索引)
- `;2;R;G;B`TrueColor
6. 必须支持默认色复位:
- `39` 复位前景
- `49` 复位背景
- `59` 复位下划线颜色
颜色状态模型(建议):
1. `fg`: `{ mode: default | p16 | p256 | rgb, value }`
2. `bg`: `{ mode: default | p16 | p256 | rgb, value }`
3. `underlineColor`: `{ mode: default | p256 | rgb, value }`
4. `flags`: `bold/dim/italic/underline/inverse/invisible/strikethrough/overline`
5. `underlineStyle`: `none/single/double/curly/dotted/dashed`
重置纪律:
1. `SGR 0` 必须重置前景、背景、下划线样式与颜色到默认(等价 xterm `_processSGR0`)。
2. `22` 仅取消 `bold``dim`,不影响颜色。
3. `24` 仅取消下划线与下划线样式,不清空其它属性。
4. `27` 仅取消 `inverse`
5. `28` 仅取消 `invisible`
6. `29` 仅取消删除线。
渲染映射规则:
1. `inverse` 采用“渲染时交换 fg/bg”策略不直接改写底层存储值。
2. `invisible` 保留背景渲染,前景以透明或背景同色处理(与终端“隐藏文字”语义一致)。
3. `bold` 只作为属性位,不应强行映射为高亮颜色(除非显式启用“粗体映射亮色”策略)。
4. `blink` 在 xterm 5.3 源码中为已记录属性但渲染支持有限,实验项目默认可不做动画实现,但要保留属性位兼容。
主题与调色板约束:
1. 16 色与 256 色索引色应来自统一调色板定义,避免 `xterm``textarea` 调色板不一致。
2. TrueColor (`rgb`) 必须按原值渲染,不经过主题二次量化。
3. 主题切换只影响“默认色与索引色映射”,不应篡改已存在的 TrueColor 单元格。
## 9.7 主题复用与轻量化策略(强制)
目标:
1. 主题实现参考并复用现有 Web/小程序成熟方案,保持视觉一致与维护成本可控。
2. xterm 移植以“核心稳定 + 轻量高效”为优先级,禁止默认堆叠非必要能力。
主题复用规则:
1. 统一采用语义 token`terminalFg/terminalBg/cursor/selection/ansi16`),禁止在组件内硬编码颜色。
2. 默认前景/背景与 `ansi16` 从现有主题配置映射生成,避免与现有产品主题出现明显偏差。
3. 256 色表采用固定映射(标准 xterm256 表),不随业务主题动态重算。
4. 实验项目只维护“主题映射层”,不复制现有 Web/小程序整套业务主题逻辑。
xterm 轻量化规则:
1. 不引入 `@xterm/*` 运行时依赖;实现以 `TerminalCore + 自研渲染器` 为主。
2. 兼容能力按需实现并按模块拆分首屏仅加载连接、输入、回显、resize 必需逻辑。
3. 默认使用原生 DOM/Canvas 路径,禁止引入重型图形加速依赖作为前置条件。
4. 非核心能力(复杂链接检测、额外动画)默认关闭,确保输入回显路径最短。
性能预算(建议):
1. 实验页 JS 增量目标gzip <= 150KB超限需在评审记录中说明来源与必要性
2. 终端首屏可输入目标:页面进入后 1 秒内可完成输入与回显(普通开发机基线)。
3. 持续输出下避免长卡顿:连续主线程阻塞 >100ms 视为性能问题并需定位。
降级开关(必须):
1. `compatLiteMode`关闭可选增强仅保留连接、输入、回显、resize。
2. `rendererFallback=textarea`:低端设备或异常场景可一键降级。
3. 主题映射失败时自动回退默认主题,禁止阻塞会话连接与输入流程。
## 9.8 主屏/备用屏落地实现(强制)
最小实现步骤:
1.`TerminalCore` 新增 `bufferSet = { normal, alternate, active }``savedCursor`
2. 解析 `DECSET/DECRST` 时接入 `47/1047/1049` 分支,不允许只做日志忽略。
3. `switchToAlternate()`:切换 `active=alternate`,同步 `viewport/baseY/cursor`
4. `switchToNormal()`:切换 `active=normal`,恢复主屏可视与滚动位置。
5. `1049h``saveCursor()` 再切换;`1049l` 先切回主屏再 `restoreCursor()`
6. 渲染器读取统一 `activeBuffer` 快照,禁止自行缓存“上一屏”造成穿透渲染。
兼容边界:
1. 若目标程序未使用备用屏序列,行为与当前方案完全一致。
2. 若收到未知私有模式,记录日志并安全忽略,不得破坏当前缓冲状态。
3. 对于不在首期范围的高级 TUI 能力,可延后,但 `47/1047/1049` 不可缺失。
## 10. 页面渲染与布局规则
## 10.1 基础布局
1. 顶部状态栏state、latency、mode
2. 中部终端视口xterm 或 textarea 输出)。
3. 底部:输入栏与触摸工具。
## 10.2 尺寸与 PTY 计算规则(必须统一)
变量:
1. `containerInnerWidthPx`
2. `containerInnerHeightPx`
3. `charWidthPx`
4. `lineHeightPx`
公式:
1. `cols = max(20, floor(containerInnerWidthPx / charWidthPx))`
2. `rows = max(8, floor(containerInnerHeightPx / lineHeightPx))`
约束:
1. 变化阈值去抖:`|cols-lastCols| + |rows-lastRows| >= 1` 才发 `resize`
2. 初次挂载后至少重试 fit/measure 3~10 次(应对路由切换延迟布局)。
## 11. 光标与坐标计算规则(重点)
说明:本节区分两类光标,避免语义混淆。
1. 终端光标:远端 PTY 回显语义对应的光标(用于激活带判定)。
2. 输入光标:本地输入框 `selectionStart/selectionEnd`(用于编辑行为)。
## 11.1 行高计算
优先级:
1. 优先取真实渲染行高DOM 测量)。
2. 无法测量时回退:`lineHeightPx = fontSize * lineHeight`
## 11.2 触点行号计算
1. `localRow = floor((clientY - viewportTop) / rowHeightPx)`
2. compat 模式:`touchRow = viewportY + localRow`
3. textarea 模式:`touchRow = viewportTopRow + localRow`
4.`clientY` 不在视口边界内,判定为无效触点。
## 11.3 compat 光标行号
1. `cursorLocalRow = core.cursorY`
2. `cursorBaseRow = core.baseY`
3. `cursorRow = cursorBaseRow + cursorLocalRow`(全局行号)
4. 激活带判断:`abs(touchRow - cursorRow) <= activationRadius`(建议 2 行)。
## 11.4 textarea 光标位置计算
输入(终端光标,来自 `TerminalCore`
1. `coreCursorRow`
2. `coreCursorCol`
3. `viewportTopRow`
4. `rows`
规则:
1. `cursorRow = clamp(coreCursorRow, 0, +inf)`
2. `cursorVisualRow = cursorRow - viewportTopRow`
3. 仅当 `0 <= cursorVisualRow < rows` 时认为光标在当前可视窗口内。
4. 激活带判断统一使用 `cursorRow`(全局行号),禁止改用输入框 `selectionStart`
5. `coreCursorRow` 语义需与 xterm 对齐:`globalRow = baseY + cursorY`
输入(本地输入光标,仅编辑用途):
1. `selectionStart`(光标字符索引)
2. `text`(当前显示文本)
3. `cols`(当前列数)
算法(必须实现成纯函数):
1.`text[0..selectionStart)` 顺序扫描。
2. 碰到 `\n``row += 1; col = 0`
3. 普通字符:`col += charDisplayWidth(ch)`
4.`col >= cols`:按自动换行规则折行:`row += floor(col / cols); col = col % cols`
`charDisplayWidth(ch)` 规则:
1. ASCII宽度 1。
2. CJK/全角:宽度 2可用简化 east-asian-width 判定)。
3. 控制字符:宽度 0。
## 11.5 坐标到光标索引(可选增强,仅输入框)
若需要“点按定位光标”:
1. 先由 `(x,y)` 推导目标 `(row,col)`
2. 再线性扫描文本映射回最近字符索引。
3. 时间复杂度 O(n),长文本需缓存行起始索引优化。
## 11.6 原生键盘弹出门控(强制)
必须按以下判定顺序执行:
1. `sessionState === connected`
2. 当前手势动作被状态机判定为 `FOCUS_KEYBOARD`
3. `inBand === true`(即 `abs(touchRow - cursorRow) <= activationRadius`)。
4. 不存在活动原生选区(否则走 `PASS_NATIVE`)。
执行动作:
1. `readOnly=false`
2. `blur -> focus` 顺序触发输入锚点激活。
3. 进入短保护窗口,防止 iOS 瞬时 blur 立刻收回键盘。
禁止动作:
1.`inBand` 区域触摸时强制 `focus()`
2. `PASS_NATIVE/PASS_SCROLL` 分支触发键盘弹出。
## 12. 输入处理规则(必须)
## 12.1 统一发送入口
1. 所有输入只走 `InputBridge.sendRaw()`
2. 键盘 `Enter` 必须映射为 `CR (\r)`(与 xterm `Keyboard.ts` 一致)。
3. 普通文本输入keypress/input按字符原样发送不做全局换行重写。
## 12.2 meta 标记
1. 常规按键:`meta.source = "keyboard"`
2. 语音/候选提交:`meta.source = "assist"`,可携带 `txnId` 去重。
## 12.3 粘贴规则
1. 粘贴前执行 xterm 对齐的行结束归一:`/\r?\n/g -> '\r'`
2. 若开启 bracketed paste`ESC[200~ + text + ESC[201~` 包裹发送。
3. 粘贴文本直接写入 stdin不走逐键模拟。
## 12.4 快捷键规则
1. `Ctrl/Cmd + C`:有选中则复制;无选中则发 `\u0003`
2. `Ctrl/Cmd + V`:读剪贴板后发送。
3. 方向键默认发送:`ESC[A`/`ESC[B`/`ESC[C`/`ESC[D`;应用光标模式下发送:`ESCOA`/`ESCOB`/`ESCOC`/`ESCOD`
## 12.5 反劫持策略(强制)
1. 输入主通道固定为原生 `textarea`,禁止把系统软键盘直接绑定到 xterm 隐藏输入节点。
2. 兼容层键盘监听仅用于桌面对照模式;移动端默认禁用兼容层直连 stdin 的按键采集。
3. 触摸事件默认透传,只有 `FOCUS_KEYBOARD` 分支可执行最小化 `preventDefault`
4. 原生输入法事件(`compositionstart/update/end``beforeinput`)必须完整透传到输入状态机,不得被 xterm 监听提前吞掉。
5. 发生冲突时遵循优先级:系统行为 > 原生输入通道 > xterm 视图行为。
## 12.6 区域过滤与事件归属(强制)
区域定义(建议使用 `data-zone` 标记):
1. `terminal-output-zone`:终端可视回显区(仅显示与文本选区)。
2. `native-input-zone`:原生输入锚点区(软键盘与 IME 主入口)。
3. `app-control-zone`:应用工具栏、按钮、菜单区。
4. `app-overlay-zone`:应用弹层、右键菜单、浮层工具区。
归属规则:
1. 键盘事件默认归 `native-input-zone` 与应用层快捷键系统xterm 仅消费“已路由到终端”的标准控制输入。
2. 鼠标点击在 `app-control-zone/app-overlay-zone` 必须 100% 透传应用层,禁止被 xterm 截获。
3. `wheel/touchmove` 默认交给最近可滚动容器;仅终端主视口滚动时才进入终端滚动链路。
4. 文本选择相关事件优先给系统原生选区xterm 不得覆盖浏览器/系统的选区手柄行为。
实现约束:
1. 在捕获阶段先执行 `eventOwnershipRouter`,返回 `APP | NATIVE | XTERM`,再分发事件。
2. 未命中白名单事件时默认 `APP`,禁止“默认给 xterm”。
3. 仅以下事件允许进入 xterm`stdin` 字符输入、终端方向键控制序列、明确授权的复制粘贴桥接。
4. 右键菜单、双击词选、三击行选等交互由应用策略统一控制,不由 xterm 内建策略隐式决定。
## 13. 关键风险与强约束
1. 风险textarea 模式无法完整支持 TUI 全屏程序。
约束:文档中明确为已知限制,不作为首期阻塞项。
2. 风险IME 双发或丢字。
约束:保留 `composition + beforeinput/input + fallback` 三层机制和去重窗口。
3. 风险:移动端误弹键盘。
约束:触摸状态机按优先级表执行,禁止随意新增 `preventDefault` 分支。
4. 风险:模式切换导致历史丢失。
约束:统一 `OutputBuffer`,切换只换渲染器,不换数据源。
5. 风险xterm 事件劫持导致软键盘、选区、滚动异常。
约束:移动端输入以原生 `textarea` 为唯一焦点源xterm 不持有输入焦点。
## 14. 分阶段实施计划(可直接执行)
### 阶段 A骨架与接口
1. 建立目录与模块骨架。
2. 完成 `sessionMachine + transport + rendererAdapter`
3. 页面可连接、可收发、可显示状态。
### 阶段 Bxterm 基线能力
1. 接入 xterm 渲染器。
2. 跑通输入、输出、resize、快捷键、粘贴。
3. 完成基线测试用例。
### 阶段 Ctextarea 迁移能力
1. 实现 textarea 渲染器。
2. 接入光标/坐标计算与输入法链路。
3. 完成 compat 内核 vs textarea 对比测试。
### 阶段 D小程序入口接入
1. 小程序新增入口按钮 + web-view 页面。
2. 地址可配置,未配置时只提示不报错。
3. 不改主流程逻辑。
### 阶段 E删除演练
1. 删除 `labs/xterm-standalone/`
2. 删除小程序入口页面与按钮。
3. 验证主工程构建与现有测试正常。
## 15. 测试与验收清单
功能验收:
1. 连接状态迁移严格符合状态机。
2. stdout/stderr 能稳定显示,无额外换行污染。
3. Enter 语义对齐 xterm按键 Enter 发送 `CR`,不发送裸 `LF`
4. 中文输入(含候选提交)无明显丢字/双发。
5. 模式切换后输出可重放,连接不中断。
6. 光标精准性:同一份输出流在 `compat``textarea` 模式下,终端光标 `(row,col)` 一致。
7. 光标附近弹键盘:仅在光标邻近行轻触触发原生键盘;非邻近行不弹键盘。
8. 长按选区场景下,键盘不误弹,原生选区与手柄保持可用。
9. `convertEol=false` 时,`LF` 仅下移不回列 0`convertEol=true``LF` 同时回列 0。
10. `CR` 只回列 0`NEL` 执行“回列 0 + 下移一行”。
11. 粘贴文本行结束归一为 `CR`,并在 bracketed paste 模式下正确包裹。
12. 自动折行与显式换行可区分:软换行行具备 `isWrapped=true` 语义。
13. 颜色回显一致性:同一输出流在 `compat``textarea` 模式下,`SGR` 属性(粗体/下划线/反显/隐藏/删除线)渲染结果一致。
14. 颜色指令完整性:`30-37/40-47/90-97/100-107/38/48/58``39/49/59/0/22/24/27/28/29` 复位行为符合预期。
15. 扩展色准确性:`38;5;INDEX``48;5;INDEX``38;2;R;G;B``48;2;R;G;B``58;2;R;G;B` 能精准渲染且模式切换后不漂移。
16. 主题切换约束:切换主题后默认色与索引色生效,历史 TrueColor 单元格颜色值保持不变。
17. 主题复用一致性:实验页主题映射与现有 Web/小程序基线一致,同主题下无明显色偏。
18. 轻量化约束:运行时不依赖 `@xterm/*`,未启用增强能力不进入首屏加载链路。
19. 降级策略有效:`compatLiteMode``rendererFallback=textarea` 能生效且核心会话稳定。
20. 原生响应优先:软键盘弹出/收起、长按选区、滚动手势在移动端与普通 `textarea` 行为一致,无明显被劫持现象。
21. 焦点纪律正确:移动端会话中 `textarea` 始终为输入焦点主体xterm 不直接持有键盘焦点。
22. 区域归属正确:`app-control-zone/app-overlay-zone` 的键鼠事件不被 xterm 捕获,应用可独立演进交互逻辑。
23. 路由兜底正确:未声明事件默认归应用层处理,不出现“事件默认落到 xterm”导致的不可控行为。
24. 备用屏切换正确:`?1049h` 进入全屏后不污染主屏历史,`?1049l` 退出后恢复原提示符与光标位置。
25. `47/1047` 兼容正确:切换主/备用屏时两屏内容与滚动状态互不污染。
性能验收:
1. 高频输出不明显卡顿(基线:普通开发机可持续滚动)。
2. 缓冲裁剪后不出现白屏或大面积闪烁。
回归验收:
1. 仅新增小程序入口改动;主页面行为一致。
2. 删除实验目录后,主项目 `typecheck/lint/test/build` 通过。
## 16. xterm 源码对照
本方案关键规则对应 `xterm.js@5.3.0` 源码:
1. `Enter -> CR``src/common/input/Keyboard.ts`
2. 粘贴换行归一(`/\r?\n/g -> \r``src/browser/Clipboard.ts`
3. `LF/VT/FF -> lineFeed``CR -> carriageReturn``NEL -> nextLine``src/common/InputHandler.ts`
4. `convertEol` 默认 `false`,且受 `CSI 20 h/l` 控制:`src/common/services/OptionsService.ts` + `InputHandler.ts`
5. 自动折行DECAWM`isWrapped``src/common/InputHandler.ts#print`
6. 光标全局行号语义(`cursorY` 相对 `baseY``typings/xterm.d.ts``IBuffer` 定义。
7. `SGR` 属性解析与颜色扩展(含 `38/48/58``39/49/59``0/22/24/27/28/29``src/common/InputHandler.ts#charAttributes`
8. 主屏/备用屏切换与 `1049` 光标保存恢复:`src/common/InputHandler.ts#setModePrivate` + `resetModePrivate`
9. 缓冲区实现与切屏基础结构:`src/common/buffer/BufferSet.ts`
## 17. 完成定义DoD
1. 本文档中的状态机、组件职责、算法规则均已对应到代码模块。
2. 实验项目可独立开发与验证,且不污染主业务逻辑。
3. 小程序只增加测试入口,无业务耦合。
4. 已完成删除演练并记录结果。
5. 换行与光标行为已与 `xterm.js@5.3.0` 源码逐条对齐,并有对应验收用例。
6. 开发者仅依赖本方案即可按阶段落地实现。
7. 主屏/备用屏与 `1049` 光标恢复能力已落地并通过验收用例。