first commit

This commit is contained in:
douboer@gmail.com
2026-03-03 13:23:14 +08:00
commit 3b7c1d558a
161 changed files with 28120 additions and 0 deletions

View File

@@ -0,0 +1,730 @@
# 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` 光标恢复能力已落地并通过验收用例。