feat(editor): allow hiding query line numbers

This commit is contained in:
jearhe authored and GitHub committed 2026-08-24 18:35:34 +08:00
1 parent eaad88cc80
commit 4a478acf07
18 files changed
+205 -7

No files matched your search

@@ -0,0 +1,74 @@
// @vitest-environment happy-dom
import { Compartment, EditorState } from "@codemirror/state";
import { EditorView, GutterMarker, gutter, lineNumbers } from "@codemirror/view";
import { afterEach, describe, expect, it, vi } from "vitest";
import { buildQueryEditorLineNumbersExtension } from "@/lib/editor/queryEditorLineNumbers";
class StatementRunMarker extends GutterMarker {}
const activeViews: EditorView[] = [];
function createEditor(showLineNumbers: boolean) {
const lineNumbersCompartment = new Compartment();
const runGutterCompartment = new Compartment();
const runMarker = new StatementRunMarker();
const selectLine = vi.fn((_view: EditorView, _line: { from: number; to: number }, _event: Event) => true);
const view = new EditorView({
state: EditorState.create({
doc: "SELECT 1;\nSELECT 2;",
extensions: [
lineNumbersCompartment.of(
buildQueryEditorLineNumbersExtension(lineNumbers, showLineNumbers, {
domEventHandlers: { mousedown: selectLine },
}),
),
runGutterCompartment.of(
gutter({
class: "cm-run-statement-gutter",
lineMarker: () => runMarker,
}),
),
],
}),
parent: document.createElement("div"),
});
activeViews.push(view);
return { lineNumbersCompartment, selectLine, view };
}
afterEach(() => {
for (const view of activeViews.splice(0)) view.destroy();
});
describe("query editor line numbers", () => {
it("keeps line selection behavior when enabled", () => {
const { selectLine, view } = createEditor(true);
const lineNumber = view.dom.querySelector<HTMLElement>(".cm-lineNumbers .cm-gutterElement");
lineNumber?.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, button: 0 }));
expect(view.dom.querySelector(".cm-lineNumbers")).not.toBeNull();
expect(selectLine).toHaveBeenCalledOnce();
});
it("reconfigures line numbers without removing the statement run gutter", () => {
const { lineNumbersCompartment, view } = createEditor(true);
expect(view.dom.querySelector(".cm-lineNumbers")).not.toBeNull();
expect(view.dom.querySelector(".cm-run-statement-gutter")).not.toBeNull();
view.dispatch({
effects: lineNumbersCompartment.reconfigure(buildQueryEditorLineNumbersExtension(lineNumbers, false, { domEventHandlers: {} })),
});
expect(view.dom.querySelector(".cm-lineNumbers")).toBeNull();
expect(view.dom.querySelector(".cm-run-statement-gutter")).not.toBeNull();
view.dispatch({
effects: lineNumbersCompartment.reconfigure(buildQueryEditorLineNumbersExtension(lineNumbers, true, { domEventHandlers: {} })),
});
expect(view.dom.querySelector(".cm-lineNumbers")).not.toBeNull();
expect(view.dom.querySelector(".cm-run-statement-gutter")).not.toBeNull();
});
});
@@ -0,0 +1,9 @@
import type { Extension } from "@codemirror/state";
import type { lineNumbers } from "@codemirror/view";
type LineNumbersFactory = typeof lineNumbers;
type LineNumbersConfig = NonNullable<Parameters<LineNumbersFactory>[0]>;
export function buildQueryEditorLineNumbersExtension(factory: LineNumbersFactory | null, enabled: boolean, config: LineNumbersConfig): Extension {
return enabled && factory ? factory(config) : [];
}
@@ -38,6 +38,10 @@ describe("EDITOR_SETTINGS_DRAFT_KEYS", () => {
expect(EDITOR_SETTINGS_DRAFT_KEYS).not.toContain("globalQueryTimeoutSecs");
});
it("includes showLineNumbers", () => {
expect(EDITOR_SETTINGS_DRAFT_KEYS).toContain("showLineNumbers");
});
it("includes continueOnErrorOnBatch", () => {
expect(EDITOR_SETTINGS_DRAFT_KEYS).toContain("continueOnErrorOnBatch");
});
@@ -103,6 +107,16 @@ describe("EDITOR_SETTINGS_DRAFT_KEYS", () => {
});
describe("editorSettingsDraftFromSettings", () => {
it("round-trips a hidden line-number preference through the draft patch", () => {
const settings = makeSettings({ showLineNumbers: true });
const draft = editorSettingsDraftFromSettings(settings);
const base = editorSettingsDraftFromSettings(settings);
draft.showLineNumbers = false;
expect(editorSettingsPatchFromDraft(draft, base)).toEqual({ showLineNumbers: false });
});
it("toggles substitution without discarding per-database overrides", () => {
const base = editorSettingsDraftFromSettings(
makeSettings({
@@ -37,6 +37,30 @@ describe("settings search", () => {
{ id: "desktop", category: "about", titleKey: "hidden", visible: ({ isWeb }) => !isWeb },
];
it("indexes the query editor line-number preference", () => {
const definition = SETTINGS_SEARCH_DEFINITIONS.find((entry) => entry.id === "editor-line-numbers");
expect(definition).toEqual({
id: "editor-line-numbers",
category: "editor",
titleKey: "settings.showLineNumbers",
descriptionKey: "settings.showLineNumbersDescription",
targetId: "editor",
});
const entries = resolveSettingsSearchEntries(
[definition!],
{ isWeb: false, visibleCategories: new Set<SettingsCategory>(["editor"]) },
(key) =>
({
"settings.showLineNumbers": "Show line numbers",
"settings.showLineNumbersDescription": "Show line numbers in the SQL editor gutter",
})[key] ?? key,
categoryLabels,
);
expect(searchSettings(entries, "line number", "en").map((entry) => entry.id)).toEqual(["editor-line-numbers"]);
});
it("does not index connection or query timeout under editor settings", () => {
expect(SETTINGS_SEARCH_DEFINITIONS.map((definition) => definition.id)).not.toContain("editor-global-connect-timeout");
expect(SETTINGS_SEARCH_DEFINITIONS.map((definition) => definition.id)).not.toContain("editor-global-query-timeout");
@@ -16,6 +16,7 @@ export const EDITOR_SETTINGS_DRAFT_KEYS = [
"executeAllOnBlankLine",
"showExecutionTargetPicker",
"showStatementRunButtons",
"showLineNumbers",
"showCurrentStatementFrame",
"showInsertValueHints",
"autoAliasTables",
@@ -109,6 +109,7 @@ export const SETTINGS_SEARCH_DEFINITIONS: readonly SettingsSearchDefinition[] =
{ id: "editor-execute-all-on-blank-line", category: "editor", titleKey: "settings.executeAllOnBlankLine", descriptionKey: "settings.executeAllOnBlankLineDescription", targetId: "editor" },
{ id: "editor-execution-target", category: "editor", titleKey: "settings.showExecutionTargetPicker", descriptionKey: "settings.showExecutionTargetPickerDescription", targetId: "editor" },
{ id: "editor-run-buttons", category: "editor", titleKey: "settings.showStatementRunButtons", descriptionKey: "settings.showStatementRunButtonsDescription", targetId: "editor" },
{ id: "editor-line-numbers", category: "editor", titleKey: "settings.showLineNumbers", descriptionKey: "settings.showLineNumbersDescription", targetId: "editor" },
{ id: "editor-statement-frame", category: "editor", titleKey: "settings.showCurrentStatementFrame", descriptionKey: "settings.showCurrentStatementFrameDescription", targetId: "editor" },
{ id: "editor-value-hints", category: "editor", titleKey: "settings.showInsertValueHints", descriptionKey: "settings.showInsertValueHintsDescription", targetId: "editor" },
{ id: "editor-word-wrap", category: "editor", titleKey: "settings.wordWrap", descriptionKey: "settings.wordWrapDescription", targetId: "editor" },