Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,23 @@ working-tree review 的显式身份,不回退到可能已经切换的工作区
错误通过现有本地化提示呈现。纯逻辑回归覆盖稀疏分块、两种操作及这些生命周期边界,
并纳入 Windows Git 随机顺序 CI;真实 WebView2 的按钮与 Git 状态联动仍需宿主验收。

Windows 单文件工作区 Diff 还要能展开省略的未改动代码(#557)。稀疏补丁里根本
没有这些行,所以“展开”无从谈起;之前强行关闭折叠,结果连展开按钮都不显示。
现在单文件打开和刷新时请求整份文件作为上下文(`contextLines` 取 Git 接受的
最大值 2147483647),结果标记为完整上下文(`is_full_context`),只有这种补丁
才开启 Monaco 的未改动区域折叠。多文件、历史和提交比较仍用稀疏补丁且不折叠,
因为它们缺少的行同样无法展示。

整份文件只有一个 `@@`,直接拿来暂存会把整个文件一次写进索引。因此 Windows
适配器按 Git 默认的 3 行上下文重新切出分块,自己生成 `@@` 范围;单测用同一次
修改的真实 `git diff` 输出逐块比对。完整上下文没有 `@@` 行,渲染器改用行上的
`actionAnchor` 标记放置按钮,并按每一侧实际显示的行数计算位置,左右两侧保持对齐。

Monaco 会把旧的折叠状态带到新内容上:挂载时的空模型没有未改动区域,第一份
真实文本的所有区域都会被当作“已展开”。渲染器在文本被替换时重新挂接模型,
让折叠状态从头计算;文本相同的刷新不重挂,读者已展开的区域保留。
真实 WebKit 回归覆盖首次折叠、单个按钮和搜索揭示,去掉重挂会失败。

预览借用主编辑器时还保存查找栏的查询、选项和显隐状态。关闭预览必须恢复
主编辑器原先的查找状态,不能把替换预览的查询栏留在原文件中。

Expand Down
2 changes: 1 addition & 1 deletion docs/development/platform-parity-matrix.csv
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ search-replace,搜索,搜索与导航,项目替换与取消,已实现,待验证,
git-status-commit,版本控制,Git,状态、暂存与提交,已实现,待验证,已实现,待验证,Git,修改、暂存、取消暂存并提交文件,确认状态、提交消息和错误回显。 Windows 另验证原生 UNC/verbatim 输入、中文/空格/长路径仓库往返及 linked worktree;末尾点/空格必须明确拒绝,外部提交/切换须触发元数据刷新。,,macos/Sources/Lithe/Views/Git; macos/Sources/Lithe/Services,windows/tauri/src/features/git; shared/contracts/application-boundary.md; windows/tauri/src/features/git/api/git-repository-path.ts; rust/lithe-core/tests/git_path_roundtrip.rs; shared/fixtures/git/windows-paths.json
git-multi-repository-change-groups,版本控制,Git,多仓库变更折叠分组,已实现,待验证,已实现,待验证,Git,在同一工作区打开多个 Git 仓库,确认变更按仓库折叠分组;只剩一个仓库有变更时仍显示仓库名。仓库级和文件级勾选与 Git 暂存区同步;子模块只有未提交文件时显示提示,不能勾选未变化的引用。,两端直接消费共享 Rust Core 的计划、依赖排序、执行结果与重试。平台负责真实暂存勾选、确认与结果界面、认证取消和项目生命周期。原生界面实测待平台验收。,macos/Sources/Lithe/Views/Git/ChangesSidebarView.swift; macos/Sources/Lithe/Views/Git/GitChangeSectionsCache.swift; macos/Sources/LitheGitModule/Models/GitModels.swift; rust/lithe-core/src/git/mod.rs,windows/tauri/src/features/git/components/status/git-status-panel.tsx; windows/tauri/src/features/git/components/git-commit-panel.tsx; windows/tauri/src/features/git/components/git-workspace-commit-review.tsx; windows/tauri/src/features/git/services/git-workspace-commit-workflow.ts; windows/tauri/src/features/git/services/git-workspace-commit-workflow.test.ts; windows/tauri/src/features/git/components/status/git-workspace-status-panel.test.tsx; windows/tauri/src-tauri/src/platform.rs; shared/fixtures/git/workspace-commit-workflow-v1.json
git-branches-remotes,版本控制,Git,分支、标签与远程,已实现,待验证,已实现,待验证,Git,创建、切换、合并分支并查看标签和远程,确认冲突与认证失败可恢复。,,macos/Sources/Lithe/Views/Git,windows/tauri/src/features/git
git-diff-review,版本控制,Git,Diff 与变更审查,已实现,待验证,已实现,待验证,Git,验证新增、删除、重命名、二进制和多文件 Diff 的展示与定位;从源代码管理打开已修改和未跟踪文件的工作区 Diff 后保持静止,确认 Diff 不会自动关闭,且只在文件不再出现在 Git 状态中时关闭。,,macos/Sources/Lithe/Views/Git; macos/Sources/Lithe/Views/Diff,windows/tauri/src/features/git; windows/tauri/src/features/viewer
git-diff-review,版本控制,Git,Diff 与变更审查,已实现,待验证,已实现,待验证,Git,验证新增、删除、重命名、二进制和多文件 Diff 的展示与定位;从源代码管理打开已修改和未跟踪文件的工作区 Diff 后保持静止,确认 Diff 不会自动关闭,且只在文件不再出现在 Git 状态中时关闭;在 Windows 单文件工作区 Diff 中点击折叠条展开省略的未改动代码,确认 Diff 保持打开且每个修改块仍可单独暂存。,,macos/Sources/Lithe/Views/Git; macos/Sources/Lithe/Views/Diff,windows/tauri/src/features/git; windows/tauri/src/features/viewer
git-history,版本控制,Git,提交历史与图谱,已实现,待验证,已实现,待验证,Git,分页浏览提交历史、分支图谱和提交详情,确认日期、作者和文件列表一致;分支最新提交的图谱连线从提交圆点开始,不超出到圆点上方;在双端使用同一包含本地分支、远端引用、标签和合并提交的仓库,核对永久图布局、图头引用排序、合并边投影及跨越 30 行的紧凑长边;在本地分支新增提交后确认分支颜色保持稳定,且不会因共用屏幕泳道直接沿用父分支颜色。双端保留各自调色板,不要求 RGB 值一致。,,macos/Sources/Lithe/Views/Git; macos/Sources/LitheGitModule/Services/GitGraphLayoutService.swift; macos/Sources/LitheGitModule/Services/GitGraphHeadOrdering.swift; macos/Sources/Lithe/Views/Git/GitGraphColor.swift,windows/tauri/src/features/git; windows/tauri/src/features/git/utils/git-graph-layout.ts; windows/tauri/src/features/git/utils/git-graph-colors.ts; windows/tauri/src/features/git/utils/git-graph-layout.test.ts
git-rebase-stash,版本控制,Git,Rebase 与 Stash,已实现,待验证,已实现,待验证,Git,执行交互式 Rebase 和 Stash 保存/恢复,确认中断、冲突和继续操作。,,macos/Sources/Lithe/Views/Git,windows/tauri/src/features/git
git-worktrees,版本控制,Git,Worktree 管理,已实现,待验证,已实现,待验证,Git,列出、创建、切换和删除 Worktree,确认路径、分支和安全检查。,,macos/Sources/Lithe/Views/Git,windows/tauri/src/features/git
Expand Down
2 changes: 1 addition & 1 deletion docs/development/platform-parity-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@
| Git | **状态、暂存与提交**<br><sub>git-status-commit</sub> | ✅ 已实现<br><sub>🔍 待验证</sub><br><sub>`macos/Sources/Lithe/Views/Git`、`macos/Sources/Lithe/Services`</sub> | ✅ 已实现<br><sub>🔍 待验证</sub><br><sub>`windows/tauri/src/features/git`、`shared/contracts/application-boundary.md`、`windows/tauri/src/features/git/api/git-repository-path.ts`、`rust/lithe-core/tests/git_path_roundtrip.rs`、`shared/fixtures/git/windows-paths.json`</sub> | Git | 修改、暂存、取消暂存并提交文件,确认状态、提交消息和错误回显。 Windows 另验证原生 UNC/verbatim 输入、中文/空格/长路径仓库往返及 linked worktree;末尾点/空格必须明确拒绝,外部提交/切换须触发元数据刷新。 | |
| Git | **多仓库变更折叠分组**<br><sub>git-multi-repository-change-groups</sub> | ✅ 已实现<br><sub>🔍 待验证</sub><br><sub>`macos/Sources/Lithe/Views/Git/ChangesSidebarView.swift`、`macos/Sources/Lithe/Views/Git/GitChangeSectionsCache.swift`、`macos/Sources/LitheGitModule/Models/GitModels.swift`、`rust/lithe-core/src/git/mod.rs`</sub> | ✅ 已实现<br><sub>🔍 待验证</sub><br><sub>`windows/tauri/src/features/git/components/status/git-status-panel.tsx`、`windows/tauri/src/features/git/components/git-commit-panel.tsx`、`windows/tauri/src/features/git/components/git-workspace-commit-review.tsx`、`windows/tauri/src/features/git/services/git-workspace-commit-workflow.ts`、`windows/tauri/src/features/git/services/git-workspace-commit-workflow.test.ts`、`windows/tauri/src/features/git/components/status/git-workspace-status-panel.test.tsx`、`windows/tauri/src-tauri/src/platform.rs`、`shared/fixtures/git/workspace-commit-workflow-v1.json`</sub> | Git | 在同一工作区打开多个 Git 仓库,确认变更按仓库折叠分组;只剩一个仓库有变更时仍显示仓库名。仓库级和文件级勾选与 Git 暂存区同步;子模块只有未提交文件时显示提示,不能勾选未变化的引用。 | 两端直接消费共享 Rust Core 的计划、依赖排序、执行结果与重试。平台负责真实暂存勾选、确认与结果界面、认证取消和项目生命周期。原生界面实测待平台验收。 |
| Git | **分支、标签与远程**<br><sub>git-branches-remotes</sub> | ✅ 已实现<br><sub>🔍 待验证</sub><br><sub>`macos/Sources/Lithe/Views/Git`</sub> | ✅ 已实现<br><sub>🔍 待验证</sub><br><sub>`windows/tauri/src/features/git`</sub> | Git | 创建、切换、合并分支并查看标签和远程,确认冲突与认证失败可恢复。 | |
| Git | **Diff 与变更审查**<br><sub>git-diff-review</sub> | ✅ 已实现<br><sub>🔍 待验证</sub><br><sub>`macos/Sources/Lithe/Views/Git`、`macos/Sources/Lithe/Views/Diff`</sub> | ✅ 已实现<br><sub>🔍 待验证</sub><br><sub>`windows/tauri/src/features/git`、`windows/tauri/src/features/viewer`</sub> | Git | 验证新增、删除、重命名、二进制和多文件 Diff 的展示与定位;从源代码管理打开已修改和未跟踪文件的工作区 Diff 后保持静止,确认 Diff 不会自动关闭,且只在文件不再出现在 Git 状态中时关闭。 | |
| Git | **Diff 与变更审查**<br><sub>git-diff-review</sub> | ✅ 已实现<br><sub>🔍 待验证</sub><br><sub>`macos/Sources/Lithe/Views/Git`、`macos/Sources/Lithe/Views/Diff`</sub> | ✅ 已实现<br><sub>🔍 待验证</sub><br><sub>`windows/tauri/src/features/git`、`windows/tauri/src/features/viewer`</sub> | Git | 验证新增、删除、重命名、二进制和多文件 Diff 的展示与定位;从源代码管理打开已修改和未跟踪文件的工作区 Diff 后保持静止,确认 Diff 不会自动关闭,且只在文件不再出现在 Git 状态中时关闭;在 Windows 单文件工作区 Diff 中点击折叠条展开省略的未改动代码,确认 Diff 保持打开且每个修改块仍可单独暂存。 | |
| Git | **提交历史与图谱**<br><sub>git-history</sub> | ✅ 已实现<br><sub>🔍 待验证</sub><br><sub>`macos/Sources/Lithe/Views/Git`、`macos/Sources/LitheGitModule/Services/GitGraphLayoutService.swift`、`macos/Sources/LitheGitModule/Services/GitGraphHeadOrdering.swift`、`macos/Sources/Lithe/Views/Git/GitGraphColor.swift`</sub> | ✅ 已实现<br><sub>🔍 待验证</sub><br><sub>`windows/tauri/src/features/git`、`windows/tauri/src/features/git/utils/git-graph-layout.ts`、`windows/tauri/src/features/git/utils/git-graph-colors.ts`、`windows/tauri/src/features/git/utils/git-graph-layout.test.ts`</sub> | Git | 分页浏览提交历史、分支图谱和提交详情,确认日期、作者和文件列表一致;分支最新提交的图谱连线从提交圆点开始,不超出到圆点上方;在双端使用同一包含本地分支、远端引用、标签和合并提交的仓库,核对永久图布局、图头引用排序、合并边投影及跨越 30 行的紧凑长边;在本地分支新增提交后确认分支颜色保持稳定,且不会因共用屏幕泳道直接沿用父分支颜色。双端保留各自调色板,不要求 RGB 值一致。 | |
| Git | **Rebase 与 Stash**<br><sub>git-rebase-stash</sub> | ✅ 已实现<br><sub>🔍 待验证</sub><br><sub>`macos/Sources/Lithe/Views/Git`</sub> | ✅ 已实现<br><sub>🔍 待验证</sub><br><sub>`windows/tauri/src/features/git`</sub> | Git | 执行交互式 Rebase 和 Stash 保存/恢复,确认中断、冲突和继续操作。 | |
| Git | **Worktree 管理**<br><sub>git-worktrees</sub> | ✅ 已实现<br><sub>🔍 待验证</sub><br><sub>`macos/Sources/Lithe/Views/Git`</sub> | ✅ 已实现<br><sub>🔍 待验证</sub><br><sub>`windows/tauri/src/features/git`</sub> | Git | 列出、创建、切换和删除 Worktree,确认路径、分支和安全检查。 | |
Expand Down
27 changes: 22 additions & 5 deletions frontend/editor/src/diff-review.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ export interface ReviewRow {
right: string | null;
kind: "context" | "changed" | "addition" | "removal" | "information";
hunkID?: string | null;
/** Renders the hunk's action band above this row. Full-file reviews have no
* `@@` information rows, so the host marks each hunk's first change instead. */
actionAnchor?: boolean;
}
export interface ReviewAction { id: string; title: string }
export interface DiffReviewInput {
Expand Down Expand Up @@ -136,8 +139,14 @@ export function mountDiffReview(container: HTMLElement,
leftRows = old.rows; rightRows = next.rows;
if (original.getLanguageId() !== input.language) monaco.editor.setModelLanguage(original, input.language);
if (modified.getLanguageId() !== input.language) monaco.editor.setModelLanguage(modified, input.language);
const replaced = original.getValue() !== old.text || modified.getValue() !== next.text;
if (original.getValue() !== old.text) original.setValue(old.text);
if (modified.getValue() !== next.text) modified.setValue(next.text);
// Monaco carries fold state across content changes: regions that did not
// exist before, such as every region of the first real text after the
// empty mount, open fully revealed. Re-attaching the models starts a
// fresh diff view model whose unchanged regions fold, like a new review.
if (replaced) { editor.setModel(null); editor.setModel({ original, modified }); }
left.updateOptions({ lineNumbers: line => String(leftRows[line - 1]?.oldLine ?? "") });
right.updateOptions({ lineNumbers: line => String(rightRows[line - 1]?.newLine ?? "") });
editor.updateOptions({ renderSideBySide: input.sideBySide ?? true,
Expand All @@ -146,11 +155,19 @@ export function mountDiffReview(container: HTMLElement,
container.classList.toggle("no-word-highlights", input.highlightWords === false);
const actions = input.actions ?? [];
if (actions.length) {
for (const [view, rows] of [[left, leftRows], [right, rightRows]] as const) {
for (const [view, side] of [[left, "left"], [right, "right"]] as const) {
const ids: string[] = [];
view.changeViewZones(accessor => {
rows.forEach((row, index) => {
if (row.kind !== "information" || !row.hunkID) return;
let projected = 0;
for (const row of input.rows) {
const visible = row[side] !== null;
// An `@@` row owns the band below it. An anchor row may be absent
// from this side (an addition on the left), so its band goes after
// the rows this side has already shown, keeping both sides level.
const afterLineNumber = row.hunkID && row.actionAnchor ? projected
: row.hunkID && row.kind === "information" && visible ? projected + 1 : null;
if (visible) projected++;
if (afterLineNumber === null) continue;
const node = document.createElement("div");
node.className = "lithe-review-actions";
if (view === right) for (const action of actions) {
Expand All @@ -159,8 +176,8 @@ export function mountDiffReview(container: HTMLElement,
button.onclick = () => { if (!disposed && request === version) onAction(row.hunkID!, action.id); };
node.append(button);
}
ids.push(accessor.addZone({ afterLineNumber: index + 1, heightInPx: 26, domNode: node }));
});
ids.push(accessor.addZone({ afterLineNumber, heightInPx: 26, domNode: node }));
}
});
zones.push({ view, ids });
}
Expand Down
39 changes: 39 additions & 0 deletions macos/Experiments/Monaco/workbench.integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,45 @@ async function verify() {
subscription?.dispose(); review.dispose(); container.remove();
}
});
// #557: the first text after the empty mount used to open with every
// unchanged region revealed, so a full-file review showed nothing to expand.
await check("first full-file diff folds unchanged regions and anchors hunk actions", async () => {
const container = document.createElement("div");
container.style.cssText = "position:absolute;inset:0;height:400px;width:1000px";
document.body.append(container);
const review = mountDiffReview(container);
const rows: ReviewRow[] = Array.from({ length: 120 }, (_, index) => ({
id: `full-${index}`, oldLine: index + 1, newLine: index + 1,
left: `value ${index}`, right: index === 4 ? "changed" : `value ${index}`,
kind: index === 4 ? "changed" : "context", hunkID: index < 8 ? "hunk-4" : null,
...(index === 4 ? { actionAnchor: true } : {}),
}));
let subscription: { dispose(): void } | undefined;
let deadline: ReturnType<typeof setTimeout> | undefined;
try {
const computed = new Promise<void>((resolve, reject) => {
deadline = setTimeout(() => reject(new Error("Full-file diff computation exceeded 5 seconds")), 5000);
subscription = review.editor.onDidUpdateDiff(() => {
if (review.editor.getLineChanges()?.length) resolve();
});
});
await Promise.all([review.update({ rows, language: "plaintext", collapse: true,
actions: [{ id: "stage", title: "Stage" }] }), computed]);
const next = review.editor.getModifiedEditor();
// Content height, unlike the viewport, shrinks only when lines are folded.
const lineHeight = next.getOption(monacoEditor.EditorOption.lineHeight);
assert(next.getContentHeight() < lineHeight * 40,
"unchanged region opened revealed instead of folded");
assert(container.querySelectorAll(".lithe-review-actions button").length === 1,
"full-file hunk did not render exactly one action band");
review.select({ revealID: "full-60", searchIDs: ["full-60"] });
assert(next.getVisibleRanges().some(range => range.startLineNumber <= 61 && range.endLineNumber >= 61),
"folded source row could not be revealed");
} finally {
if (deadline !== undefined) clearTimeout(deadline);
subscription?.dispose(); review.dispose(); container.remove();
}
});
await check("inline diff navigation scrolls to a removed source row", async () => {
const container = document.createElement("div");
container.style.cssText = "position:absolute;inset:0;height:400px;width:1000px";
Expand Down
Loading
Loading