Large diffs are not rendered by default. Load diff
Large diffs are not rendered by default. Load diff
1+import { Button } from "vs/base/browser/ui/button/button.js";
2+import { IconLabel } from "vs/base/browser/ui/iconLabel/iconLabel.js";
3+import { observableValue } from "vs/base/common/observable.js";
4+/*---------------------------------------------------------------------------------------------
5+ * Copyright (c) dev.fast. All rights reserved.
6+ * Licensed under the MIT License. See LICENSE in the repository root for license information.
7+ *--------------------------------------------------------------------------------------------*/
8+import { isEqual } from "vs/base/common/resources.js";
9+import { URI } from "vs/base/common/uri.js";
10+import type { IDiffCodeEditorWidgetOptions } from "vs/editor/browser/widget/diffEditor/diffEditorWidget.js";
11+import type {
12+ IResourceLabel,
13+ IResourceHeaderMetadata,
14+ IWorkbenchUIElementFactory,
15+} from "vs/editor/browser/widget/multiDiffEditor/workbenchUIElementFactory.js";
16+import { IHoverService } from "vs/platform/hover/browser/hover.js";
17+
18+import type {
19+ ReviewDiffProgressState,
20+ ReviewDiffSection,
21+} from "../../protocol.js";
22+import type { ReviewDiffFileWire } from "../../protocol.js";
23+import { reviewBottomScrollPadding } from "../browser/reviewScrollPadding.js";
24+import {
25+ reviewCountsTooltip,
26+ ReviewTooltip,
27+ type ReviewTooltipContent,
28+ ReviewViewedCheckbox,
29+} from "../browser/reviewTooltip.js";
30+
31+export interface ReviewMultiDiffHeaderEntry {
32+ readonly original: URI | undefined;
33+ readonly modified: URI | undefined;
34+ readonly additions?: number;
35+ readonly deletions?: number;
36+ /** Why the file starts collapsed, e.g. "Generated file · hidden by default". */
37+ readonly note?: string;
38+ /** Tooltip for the note, e.g. why a file stays folded. */
39+ readonly noteTooltip?: ReviewTooltipContent;
40+ /** A file with no text to diff: it stays folded and its header never opens it. */
41+ readonly collapseLocked?: boolean;
42+ /** Tooltip for the counts: what is left of the file, then its whole. */
43+ readonly countsTooltip?: ReviewTooltipContent;
44+ readonly sectionId?: string;
45+ readonly section?: ReviewDiffSection;
46+ readonly sectionCollapsed?: boolean;
47+ readonly onToggleSectionCollapsed?: () => void;
48+ readonly onToggleSection?: () => void;
49+ readonly onDidOpen?: () => void;
50+ readonly viewedState?: ReviewDiffProgressState;
51+ readonly viewedScope?: "lens";
52+ readonly onToggleViewed?: () => void;
53+}
54+
55+export class ReviewMultiDiffUIElementFactory implements IWorkbenchUIElementFactory {
56+ bottomScrollPadding = reviewBottomScrollPadding;
57+ alwaysShowScrollbars = false;
58+ scrollbarBelowResourceHeader = false;
59+
60+ get headerClickToCollapse(): boolean {
61+ return !this.hideResourceHeader;
62+ }
63+
64+ private readonly headers = new Set<() => void>();
65+
66+ /** Re-reads every live header's entry, for counts that change without the items changing. */
67+ refreshHeaders(): void {
68+ for (const refresh of this.headers) refresh();
69+ }
70+
71+ constructor(
72+ private readonly entries: () => readonly ReviewMultiDiffHeaderEntry[],
73+ readonly horizontalScrollbar: "auto" | "hidden",
74+ readonly overflowWidgetsDomNode: HTMLElement | undefined,
75+ readonly hideResourceHeader = false,
76+ readonly codeEditorWidgetOptions: IDiffCodeEditorWidgetOptions | undefined,
77+ @IHoverService private readonly hoverService: IHoverService,
78+ ) {}
79+
80+ isResourceCollapseLocked(
81+ uris: Parameters<IResourceHeaderMetadata["setUris"]>[0],
82+ ): boolean {
83+ return (
84+ !!uris &&
85+ !!this.entries().find(
86+ (entry) =>
87+ sameResource(entry.original, uris.original) &&
88+ sameResource(entry.modified, uris.modified),
89+ )?.collapseLocked
90+ );
91+ }
92+
93+ getResourceSectionId(
94+ uris: Parameters<IResourceHeaderMetadata["setUris"]>[0],
95+ ): string | undefined {
96+ // Lens entries encode their section identity in both source URI fragments.
97+ return uris?.modified?.fragment || uris?.original?.fragment || undefined;
98+ }
99+
100+ createResourceSectionHeader(element: HTMLElement, sticky = false) {
101+ const height = observableValue<number>(this, 0);
102+ const bodyHidden = observableValue(this, false);
103+ let uris: Parameters<IResourceHeaderMetadata["setUris"]>[0];
104+ let current: ReviewMultiDiffHeaderEntry | undefined;
105+ const counts = document.createElement("span");
106+ counts.className = "review-diff-group-counts";
107+ const countsTooltip = new ReviewTooltip(this.hoverService, counts);
108+ const viewed = new ReviewViewedCheckbox(element.ownerDocument, () =>
109+ current?.onToggleSection?.(),
110+ );
111+ const refresh = () => {
112+ const entry = (current =
113+ uris &&
114+ this.entries().find(
115+ (entry) =>
116+ sameResource(entry.original, uris!.original) &&
117+ sameResource(entry.modified, uris!.modified),
118+ ));
119+ const section =
120+ sticky && entry?.sectionId
121+ ? this.entries().find(
122+ (candidate) =>
123+ candidate.sectionId === entry.sectionId && candidate.section,
124+ )?.section
125+ : entry?.section;
126+ bodyHidden.set(entry?.sectionCollapsed ?? false, undefined);
127+ element.replaceChildren();
128+ element.className = "review-diff-group";
129+ element.hidden = !section;
130+ height.set(section ? 48 : 0, undefined);