fix(editor): anchor wrapped line numbers via inline style

This commit is contained in:
Abeautifulsnow
2026-09-03 18:50:05 +08:00
committed by GitHub
parent 767cc809a1
commit dd11e42748
4 changed files with 29 additions and 9 deletions
+1 -2
View File
@@ -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" });
});
});
@@ -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<HTMLElement>(".cm-lineNumbers .cm-gutterElement");
+4 -3
View File
@@ -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",
},
@@ -4,8 +4,6 @@ import type { lineNumbers } from "@codemirror/view";
type LineNumbersFactory = typeof lineNumbers;
type LineNumbersConfig = NonNullable<Parameters<LineNumbersFactory>[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);
},
});
}