feat(vcs): Preview the selected file's diff in the Git Log

Selecting a single changed file in the Git Log's changes pane shows its diff
in the editor's preview tab without taking focus, so arrow keys walk through
the files. View Options › Show Diff Preview turns it off; double-click and
Show Diff still open a regular diff tab.
This commit is contained in:
l0ng-ai committed 2026-10-29 08:48:23 +08:00
1 parent c6621c5c36
commit f1ff2731f0
7 files changed
+58 -6

No files matched your search

+30 -2
View File
@@ -56,7 +56,7 @@ import {
updateLogOptions,
useGitLog,
} from "./logStore";
import { refLabels, useLogOptions, type LogOptions } from "./logOptions";
import { logOptions, refLabels, useLogOptions, type LogOptions } from "./logOptions";
import { KIND_COLOR, baseName, dirName, fileCount, formatTime, relTo, type EffectiveKind } from "./model";
import { clearConsole, useVcsConsole } from "./ops";
import { currentRepo, loadBranches, useVcs } from "./store";
@@ -420,6 +420,19 @@ export function BranchesPanel() {
// --- filters ----------------------------------------------------------------
const DAY = 86_400_000;
/** Keyboard moves through several files quickly: preview the one the selection settles on. */
const PREVIEW_DELAY = 120;
function fileNodeAt<F>(nodes: readonly FileTreeNode<F>[], key: string): FileTreeNode<F> | null {
for (const n of nodes) {
if (n.key === key) return n.file ? n : null;
if (!n.file) {
const hit = fileNodeAt(n.children, key);
if (hit) return hit;
}
}
return null;
}
const DATE_OPTIONS: { label: string; days: number }[] = [
{ label: "Last 24 Hours", days: 1 },
{ label: "Last 7 Days", days: 7 },
@@ -1103,6 +1116,16 @@ export function CommitDetails({ commits }: { commits: Commit[] }) {
const ordered = useMemo(() => filesUnder(tree), [tree]);
const chosenChanges = useMemo(() => filesUnder(tree, selectedKeys), [tree, selectedKeys]);
// Show Diff Preview: a single selected file follows into the editor's preview tab.
const previewTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
useEffect(() => () => clearTimeout(previewTimer.current), []);
const previewSelection = (sel: ReadonlySet<string>) => {
clearTimeout(previewTimer.current);
if (!logOptions().diffPreview || sel.size !== 1) return;
const file = fileNodeAt(tree, [...sel][0]!)?.file;
if (file) previewTimer.current = setTimeout(() => void showChangesDiff([file], true), PREVIEW_DELAY);
};
useEffect(() => {
useLogChanges.setState({ commits: chosen, all: ordered, selected: chosenChanges });
}, [chosen, ordered, chosenChanges]);
@@ -1121,6 +1144,8 @@ export function CommitDetails({ commits }: { commits: Commit[] }) {
menuBelow(e, [
{ kind: "header", label: "Group By" },
{ kind: "action", action: "vcs.log.changes.groupByDirectory" },
{ kind: "separator" },
{ kind: "action", action: "vcs.log.changes.diffPreview" },
]);
const onFileMenu = (node: FileTreeNode<LogChange> | null, sel: ReadonlySet<string>, e: ReactMouseEvent) => {
@@ -1190,7 +1215,10 @@ export function CommitDetails({ commits }: { commits: Commit[] }) {
expanded={expanded ?? new Set(allDirs)}
onExpandedChange={(next) => setExpanded(next)}
selected={selectedKeys}
onSelectionChange={(sel) => setSelectedKeys(sel)}
onSelectionChange={(sel) => {
setSelectedKeys(sel);
previewSelection(sel);
}}
renderNode={(n) => <ChangedFileRow node={n} flat={!groupByDirectory} />}
onActivate={(n) => n.file && void showChangesDiff([n.file])}
onContextMenu={onFileMenu}
@@ -121,6 +121,12 @@ describe("log options", () => {
});
});
it("previews the selected file's diff by default and remembers turning it off", () => {
expect(DEFAULT_LOG_OPTIONS.diffPreview).toBe(true);
expect(parseLogOptions({ diffPreview: false }).diffPreview).toBe(false);
expect(setLogOptions({ diffPreview: false })).toBe(false);
});
it("reloads only for options that change the query", () => {
expect(setLogOptions({ compactRefs: false })).toBe(false);
expect(setLogOptions({ noMerges: true })).toBe(true);
+2 -2
View File
@@ -117,8 +117,8 @@ export function diffTitle(data: DiffData): string {
return `${baseName(data.path)} (${sideLabel(data.left)} ↔ ${sideLabel(data.right)})`;
}
export async function openDiff(data: DiffData, opts: { preview?: boolean } = {}) {
await editorService().openInput(diffInput(data), { preview: opts.preview, focus: true });
export async function openDiff(data: DiffData, opts: { preview?: boolean; focus?: boolean } = {}) {
await editorService().openInput(diffInput(data), { preview: opts.preview, focus: opts.focus ?? true });
}
/** Default local-changes diff for a working file: HEAD (or empty) vs the working copy. */
+6 -2
View File
@@ -124,8 +124,11 @@ export function changesArg(arg: unknown): LogChange[] {
return useLogChanges.getState().selected;
}
/** Show Diff: the first file, with every file of the pane in the chain (F7 moves along). */
export async function showChangesDiff(changes: readonly LogChange[]) {
/**
* Show Diff: the first file, with every file of the pane in the chain (F7 moves along).
* `preview`: in the editor's preview tab without taking focus (selection in the pane).
*/
export async function showChangesDiff(changes: readonly LogChange[], preview = false) {
const first = changes[0];
if (!first) return;
const all = useLogChanges.getState().all;
@@ -135,6 +138,7 @@ export async function showChangesDiff(changes: readonly LogChange[]) {
chain.map((c) => c.diff),
Math.max(0, chain.indexOf(first)),
),
preview ? { preview: true, focus: false } : {},
);
}
+3
View File
@@ -26,6 +26,8 @@ export interface LogOptions {
showBranches: boolean;
/** Changed files grouped by directory (else a flat list). */
groupByDirectory: boolean;
/** Selecting a changed file shows its diff in the editor's preview tab. */
diffPreview: boolean;
}
/** IDEA's defaults. */
@@ -47,6 +49,7 @@ export const DEFAULT_LOG_OPTIONS: LogOptions = {
highlightCurrentBranch: true,
showBranches: true,
groupByDirectory: true,
diffPreview: true,
};
/** Options that change which commits the backend returns (a reload is needed). */
+9
View File
@@ -207,5 +207,14 @@ export function registerLogPaneActions(): () => void {
toggled: () => logOptions().groupByDirectory,
run: () => updateLogOptions({ groupByDirectory: !logOptions().groupByDirectory }),
},
{
id: "vcs.log.changes.diffPreview",
title: "Show Diff Preview",
category: "Git Log",
description: "Selecting a changed file shows its diff in the editor's preview tab",
when: VCS,
toggled: () => logOptions().diffPreview,
run: () => updateLogOptions({ diffPreview: !logOptions().diffPreview }),
},
]);
}
+2
View File
@@ -1637,6 +1637,8 @@
"Select a problem to see its details": "选择一个问题以查看详情",
"Select an item to preview": "选择一项以预览",
"Severity": "严重性",
"Selecting a changed file shows its diff in the editor's preview tab": "选中已更改的文件时,在编辑器的预览标签页中显示其差异",
"Show Diff Preview": "显示差异预览",
"Show Quick Fixes": "显示快速修复",
"Snappy": "迅捷",
"Sort By": "排序依据",