perf(layout): track panel resizing at a stepped cadence

This commit is contained in:
dengjie zhang authored and GitHub committed 2026-09-18 01:48:27 +08:00
1 parent a78fc48a7b
commit 8eba12ea2f
17 files changed
+705 -34

No files matched your search

@@ -153,6 +153,8 @@ import type { SqlHighlighter } from "@/lib/sql/sqlHighlighter";
import { copySqlAsRichText } from "@/lib/sql/sqlRichText";
import { EDITOR_FONT_FAMILY_CSS_VAR, EDITOR_FONT_SIZE_CSS_VAR, editorDiagnosticColors, editorThemeAppearanceFor, loadEditorTheme, editorFontTheme, shellLineCommentTheme, sqlCompletionTheme, sqlSemanticHighlightTheme } from "@/lib/editor/editorThemes";
import { createStatementGutterMarkerDom, shouldShowStatementGutter } from "@/lib/editor/codemirrorStatementGutter";
import { isPanelResizing } from "@/lib/app/panelResizeState";
import { uiTuning } from "@/lib/app/uiTuning";
import { createQueryEditorSqlShortcutDomHandler, isCharacterProducingShortcut } from "@/lib/editor/queryEditorSqlShortcut";
import { createQueryEditorReplaceShortcutBindings, createQueryEditorReplaceShortcutHandler, createQueryEditorSearchKeymap } from "@/lib/editor/queryEditorSearchKeymap";
import { createQueryEditorEscapeHandler } from "@/lib/editor/queryEditorEscape";
@@ -297,6 +299,90 @@ const emit = defineEmits<{
}>();
const editorRef = ref<HTMLDivElement>();
// While any panel divider is dragged, CodeMirror re-wraps and re-measures
// every visible line on every frame the editor box changes — with a 100+ line
// script that is the dominant drag cost (profiler: 2k+ measure() calls with
// getBoundingClientRect in a 3s drag). We pin the editor root to an explicit
// pixel size and then step-update that pin at most every TRACK_EVERY_FRAMES
// animation frames (~30fps), which keeps the editor visually following the
// divider while cutting the relayout frequency (and the layout thrash from
// interleaved reads/writes) by half or more. The step interval is tunable via
// ~/.dbx/ui-tuning.json (panelResizeTrackEveryFrames). The pin is released
// once on pointer-up for one final exact layout.
let frozenEditorBox: { width: number; height: number } | null = null;
let editorTrackFrameId = 0;
let editorTrackFrameCount = 0;
function editorAvailableSize(): { width: number; height: number } | null {
const host = editorRef.value?.parentElement;
if (!host) return null;
const width = host.clientWidth;
const height = host.clientHeight;
if (width <= 0 || height <= 0) return null;
return { width, height };
}
function pinEditorBox(size: { width: number; height: number }) {
const root = editorRef.value;
if (!root) return;
frozenEditorBox = size;
root.style.width = `${Math.round(size.width)}px`;
root.style.height = `${Math.round(size.height)}px`;
root.style.maxWidth = "none";
root.style.minHeight = "0";
}
function releaseEditorBox() {
const root = editorRef.value;
frozenEditorBox = null;
if (!root) return;
root.style.width = "";
root.style.height = "";
root.style.maxWidth = "";
root.style.minHeight = "";
}
function stopEditorBoxTracking() {
if (editorTrackFrameId) {
cancelAnimationFrame(editorTrackFrameId);
editorTrackFrameId = 0;
}
editorTrackFrameCount = 0;
}
function editorBoxTrackStep() {
editorTrackFrameId = requestAnimationFrame(editorBoxTrackStep);
if (++editorTrackFrameCount < uiTuning.value.panelResizeTrackEveryFrames) return;
editorTrackFrameCount = 0;
const next = editorAvailableSize();
if (!next || !frozenEditorBox) return;
if (Math.round(next.width) === Math.round(frozenEditorBox.width) && Math.round(next.height) === Math.round(frozenEditorBox.height)) return;
// Writing the pin keeps the editor a fixed-size box: the parent's layout is
// reflowed (cheap, the editor subtree is `contain`ed and skipped) but
// CodeMirror only observes one discrete size change per step.
pinEditorBox(next);
}
watch(
isPanelResizing,
(resizing) => {
const root = editorRef.value;
if (!root) return;
if (resizing) {
if (frozenEditorBox) return;
const size = editorAvailableSize();
if (!size) return;
pinEditorBox(size);
editorTrackFrameId = requestAnimationFrame(editorBoxTrackStep);
} else {
stopEditorBoxTracking();
if (frozenEditorBox) releaseEditorBox();
}
},
{ flush: "sync" },
);
onBeforeUnmount(stopEditorBoxTracking);
const view = shallowRef<EditorViewType | null>(null);
const contextMenuOpen = ref(false);
let contextMenuPointerCleanup: (() => void) | null = null;