diff --git a/src/features/vcs/__tests__/show-diff-chain.test.ts b/src/features/vcs/__tests__/show-diff-chain.test.ts index 5a79094fe..b917a11e6 100644 --- a/src/features/vcs/__tests__/show-diff-chain.test.ts +++ b/src/features/vcs/__tests__/show-diff-chain.test.ts @@ -1,6 +1,7 @@ import { afterEach, beforeEach, expect, it, vi } from "vitest"; import type { FileStatus } from "@/bindings"; import { getAction } from "@/platform/actions"; +import { setContext } from "@/platform/context"; import { registerVcsActions } from "../actions"; import { useVcs } from "../store"; import { useCommit } from "../commitStore"; @@ -8,10 +9,11 @@ import { initialChangelists, useChangelists } from "../changelists"; import { chainNeighbour } from "../diffInput"; const opened = vi.hoisted(() => vi.fn()); +const project = vi.hoisted(() => ({ paths: [] as string[] })); vi.mock("@/workbench/contributions", async (original) => ({ ...(await original()), editorService: () => ({ openInput: opened }), - projectViewSelection: () => [], + projectViewSelection: () => project.paths, })); vi.mock("../comparePaths", () => ({ showDirectoryWorkingDiff: async () => false })); @@ -29,9 +31,11 @@ let unregister: () => void; beforeEach(() => { opened.mockClear(); + project.paths = []; + setContext("activeToolWindow", ""); const files = [status("z.ts"), status("b.ts"), status("a.ts"), status("other.ts")]; useVcs.setState({ root: "/repo", roots: ["/repo"], status: files, byPath: new Map(files.map((f) => [f.path, f])) }); - useCommit.setState({ groupByDir: false }); + useCommit.setState({ groupByDir: false, selection: [] }); const state = initialChangelists(); state.lists.push({ id: "other", name: "Other", comment: "" }); state.files = { "other.ts": "other" }; @@ -41,6 +45,7 @@ beforeEach(() => { afterEach(() => { unregister(); + setContext("activeToolWindow", ""); useVcs.setState(vcs); useCommit.setState(commit); useChangelists.setState(lists); @@ -90,3 +95,20 @@ it("keeps repository grouping in a changelist's chain", async () => { "/repo/module/b.ts", ]); }); + +it("shows the selected change rather than a Project view selection kept from earlier", async () => { + // The Project view keeps its selection while the Commit window has focus: ⌘D must open + // the file selected in the changes tree, not the one still selected there. + project.paths = ["/repo/z.ts"]; + setContext("activeToolWindow", "commit"); + useCommit.setState({ selection: ["/repo/b.ts"] }); + await getAction("vcs.showDiff")!.run({ arg: undefined, ctx: { vcsAvailable: true } }); + expect(opened.mock.calls[0]![0].data.path).toBe("/repo/b.ts"); +}); + +it("shows a Project view selection outside the changes tree", async () => { + project.paths = ["/repo/z.ts"]; + setContext("activeToolWindow", "project"); + await getAction("vcs.showDiff")!.run({ arg: undefined, ctx: { vcsAvailable: true } }); + expect(opened.mock.calls[0]![0].data.path).toBe("/repo/z.ts"); +}); diff --git a/src/features/vcs/actions.tsx b/src/features/vcs/actions.tsx index 032119654..a1ca25733 100644 --- a/src/features/vcs/actions.tsx +++ b/src/features/vcs/actions.tsx @@ -75,16 +75,34 @@ export const commitWindowFocused = () => { return active === "commit" || (active === "git" && useGitLog.getState().tab === "changes"); }; -/** Files the current context refers to: argument, commit view selection, or the active file. */ -function contextPaths(arg: unknown): string[] { +/** The action's own paths, or the changes tree's selection while the tree has focus. */ +function changedPaths(arg: unknown): string[] { const explicit = argPaths(arg); if (explicit?.length) return explicit; const sel = useCommit.getState().selection; - if (commitWindowFocused() && sel.length) return sel; + return commitWindowFocused() ? sel : []; +} + +/** Files the current context refers to: argument, commit view selection, or the active file. */ +function contextPaths(arg: unknown): string[] { + const changed = changedPaths(arg); + if (changed.length) return changed; const active = maybeEditorService()?.activePath(); return active ? [active] : []; } +/** + * Paths a selection-based action targets: the argument, else the changes tree's selection, + * else the Project view's — it keeps its selection while another view has focus — else the + * active file. + */ +function selectedPaths(arg: unknown): string[] { + const changed = changedPaths(arg); + if (changed.length) return changed; + const project = projectViewSelection(); + return project.length ? project : contextPaths(arg); +} + function showGitWindow() { toolWindowService().show("git", true); } @@ -321,8 +339,7 @@ export function registerVcsActions(): () => void { if (ctx.shelfFocus && arg === undefined) return showShelfDiff(); const line = arg && typeof arg === "object" && "line" in arg ? Number((arg as { line: unknown }).line) : undefined; - const selection = arg === undefined ? projectViewSelection() : []; - const paths = selection.length ? selection : contextPaths(arg); + const paths = selectedPaths(arg); if (!paths.length) { notifications().notify({ group: "vcs", level: "info", title: "Select a changed file to show its diff" }); return; @@ -354,8 +371,7 @@ export function registerVcsActions(): () => void { icon: "diff", when: VCS, run: async ({ arg }) => { - const selection = arg === undefined ? projectViewSelection() : []; - const paths = selection.length ? selection : contextPaths(arg); + const paths = selectedPaths(arg); if (!paths.length) { notifications().notify({ group: "vcs", level: "info", title: "Open or select a file or folder to compare" }); return;