diff --git a/src/features/vcs/DiffEditor.module.css b/src/features/vcs/DiffEditor.module.css index f0bd15913..0cbfd2cde 100644 --- a/src/features/vcs/DiffEditor.module.css +++ b/src/features/vcs/DiffEditor.module.css @@ -71,6 +71,17 @@ .label + .label { border-left: 1px solid var(--border); } +/* Side by side, the right label starts at the right pane (`--diff-b-start`, set by the viewer). */ +.labels > .label:first-child:not(:only-child) { + flex: 0 0 var(--diff-b-start, 50%); +} +.labelFile { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + color: var(--fg-subtle); + font-family: var(--font-ui); +} .badge { padding: 0 5px; border-radius: var(--radius-small); @@ -93,13 +104,14 @@ border-bottom: none; } .findRowSplit > div { - flex: 0 0 50%; + flex: 0 0 var(--diff-b-start, 50%); border-right: 1px solid var(--border); } .findRowRight { justify-content: flex-end; } .findRowRight > div { + flex-basis: calc(100% - var(--diff-b-start, 50%)); border-right: none; border-left: 1px solid var(--border); } diff --git a/src/features/vcs/DiffEditor.tsx b/src/features/vcs/DiffEditor.tsx index 4de40e095..f65e3f92b 100644 --- a/src/features/vcs/DiffEditor.tsx +++ b/src/features/vcs/DiffEditor.tsx @@ -88,7 +88,7 @@ import { inclusionGutter } from "./hunkInclusion"; import { hunksOverlapping } from "./hunks"; import { effectiveKind, stripSlash } from "./model"; import { errorMessage, logConsole, readWorkingText, runOp, saveAll } from "./ops"; -import { refreshNow, useVcs } from "./store"; +import { refreshNow, repoOf, useVcs } from "./store"; import styles from "./DiffEditor.module.css"; /** Commands of the focused diff viewer (diff.* actions, vcs.nextChange / prevChange). */ @@ -252,12 +252,14 @@ export function DiffEditor({ const [chunkInfo, setChunkInfo] = useState({ count: 0, current: -1, moves: 0, whitespaceOnly: false }); const [busy, setBusy] = useState(false); const hostRef = useRef(null); + const rootRef = useRef(null); const viewsRef = useRef(null); const staticsRef = useRef(statics); const focusedSide = useRef("b"); /** Bumped when the views are rebuilt, so the find bar re-applies its query. */ const [viewsGen, setViewsGen] = useState(0); const version = useVcs((s) => s.version); + const heads = useVcs((s) => s.heads); const leftKey = sideKey(left); const rightKey = sideKey(right); const contextKey = JSON.stringify(data.context ?? null); @@ -577,6 +579,27 @@ export function DiffEditor({ views = { a: null, b: view, merge: null, panes: null, destroy: () => view.destroy() }; if (scrollTop != null) requestAnimationFrame(() => (view.scrollDOM.scrollTop = scrollTop)); } + // The labels and the find bar above side-by-side panes start the right side where its pane + // does, wherever the divider (dragged, see UnalignedPanes) and the window put it. + const root = rootRef.current; + if (views.a && root && typeof ResizeObserver !== "undefined") { + const paneB = views.b.dom; + const align = () => + root.style.setProperty( + "--diff-b-start", + `${paneB.getBoundingClientRect().left - root.getBoundingClientRect().left}px`, + ); + const observer = new ResizeObserver(align); + observer.observe(root); + observer.observe(paneB); + align(); + const destroyPanes = views.destroy; + views.destroy = () => { + observer.disconnect(); + root.style.removeProperty("--diff-b-start"); + destroyPanes(); + }; + } viewsRef.current = views; const built = viewerHost(views, dataRef.current, opts.mode, () => focusedSide.current); const detach = contribs.map((c) => c.attach?.(built)); @@ -1106,8 +1129,23 @@ export function DiffEditor({ const canStage = editableB && isWorktree(right) && left.type === "rev" && !!useVcs.getState().root; const hasSource = !!source; + /** IDEA's title of a revision: its short commit id (HEAD resolved) and the file's path in the repository. */ + const revTitle = (s: DiffSide): { label: string; file: string | null } => { + if (s.type !== "rev") return { label: sideLabel(s), file: null }; + const file = s.path ?? path; + const root = repoOf(file); + const commit = !s.label && s.rev === "HEAD" && root ? heads[root]?.headCommit : null; + return { + label: commit ? commit.slice(0, 8) : sideLabel(s), + file: root && file.startsWith(`${root}/`) ? file.slice(root.length + 1) : file, + }; + }; + const titleA = revTitle(left); + const titleB = revTitle(right); + return (
{ @@ -1278,8 +1316,9 @@ export function DiffEditor({
{opts.mode === "split" ? (
- - {sideLabel(left)} + + {titleA.label} + {titleA.file && {titleA.file}} {eolDiffers && eolA && {badgeA}} {editableA && ( @@ -1287,8 +1326,9 @@ export function DiffEditor({ )} - - {sideLabel(right)} + + {titleB.label} + {titleB.file && {titleB.file}} {eolDiffers && eolB && {badgeB}} {editableB && ( {left.type === "rev" ? tx("working copy") : tx("editable")} diff --git a/src/features/vcs/__tests__/diffToolbar.test.tsx b/src/features/vcs/__tests__/diffToolbar.test.tsx index 25ee8f9c3..31a9776fa 100644 --- a/src/features/vcs/__tests__/diffToolbar.test.tsx +++ b/src/features/vcs/__tests__/diffToolbar.test.tsx @@ -51,6 +51,7 @@ import { DiffEditorTab } from "../diff-editor-tab"; import { diffInput, withChain, type DiffData } from "../diffInput"; import { useToolWindowLayout } from "@/workbench/toolwindows/store"; import { useProject } from "@/platform/project"; +import { useVcs } from "../store"; import { applyKeymapSettings, installKeyboardDispatcher, useKeymap } from "@/platform/keymap"; import { setContext } from "@/platform/context"; import { registerCompareActions } from "../compare"; @@ -159,6 +160,18 @@ describe("Diff toolbar", () => { expect(screen.queryByText("Contents differ only in line separators")).toBeNull(); }); + it("titles a revision with its short commit id, HEAD resolved, and the file's repository path", async () => { + const before = useVcs.getState(); + useVcs.setState({ roots: ["/p"], heads: { "/p": { head: "main", headCommit: "81a179900123456789abcdef" } } }); + const mounted = render( + , + ); + await waitFor(() => expect(mounted.container.querySelectorAll(".cm-editor")).toHaveLength(2)); + expect(screen.getByTitle("81a17990 src/a.ts")).toBeInTheDocument(); + expect(screen.getByTitle("abc123 src/a.ts")).toBeInTheDocument(); + mounted.unmount(); + useVcs.setState({ roots: before.roots, heads: before.heads }); + }); it("puts Jump to Source right after Next Difference, in the navigation group", async () => { render(); await screen.findByRole("button", { name: "Jump to Source" });