From 686c34778298d563f4bdec71485f8e5138825980 Mon Sep 17 00:00:00 2001 From: Melody Ruch <396921921@qq.com> Date: Fri, 4 Sep 2026 20:41:40 +0800 Subject: [PATCH] fix(editor): highlight full statement when clicking long SQL in summary --- .../src/components/editor/QueryEditor.vue | 26 ++++++++++- .../queryEditorResultSourceHighlight.spec.ts | 45 +++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 apps/desktop/src/lib/__tests__/editor/queryEditorResultSourceHighlight.spec.ts diff --git a/apps/desktop/src/components/editor/QueryEditor.vue b/apps/desktop/src/components/editor/QueryEditor.vue index 313e95e7e4..95be8e7856 100644 --- a/apps/desktop/src/components/editor/QueryEditor.vue +++ b/apps/desktop/src/components/editor/QueryEditor.vue @@ -5763,7 +5763,27 @@ onMounted(async () => { }, provide: (field) => lineNumberMarkers.from(field), }); - return field; + + const highlightField = StateField.define({ + create() { + return Decoration.none; + }, + update(decorations, transaction) { + for (const effect of transaction.effects) { + if (effect.is(effectType)) { + const range = effect.value; + if (!range) return Decoration.none; + const from = Math.max(0, Math.min(range.from, transaction.state.doc.length)); + const to = Math.max(from, Math.min(range.to, transaction.state.doc.length)); + return from === to ? Decoration.none : Decoration.set([Decoration.mark({ class: "cm-db-result-source-highlight" }).range(from, to)]); + } + } + if (transaction.docChanged || transaction.selection) return Decoration.none; + return decorations; + }, + provide: (field) => EditorView.decorations.from(field), + }); + return [field, highlightField]; }; class StatementExecutionStateMarker extends GutterMarker { @@ -7122,6 +7142,10 @@ defineExpose({ background: var(--dbx-editor-selection-background, rgba(59, 130, 246, 0.35)); } +:deep(.cm-db-result-source-highlight) { + background: var(--dbx-editor-selection-background, rgba(126, 34, 206, 0.2)); +} + :deep(.cm-lineNumbers .cm-db-result-source-line-number) { color: rgb(126 34 206) !important; font-weight: 700; diff --git a/apps/desktop/src/lib/__tests__/editor/queryEditorResultSourceHighlight.spec.ts b/apps/desktop/src/lib/__tests__/editor/queryEditorResultSourceHighlight.spec.ts new file mode 100644 index 0000000000..bd2888a40c --- /dev/null +++ b/apps/desktop/src/lib/__tests__/editor/queryEditorResultSourceHighlight.spec.ts @@ -0,0 +1,45 @@ +import { readFileSync } from "node:fs"; +import { describe, expect, it } from "vitest"; + +const queryEditorSource = readFileSync(new URL("../../../components/editor/QueryEditor.vue", import.meta.url), "utf8"); + +// Regression test for issue #8096: clicking a long SQL statement in the +// execution-summary panel (previewStatementRange) used to only recolor the +// gutter line number, which is easy to miss and reads as "click does +// nothing" for statements that are already scrolled into view. The fix adds +// a visible text-highlight decoration alongside the existing gutter marker. +describe("QueryEditor result-source preview highlight", () => { + it("builds a highlight decoration field in addition to the gutter line-number field", () => { + const extensionStart = queryEditorSource.indexOf("buildResultSourceRangeExtension = () => {"); + expect(extensionStart).toBeGreaterThan(-1); + const extensionEnd = queryEditorSource.indexOf("\n };", extensionStart); + const extensionBody = queryEditorSource.slice(extensionStart, extensionEnd); + + expect(extensionBody).toContain("lineNumberMarkers.from(field)"); + expect(extensionBody).toContain("const highlightField = StateField.define({"); + expect(extensionBody).toContain('Decoration.mark({ class: "cm-db-result-source-highlight" })'); + expect(extensionBody).toContain("EditorView.decorations.from(field)"); + expect(extensionBody).toContain("return [field, highlightField];"); + }); + + it("drives both the gutter marker and the highlight decoration from the same effect", () => { + const extensionStart = queryEditorSource.indexOf("buildResultSourceRangeExtension = () => {"); + const extensionEnd = queryEditorSource.indexOf("\n };", extensionStart); + const extensionBody = queryEditorSource.slice(extensionStart, extensionEnd); + const effectUses = extensionBody.match(/effect\.is\(effectType\)/g) ?? []; + + expect(effectUses.length).toBe(2); + }); + + it("defines a visible background style for the highlight class", () => { + expect(queryEditorSource).toMatch(/:deep\(\.cm-db-result-source-highlight\)\s*{\s*background:/); + }); + + it("keeps previewStatementRange wired to setResultSourceRangeEffect via setResultSourceRange", () => { + const fnStart = queryEditorSource.indexOf("function previewStatementRange("); + const fnEnd = queryEditorSource.indexOf("\n}", fnStart); + const fnBody = queryEditorSource.slice(fnStart, fnEnd); + + expect(fnBody).toContain("setResultSourceRangeEffect.of({ from, to })"); + }); +});