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

@@ -325,6 +325,7 @@ const executeModeLabel = computed(() => translateWithExecuteShortcut("settings.e
const editExecuteAllOnBlankLine = ref(settingsStore.editorSettings.executeAllOnBlankLine);
const editShowExecutionTargetPicker = ref(settingsStore.editorSettings.showExecutionTargetPicker);
const editShowStatementRunButtons = ref(settingsStore.editorSettings.showStatementRunButtons);
const editShowLineNumbers = ref(settingsStore.editorSettings.showLineNumbers);
const editShowCurrentStatementFrame = ref(settingsStore.editorSettings.showCurrentStatementFrame);
const editShowInsertValueHints = ref(settingsStore.editorSettings.showInsertValueHints);
const editAutoAliasTables = ref(settingsStore.editorSettings.autoAliasTables);
@@ -526,6 +527,7 @@ function currentEditorSettingsDraft(): EditorSettingsDraft {
executeAllOnBlankLine: editExecuteAllOnBlankLine.value,
showExecutionTargetPicker: editShowExecutionTargetPicker.value,
showStatementRunButtons: editShowStatementRunButtons.value,
showLineNumbers: editShowLineNumbers.value,
showCurrentStatementFrame: editShowCurrentStatementFrame.value,
showInsertValueHints: editShowInsertValueHints.value,
autoAliasTables: editAutoAliasTables.value,
@@ -812,6 +814,7 @@ function syncEditorSettingsDraftFromStore() {
editExecuteAllOnBlankLine.value = settingsStore.editorSettings.executeAllOnBlankLine;
editShowExecutionTargetPicker.value = settingsStore.editorSettings.showExecutionTargetPicker;
editShowStatementRunButtons.value = settingsStore.editorSettings.showStatementRunButtons;
editShowLineNumbers.value = settingsStore.editorSettings.showLineNumbers;
editShowCurrentStatementFrame.value = settingsStore.editorSettings.showCurrentStatementFrame;
editShowInsertValueHints.value = settingsStore.editorSettings.showInsertValueHints;
editAutoAliasTables.value = settingsStore.editorSettings.autoAliasTables;
@@ -1063,6 +1066,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
editExecuteAllOnBlankLine.value = DEFAULT_EDITOR_SETTINGS.executeAllOnBlankLine;
editShowExecutionTargetPicker.value = DEFAULT_EDITOR_SETTINGS.showExecutionTargetPicker;
editShowStatementRunButtons.value = DEFAULT_EDITOR_SETTINGS.showStatementRunButtons;
editShowLineNumbers.value = DEFAULT_EDITOR_SETTINGS.showLineNumbers;
editShowCurrentStatementFrame.value = DEFAULT_EDITOR_SETTINGS.showCurrentStatementFrame;
editShowInsertValueHints.value = DEFAULT_EDITOR_SETTINGS.showInsertValueHints;
editAutoAliasTables.value = DEFAULT_EDITOR_SETTINGS.autoAliasTables;
@@ -1175,6 +1179,7 @@ function resetAllDefaults() {
editExecuteAllOnBlankLine.value = DEFAULT_EDITOR_SETTINGS.executeAllOnBlankLine;
editShowExecutionTargetPicker.value = DEFAULT_EDITOR_SETTINGS.showExecutionTargetPicker;
editShowStatementRunButtons.value = DEFAULT_EDITOR_SETTINGS.showStatementRunButtons;
editShowLineNumbers.value = DEFAULT_EDITOR_SETTINGS.showLineNumbers;
editShowCurrentStatementFrame.value = DEFAULT_EDITOR_SETTINGS.showCurrentStatementFrame;
editShowInsertValueHints.value = DEFAULT_EDITOR_SETTINGS.showInsertValueHints;
editAutoAliasTables.value = DEFAULT_EDITOR_SETTINGS.autoAliasTables;
@@ -3938,6 +3943,16 @@ onUnmounted(() => {
<Switch id="editor-show-statement-run-buttons" v-model="editShowStatementRunButtons" class="mt-0.5" />
</div>
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
<div class="space-y-1">
<Label for="editor-show-line-numbers">{{ t("settings.showLineNumbers") }}</Label>
<p class="text-xs text-muted-foreground">
{{ t("settings.showLineNumbersDescription") }}
</p>
</div>
<Switch id="editor-show-line-numbers" v-model="editShowLineNumbers" class="mt-0.5" />
</div>
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
<div class="space-y-1">
<Label for="editor-show-current-statement-frame">{{ t("settings.showCurrentStatementFrame") }}</Label>
@@ -107,6 +107,7 @@ import type { SqlHighlighter } from "@/lib/sql/sqlHighlighter";
import { EDITOR_FONT_FAMILY_CSS_VAR, EDITOR_FONT_SIZE_CSS_VAR, editorDiagnosticColors, editorThemeAppearanceFor, loadEditorTheme, editorFontTheme, shellLineCommentTheme, sqlCompletionTheme, sqlSemanticHighlightTheme } from "@/lib/editor/editorThemes";
import { createStatementGutterMarkerDom, shouldShowStatementGutter } from "@/lib/editor/codemirrorStatementGutter";
import { createQueryEditorSearchKeymap } from "@/lib/editor/queryEditorSearchKeymap";
import { buildQueryEditorLineNumbersExtension } from "@/lib/editor/queryEditorLineNumbers";
import { searchKeymapWithoutModD } from "@/lib/editor/codemirrorSearchKeymap";
import { defaultKeymapForGlobalShortcuts } from "@/lib/editor/codemirrorDefaultKeymap";
import { appendSqlCompletionSpace } from "@/lib/editor/sqlCompletionInsertion";
@@ -512,12 +513,14 @@ interface EditorGestureEvent extends Event {
}
let editorViewModule: typeof import("@codemirror/view") | null = null;
let codeMirrorLineNumbers: typeof import("@codemirror/view").lineNumbers | null = null;
let codeMirrorPrec: typeof import("@codemirror/state").Prec | null = null;
let codeMirrorEditorSelection: typeof import("@codemirror/state").EditorSelection | null = null;
let hoverCloseEffect: StateEffect<unknown> | null = null;
let fontThemeComp: import("@codemirror/state").Compartment | null = null;
let codeMirrorTheme: import("@codemirror/state").Compartment | null = null;
let wordWrapComp: import("@codemirror/state").Compartment | null = null;
let lineNumbersComp: import("@codemirror/state").Compartment | null = null;
let vimModeComp: import("@codemirror/state").Compartment | null = null;
let closeBracketsComp: import("@codemirror/state").Compartment | null = null;
let sqlLanguageComp: import("@codemirror/state").Compartment | null = null;
@@ -694,6 +697,7 @@ const queryEditorAppearanceSettings = computed(() => {
autoCloseBrackets: settings.autoCloseBrackets,
showCurrentStatementFrame: settings.showCurrentStatementFrame,
showInsertValueHints: settings.showInsertValueHints,
showLineNumbers: settings.showLineNumbers,
shortcuts: settings.shortcuts,
showStatementRunButtons: settings.showStatementRunButtons,
};
@@ -2053,6 +2057,14 @@ function wordWrapExtension() {
return props.forceWordWrap || settingsStore.editorSettings.wordWrap ? editorViewModule.EditorView.lineWrapping : [];
}
function lineNumbersExtension(enabled = settingsStore.editorSettings.showLineNumbers) {
return buildQueryEditorLineNumbersExtension(codeMirrorLineNumbers, enabled, {
domEventHandlers: {
mousedown: selectSqlLineFromGutter,
},
});
}
function closeBracketsExtension(enabled = settingsStore.editorSettings.autoCloseBrackets) {
if (!enabled || !codeMirrorCloseBrackets) return [];
const exts: import("@codemirror/state").Extension[] = [codeMirrorCloseBrackets()];
@@ -4649,12 +4661,14 @@ onMounted(async () => {
rectangularSelection,
} as typeof import("@codemirror/view");
hoverCloseEffect = closeHoverTooltips;
codeMirrorLineNumbers = lineNumbers;
codeMirrorPrec = Prec;
codeMirrorEditorSelection = EditorSelection;
codeMirrorSnippetCompletion = snippetCompletion;
fontThemeComp = new Compartment();
codeMirrorTheme = new Compartment();
wordWrapComp = new Compartment();
lineNumbersComp = new Compartment();
vimModeComp = new Compartment();
closeBracketsComp = new Compartment();
sqlLanguageComp = new Compartment();
@@ -5115,11 +5129,7 @@ onMounted(async () => {
scrollToMatch: (range) => EditorView.scrollIntoView(range, { y: "center" }),
}),
runGutterComp.of(runStatementGutterExtension()),
lineNumbers({
domEventHandlers: {
mousedown: selectSqlLineFromGutter,
},
}),
lineNumbersComp.of(lineNumbersExtension(initialSettings.showLineNumbers)),
currentStatementFrameExtension,
highlightActiveLineGutter(),
highlightSpecialChars(),
@@ -5763,7 +5773,7 @@ function getCurrentCustomThemeColors() {
watch(
[queryEditorAppearanceSettings, () => isDark.value, () => themePalette.value, editorThemeAppearance],
async ([ss]) => {
if (!view.value || !codeMirrorTheme || !fontThemeComp || !wordWrapComp || !vimModeComp || !closeBracketsComp || !runGutterComp || !runKeymapComp || !editorViewModule) {
if (!view.value || !codeMirrorTheme || !fontThemeComp || !wordWrapComp || !lineNumbersComp || !vimModeComp || !closeBracketsComp || !runGutterComp || !runKeymapComp || !editorViewModule) {
return;
}
if (!isGestureZooming.value && !zoomCommitScheduler.hasPendingCommit() && liveFontSize.value !== ss.fontSize) {
@@ -5773,13 +5783,14 @@ watch(
syncEditorDiagnosticCssVars();
const themeColors = getCurrentCustomThemeColors();
const [themeExt] = await Promise.all([loadEditorTheme(ss.theme, editorThemeAppearance(), themeColors, themePalette.value), ss.vimModeEnabled ? ensureCodeMirrorVim() : Promise.resolve(false)]);
if (!view.value || !codeMirrorTheme || !wordWrapComp || !vimModeComp || !closeBracketsComp || !runGutterComp || !runKeymapComp || !editorViewModule) {
if (!view.value || !codeMirrorTheme || !wordWrapComp || !lineNumbersComp || !vimModeComp || !closeBracketsComp || !runGutterComp || !runKeymapComp || !editorViewModule) {
return;
}
view.value.dispatch({
effects: [
codeMirrorTheme.reconfigure(themeExt),
wordWrapComp.reconfigure(props.forceWordWrap || ss.wordWrap ? editorViewModule.EditorView.lineWrapping : []),
lineNumbersComp.reconfigure(lineNumbersExtension(ss.showLineNumbers)),
vimModeComp.reconfigure(vimModeExtension(settingsStore.editorSettings.vimModeEnabled)),
closeBracketsComp.reconfigure(closeBracketsExtension(settingsStore.editorSettings.autoCloseBrackets)),
runGutterComp.reconfigure(runStatementGutterExtension()),
+2
View File
@@ -6369,6 +6369,8 @@ export default {
showExecutionTargetPickerDescription: "When enabled, running without a selection lets you choose between the current statement and all SQL.",
showStatementRunButtons: "Show left-side run buttons",
showStatementRunButtonsDescription: "Show per-statement run buttons in the SQL editor gutter. Keyboard shortcuts and context menu execution still work when disabled.",
showLineNumbers: "Show line numbers",
showLineNumbersDescription: "Show line numbers in the SQL editor gutter",
showCurrentStatementFrame: "Show current statement frame",
showCurrentStatementFrameDescription: "When enabled, the SQL editor draws an outline around the current executable statement; when disabled, the outline is hidden.",
showInsertValueHints: "Show INSERT value column hints",
+2
View File
@@ -6055,6 +6055,8 @@ export default withEnglishFallback({
showExecutionTargetPickerDescription: "Al activarlo, ejecutar sin selección permite elegir entre la sentencia actual y todo el SQL.",
showStatementRunButtons: "Mostrar botones de ejecución laterales",
showStatementRunButtonsDescription: "Muestra botones para ejecutar cada sentencia en el margen del editor SQL. Los atajos de teclado y el menú contextual seguirán funcionando al desactivarlo.",
showLineNumbers: "Mostrar números de línea",
showLineNumbersDescription: "Mostrar números de línea en el margen del editor SQL",
showCurrentStatementFrame: "Mostrar marco de la sentencia actual",
showCurrentStatementFrameDescription: "Al activarlo, el editor SQL dibuja un contorno alrededor de la sentencia ejecutable actual; al desactivarlo, se oculta.",
showInsertValueHints: "Mostrar pistas de columnas en VALUES de INSERT",
+2
View File
@@ -6055,6 +6055,8 @@ export default withEnglishFallback({
showExecutionTargetPickerDescription: "Se attivo, l'esecuzione senza selezione permette di scegliere tra istruzione corrente e tutto l'SQL.",
showStatementRunButtons: "Mostra pulsanti di esecuzione laterali",
showStatementRunButtonsDescription: "Mostra nel margine dell'editor SQL i pulsanti per eseguire ogni istruzione. Scorciatoie da tastiera e menu contestuale continuano a funzionare quando disattivati.",
showLineNumbers: "Mostra numeri di riga",
showLineNumbersDescription: "Mostra i numeri di riga nel margine dell'editor SQL",
showCurrentStatementFrame: "Mostra cornice istruzione corrente",
showCurrentStatementFrameDescription: "Se attivo, l'editor SQL disegna un contorno intorno all'istruzione eseguibile corrente; se disattivato, il contorno è nascosto.",
showInsertValueHints: "Mostra suggerimenti colonne nei VALUES di INSERT",
+2
View File
@@ -6081,6 +6081,8 @@ export default withEnglishFallback({
showExecutionTargetPickerDescription: "有効にすると、選択なしで実行するときに現在の文とすべてのSQLを一時的に選べます。",
showStatementRunButtons: "左側の実行ボタンを表示",
showStatementRunButtonsDescription: "SQLエディタのガターに文ごとの実行ボタンを表示します。無効にしてもキーボードショートカットとコンテキストメニューからの実行は引き続き使えます。",
showLineNumbers: "行番号を表示",
showLineNumbersDescription: "SQLエディタのガターに行番号を表示します",
showCurrentStatementFrame: "現在の文の枠線を表示",
showCurrentStatementFrameDescription: "有効にすると、SQLエディタで現在実行可能な文を枠線で示します。無効にすると枠線を表示しません。",
showInsertValueHints: "INSERT 値の列名ヒントを表示",
+2
View File
@@ -5756,6 +5756,8 @@ export default withEnglishFallback({
showExecutionTargetPickerDescription: "활성화하면 선택 없이 실행할 때 현재 구문과 전체 SQL 중에서 선택할 수 있습니다.",
showStatementRunButtons: "왼쪽 실행 버튼 표시",
showStatementRunButtonsDescription: "SQL 편집기 여백에 구문별 실행 버튼을 표시합니다. 비활성화해도 키보드 단축키와 컨텍스트 메뉴 실행은 계속 작동합니다.",
showLineNumbers: "줄 번호 표시",
showLineNumbersDescription: "SQL 편집기 여백에 줄 번호를 표시합니다",
showCurrentStatementFrame: "현재 구문 프레임 표시",
showCurrentStatementFrameDescription: "활성화하면 SQL 편집기가 현재 실행 가능한 구문 주위에 윤곽선을 그립니다. 비활성화하면 윤곽선이 숨겨집니다.",
showInsertValueHints: "INSERT 값 컬럼 힌트 표시",
+2
View File
@@ -6057,6 +6057,8 @@ export default withEnglishFallback({
showExecutionTargetPickerDescription: "Quando ativado, executar sem seleção permite escolher entre a instrução atual e todo o SQL.",
showStatementRunButtons: "Mostrar botões de execução laterais",
showStatementRunButtonsDescription: "Mostra botões para executar cada instrução na margem do editor SQL. Atalhos de teclado e execução pelo menu de contexto continuam funcionando quando desativado.",
showLineNumbers: "Mostrar números de linha",
showLineNumbersDescription: "Mostrar números de linha na margem do editor SQL",
showCurrentStatementFrame: "Mostrar moldura da instrução atual",
showCurrentStatementFrameDescription: "Quando ativado, o editor SQL desenha um contorno ao redor da instrução executável atual; quando desativado, o contorno fica oculto.",
showInsertValueHints: "Mostrar dicas de colunas em VALUES de INSERT",
+2
View File
@@ -6352,6 +6352,8 @@ export default withEnglishFallback({
showExecutionTargetPickerDescription: "开启后,无选区执行时可在当前语句和全部 SQL 之间临时选择。",
showStatementRunButtons: "显示左侧执行按钮",
showStatementRunButtonsDescription: "在 SQL 编辑器左侧显示按语句执行的快捷按钮。关闭后仍可通过快捷键和右键菜单执行。",
showLineNumbers: "显示行号",
showLineNumbersDescription: "在 SQL 编辑器左侧显示行号",
showCurrentStatementFrame: "显示当前语句外框线",
showCurrentStatementFrameDescription: "开启后,在 SQL 编辑器中用外框线标出当前可执行语句;关闭后不显示外框线。",
showInsertValueHints: "显示 INSERT 值列名提示",
+2
View File
@@ -5373,6 +5373,8 @@ export default withEnglishFallback({
showExecutionTargetPickerDescription: "啟用後,無選取執行時可在目前語句與全部 SQL 之間臨時選擇。",
showStatementRunButtons: "顯示左側執行按鈕",
showStatementRunButtonsDescription: "在 SQL 編輯器左側顯示按語句執行的快捷按鈕。關閉後仍可透過快捷鍵和右鍵選單執行。",
showLineNumbers: "顯示行號",
showLineNumbersDescription: "在 SQL 編輯器左側顯示行號",
showCurrentStatementFrame: "顯示目前語句外框線",
showCurrentStatementFrameDescription: "啟用後,在 SQL 編輯器中用外框線標出目前可執行語句;關閉後不顯示外框線。",
showInsertValueHints: "顯示 INSERT 值欄位提示",
@@ -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" },
+29
View File
@@ -131,6 +131,12 @@ describe("normalizeEditorSettings", () => {
expect(normalizeEditorSettings({ sidebarConnectionSortMode: "invalid" as any }).sidebarConnectionSortMode).toBe("manual");
});
it("shows line numbers by default and preserves an explicit opt-out", () => {
expect(normalizeEditorSettings({}).showLineNumbers).toBe(true);
expect(normalizeEditorSettings({ showLineNumbers: false }).showLineNumbers).toBe(false);
expect(normalizeEditorSettings({ showLineNumbers: "false" } as any).showLineNumbers).toBe(true);
});
it("shows the current statement frame by default", () => {
expect(normalizeEditorSettings({}).showCurrentStatementFrame).toBe(true);
});
@@ -726,6 +732,29 @@ describe("settingsStore persisted settings initialization", () => {
expect(saveEditorSettings).toHaveBeenLastCalledWith(expect.objectContaining({ openDataTabsNextToActive: false }));
});
it("loads, persists, and reloads hidden query editor line numbers", async () => {
let persistedSettings: Record<string, unknown> = { showLineNumbers: true };
const loadEditorSettings = vi.fn(async () => JSON.parse(JSON.stringify(persistedSettings)));
const saveEditorSettings = vi.fn(async (settings: Record<string, unknown>) => {
persistedSettings = JSON.parse(JSON.stringify(settings));
});
vi.doMock("@/lib/backend/api", () => ({ loadEditorSettings, saveEditorSettings }));
const { useSettingsStore } = await import("@/stores/settingsStore");
const store = useSettingsStore();
await store.initEditorSettings();
await store.updateEditorSettingsAndPersist({ showLineNumbers: false });
expect(store.editorSettings.showLineNumbers).toBe(false);
expect(saveEditorSettings).toHaveBeenLastCalledWith(expect.objectContaining({ showLineNumbers: false }));
setActivePinia(createPinia());
const restartedStore = useSettingsStore();
await restartedStore.initEditorSettings();
expect(restartedStore.editorSettings.showLineNumbers).toBe(false);
});
it("shares concurrent initialization and applies startup changes after saved settings load", async () => {
let resolveLoad!: (value: unknown) => void;
const loadEditorSettings = vi.fn(
+4
View File
@@ -525,6 +525,7 @@ export interface EditorSettings {
timeoutInheritanceMigrationVersion: number;
showExecutionTargetPicker: boolean;
showStatementRunButtons: boolean;
showLineNumbers: boolean;
showCurrentStatementFrame: boolean;
showInsertValueHints: boolean;
autoAliasTables: boolean;
@@ -733,6 +734,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
timeoutInheritanceMigrationVersion: 2,
showExecutionTargetPicker: false,
showStatementRunButtons: true,
showLineNumbers: true,
showCurrentStatementFrame: true,
showInsertValueHints: true,
autoAliasTables: true,
@@ -1105,6 +1107,7 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
: 0,
showExecutionTargetPicker: settings.showExecutionTargetPicker ?? DEFAULT_EDITOR_SETTINGS.showExecutionTargetPicker,
showStatementRunButtons: typeof settings.showStatementRunButtons === "boolean" ? settings.showStatementRunButtons : DEFAULT_EDITOR_SETTINGS.showStatementRunButtons,
showLineNumbers: typeof settings.showLineNumbers === "boolean" ? settings.showLineNumbers : DEFAULT_EDITOR_SETTINGS.showLineNumbers,
showCurrentStatementFrame: typeof settings.showCurrentStatementFrame === "boolean" ? settings.showCurrentStatementFrame : DEFAULT_EDITOR_SETTINGS.showCurrentStatementFrame,
showInsertValueHints: typeof settings.showInsertValueHints === "boolean" ? settings.showInsertValueHints : DEFAULT_EDITOR_SETTINGS.showInsertValueHints,
autoAliasTables: settings.autoAliasTables ?? DEFAULT_EDITOR_SETTINGS.autoAliasTables,
@@ -1705,6 +1708,7 @@ export const useSettingsStore = defineStore("settings", () => {
if (partial.timeoutInheritanceMigrationVersion !== undefined) editorSettings.value.timeoutInheritanceMigrationVersion = Math.max(0, Math.floor(partial.timeoutInheritanceMigrationVersion));
if (partial.showExecutionTargetPicker !== undefined) editorSettings.value.showExecutionTargetPicker = partial.showExecutionTargetPicker;
if (partial.showStatementRunButtons !== undefined) editorSettings.value.showStatementRunButtons = partial.showStatementRunButtons === true;
if (partial.showLineNumbers !== undefined) editorSettings.value.showLineNumbers = partial.showLineNumbers === true;
if (partial.showCurrentStatementFrame !== undefined) editorSettings.value.showCurrentStatementFrame = partial.showCurrentStatementFrame === true;
if (partial.showInsertValueHints !== undefined) editorSettings.value.showInsertValueHints = partial.showInsertValueHints === true;
if (partial.autoAliasTables !== undefined) editorSettings.value.autoAliasTables = partial.autoAliasTables;