Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/EVIDENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ R1 固定总出口的源码为 `ce9b24d`,早期 `2dc8d6d` / `9b16acc` 仍分
| R2 流式字素与固定 emoji(#39) | [合同](research/streaming-graphemes.md)、[红/绿及实际场景验证](../experiments/shell/results/2026-10-26-streaming-graphemes/validation.json) | 已合并 #39:.4 的固定 ZWJ/keycap 原子格/选择/绘制和单列 resize。766 个规范用例只验证分段 helper;64 标量超限按明确无损分片,不宣称完整字素布局或完整 Unicode/IME。 |
| 真实备用屏切换(#27 限定切片) | [五阶段原生快照与候选验证](../experiments/shell/results/2026-10-26-alternate-screen/validation.json)、[候选接口](../experiments/core/CANDIDATE.md) | 真实 1049h/1049l 五阶段的源 VT、CHAR_INFO、样式/光标恢复及候选历史隔离;origin/wrap、margin/resize 交叉与 #27 整体验收另做。 |
| 局部滚动区域(#27 限定切片) | [原生阶段快照、独立 transport 重放与候选验证](../experiments/shell/results/2026-10-26-scroll-region/validation.json)、[候选接口](../experiments/core/CANDIDATE.md) | 真实 DECSTBM 局部滚动的源、原生快照与 transport 独立重放;区域外保持/历史隔离通过,不要求控制字节原样回显。 |
| ConPTY resize 重放、历史与 Find/选区(#29) | [限定合同](adr/0005-physical-row-resize.md)、[重放与历史验证](../experiments/core/results/2026-10-26-resize-replay/validation.json)、[候选接口](../experiments/core/CANDIDATE.md) | 固定 861B resize 重放的行身份、Find/选区、连续同步、历史合并/清空与上限;不等于所有 resize/reflow、真实 DPI 或 WindTerm 运行时。 |
| ConPTY resize 重放、历史与 Find/选区(#29) | [限定合同](adr/0005-physical-row-resize.md)、[重放与历史验证](../experiments/core/results/2026-10-26-resize-replay/validation.json)、[文字列位置回归](../experiments/core/results/2026-10-27-replay-coordinates/README.md)、[候选接口](../experiments/core/CANDIDATE.md) | 固定 861B 重放的行身份、连续同步、历史合并及回放后清空;独立列位置回归检查移位后的选区失效和 Find 精确列。冻结快照尺寸、回放期间清空、完整 resize/reflow 及 WindTerm 运行时仍未验收。 |
| R2 复杂标量序列输入(#30 限定切片) | [真实键盘接收验证](../experiments/shell/results/2026-10-26-unicode-keyboard/validation.json)、[场景与复现](../experiments/shell/README.md) | 固定 ZWJ/keycap 通过 WM_CHAR/ConPTY/ReadConsoleW 精确接收;只证明输入保真,不证明绘制、字素宽度或完整应用键盘模式。 |
| 自有窗口绘制正确性 | [呈现探针](../experiments/presentation/README.md)、[共享 renderer](../experiments/presentation/render.hpp)、[度量测试](../experiments/presentation/renderer_metrics_test.cpp) | 自有 DIB 的 retained/full RGB、字体/光标/缺字负例与覆盖;BitBlt/GdiFlush 提交不等于实际上屏,presented_done=null。 |
| 进程生命周期和平台边界 | [ConPTY 探针](../experiments/conpty/README.md)、[跨环境记录](research/conpty-platform-check-2026-10-22.md)、[旧平台探针](../.github/workflows/conpty-compat.yml) | 受控后代、取消/超时和短批次回收;Windows 11/Server 2025 有固定记录,Server 2022 稳态句柄门槛仍失败。长期无泄漏未知。 |
Expand Down
2 changes: 1 addition & 1 deletion docs/FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
| 中文 IME 与键盘 | **限定验收**:基础按键/修饰键/UTF-16 与实际接收字节;[固定 ZWJ/keycap 输入](../experiments/shell/results/2026-10-26-unicode-keyboard/validation.json)在默认/候选各两轮精确接收。固定 Microsoft 拼音[产品场景](../research/ime/results/2026-10-24-product-ime-contexts/validation.json)覆盖两区域组合/提交/取消、候选、输出/resize/zoom/受控 DPI、隐藏/关闭、历史阅读和原生控件 | 限定 Windows Server 2025 固定拼音及已有场景;同次组合由八会话工作流另验收。固定字素绘制/选择已由 #39 限定完成,但所有输入法、应用键盘模式、完整 IME 和真实不同 DPI 跨屏仍未完成 | R1 / R2 |
| VT 核心 | **实现中,未验收**:默认自研支持基础光标/擦除(含 ECH)、CR/LF/BS/TAB、有限 SGR 和有界恢复。显式候选 `.4` 接入 `Term/Processor`、C++ 拥有值适配、稳定行元数据、每缓冲区 margin 与流式字素;[共同轨迹](../experiments/differential/results/2026-10-26-candidate-common-v6/summary.json)及[源 margin 合同](../experiments/shell/results/2026-10-26-margin-state/validation.json)为各自固定版本结果 | [#27](https://github.com/JavaZeroo/termforge/issues/27) 的 origin/wrap、插删、模式/resize 交叉未闭合;[#41](https://github.com/JavaZeroo/termforge/issues/41) 系统 ConPTY 已丢失的 margin 不能由末端补回。ECH pending-wrap 差异见[限定合同](research/more-capture-ech.md);#36/#38、完整呈现和平台支持独立验收 | R2 / R4 |
| Unicode 与文字显示 | **实现中,未验收**:默认自研保留 Unicode 15。显式候选 `.4` 的 [#39 Unicode 17 流式字素合同](research/streaming-graphemes.md)已限定验收:固定 ZWJ/keycap 的单 lead/两列、光标、SGR/查询、原子选择复制、单列 resize 与有界无损分片;实际 ConPTY/DWrite 核对固定样本的单 glyph、字体和网格 | [#30](https://github.com/JavaZeroo/termforge/issues/30) 仍需完整输入/IME 交叉;所有脚本/整形、通用 fallback、所有原生重放、真彩色/更多属性、Powerline 和真实跨屏未验收。分段 helper 的规范数据通过不代表完整终端 Unicode 正确性;[#54](https://github.com/JavaZeroo/termforge/issues/54) 保留已观察的性能成本 | R2 / R4 |
| resize 与 reflow | **限定验收**:默认按 ADR-0005 物理行裁剪;候选在[固定 ConPTY 重放](../experiments/core/results/2026-10-26-resize-replay/validation.json)与连续同步中保持行身份、Find/选区和有界历史,淘汰/清空不复活;候选 `.4` 另覆盖主/备用屏缩到单列 | [#29](https://github.com/JavaZeroo/termforge/issues/29) 的完整 resize 交叉未完成;固定事务不等于完整 reflow 或 WindTerm 等价。系统/实验 ConPTY、备用屏/滚动区、真实 DPI、呈现、性能及 #36/#38 分别验收;默认缩窄不可恢复 | R2 |
| resize 与 reflow | **限定验收**:默认按 ADR-0005 物理行裁剪;候选在[固定 ConPTY 重放](../experiments/core/results/2026-10-26-resize-replay/validation.json)与连续同步中保持行身份、Find/选区和有界历史,覆盖限定淘汰及回放完成后的清空;[列位置回归](../experiments/core/results/2026-10-27-replay-coordinates/README.md)核对文字移位后选择失效和 Find 重定位;候选 `.4` 另覆盖主/备用屏缩到单列 | [#29](https://github.com/JavaZeroo/termforge/issues/29) 的冻结快照尺寸、回放期间清空和完整 resize 交叉未完成;固定事务不等于完整 reflow 或 WindTerm 等价。系统/实验 ConPTY、备用屏/滚动区、真实 DPI、呈现、性能及 #36/#38 分别验收;默认缩窄不可恢复 | R2 |
| 本地 shell 选择 | **实现中,未验收**:`ShellLaunch` 分离可执行文件、参数、工作目录及 shell 命令差异;cmd、Windows PowerShell 5.1、PowerShell 7、显式 Ubuntu WSL 各有[两轮真实启动/目录/尺寸/正常退出证据](../experiments/shell/results/2026-10-24-r2-shell-lifecycle/validation.json),无效程序启动错误可见且可关闭后恢复 cmd。四 shell 测试已补 [WSL 原生尺寸程序返回后的新提示符同步](research/wsl-size-return.md),正常退出证据只在实际全部回收后写入;默认/system 的事件反例、原两轮及完整回归分别留证 | 仅声明上述固定入口和隔离目录;#80 原 hosted stage6 是否同因仍未知,#81 hosted 准备超时另行定位;任意 profile/参数、通用分屏/排序、全部运行时错误提示及管理员 shell 未完成 | R2;权限场景 R4 |
| 常用 TUI | **实现中,未验收**:显式候选 + 系统 ConPTY 有固定 Vim 进入、编辑、60×12 resize、退出写盘、主屏恢复及光标回复的[产品证据](../experiments/core/CANDIDATE.md);More 尾部 ECH 与捕获端点有[限定修复](research/more-capture-ech.md) | [#28](https://github.com/JavaZeroo/termforge/issues/28) 的更多应用及完整输入/输出模式未完成;PR #50 实验后端的 GUI Vim [#52](https://github.com/JavaZeroo/termforge/issues/52) 仍有失败;[native 捕获目录复用](research/vim-fixture-isolation.md)已独立定位并修正,不能代替 GUI 或完整 TUI 验收。headless 比较与自有 DIB 不等于真实呈现,`presented_done=null` | R2 |

Expand Down
4 changes: 3 additions & 1 deletion docs/adr/0005-physical-row-resize.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,13 @@ API 依据是 [Microsoft ResizePseudoConsole](https://learn.microsoft.com/en-us/
上述物理行合同继续适用于默认自研回退,包括缩窄不可恢复及 resize 改变行版本的规则。显式 `TERMFORGE_BUILD_CANDIDATE_ADAPTER=ON` 的候选采用以下独立合同,不把上游 `Term::resize` 的结果直接称为默认物理行策略或完整 reflow。

- 候选在 resize 时保存有界屏幕快照,识别可跨 feed 的 `CSI ?25l` / `CSI ?25h` 重放。等待期间提供一致快照;等待超过 8192 字节或 `finish` 时解除冻结并同步实际状态。只有 resize 后的该事务才进入此路径,普通应用隐藏光标不等于已验证的 resize 重放。
- 行键包含换行、wrap、裁剪状态、文字、宽度、样式、组合标记和 written 元数据,仅去除真正未写入的首尾 padding。只有旧、新集合中都唯一且顺序一致的键才能保留 ID/version;重复文本不盲配。最终所有保留行必须具有非零、唯一、严格递增的 ID,冲突行取得新身份和版本,不能让选区或 Find 错配另一段文字。
- 行键包含换行、wrap、裁剪状态、文字、宽度、样式、组合标记、written 元数据及首个非 padding 单元格的列号。首部未写 padding 的数量决定文字位置,不能因文字字节相同而忽略;只有末尾真正未写 padding 不影响匹配。缓存身份映射也执行相同列位置检查。只有旧、新集合中都唯一且顺序一致的键才能保留 ID/version;重复文本不盲配。最终所有保留行必须具有非零、唯一、严格递增的 ID,冲突行取得新身份和版本,不能让选区或 Find 错配另一段文字。
- 上游增高可先把历史拉入可见区,随后 ConPTY 重放覆盖这些行。候选在完整重放后保留 TermForge 的有界历史,后续同步按 ID 和完整行键合并历史及可见行;同 ID 不同内容不视为同一行。历史容量和淘汰计数仍有界,真实淘汰不恢复,显式清空历史不复活旧行。无完整重放、无法确定语义的 reflow 和任意空白行身份均不在此保证内。
- Find 和选区同时检查 ID、位置、版本和实际投影文本。非软换行的物理列宽或后继 ID 变化不单独使命中失效;从文末新增后继会引入分隔符,仍需重扫。真正的软换行继续要求完整元数据和后继关系。半开选区排除的第 0 列末端空行可以在仍为空白时替换身份;其它行变化、非空端点及实际淘汰仍使选区原子失效。
- 候选合并不表示通用逻辑行 reflow、备用屏/滚动区全语义、所有应用、真实 DPI、呈现完成或性能验收通过。测试辅助读取使用共享文本投影,宽字符 spacer 不算额外空格,显式样式尾空格仍逐字校验。

固定 WindTerm 2.7.0 的限定静态复核中,`0x140238f90` 仅在缩短时进入 `0x140233a30` 的保存路径;增高走 `0x1402ee110`,分配并初始化新屏幕行,不消耗 scrollback 来回填视口。该路径支持独立保留旧历史,与上述保守合同一致,但不要求照抄上游或宣称所有 WindTerm 配置均如此。本地 `local/analysis/windterm-resize-history-20261026/` 的 44 个定向函数均完成反编译并核对关键汇编,目标 SHA-256 为 `7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0`。原始输出只留本地;这是静态、限定路径证据,不是运行时全量行为比较,也不覆盖 Document/Scintilla 的所有动态连接。

命令、哈希、默认/候选完整回归及未满足项见[重放与历史验证记录](../../experiments/core/results/2026-10-26-resize-replay/validation.json)。

相同文字左右移位后的选区失效和 Find 重新定位另见[列位置回归](../../experiments/core/results/2026-10-27-replay-coordinates/README.md)。这是现有身份/版本合同的修复;它不裁决 WindTerm 的重排、历史回填或选择保留策略,也不补足冻结快照尺寸和回放期间清空历史的验证。
2 changes: 2 additions & 0 deletions experiments/core/CANDIDATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ Linux 使用相应 CMake 配置。候选 workflow 在 Windows/Ubuntu 构建 `can
- 不走 JSON。Rust/C++ 文本由调用方拥有,未做增量 FFI 或性能验收;候选 damage 的产品化仍属后续。
- `screen.resize` 与候选 `Term::resize` 同步尺寸;默认自研回退继续采用 ADR-0005 的物理行裁剪,候选不把它称为完整 reflow。真实 ConPTY 重放可能先由上游把历史拉入可见区再覆盖,候选在完整重放后合并有界历史,按 ID 和完整行键去重,保留 Find/选区所需的稳定身份,且不让冲突产生重复 ID。连续同步缓存原始 FFI ID 与最终身份的映射,仅在需要重放的未知旧 ID 时回退到完整键匹配,不重复扫描全部历史。真实淘汰不恢复,显式清空不复活;普通空白行、未决 reflow、备用屏/滚动区全语义不在此保证内。合同与验证见 [resize/历史记录](results/2026-10-26-resize-replay/validation.json)。

回放行键与后续缓存匹配同时保留文字的起始列,只忽略末尾未写 padding。相同 `ABC` 从第 0 列移到第 2 列(或反向)不能沿用旧版本供选择/Find 使用;原选区失效,Find 重扫后命中实际列。独立[列位置回归](results/2026-10-27-replay-coordinates/README.md)覆盖回放中/回放后、整段/逐字节和原位对照。这不补足冻结快照尺寸、回放期间清空或原版行为的验证;已有历史清空证据只覆盖回放完成后的场景。

## 真实产品场景

`candidate_shell_tui` 在同一 `ShellWindow` 和 ConPTY 路径上连续执行两轮真实 Vim:进入全屏编辑、插入文字、resize 到 60×12、退出写盘、确认主屏 shell 返回。本机 Vim 输出未发送 `1049/1047/47`,所以此场景不计作备用屏验收;1049 的固定核心用例与真实 Vim 证据分开。固定自动命令写入 `candidate-vim.vim` 并以短 `-S` 命令加载,避免交互式 cmd 拆分长启动命令。启动和插入由 Vim 写入的真实 `VimEnter`/`InsertEnter` 事件文件确认,避免 `echon` 消息被重绘覆盖;随后通过插入模式的 `Ctrl-O` 调用 Vim 命令,实际保存 `&columns/&lines` 并检查 60×12,再以 `Ctrl-O:wq` 写盘退出。不把 ConPTY 重绘或 `ResizePseudoConsole` 返回当作 Vim 已就绪。本路径不声称验证 Vim 的 Escape 行为,独立控制台 ESC 接收另测。`-W` 日志核对插入文本与 `:wq` 顺序,其中可能含 Vim 内部特殊键,不能冒充纯键盘字节。随后用独立 `shell_query_client.exe` 发出真实 `CSI 6n`,保存终端经 `InputQueue` 返回的光标报告,并用 `shell_size_client.exe` 核对子进程尺寸。另有 resize 后独立 ESC 接收检查。
Expand Down
6 changes: 4 additions & 2 deletions experiments/core/candidate_terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ bool same_replay_line(const Line& a, const Line& b) {
if (a.ending != b.ending || a.wrap_columns != b.wrap_columns || a.cropped != b.cropped) return false;
size_t a_first, a_last, b_first, b_last;
replay_bounds(a, a_first, a_last); replay_bounds(b, b_first, b_last);
if (a_first == a_last || b_first == b_last || a_last - a_first != b_last - b_first) return false;
if (a_first == a_last || b_first == b_last || a_first != b_first || a_last != b_last) return false;
return std::equal(a.begin() + a_first, a.begin() + a_last, b.begin() + b_first, same_cell);
}

Expand All @@ -73,7 +73,9 @@ std::string replay_key(const Line& line) {
if (first == last) return {};
std::ostringstream out;
out.imbue(std::locale::classic());
out << static_cast<int>(line.ending) << ':' << line.wrap_columns << ':' << line.cropped << ':';
// Leading padding locates the text: equal bytes at different columns cannot
// retain a version used by selection and Find. Only trailing padding is inert.
out << static_cast<int>(line.ending) << ':' << line.wrap_columns << ':' << line.cropped << ':' << first << ':';
for (size_t index = first; index < last; ++index) {
const auto& cell = line[index];
out << cell.text.size() << ':' << cell.text << cell.width << ',' << cell.style.fg << ','
Expand Down
Loading
Loading