feat(app): make Explorer a first-class pane host (#3826)

This commit is contained in:
Mohamed Boudra
2026-08-25 13:25:57 +02:00
committed by GitHub
parent 54299a2e6c
commit c914bcb44b
191 changed files with 8634 additions and 4730 deletions
@@ -8,12 +8,7 @@ import { DocumentFileHeader } from "./document-file-header";
import { hitTestDiffDocument, selectedSourceText } from "./hit-testing";
import { retainHorizontalOffsetMapForPaths } from "./horizontal-offsets";
import { HorizontalScroll } from "./horizontal-scroll.web";
import {
buildDiffDocumentModel,
FILE_HEADER_HEIGHT,
resolveRelayoutScrollTop,
retainReusableModels,
} from "./model";
import { buildDiffDocumentModel, FILE_HEADER_HEIGHT, resolveRelayoutScrollTop } from "./model";
import { paintWebViewport } from "./paint.web";
import { hasPointerDragStarted } from "./pointer-gesture";
import { createMeasuredAdvances } from "./text-measurement";
@@ -25,22 +20,20 @@ import type {
DiffTypography,
TextMeasurer,
} from "./types";
import { useDiffDocumentWorkspaceCache } from "./workspace-cache";
const DEFAULT_MONO_STACK = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace";
const RESIZE_SETTLE_DELAY_MS = 120;
export function DiffSurface(props: DiffSurfaceProps) {
const { t } = useTranslation();
const workspaceCache = useDiffDocumentWorkspaceCache();
const rootRef = useRef<HTMLDivElement>(null);
const canvasRef = useRef<HTMLCanvasElement>(null);
const canvasScratchRef = useRef<HTMLCanvasElement>(null);
const scrollRef = useRef<HTMLDivElement>(null);
const modelRef = useRef<ReturnType<typeof buildDiffDocumentModel> | null>(null);
const previousModelRef = useRef<ReturnType<typeof buildDiffDocumentModel> | null>(null);
const reusableModelRef = useRef<{
dependencies: readonly unknown[];
models: ReturnType<typeof buildDiffDocumentModel>[];
} | null>(null);
const consumedFocusRef = useRef<string | null>(null);
const scrollTopRef = useRef(0);
const horizontalOffsetsRef = useRef(new Map<string, number>());
@@ -60,7 +53,6 @@ export function DiffSurface(props: DiffSurfaceProps) {
const forcePaintRef = useRef(true);
const canvasWindowRef = useRef({ top: 0, height: 0 });
const [viewport, setViewport] = useState({ width: 0, height: 0 });
const [loadedTypography, setLoadedTypography] = useState<DiffTypography | null>(null);
const [hoveredAffordance, setHoveredAffordance] = useState<{
hit: Extract<DiffHit, { kind: "cell" }>;
left: number;
@@ -81,10 +73,28 @@ export function DiffSurface(props: DiffSurfaceProps) {
}),
[family, props.displayPreferences.codeFontSize],
);
const measurement = useMemo(
() => (loadedTypography ? createWebTextMeasurer(loadedTypography) : null),
[loadedTypography],
const typographyResource = useMemo(
() =>
workspaceCache.typography({
typography: desiredTypography,
load: async () => {
await Promise.all([
document.fonts.load(`400 ${desiredTypography.size}px ${desiredTypography.family}`),
document.fonts.load(`600 ${desiredTypography.size}px ${desiredTypography.family}`),
]);
await document.fonts.ready;
},
createMeasurer: () => createWebTextMeasurer(desiredTypography),
}),
[desiredTypography, workspaceCache],
);
const [readyTypographyResource, setReadyTypographyResource] = useState<ReturnType<
typeof workspaceCache.typography
> | null>(() => (typographyResource.isReady() ? typographyResource : null));
const loadedTypography =
readyTypographyResource === typographyResource ? typographyResource.typography : null;
const measurement =
readyTypographyResource === typographyResource ? typographyResource.measureText : null;
const reviewActions = props.mode.kind === "working" ? props.mode.reviewActions : undefined;
const model = useMemo(() => {
if (!loadedTypography || !measurement) {
@@ -95,22 +105,7 @@ export function DiffSurface(props: DiffSurfaceProps) {
lineHeight: desiredTypography.lineHeight,
});
}
const dependencies = [
props.files,
props.displayPreferences.layout,
props.displayPreferences.wrapLines,
viewport.width,
loadedTypography,
measurement,
props.palette,
t,
] as const;
const previous = reusableModelRef.current;
const canReuse = previous?.dependencies.every(
(dependency, index) => dependency === dependencies[index],
);
const reuseFrom = canReuse ? previous?.models : undefined;
const next = buildDiffDocumentModel({
return workspaceCache.buildModel({
files: props.files,
collapsedFilePaths: props.collapsedFilePaths,
layout: props.displayPreferences.layout,
@@ -124,13 +119,7 @@ export function DiffSurface(props: DiffSurfaceProps) {
binary: t("workspace.git.diff.binaryFile"),
tooLarge: t("workspace.git.diff.tooLarge"),
},
reuseFrom,
});
reusableModelRef.current = {
dependencies,
models: retainReusableModels(reuseFrom, next),
};
return next;
}, [
measurement,
props.collapsedFilePaths,
@@ -143,6 +132,7 @@ export function DiffSurface(props: DiffSurfaceProps) {
desiredTypography.lineHeight,
loadedTypography,
viewport.width,
workspaceCache,
]);
modelRef.current = model;
@@ -280,18 +270,19 @@ export function DiffSurface(props: DiffSurfaceProps) {
}, []);
useEffect(() => {
let cancelled = false;
void (async () => {
await Promise.all([
document.fonts.load(`400 ${desiredTypography.size}px ${desiredTypography.family}`),
document.fonts.load(`600 ${desiredTypography.size}px ${desiredTypography.family}`),
]);
await document.fonts.ready;
if (!cancelled) setLoadedTypography(desiredTypography);
})();
if (typographyResource.isReady()) {
setReadyTypographyResource(typographyResource);
return;
}
setReadyTypographyResource(null);
void typographyResource.load().then(() => {
if (!cancelled) setReadyTypographyResource(typographyResource);
return undefined;
});
return () => {
cancelled = true;
};
}, [desiredTypography]);
}, [typographyResource]);
useLayoutEffect(() => {
const previous = previousModelRef.current;
const scroll = scrollRef.current;