From 2f134585ea7128ce376ae8e3591747e47ca79084 Mon Sep 17 00:00:00 2001 From: gggaiitx <62124152+gggaiitx@users.noreply.github.com> Date: Thu, 9 Jul 2026 09:53:01 +0800 Subject: [PATCH] feat(editor): add auto close brackets setting --- .../editor/EditorSettingsDialog.vue | 14 +++ .../src/components/editor/QueryEditor.vue | 24 +++++- apps/desktop/src/i18n/locales/en.ts | 2 + apps/desktop/src/i18n/locales/es.ts | 2 + apps/desktop/src/i18n/locales/it.ts | 2 + apps/desktop/src/i18n/locales/ja.ts | 2 + apps/desktop/src/i18n/locales/pt-BR.ts | 2 + apps/desktop/src/i18n/locales/zh-CN.ts | 2 + apps/desktop/src/i18n/locales/zh-TW.ts | 2 + apps/desktop/src/stores/settingsStore.ts | 4 + .../app-tests/closeBracketsExtension.test.ts | 86 +++++++++++++++++++ packages/app-tests/settingsStore.test.ts | 7 ++ 12 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 packages/app-tests/closeBracketsExtension.test.ts diff --git a/apps/desktop/src/components/editor/EditorSettingsDialog.vue b/apps/desktop/src/components/editor/EditorSettingsDialog.vue index bcbcf61563..16ff2989f7 100644 --- a/apps/desktop/src/components/editor/EditorSettingsDialog.vue +++ b/apps/desktop/src/components/editor/EditorSettingsDialog.vue @@ -247,6 +247,7 @@ const editShowCurrentStatementFrame = ref(settingsStore.editorSettings.showCurre const editAutoAliasTables = ref(settingsStore.editorSettings.autoAliasTables); const editWordWrap = ref(settingsStore.editorSettings.wordWrap); const editVimModeEnabled = ref(settingsStore.editorSettings.vimModeEnabled); +const editAutoCloseBrackets = ref(settingsStore.editorSettings.autoCloseBrackets); const editSqlSemanticDiagnosticsMode = ref(settingsStore.editorSettings.sqlSemanticDiagnosticsMode); const editSqlSemanticDiagnosticsEnabled = ref(settingsStore.editorSettings.sqlSemanticDiagnosticsEnabled); const editConfirmDangerousSqlExecution = ref(settingsStore.editorSettings.confirmDangerousSqlExecution); @@ -570,6 +571,7 @@ watch( editAutoAliasTables.value = settingsStore.editorSettings.autoAliasTables; editWordWrap.value = settingsStore.editorSettings.wordWrap; editVimModeEnabled.value = settingsStore.editorSettings.vimModeEnabled; + editAutoCloseBrackets.value = settingsStore.editorSettings.autoCloseBrackets; editSqlSemanticDiagnosticsMode.value = settingsStore.editorSettings.sqlSemanticDiagnosticsMode; editSqlSemanticDiagnosticsEnabled.value = settingsStore.editorSettings.sqlSemanticDiagnosticsEnabled; editConfirmDangerousSqlExecution.value = settingsStore.editorSettings.confirmDangerousSqlExecution; @@ -675,6 +677,7 @@ function hasChanges(): boolean { editAutoAliasTables.value !== settingsStore.editorSettings.autoAliasTables || editWordWrap.value !== settingsStore.editorSettings.wordWrap || editVimModeEnabled.value !== settingsStore.editorSettings.vimModeEnabled || + editAutoCloseBrackets.value !== settingsStore.editorSettings.autoCloseBrackets || editSqlSemanticDiagnosticsMode.value !== settingsStore.editorSettings.sqlSemanticDiagnosticsMode || editSqlSemanticDiagnosticsEnabled.value !== settingsStore.editorSettings.sqlSemanticDiagnosticsEnabled || editConfirmDangerousSqlExecution.value !== settingsStore.editorSettings.confirmDangerousSqlExecution || @@ -736,6 +739,7 @@ async function persistSettings() { autoAliasTables: editAutoAliasTables.value, wordWrap: editWordWrap.value, vimModeEnabled: editVimModeEnabled.value, + autoCloseBrackets: editAutoCloseBrackets.value, sqlSemanticDiagnosticsMode: editSqlSemanticDiagnosticsMode.value, confirmDangerousSqlExecution: editConfirmDangerousSqlExecution.value, confirmUnsavedSqlClose: editConfirmUnsavedSqlClose.value, @@ -820,6 +824,7 @@ function resetDefaultsForTab(tab: SettingsCategory) { editAutoAliasTables.value = DEFAULT_EDITOR_SETTINGS.autoAliasTables; editWordWrap.value = DEFAULT_EDITOR_SETTINGS.wordWrap; editVimModeEnabled.value = DEFAULT_EDITOR_SETTINGS.vimModeEnabled; + editAutoCloseBrackets.value = DEFAULT_EDITOR_SETTINGS.autoCloseBrackets; editSqlSemanticDiagnosticsMode.value = DEFAULT_EDITOR_SETTINGS.sqlSemanticDiagnosticsMode; editSqlSemanticDiagnosticsEnabled.value = DEFAULT_EDITOR_SETTINGS.sqlSemanticDiagnosticsEnabled; editConfirmDangerousSqlExecution.value = DEFAULT_EDITOR_SETTINGS.confirmDangerousSqlExecution; @@ -891,6 +896,7 @@ function resetAllDefaults() { editAutoAliasTables.value = DEFAULT_EDITOR_SETTINGS.autoAliasTables; editWordWrap.value = DEFAULT_EDITOR_SETTINGS.wordWrap; editVimModeEnabled.value = DEFAULT_EDITOR_SETTINGS.vimModeEnabled; + editAutoCloseBrackets.value = DEFAULT_EDITOR_SETTINGS.autoCloseBrackets; editSqlSemanticDiagnosticsMode.value = DEFAULT_EDITOR_SETTINGS.sqlSemanticDiagnosticsMode; editSqlSemanticDiagnosticsEnabled.value = DEFAULT_EDITOR_SETTINGS.sqlSemanticDiagnosticsEnabled; editConfirmDangerousSqlExecution.value = DEFAULT_EDITOR_SETTINGS.confirmDangerousSqlExecution; @@ -2590,6 +2596,14 @@ onUnmounted(cleanupPreviewEditor); +
+
+ +

{{ t("settings.autoCloseBracketsDescription") }}

+
+ +
+
diff --git a/apps/desktop/src/components/editor/QueryEditor.vue b/apps/desktop/src/components/editor/QueryEditor.vue index f6d1b16c27..4fae718970 100644 --- a/apps/desktop/src/components/editor/QueryEditor.vue +++ b/apps/desktop/src/components/editor/QueryEditor.vue @@ -202,6 +202,9 @@ 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 vimModeComp: import("@codemirror/state").Compartment | null = null; +let closeBracketsComp: import("@codemirror/state").Compartment | null = null; +let codeMirrorCloseBrackets: typeof import("@codemirror/autocomplete").closeBrackets | null = null; +let codeMirrorCloseBracketsKeymap: readonly import("@codemirror/view").KeyBinding[] | null = null; let readOnlyComp: import("@codemirror/state").Compartment | null = null; let runGutterComp: import("@codemirror/state").Compartment | null = null; let runKeymapComp: import("@codemirror/state").Compartment | null = null; @@ -287,6 +290,7 @@ const queryEditorAppearanceSettings = computed(() => { activeCustomThemeId: settings.activeCustomThemeId, wordWrap: settings.wordWrap, vimModeEnabled: settings.vimModeEnabled, + autoCloseBrackets: settings.autoCloseBrackets, showCurrentStatementFrame: settings.showCurrentStatementFrame, shortcuts: settings.shortcuts, showStatementRunButtons: settings.showStatementRunButtons, @@ -965,6 +969,15 @@ function wordWrapExtension() { return props.forceWordWrap || settingsStore.editorSettings.wordWrap ? editorViewModule.EditorView.lineWrapping : []; } +function closeBracketsExtension(enabled = settingsStore.editorSettings.autoCloseBrackets) { + if (!enabled || !codeMirrorCloseBrackets) return []; + const exts: import("@codemirror/state").Extension[] = [codeMirrorCloseBrackets()]; + if (codeMirrorCloseBracketsKeymap?.length && codeMirrorPrec && editorViewModule) { + exts.push(codeMirrorPrec.highest(editorViewModule.keymap.of([...codeMirrorCloseBracketsKeymap]))); + } + return exts; +} + function vimModeExtension(enabled = settingsStore.editorSettings.vimModeEnabled) { if (!codeMirrorVim || !enabled) return []; const vimExtension = codeMirrorVim({ status: true }); @@ -2492,6 +2505,9 @@ onMounted(async () => { codeMirrorTheme = new Compartment(); wordWrapComp = new Compartment(); vimModeComp = new Compartment(); + closeBracketsComp = new Compartment(); + codeMirrorCloseBrackets = closeBrackets; + codeMirrorCloseBracketsKeymap = closeBracketsKeymap; readOnlyComp = new Compartment(); runGutterComp = new Compartment(); runKeymapComp = new Compartment(); @@ -2755,7 +2771,7 @@ onMounted(async () => { completionComp.of(buildSqlCompletionExtension()), sqlCompletionTheme(EditorView), codeMirrorTheme.of(theme), - closeBrackets(), + closeBracketsComp.of(closeBracketsExtension(initialSettings.autoCloseBrackets)), bracketMatching(), hoverTooltip((currentView, pos) => resolveSqlHoverTooltip(currentView, pos)), buildSqlSignatureExtension(), @@ -2764,7 +2780,6 @@ onMounted(async () => { Prec.highest( keymap.of([ { key: "'", run: handleSqlSingleQuote }, - ...closeBracketsKeymap, { key: "Tab", run: handleTab }, { key: "Escape", @@ -3100,7 +3115,7 @@ function getCurrentCustomThemeColors() { watch( [queryEditorAppearanceSettings, () => isDark.value, () => themePalette.value], async ([ss]) => { - if (!view.value || !codeMirrorTheme || !fontThemeComp || !wordWrapComp || !vimModeComp || !runGutterComp || !runKeymapComp || !editorViewModule) { + if (!view.value || !codeMirrorTheme || !fontThemeComp || !wordWrapComp || !vimModeComp || !closeBracketsComp || !runGutterComp || !runKeymapComp || !editorViewModule) { return; } if (!isGestureZooming.value && !zoomCommitScheduler.hasPendingCommit() && liveFontSize.value !== ss.fontSize) { @@ -3109,7 +3124,7 @@ watch( syncEditorFontCssVars(liveFontSize.value, ss.fontFamily); 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 || !runGutterComp || !runKeymapComp || !editorViewModule) { + if (!view.value || !codeMirrorTheme || !wordWrapComp || !vimModeComp || !closeBracketsComp || !runGutterComp || !runKeymapComp || !editorViewModule) { return; } view.value.dispatch({ @@ -3117,6 +3132,7 @@ watch( codeMirrorTheme.reconfigure(themeExt), wordWrapComp.reconfigure(props.forceWordWrap || ss.wordWrap ? editorViewModule.EditorView.lineWrapping : []), vimModeComp.reconfigure(vimModeExtension(settingsStore.editorSettings.vimModeEnabled)), + closeBracketsComp.reconfigure(closeBracketsExtension(settingsStore.editorSettings.autoCloseBrackets)), runGutterComp.reconfigure(props.hideExecutionControls ? [] : (buildRunStatementGutterExtension?.() ?? [])), runKeymapComp.reconfigure(runKeymapExtension(editorViewModule.keymap)), ], diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index f29b8a9c2e..fc5bf438f3 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -3088,6 +3088,8 @@ export default { wordWrapDescription: "Wrap long SQL lines within the editor width", vimMode: "Vim mode", vimModeDescription: "Use Vim-style modal editing in the SQL editor", + autoCloseBrackets: "Auto-close brackets", + autoCloseBracketsDescription: "Automatically insert closing brackets and quotes when typing an opening one", sqlSemanticDiagnosticsEnabled: "SQL semantic diagnostics", sqlSemanticDiagnosticsEnabledDescription: "When enabled, the editor reports semantic issues such as unknown tables and columns. Disable it to reduce parsing and metadata checks for large SQL.", confirmDangerousSqlExecution: "Confirm before dangerous SQL", diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index 87321aa46a..43c9f1a814 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -2989,6 +2989,8 @@ export default withEnglishFallback({ wordWrapDescription: "Ajustar las líneas largas de SQL al ancho del editor", vimMode: "Modo Vim", vimModeDescription: "Usar edición modal estilo Vim en el editor SQL", + autoCloseBrackets: "Cerrar paréntesis automáticamente", + autoCloseBracketsDescription: "Insertar automáticamente paréntesis y comillas de cierre al escribir los de apertura", sqlSemanticDiagnosticsEnabled: "Diagnóstico semántico de SQL", sqlSemanticDiagnosticsEnabledDescription: "Al activarse, el editor informa de problemas semánticos como tablas y columnas desconocidas. Desactívalo para reducir el análisis y las comprobaciones de metadatos en SQL grandes.", confirmDangerousSqlExecution: "Confirmar antes de SQL peligroso", diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts index 3600cbd6a1..c5e4c38368 100644 --- a/apps/desktop/src/i18n/locales/it.ts +++ b/apps/desktop/src/i18n/locales/it.ts @@ -2987,6 +2987,8 @@ export default withEnglishFallback({ wordWrapDescription: "Incolonna le righe SQL lunghe entro la larghezza dell'editor", vimMode: "Modalita Vim", vimModeDescription: "Usa la modifica modale in stile Vim nell'editor SQL", + autoCloseBrackets: "Chiusura automatica parentesi", + autoCloseBracketsDescription: "Inserisci automaticamente parentesi e virgolette di chiusura quando digiti quelle di apertura", sqlSemanticDiagnosticsEnabled: "Diagnostica semantica SQL", sqlSemanticDiagnosticsEnabledDescription: "Se abilitata, l'editor segnala problemi semantici come tabelle e colonne sconosciute. Disabilitala per ridurre l'analisi e i controlli dei metadati per file SQL di grandi dimensioni.", confirmDangerousSqlExecution: "Conferma prima di SQL pericoloso", diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts index 957f8a00d0..11f52331dc 100644 --- a/apps/desktop/src/i18n/locales/ja.ts +++ b/apps/desktop/src/i18n/locales/ja.ts @@ -2978,6 +2978,8 @@ export default withEnglishFallback({ wordWrapDescription: "長いSQL行をエディタ幅内で折り返します", vimMode: "Vimモード", vimModeDescription: "SQLエディタでVim形式のモーダル編集を使用します", + autoCloseBrackets: "自動括弧補完", + autoCloseBracketsDescription: "左括弧や左引用符を入力すると、対応する右括弧や右引用符を自動的に挿入します", sqlSemanticDiagnosticsEnabled: "SQLセマンティック診断", sqlSemanticDiagnosticsEnabledDescription: "有効時、エディタは不明なテーブルや列などの意味的な問題を表示します。大きなSQLの解析とメタデータ確認の負荷を減らすには無効にします。", confirmDangerousSqlExecution: "危険なSQLの前に確認", diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts index 55cfaee8f2..9315b96436 100644 --- a/apps/desktop/src/i18n/locales/pt-BR.ts +++ b/apps/desktop/src/i18n/locales/pt-BR.ts @@ -2988,6 +2988,8 @@ export default withEnglishFallback({ wordWrapDescription: "Quebrar linhas SQL longas dentro da largura do editor", vimMode: "Modo Vim", vimModeDescription: "Usar edição modal no estilo Vim no editor SQL", + autoCloseBrackets: "Fechar parênteses automaticamente", + autoCloseBracketsDescription: "Inserir automaticamente parênteses e aspas de fechamento ao digitar os de abertura", sqlSemanticDiagnosticsEnabled: "Diagnóstico semântico de SQL", sqlSemanticDiagnosticsEnabledDescription: "Quando ativado, o editor relata problemas semânticos como tabelas e colunas desconhecidas. Desative para reduzir a análise e verificações de metadados em SQL grande.", confirmDangerousSqlExecution: "Confirmar antes de SQL perigoso", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index a664d11c0b..128d2294ce 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -3088,6 +3088,8 @@ export default withEnglishFallback({ wordWrapDescription: "长 SQL 在编辑器宽度内自动折行显示", vimMode: "Vim 模式", vimModeDescription: "在 SQL 编辑器中使用 Vim 风格的模态编辑", + autoCloseBrackets: "自动成对补全", + autoCloseBracketsDescription: "输入左括号或左引号时自动补全对应的右括号或右引号", sqlSemanticDiagnosticsEnabled: "SQL 语义诊断", sqlSemanticDiagnosticsEnabledDescription: "开启后,编辑器会提示未知表、字段等语义问题;关闭可减少 SQL 解析和元数据检查的性能开销。", confirmDangerousSqlExecution: "执行危险 SQL 前弹出确认", diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts index 398460c722..d44c192fc5 100644 --- a/apps/desktop/src/i18n/locales/zh-TW.ts +++ b/apps/desktop/src/i18n/locales/zh-TW.ts @@ -2838,6 +2838,8 @@ export default withEnglishFallback({ wordWrapDescription: "長 SQL 在編輯器寬度內自動折行顯示", vimMode: "Vim 模式", vimModeDescription: "在 SQL 編輯器中使用 Vim 風格的模態編輯", + autoCloseBrackets: "自動成對補全", + autoCloseBracketsDescription: "輸入左括號或左引號時自動補全對應的右括號或右引號", sqlSemanticDiagnosticsEnabled: "SQL 語意診斷", sqlSemanticDiagnosticsEnabledDescription: "開啟後,編輯器會提示未知資料表、欄位等語意問題;關閉可減少 SQL 解析和中繼資料檢查的效能負擔。", confirmDangerousSqlExecution: "執行危險 SQL 前彈出確認", diff --git a/apps/desktop/src/stores/settingsStore.ts b/apps/desktop/src/stores/settingsStore.ts index f9e5e6aa1f..fb50db8ad0 100644 --- a/apps/desktop/src/stores/settingsStore.ts +++ b/apps/desktop/src/stores/settingsStore.ts @@ -375,6 +375,7 @@ export interface EditorSettings { autoAliasTables: boolean; wordWrap: boolean; vimModeEnabled: boolean; + autoCloseBrackets: boolean; sqlSemanticDiagnosticsMode: SqlSemanticDiagnosticsMode; sqlSemanticDiagnosticsEnabled: boolean; confirmDangerousSqlExecution: boolean; @@ -503,6 +504,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = { autoAliasTables: true, wordWrap: false, vimModeEnabled: false, + autoCloseBrackets: true, sqlSemanticDiagnosticsMode: "auto", sqlSemanticDiagnosticsEnabled: SQL_SEMANTIC_DIAGNOSTICS_AUTO_ENABLED, confirmDangerousSqlExecution: true, @@ -728,6 +730,7 @@ export function normalizeEditorSettings(settings: Partial, exist autoAliasTables: settings.autoAliasTables ?? DEFAULT_EDITOR_SETTINGS.autoAliasTables, wordWrap: settings.wordWrap ?? DEFAULT_EDITOR_SETTINGS.wordWrap, vimModeEnabled: typeof settings.vimModeEnabled === "boolean" ? settings.vimModeEnabled : DEFAULT_EDITOR_SETTINGS.vimModeEnabled, + autoCloseBrackets: typeof settings.autoCloseBrackets === "boolean" ? settings.autoCloseBrackets : DEFAULT_EDITOR_SETTINGS.autoCloseBrackets, sqlSemanticDiagnosticsMode, sqlSemanticDiagnosticsEnabled: sqlSemanticDiagnosticsEnabledForMode(sqlSemanticDiagnosticsMode), confirmDangerousSqlExecution: settings.confirmDangerousSqlExecution ?? DEFAULT_EDITOR_SETTINGS.confirmDangerousSqlExecution, @@ -963,6 +966,7 @@ export const useSettingsStore = defineStore("settings", () => { if (partial.autoAliasTables !== undefined) editorSettings.value.autoAliasTables = partial.autoAliasTables; if (partial.wordWrap !== undefined) editorSettings.value.wordWrap = partial.wordWrap; if (partial.vimModeEnabled !== undefined) editorSettings.value.vimModeEnabled = partial.vimModeEnabled === true; + if (partial.autoCloseBrackets !== undefined) editorSettings.value.autoCloseBrackets = partial.autoCloseBrackets === true; if (partial.sqlSemanticDiagnosticsMode !== undefined || partial.sqlSemanticDiagnosticsEnabled !== undefined) { const nextMode = normalizeSqlSemanticDiagnosticsMode(partial.sqlSemanticDiagnosticsMode, partial.sqlSemanticDiagnosticsEnabled); editorSettings.value.sqlSemanticDiagnosticsMode = nextMode; diff --git a/packages/app-tests/closeBracketsExtension.test.ts b/packages/app-tests/closeBracketsExtension.test.ts new file mode 100644 index 0000000000..a772fdba31 --- /dev/null +++ b/packages/app-tests/closeBracketsExtension.test.ts @@ -0,0 +1,86 @@ +import { strict as assert } from "node:assert"; +import { test } from "vitest"; +import { closeBrackets, closeBracketsKeymap } from "@codemirror/autocomplete"; +import { EditorState, Compartment } from "@codemirror/state"; +import { keymap } from "@codemirror/view"; + +/** + * Replicates the closeBracketsExtension logic from QueryEditor.vue so we can + * verify that the Compartment-based toggle correctly includes/excludes both + * the closeBrackets() extension and the closeBracketsKeymap. + */ +function closeBracketsExtension(enabled: boolean) { + if (!enabled) return []; + const exts: any[] = [closeBrackets()]; + if (closeBracketsKeymap.length) { + exts.push(keymap.of([...closeBracketsKeymap])); + } + return exts; +} + +test("closeBracketsExtension returns empty array when disabled", () => { + const exts = closeBracketsExtension(false); + + assert.deepEqual(exts, []); +}); + +test("closeBracketsExtension includes closeBrackets and its keymap when enabled", () => { + const exts = closeBracketsExtension(true); + + assert.equal(exts.length, 2, "should contain closeBrackets() and keymap"); +}); + +test("closeBracketsKeymap is non-empty so the keymap entry is always included when enabled", () => { + assert.ok(closeBracketsKeymap.length > 0, "closeBracketsKeymap should have bindings"); +}); + +test("Compartment can create EditorState with closeBrackets enabled", () => { + const comp = new Compartment(); + const state = EditorState.create({ + doc: "select 1", + extensions: [comp.of(closeBracketsExtension(true))], + }); + + assert.ok(state.toJSON().doc, "state should be created with closeBrackets enabled"); +}); + +test("Compartment reconfigure from enabled to disabled succeeds", () => { + const comp = new Compartment(); + const stateOn = EditorState.create({ + doc: "select 1", + extensions: [comp.of(closeBracketsExtension(true))], + }); + + // Toggle off — this verifies that closeBracketsExtension(false) returns [] + // which means both the extension and the keymap are removed + const stateOff = stateOn.update({ effects: comp.reconfigure(closeBracketsExtension(false)) }).state; + + assert.equal(stateOff.toJSON().doc, "select 1", "state should still be valid after reconfigure to disabled"); +}); + +test("Compartment reconfigure from disabled to enabled succeeds", () => { + const comp = new Compartment(); + const stateOff = EditorState.create({ + doc: "select 1", + extensions: [comp.of(closeBracketsExtension(false))], + }); + + // Toggle on — this verifies that closeBracketsExtension(true) returns both + // the closeBrackets() extension and the keymap + const stateOn = stateOff.update({ effects: comp.reconfigure(closeBracketsExtension(true)) }).state; + + assert.equal(stateOn.toJSON().doc, "select 1", "state should still be valid after reconfigure to enabled"); +}); + +test("closeBracketsExtension(false) produces zero extensions", () => { + // The key assertion: when disabled, both closeBrackets() and + // closeBracketsKeymap are absent — no partial keymap leakage + const exts = closeBracketsExtension(false); + assert.equal(exts.length, 0); +}); + +test("closeBracketsExtension(true) produces exactly two extension groups", () => { + // When enabled, both closeBrackets() and the keymap are present + const exts = closeBracketsExtension(true); + assert.equal(exts.length, 2); +}); diff --git a/packages/app-tests/settingsStore.test.ts b/packages/app-tests/settingsStore.test.ts index cc3febed02..f089e62e46 100644 --- a/packages/app-tests/settingsStore.test.ts +++ b/packages/app-tests/settingsStore.test.ts @@ -165,6 +165,13 @@ test("defaults Vim mode to off and preserves saved booleans", () => { assert.equal(normalizeEditorSettings({ vimModeEnabled: "yes" as any }).vimModeEnabled, false); }); +test("defaults auto-close brackets to on and preserves saved booleans", () => { + assert.equal(DEFAULT_EDITOR_SETTINGS.autoCloseBrackets, true); + assert.equal(normalizeEditorSettings({}).autoCloseBrackets, true); + assert.equal(normalizeEditorSettings({ autoCloseBrackets: false }).autoCloseBrackets, false); + assert.equal(normalizeEditorSettings({ autoCloseBrackets: "nope" as any }).autoCloseBrackets, true); +}); + test("defaults update notifications to enabled", () => { assert.equal(DEFAULT_EDITOR_SETTINGS.updateNotificationsEnabled, true); assert.equal(normalizeEditorSettings({}).updateNotificationsEnabled, true);