fix(vcs): show the diff of the file selected in the changes tree (#1480)

Show Diff (Cmd+D) in the Commit window or the Git window's Local Changes opened the
file still selected in the Project view, which keeps its selection while another view
has focus, instead of the change selected in the tree. Show Diff and Compare with
Branch or Revision now take the action's paths first, then the changes tree's
selection while it has focus, then the Project view's selection, then the active file.

Co-authored-by: DomKing <[email protected]>
Цей коміт міститься в:
DomKingтаDomKing authored and GitHub committed 2026-11-05 14:48:59 +08:00
1 джерело a0595f5c12
коміт e7952ce3d8
2 files changed
+47 -9

No files matched your search

+24 -2
Переглянути файл
@@ -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<typeof import("@/workbench/contributions")>()),
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");
});
+23 -7
Переглянути файл
@@ -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;