diff --git a/src/features/vcs/GitLog.tsx b/src/features/vcs/GitLog.tsx index b11fd3981..16e886ecc 100644 --- a/src/features/vcs/GitLog.tsx +++ b/src/features/vcs/GitLog.tsx @@ -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(nodes: readonly FileTreeNode[], key: string): FileTreeNode | 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 | undefined>(undefined); + useEffect(() => () => clearTimeout(previewTimer.current), []); + const previewSelection = (sel: ReadonlySet) => { + 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 | null, sel: ReadonlySet, 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) => } onActivate={(n) => n.file && void showChangesDiff([n.file])} onContextMenu={onFileMenu} diff --git a/src/features/vcs/__tests__/logOptions.test.ts b/src/features/vcs/__tests__/logOptions.test.ts index eb42b26b5..c12e0715c 100644 --- a/src/features/vcs/__tests__/logOptions.test.ts +++ b/src/features/vcs/__tests__/logOptions.test.ts @@ -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); diff --git a/src/features/vcs/diffInput.ts b/src/features/vcs/diffInput.ts index cd4a4d776..493485fa9 100644 --- a/src/features/vcs/diffInput.ts +++ b/src/features/vcs/diffInput.ts @@ -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. */ diff --git a/src/features/vcs/logChanges.ts b/src/features/vcs/logChanges.ts index aeed6a650..ece1d21ac 100644 --- a/src/features/vcs/logChanges.ts +++ b/src/features/vcs/logChanges.ts @@ -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 } : {}, ); } diff --git a/src/features/vcs/logOptions.ts b/src/features/vcs/logOptions.ts index bb56ad2a1..611ec3a6c 100644 --- a/src/features/vcs/logOptions.ts +++ b/src/features/vcs/logOptions.ts @@ -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). */ diff --git a/src/features/vcs/logPaneActions.ts b/src/features/vcs/logPaneActions.ts index 900dbb8c9..a99b30a0d 100644 --- a/src/features/vcs/logPaneActions.ts +++ b/src/features/vcs/logPaneActions.ts @@ -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 }), + }, ]); } diff --git a/src/platform/locales/text/zh.json b/src/platform/locales/text/zh.json index fc488ad34..5d3e1bfe3 100644 --- a/src/platform/locales/text/zh.json +++ b/src/platform/locales/text/zh.json @@ -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": "排序依据",