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).
This commit is contained in:
l0ng-ai
2026-10-29 01:38:22 +08:00
committed by GitHub
parent cfd2970531
commit b35035f8d9
9 changed files with 80 additions and 7 deletions
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+4
View File
@@ -316,6 +316,10 @@ export const intellijMacos: Record<string, string[]> = {
"diff.nextDifference": ["F7"],
"diff.prevDifference": ["Shift+F7"],
"diff.jumpToSource": ["F4"],
"diff.applyLeftSide": ["Ctrl+Meta+Right"],
"diff.applyRightSide": ["Ctrl+Meta+Left"],
"diff.nextFile": ["Ctrl+Shift+Right"],
"diff.prevFile": ["Ctrl+Shift+Left"],
// After Show Diff: ⌘D is Compare With… in the project view.
"project.compareWith": ["Meta+D"],
// Editing extras, image viewer (keyWhen: image focused) and Markdown editor layouts
+4
View File
@@ -314,6 +314,10 @@ export const intellijWindows: Record<string, string[]> = {
"diff.nextDifference": ["F7"],
"diff.prevDifference": ["Shift+F7"],
"diff.jumpToSource": ["F4"],
"diff.applyLeftSide": ["Ctrl+Alt+R"],
"diff.applyRightSide": ["Ctrl+Alt+A"],
"diff.nextFile": ["Alt+Shift+Right"],
"diff.prevFile": ["Alt+Shift+Left"],
// After Show Diff: Ctrl+D is Compare With… in the project view.
"project.compareWith": ["Ctrl+D"],
// Editing extras, image viewer (keyWhen: image focused) and Markdown editor layouts
+4
View File
@@ -324,6 +324,10 @@ export const vscodeMacos: Record<string, string[]> = {
"markdown.previewOnly": ["Meta+Shift+V"],
// Diff editor (VS Code: workbench.action.compareEditor.nextChange / previousChange)
"diff.nextDifference": ["F7"],
"diff.applyLeftSide": [],
"diff.applyRightSide": [],
"diff.nextFile": [],
"diff.prevFile": [],
"diff.prevDifference": ["Shift+F7"],
"diff.jumpToSource": [],
// HTTP Client / Database console: run the request or statement at the caret.
+7 -1
View File
@@ -4599,5 +4599,11 @@
"Add Mouse Shortcut": "添加鼠标快捷键",
"Click here with a mouse side button, holding any modifiers": "按住任意修饰键,用鼠标侧键点击此处",
"Mouse shortcut": "鼠标快捷键",
"Mouse Shortcut: {action}": "鼠标快捷键:{action}"
"Mouse Shortcut: {action}": "鼠标快捷键:{action}",
"Accept Left Side": "接受左侧",
"Accept Right Side": "接受右侧",
"Copies the change at the caret from the left side to the right one": "将光标处的变更从左侧复制到右侧",
"Copies the change at the caret from the right side to the left one": "将光标处的变更从右侧复制到左侧",
"Compare Next File": "比较下一个文件",
"Compare Previous File": "比较上一个文件"
}