同步滚动加固、Minimap 交互、折叠策略。这一轮修掉了 4 个真实缺陷,全部先在浏览器里复现、再修、再验证。
现象:连按下一个差异,第二次之后位移不再变化。
根因:导航锚点用的是 win.start,它包含视口上方的 overscan 行,永远在视口顶部之上一段。跳到 hunk R 后锚点仍 < R,nextChangeHunk(1) 又返回 R —— 同一个目标无限循环。
修法:Window 增加 firstVisible(不含 overscan 的真实顶部),导航锚定它。
现象:修复 1 后,在 1240 行文档上按 ⌘] 完全不动了。
根因:折叠使显示项与源行成了两套坐标系。(displayIndex − context) × rowHeight 对靠前的目标算出 0,scrollTop 被 clamp 到 0,视口回到顶部,锚点仍是第 0 行 —— 又回到缺陷 1 的循环。同时 displayIndexForRow 只能落到可见项,目标行若被折叠条盖住,它只解析到折叠条本身。
修法:
- 跳转位移一律在显示索引空间计算,不再混用源行;
- 新增
foldHidingRow():命中被折叠的行先展开该折叠再跳; - 引入
navHunk记录"上次到达的 hunk",前进/后退按 hunk 序号走,摆脱对滚动位置的依赖; programmaticScroll标志区分程序滚动与用户滚动,避免自身回显清掉锚点。
现象:代码行被裁掉且无法查看;左栏横向滚动右栏不跟随。
根因:.code 上有 overflow: hidden,.window 是绝对定位且 width: 100%——绝对定位子元素不贡献固有宽度,父级 max-content 也算不出宽度,于是永远没有横向滚动条。
修法:.window 改 width: max-content,去掉 .code/.cell 的裁剪与 contain: layout;横向偏移纳入同一套「单一权威」同步。
现象:展开视口上方的折叠条,眼前内容突然上移,丢失阅读位置。
根因:折叠条被替换成 N 行,视口上方内容增高 N−1 行,但 scrollTop 未补偿。
修法:scrollTopAfterExpand() 按折叠条是否在视口上方决定是否补偿。
| 能力 | 实现 |
|---|---|
| Minimap 点击跳转 | 按 Y 比例映射到源行 |
| Minimap 拖动擦洗 | Pointer Capture,可拖出条外继续 |
| Minimap 键盘 | ↑↓ / PageUp/Down / Home / End,带 aria-valuetext |
| 折叠全部 / 展开全部 | 工具栏按钮 + ⌘⌥E,按当前状态切换 |
| Minimap 视口指示器 | 改用真实可见行数,不再含 overscan |
Rust
cargo test 51 + 19 + 1 + 5 = 76 passed
cargo clippy 0 warnings
前端
npm test 34 passed(viewport 28 + 渲染模型 2 + 其他 4)
svelte-check 0 errors, 0 warnings
npm run build 60 KB JS / 8 KB CSS(gzip 22.6 / 2.3 KB)
浏览器实测(真实 DOM,非推测)
- 1240 行文档只渲染 38 行 → 虚拟滚动生效
- 左栏 scrollTop=1200 → 右栏 1200;右栏 scrollTop=400 → 左栏 400 → 双向严格同步
del底色实测rgba(244, 63, 94, 0.08),ins实测rgba(34, 197, 94, 0.08)- pill 实测
rgba(244, 63, 94, 0.28)+border-radius: 2px - 斜纹为
repeating-linear-gradient(45deg, …) - 行号
font-variant-numeric: tabular-nums - 折叠条显示「⋯ 38 行未变动」
- ⌘] 连续 6 次、⌘[ 连续 3 次,位移单调推进/回退
- ⌘D 双向切换分栏/内联正常
性能(21,500 行文档)
折叠投影 0.3ms
224 次窗口计算 0.1ms
单屏最多渲染 65 行
应用冒烟:release 二进制 4.8 MB,启动后进程稳定、窗口创建、无 stderr 输出。
vite dev 下可用 window.__litediffMock 注入 fixture 跑真实 UI。这让前端能在没有 Rust 后端时完整调试布局;打包后该分支不可达(真实 IPC 恒存在)。
- 就地编辑 / ⌘S 覆写 / "采纳更改"箭头:后端
save_file(原子写)已就绪,前端编辑器与虚拟滚动冲突,需要单独设计。 - 内联视图未虚拟化:显示项翻倍仍在预算内,10 万行级需并入同一窗口算式。
- 文件夹拖拽:
expand_directory已实现,前端只处理单文件。 ⌘⇧W语义:当前是ignore_all_whitespace(忽略全部空白),原始规格写的是"忽略首尾空白与换行符"。严格按字面只需改一行。
缺陷 1/2 暴露了一个更深的产品问题:折叠状态下,相邻差异块在视觉上可能只隔几十像素(因为中间的大段未变动内容被折叠了)。我现在的做法是每步都精确跳到目标 hunk,此时视口里常常同时出现多个差异块。
如果你希望"⌘] 一次只看一个差异"(即跳转时自动展开目标块前后的上下文),这需要改折叠策略,我等你确认再动。