feat(vcs): Accept a diff change from the keyboard and step between diff files (#158)

- Accept Left Side / Accept Right Side copy the change at the caret to the
  other side of a side-by-side diff (⌃⌘→ / ⌃⌘← on macOS, Ctrl+Alt+R /
  Ctrl+Alt+A on Windows).
- Compare Next File / Compare Previous File open the neighbouring file of
  the diff chain directly (⌃⇧→ / ⌃⇧← on macOS, Alt+Shift+→ / ← on Windows).
Цей коміт міститься в:
l0ng-ai authored and GitHub committed 2026-10-29 01:38:22 +08:00
1 джерело cfd2970531
коміт b35035f8d9
9 files changed
+80 -7

No files matched your search

+2 -1
Переглянути файл
@@ -66,6 +66,7 @@ describe("Download JDK dialog", () => {
it("preselects the version a fix asks for", async () => {
render(<DownloadJdkDialog initialVersion={21} onClose={() => {}} onDownloaded={() => {}} />);
await waitFor(() => expect(screen.getByRole("button", { name: "Download" })).toBeEnabled());
expect(screen.getByRole("button", { name: "Version" })).toHaveTextContent("21");
// The version list loads after the Download button enables on a busy runner.
await waitFor(() => expect(screen.getByRole("button", { name: "Version" })).toHaveTextContent("21"));
});
});
+20 -4
Переглянути файл
@@ -24,7 +24,7 @@ import { tx } from "@/platform/i18n";
import { IconButton, Select, Spinner, T } from "@/ui";
import { editorService, type EditorInput } from "@/workbench/contributions";
import { languageFor, viewerBase } from "./cm";
import { applyChunk, arrowControls, hoverRevert } from "./diffControls";
import { applyChunk, arrowControls, chunkAt, hoverRevert } from "./diffControls";
import {
HIGHLIGHT_OPTIONS,
IGNORE_OPTIONS,
@@ -586,10 +586,26 @@ export function DiffEditor({ input }: { input: EditorInput }) {
e.stopPropagation();
};
const nav = useRef<DiffNavigator>({ next: () => step(1), prev: () => step(-1), jumpToSource });
/** ⌃⌘→ / ⌃⌘←: the change at the caret applied toward the other, editable side. */
const applyChange = (source: "a" | "b") => {
const views = viewsRef.current;
const merge = views?.merge;
if (!merge || !views.a || !(source === "a" ? editableB : editableA)) return false;
const side = focusedSide.current;
const view = side === "a" ? views.a : views.b;
const index = chunkAt(merge.chunks, side, view.state.selection.main.head, view.state.doc.length);
if (index < 0) return false;
applyChunk(merge, index, source);
return true;
};
const file = (dir: 1 | -1) => {
const neighbour = chainNeighbour(data, dir);
if (neighbour) void openDiff(neighbour, { preview: true });
};
const nav = useRef<DiffNavigator>({ next: () => step(1), prev: () => step(-1), jumpToSource, applyChange, file });
useEffect(() => {
nav.current = { next: () => step(1), prev: () => step(-1), jumpToSource };
}, [step, jumpToSource]);
nav.current = { next: () => step(1), prev: () => step(-1), jumpToSource, applyChange, file };
});
useEffect(
() => () => {
+34
Переглянути файл
@@ -125,6 +125,40 @@ export function registerCompareActions(): () => void {
when: "diffEditorFocus",
run: () => focusedDiffNavigator()?.prev(),
},
{
id: "diff.nextFile",
title: "Compare Next File",
category: "Diff",
icon: "arrow-right",
when: "diffEditorFocus",
run: () => focusedDiffNavigator()?.file(1),
},
{
id: "diff.prevFile",
title: "Compare Previous File",
category: "Diff",
icon: "arrow-left",
when: "diffEditorFocus",
run: () => focusedDiffNavigator()?.file(-1),
},
{
id: "diff.applyLeftSide",
title: "Accept Left Side",
description: "Copies the change at the caret from the left side to the right one",
category: "Diff",
icon: "arrow-right",
when: "diffEditorFocus",
run: () => void focusedDiffNavigator()?.applyChange("a"),
},
{
id: "diff.applyRightSide",
title: "Accept Right Side",
description: "Copies the change at the caret from the right side to the left one",
category: "Diff",
icon: "arrow-left",
when: "diffEditorFocus",
run: () => void focusedDiffNavigator()?.applyChange("b"),
},
{
id: "diff.jumpToSource",
title: "Jump to Source",
+1 -1
Переглянути файл
@@ -84,7 +84,7 @@ class RevertWidget extends WidgetType {
}
}
function chunkAt(chunks: readonly Chunk[], side: "a" | "b", pos: number, docLength: number): number {
export function chunkAt(chunks: readonly Chunk[], side: "a" | "b", pos: number, docLength: number): number {
return chunks.findIndex((c) => {
const [from, to] = side === "a" ? [c.fromA, c.toA] : [c.fromB, c.toB];
return from === to ? pos === from : pos >= from && pos < Math.max(to, from + 1) && pos <= docLength;
+4
Переглянути файл
@@ -4,6 +4,10 @@ export interface DiffNavigator {
next(): void;
prev(): void;
jumpToSource(): void;
/** Apply the change at the caret from the left side to the right (`a`) or back (`b`). */
applyChange(source: "a" | "b"): boolean;
/** Open the next / previous file of the diff chain (changes of a commit or change list). */
file(dir: 1 | -1): void;
}
let focused: DiffNavigator | null = null;