From dd11e427485aead22bb7e966766f7b960a5aae7a Mon Sep 17 00:00:00 2001 From: Abeautifulsnow Date: Thu, 3 Sep 2026 18:50:05 +0800 Subject: [PATCH] fix(editor): anchor wrapped line numbers via inline style --- .../src/lib/__tests__/editorThemes.spec.ts | 3 +-- .../__tests__/queryEditorLineNumbers.spec.ts | 17 ++++++++++++++++- apps/desktop/src/lib/editor/editorThemes.ts | 7 ++++--- .../src/lib/editor/queryEditorLineNumbers.ts | 11 ++++++++--- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/apps/desktop/src/lib/__tests__/editorThemes.spec.ts b/apps/desktop/src/lib/__tests__/editorThemes.spec.ts index cffac09a47..890b91495a 100644 --- a/apps/desktop/src/lib/__tests__/editorThemes.spec.ts +++ b/apps/desktop/src/lib/__tests__/editorThemes.spec.ts @@ -192,7 +192,7 @@ describe("SQL completion theme", () => { }); describe("editor gutters", () => { - it("anchors line numbers to the first visual row of wrapped lines", () => { + it("keeps single line numbers vertically centered in the base rule", () => { const rules = buildEditorFontThemeRules(); expect(rules[".cm-lineNumbers .cm-gutterElement"]).toMatchObject({ @@ -200,6 +200,5 @@ describe("editor gutters", () => { display: "flex", justifyContent: "flex-end", }); - expect(rules[".cm-lineNumbers .cm-gutterElement.cm-db-wrapped-line-number"]).toMatchObject({ alignItems: "flex-start" }); }); }); diff --git a/apps/desktop/src/lib/editor/__tests__/queryEditorLineNumbers.spec.ts b/apps/desktop/src/lib/editor/__tests__/queryEditorLineNumbers.spec.ts index d1de217ab8..72236340c7 100644 --- a/apps/desktop/src/lib/editor/__tests__/queryEditorLineNumbers.spec.ts +++ b/apps/desktop/src/lib/editor/__tests__/queryEditorLineNumbers.spec.ts @@ -2,7 +2,7 @@ import { Compartment, EditorState } from "@codemirror/state"; import { EditorView, GutterMarker, gutter, lineNumbers } from "@codemirror/view"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { buildQueryEditorLineNumbersExtension, isWrappedLineNumberGutter } from "@/lib/editor/queryEditorLineNumbers"; +import { buildQueryEditorLineNumbersExtension, isWrappedLineNumberGutter, setQueryEditorLineNumberAlignment } from "@/lib/editor/queryEditorLineNumbers"; class StatementRunMarker extends GutterMarker {} @@ -47,6 +47,21 @@ describe("query editor line numbers", () => { expect(isWrappedLineNumberGutter(42, 20.8)).toBe(true); }); + it("keeps wrapped-line alignment when CodeMirror rebuilds a gutter marker class", () => { + const element = document.createElement("div"); + element.className = "cm-gutterElement"; + + setQueryEditorLineNumberAlignment(element, true); + // GutterElement#setMarkers owns and replaces className for active-line and + // selection marker changes. The measured alignment must survive it. + element.className = "cm-gutterElement cm-activeLineGutter"; + + expect(element.style.alignItems).toBe("flex-start"); + + setQueryEditorLineNumberAlignment(element, false); + expect(element.style.alignItems).toBe(""); + }); + it("keeps line selection behavior when enabled", () => { const { selectLine, view } = createEditor(true); const lineNumber = view.dom.querySelector(".cm-lineNumbers .cm-gutterElement"); diff --git a/apps/desktop/src/lib/editor/editorThemes.ts b/apps/desktop/src/lib/editor/editorThemes.ts index e86cea4d2c..2461026223 100644 --- a/apps/desktop/src/lib/editor/editorThemes.ts +++ b/apps/desktop/src/lib/editor/editorThemes.ts @@ -810,6 +810,10 @@ export function buildEditorFontThemeRules(opts?: { fixedHeight?: boolean; scroll width: "1px", zIndex: "10", }, + // Single lines render vertically centered here. Wrapped lines are anchored + // to the first visual row by createQueryEditorLineNumberAlignmentExtension, + // which sets an inline `align-items: flex-start` (inline style survives + // CodeMirror's className rebuild, unlike a toggled class). ".cm-lineNumbers .cm-gutterElement": { alignItems: "center", cursor: "pointer", @@ -818,9 +822,6 @@ export function buildEditorFontThemeRules(opts?: { fixedHeight?: boolean; scroll paddingRight: "8px", userSelect: "none", }, - ".cm-lineNumbers .cm-gutterElement.cm-db-wrapped-line-number": { - alignItems: "flex-start", - }, ".cm-run-statement-gutter": { minWidth: "28px", }, diff --git a/apps/desktop/src/lib/editor/queryEditorLineNumbers.ts b/apps/desktop/src/lib/editor/queryEditorLineNumbers.ts index e57030e6d7..5b6f810b80 100644 --- a/apps/desktop/src/lib/editor/queryEditorLineNumbers.ts +++ b/apps/desktop/src/lib/editor/queryEditorLineNumbers.ts @@ -4,8 +4,6 @@ import type { lineNumbers } from "@codemirror/view"; type LineNumbersFactory = typeof lineNumbers; type LineNumbersConfig = NonNullable[0]>; -export const WRAPPED_LINE_NUMBER_GUTTER_CLASS = "cm-db-wrapped-line-number"; - export function isWrappedLineNumberGutter(gutterHeight: number, lineHeight: number): boolean { return gutterHeight > lineHeight + 1; } @@ -14,6 +12,13 @@ export function buildQueryEditorLineNumbersExtension(factory: LineNumbersFactory return enabled && factory ? factory(config) : []; } +export function setQueryEditorLineNumberAlignment(element: HTMLElement, wrapped: boolean): void { + // CodeMirror rebuilds gutter class names as marker state changes. Keep this + // measured layout state inline so selection and active-line updates cannot + // restore the base centered alignment for a wrapped line. + element.style.alignItems = wrapped ? "flex-start" : ""; +} + export function createQueryEditorLineNumberAlignmentExtension(ViewPlugin: typeof import("@codemirror/view").ViewPlugin): Extension { return ViewPlugin.fromClass( class { @@ -44,7 +49,7 @@ export function createQueryEditorLineNumberAlignmentExtension(ViewPlugin: typeof }, write: (measurements) => { this.measureScheduled = false; - for (const { element, wrapped } of measurements) element.classList.toggle(WRAPPED_LINE_NUMBER_GUTTER_CLASS, wrapped); + for (const { element, wrapped } of measurements) setQueryEditorLineNumberAlignment(element, wrapped); }, }); }