From cb86f11b6f26df494ea8254bdc67a071a31f79b8 Mon Sep 17 00:00:00 2001 From: zipg <4047349+zipg@users.noreply.github.com> Date: Sat, 12 Sep 2026 21:07:11 +0800 Subject: [PATCH 1/3] fix(editor): keep SQL table names highlighted --- apps/desktop/src/components/editor/QueryEditor.vue | 9 ++++++--- apps/desktop/src/lib/editor/editorThemes.ts | 5 ++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src/components/editor/QueryEditor.vue b/apps/desktop/src/components/editor/QueryEditor.vue index 58941ecf9c..b40040a1c1 100644 --- a/apps/desktop/src/components/editor/QueryEditor.vue +++ b/apps/desktop/src/components/editor/QueryEditor.vue @@ -6077,6 +6077,7 @@ onMounted(async () => { queryEditorLineCommentToken(props.databaseType) === "//" ? shellLineCommentHighlightPlugin : [], ]; const MAX_SQL_SEMANTIC_HIGHLIGHT_WINDOWS = 32; + const MAX_FULL_DOCUMENT_SQL_SEMANTIC_HIGHLIGHT_LENGTH = 128_000; const SQL_SEMANTIC_HIGHLIGHT_DEBOUNCE_MS = 100; const refreshSqlSemanticHighlightEffect = StateEffect.define(); buildSqlSemanticHighlightExtension = () => [ @@ -6149,7 +6150,8 @@ onMounted(async () => { spans: Array<{ start: number; end: number }>; }> = []; const pendingWindows: Array<{ from: number; to: number }> = []; - for (const visibleRange of currentView.visibleRanges) { + const rangesToHighlight = this.cachedWindows.length === 0 && sql.length <= MAX_FULL_DOCUMENT_SQL_SEMANTIC_HIGHLIGHT_LENGTH ? [{ from: 0, to: sql.length }] : currentView.visibleRanges; + for (const visibleRange of rangesToHighlight) { const cached = this.cachedWindows.find((candidate) => candidate.from <= visibleRange.from && candidate.to >= visibleRange.to); if (cached) { if (!windows.includes(cached)) windows.push(cached); @@ -6169,8 +6171,9 @@ onMounted(async () => { } if (pendingWindows.length > 0) { - const tree = ensureSyntaxTree(currentView.state, Math.max(...pendingWindows.map((window) => window.to)), 25); - if (!tree) return Decoration.set([]); + const requestedTo = Math.max(...pendingWindows.map((window) => window.to)); + const tree = ensureSyntaxTree(currentView.state, requestedTo, requestedTo === sql.length ? 250 : 25); + if (!tree) return this.decorations; for (const window of pendingWindows) { const entry = { ...window, diff --git a/apps/desktop/src/lib/editor/editorThemes.ts b/apps/desktop/src/lib/editor/editorThemes.ts index 61da9441f4..34df9aee26 100644 --- a/apps/desktop/src/lib/editor/editorThemes.ts +++ b/apps/desktop/src/lib/editor/editorThemes.ts @@ -31,7 +31,10 @@ export function createRunStatementButtonDom(ariaLabel = "Execute statement"): HT export function sqlSemanticHighlightTheme(EditorView: typeof import("@codemirror/view").EditorView): Extension { return EditorView.theme({ ".cm-sql-table-name, .cm-sql-table-name *": { - color: `var(${SQL_TABLE_COLOR_CSS_VAR}) !important`, + // Built-in CodeMirror themes do not define the editor-specific table color + // variable. Keep semantic table names visible there as well, while custom + // and IDE themes continue to use their configured table color. + color: `var(${SQL_TABLE_COLOR_CSS_VAR}, #b4530b) !important`, }, }); } From 1ee0dbf7cd41db9e36e19d421ee4f67073c048a0 Mon Sep 17 00:00:00 2001 From: t8y2 Date: Sun, 13 Sep 2026 00:44:52 +0800 Subject: [PATCH 2/3] fix(editor): guard plugin decorations and prewarm full document once initialize decorations to Decoration.none in the constructor so the !tree fallback can never surface undefined into the codemirror decoration pipeline, and gate the full-document prewarm behind a one-shot per-document flag so post-edit refreshes return to visible-range computation --- apps/desktop/src/components/editor/QueryEditor.vue | 9 ++++++++- .../queryEditorSemanticHighlightPerformance.spec.ts | 7 +++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/components/editor/QueryEditor.vue b/apps/desktop/src/components/editor/QueryEditor.vue index b40040a1c1..8596d21f8b 100644 --- a/apps/desktop/src/components/editor/QueryEditor.vue +++ b/apps/desktop/src/components/editor/QueryEditor.vue @@ -6089,6 +6089,7 @@ onMounted(async () => { if (this.currentView.dom.isConnected) this.currentView.dispatch({ effects: refreshSqlSemanticHighlightEffect.of(null) }); }, SQL_SEMANTIC_HIGHLIGHT_DEBOUNCE_MS); private cachedDoc: import("@codemirror/state").Text | null = null; + private prewarmedDoc: import("@codemirror/state").Text | null = null; private cachedSql = ""; private cachedDialectId = ""; private cachedDatabaseType: DatabaseType | undefined; @@ -6099,6 +6100,7 @@ onMounted(async () => { }> = []; constructor(private currentView: import("@codemirror/view").EditorView) { + this.decorations = Decoration.none; this.decorations = this.buildDecorations(currentView); } @@ -6144,13 +6146,17 @@ onMounted(async () => { } const sql = this.cachedSql; + const shouldPrewarmFullDocument = + this.cachedWindows.length === 0 && + this.prewarmedDoc !== doc && + sql.length <= MAX_FULL_DOCUMENT_SQL_SEMANTIC_HIGHLIGHT_LENGTH; const windows: Array<{ from: number; to: number; spans: Array<{ start: number; end: number }>; }> = []; const pendingWindows: Array<{ from: number; to: number }> = []; - const rangesToHighlight = this.cachedWindows.length === 0 && sql.length <= MAX_FULL_DOCUMENT_SQL_SEMANTIC_HIGHLIGHT_LENGTH ? [{ from: 0, to: sql.length }] : currentView.visibleRanges; + const rangesToHighlight = shouldPrewarmFullDocument ? [{ from: 0, to: sql.length }] : currentView.visibleRanges; for (const visibleRange of rangesToHighlight) { const cached = this.cachedWindows.find((candidate) => candidate.from <= visibleRange.from && candidate.to >= visibleRange.to); if (cached) { @@ -6174,6 +6180,7 @@ onMounted(async () => { const requestedTo = Math.max(...pendingWindows.map((window) => window.to)); const tree = ensureSyntaxTree(currentView.state, requestedTo, requestedTo === sql.length ? 250 : 25); if (!tree) return this.decorations; + if (shouldPrewarmFullDocument) this.prewarmedDoc = doc; for (const window of pendingWindows) { const entry = { ...window, diff --git a/apps/desktop/src/lib/__tests__/editor/queryEditorSemanticHighlightPerformance.spec.ts b/apps/desktop/src/lib/__tests__/editor/queryEditorSemanticHighlightPerformance.spec.ts index eb7a1f444c..c761d3eb37 100644 --- a/apps/desktop/src/lib/__tests__/editor/queryEditorSemanticHighlightPerformance.spec.ts +++ b/apps/desktop/src/lib/__tests__/editor/queryEditorSemanticHighlightPerformance.spec.ts @@ -29,6 +29,13 @@ describe("QueryEditor semantic highlighting while scrolling", () => { expect(queryEditorSource).toContain("refreshSqlSemanticHighlightEffect.of(null)"); }); + it("never exposes undefined decorations and prewarms the full document only once per document", () => { + expect(queryEditorSource).toContain("this.decorations = Decoration.none;"); + expect(queryEditorSource).toContain("private prewarmedDoc: import(\"@codemirror/state\").Text | null = null;"); + expect(queryEditorSource).toContain("const shouldPrewarmFullDocument ="); + expect(queryEditorSource).toContain("if (shouldPrewarmFullDocument) this.prewarmedDoc = doc;"); + }); + it("keeps preview and diagnostics on the shared statement-range cache", () => { expect(queryEditorSource).toContain("executableStatementRangeCache = executableStatementRangeCacheForDoc"); expect(queryEditorSource).toContain('props.databaseType === "sqlserver" ? undefined : executableStatementRangeCache?.ranges'); From 9939bce92dc4cfe0420c505e40645c58e24d2acb Mon Sep 17 00:00:00 2001 From: t8y2 Date: Sun, 13 Sep 2026 01:49:57 +0800 Subject: [PATCH 3/3] style: oxfmt --- apps/desktop/src/components/editor/QueryEditor.vue | 5 +---- .../editor/queryEditorSemanticHighlightPerformance.spec.ts | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/desktop/src/components/editor/QueryEditor.vue b/apps/desktop/src/components/editor/QueryEditor.vue index 8596d21f8b..d2c4ee5e67 100644 --- a/apps/desktop/src/components/editor/QueryEditor.vue +++ b/apps/desktop/src/components/editor/QueryEditor.vue @@ -6146,10 +6146,7 @@ onMounted(async () => { } const sql = this.cachedSql; - const shouldPrewarmFullDocument = - this.cachedWindows.length === 0 && - this.prewarmedDoc !== doc && - sql.length <= MAX_FULL_DOCUMENT_SQL_SEMANTIC_HIGHLIGHT_LENGTH; + const shouldPrewarmFullDocument = this.cachedWindows.length === 0 && this.prewarmedDoc !== doc && sql.length <= MAX_FULL_DOCUMENT_SQL_SEMANTIC_HIGHLIGHT_LENGTH; const windows: Array<{ from: number; to: number; diff --git a/apps/desktop/src/lib/__tests__/editor/queryEditorSemanticHighlightPerformance.spec.ts b/apps/desktop/src/lib/__tests__/editor/queryEditorSemanticHighlightPerformance.spec.ts index c761d3eb37..17568c329b 100644 --- a/apps/desktop/src/lib/__tests__/editor/queryEditorSemanticHighlightPerformance.spec.ts +++ b/apps/desktop/src/lib/__tests__/editor/queryEditorSemanticHighlightPerformance.spec.ts @@ -31,7 +31,7 @@ describe("QueryEditor semantic highlighting while scrolling", () => { it("never exposes undefined decorations and prewarms the full document only once per document", () => { expect(queryEditorSource).toContain("this.decorations = Decoration.none;"); - expect(queryEditorSource).toContain("private prewarmedDoc: import(\"@codemirror/state\").Text | null = null;"); + expect(queryEditorSource).toContain('private prewarmedDoc: import("@codemirror/state").Text | null = null;'); expect(queryEditorSource).toContain("const shouldPrewarmFullDocument ="); expect(queryEditorSource).toContain("if (shouldPrewarmFullDocument) this.prewarmedDoc = doc;"); });