diff --git a/apps/desktop/src/components/editor/EditorSettingsDialog.vue b/apps/desktop/src/components/editor/EditorSettingsDialog.vue index edc85b31ae..eb769d00d3 100644 --- a/apps/desktop/src/components/editor/EditorSettingsDialog.vue +++ b/apps/desktop/src/components/editor/EditorSettingsDialog.vue @@ -213,6 +213,7 @@ const editExecuteMode = ref(settingsStore.editorSettings.executeMode); const editShowExecutionTargetPicker = ref(settingsStore.editorSettings.showExecutionTargetPicker); const editAutoAliasTables = ref(settingsStore.editorSettings.autoAliasTables); const editWordWrap = ref(settingsStore.editorSettings.wordWrap); +const editVimModeEnabled = ref(settingsStore.editorSettings.vimModeEnabled); const editSqlSemanticDiagnosticsMode = ref(settingsStore.editorSettings.sqlSemanticDiagnosticsMode); const editSqlSemanticDiagnosticsEnabled = ref(settingsStore.editorSettings.sqlSemanticDiagnosticsEnabled); const editConfirmDangerousSqlExecution = ref(settingsStore.editorSettings.confirmDangerousSqlExecution); @@ -512,6 +513,7 @@ watch( editShowExecutionTargetPicker.value = settingsStore.editorSettings.showExecutionTargetPicker; editAutoAliasTables.value = settingsStore.editorSettings.autoAliasTables; editWordWrap.value = settingsStore.editorSettings.wordWrap; + editVimModeEnabled.value = settingsStore.editorSettings.vimModeEnabled; editSqlSemanticDiagnosticsMode.value = settingsStore.editorSettings.sqlSemanticDiagnosticsMode; editSqlSemanticDiagnosticsEnabled.value = settingsStore.editorSettings.sqlSemanticDiagnosticsEnabled; editConfirmDangerousSqlExecution.value = settingsStore.editorSettings.confirmDangerousSqlExecution; @@ -581,6 +583,7 @@ function hasChanges(): boolean { editShowExecutionTargetPicker.value !== settingsStore.editorSettings.showExecutionTargetPicker || editAutoAliasTables.value !== settingsStore.editorSettings.autoAliasTables || editWordWrap.value !== settingsStore.editorSettings.wordWrap || + editVimModeEnabled.value !== settingsStore.editorSettings.vimModeEnabled || editSqlSemanticDiagnosticsMode.value !== settingsStore.editorSettings.sqlSemanticDiagnosticsMode || editSqlSemanticDiagnosticsEnabled.value !== settingsStore.editorSettings.sqlSemanticDiagnosticsEnabled || editConfirmDangerousSqlExecution.value !== settingsStore.editorSettings.confirmDangerousSqlExecution || @@ -636,6 +639,7 @@ async function persistSettings() { showExecutionTargetPicker: editShowExecutionTargetPicker.value, autoAliasTables: editAutoAliasTables.value, wordWrap: editWordWrap.value, + vimModeEnabled: editVimModeEnabled.value, sqlSemanticDiagnosticsMode: editSqlSemanticDiagnosticsMode.value, confirmDangerousSqlExecution: editConfirmDangerousSqlExecution.value, confirmUnsavedSqlClose: editConfirmUnsavedSqlClose.value, @@ -700,6 +704,7 @@ function resetDefaultsForTab(tab: SettingsCategory) { editShowExecutionTargetPicker.value = DEFAULT_EDITOR_SETTINGS.showExecutionTargetPicker; editAutoAliasTables.value = DEFAULT_EDITOR_SETTINGS.autoAliasTables; editWordWrap.value = DEFAULT_EDITOR_SETTINGS.wordWrap; + editVimModeEnabled.value = DEFAULT_EDITOR_SETTINGS.vimModeEnabled; editSqlSemanticDiagnosticsMode.value = DEFAULT_EDITOR_SETTINGS.sqlSemanticDiagnosticsMode; editSqlSemanticDiagnosticsEnabled.value = DEFAULT_EDITOR_SETTINGS.sqlSemanticDiagnosticsEnabled; editConfirmDangerousSqlExecution.value = DEFAULT_EDITOR_SETTINGS.confirmDangerousSqlExecution; @@ -765,6 +770,7 @@ function resetAllDefaults() { editShowExecutionTargetPicker.value = DEFAULT_EDITOR_SETTINGS.showExecutionTargetPicker; editAutoAliasTables.value = DEFAULT_EDITOR_SETTINGS.autoAliasTables; editWordWrap.value = DEFAULT_EDITOR_SETTINGS.wordWrap; + editVimModeEnabled.value = DEFAULT_EDITOR_SETTINGS.vimModeEnabled; editSqlSemanticDiagnosticsMode.value = DEFAULT_EDITOR_SETTINGS.sqlSemanticDiagnosticsMode; editSqlSemanticDiagnosticsEnabled.value = DEFAULT_EDITOR_SETTINGS.sqlSemanticDiagnosticsEnabled; editConfirmDangerousSqlExecution.value = DEFAULT_EDITOR_SETTINGS.confirmDangerousSqlExecution; @@ -2164,6 +2170,14 @@ onUnmounted(cleanupPreviewEditor); +
+
+ +

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

+
+ +
+
diff --git a/apps/desktop/src/components/editor/QueryEditor.vue b/apps/desktop/src/components/editor/QueryEditor.vue index 93ee614c4f..76489372af 100644 --- a/apps/desktop/src/components/editor/QueryEditor.vue +++ b/apps/desktop/src/components/editor/QueryEditor.vue @@ -192,10 +192,16 @@ let codeMirrorEditorSelection: typeof import("@codemirror/state").EditorSelectio 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 readOnlyComp: import("@codemirror/state").Compartment | null = null; let runKeymapComp: import("@codemirror/state").Compartment | null = null; let completionComp: import("@codemirror/state").Compartment | null = null; let diagnosticComp: import("@codemirror/state").Compartment | null = null; +let codeMirrorVim: typeof import("@replit/codemirror-vim").vim | null = null; +let codeMirrorVimApi: typeof import("@replit/codemirror-vim").Vim | null = null; +let codeMirrorGetVimCm: typeof import("@replit/codemirror-vim").getCM | null = null; +let codeMirrorVimImportPromise: Promise | null = null; +let dbxVimCommandsConfigured = false; let buildSqlDiagnosticExtension: (() => import("@codemirror/state").Extension) | null = null; let buildSqlSignatureExtension: (() => import("@codemirror/state").Extension) | null = null; let buildSqlCompletionExtension: (() => import("@codemirror/state").Extension) | null = null; @@ -236,6 +242,7 @@ let executableStatementRangeCache: ExecutableStatementRangeCache | null = null; let editorScrollbarPointerCleanup: (() => void) | null = null; const EDITOR_SCROLLBAR_POINTER_GUTTER_PX = 18; const tableNavigationHoverClass = "query-editor--table-navigation-hover"; +const DBX_VIM_SAVE_EVENT = "dbx-vim-save"; function editorThemeAppearance() { return isDark.value ? "dark" : "light"; @@ -264,6 +271,7 @@ const queryEditorAppearanceSettings = computed(() => { customThemes: settings.customThemes, activeCustomThemeId: settings.activeCustomThemeId, wordWrap: settings.wordWrap, + vimModeEnabled: settings.vimModeEnabled, shortcuts: settings.shortcuts, }; }); @@ -730,6 +738,53 @@ function wordWrapExtension() { return props.forceWordWrap || settingsStore.editorSettings.wordWrap ? editorViewModule.EditorView.lineWrapping : []; } +function vimModeExtension(enabled = settingsStore.editorSettings.vimModeEnabled) { + if (!codeMirrorVim || !enabled) return []; + const vimExtension = codeMirrorVim({ status: true }); + if (!codeMirrorPrec || !editorViewModule || !codeMirrorGetVimCm || !codeMirrorVimApi) return vimExtension; + + // Beekeeper treats Vim as a first-class editor keymap. Keep it above DBX's + // normal shortcuts so regular normal-mode keys are not stolen by other maps. + return codeMirrorPrec.highest([ + editorViewModule.keymap.of([ + { + key: "Ctrl-[", + mac: "Ctrl-[", + linux: "Ctrl-[", + win: "Ctrl-[", + run(currentView) { + const cm = codeMirrorGetVimCm?.(currentView); + if (cm?.state.vim?.insertMode) { + codeMirrorVimApi?.exitInsertMode(cm as any, true); + return true; + } + return false; + }, + }, + ]), + vimExtension, + ]); +} + +function configureDbxVimCommands(vimApi: typeof import("@replit/codemirror-vim").Vim) { + if (dbxVimCommandsConfigured) return; + dbxVimCommandsConfigured = true; + vimApi.defineEx("write", "w", (cm) => { + cm.cm6?.contentDOM.dispatchEvent(new CustomEvent(DBX_VIM_SAVE_EVENT, { bubbles: true })); + }); +} + +async function ensureCodeMirrorVim() { + if (codeMirrorVim && codeMirrorVimApi && codeMirrorGetVimCm) return true; + codeMirrorVimImportPromise ??= import("@replit/codemirror-vim"); + const { vim, Vim, getCM } = await codeMirrorVimImportPromise; + codeMirrorVim = vim; + codeMirrorVimApi = Vim; + codeMirrorGetVimCm = getCM; + configureDbxVimCommands(Vim); + return true; +} + function indentExtension() { if (!codeMirrorIndentUnit) return []; return codeMirrorIndentUnit.of(editorIndentUnit()); @@ -2089,6 +2144,7 @@ onMounted(async () => { fontThemeComp = new Compartment(); codeMirrorTheme = new Compartment(); wordWrapComp = new Compartment(); + vimModeComp = new Compartment(); readOnlyComp = new Compartment(); runKeymapComp = new Compartment(); completionComp = new Compartment(); @@ -2201,6 +2257,9 @@ onMounted(async () => { const initialSettings = settingsStore.editorSettings; const theme = await loadEditorTheme(initialSettings.theme, editorThemeAppearance(), getCurrentCustomThemeColors()); + if (initialSettings.vimModeEnabled) { + await ensureCodeMirrorVim(); + } class RunStatementGutterMarker extends GutterMarker { toDOM() { @@ -2291,6 +2350,8 @@ onMounted(async () => { syntaxHighlighting(defaultHighlightStyle, { fallback: true }), crosshairCursor(), activeLineHighlighter, + // Vim must be mounted before DBX/default keymaps so normal-mode keys are handled first. + vimModeComp.of(vimModeExtension(initialSettings.vimModeEnabled)), keymap.of([...defaultKeymap, ...searchKeymap, ...historyKeymap, ...foldKeymap, ...completionKeymap]), langSql.sql({ dialect }), tooltips({ parent: document.body }), @@ -2377,6 +2438,10 @@ onMounted(async () => { window.setTimeout(flushImeComposition, 0); return false; }, + [DBX_VIM_SAVE_EVENT]() { + emit("save"); + return true; + }, wheel(event) { if (!event.metaKey && !event.ctrlKey) return false; event.preventDefault(); @@ -2622,7 +2687,7 @@ function getCurrentCustomThemeColors() { watch( [queryEditorAppearanceSettings, () => isDark.value], async ([ss]) => { - if (!view.value || !codeMirrorTheme || !fontThemeComp || !wordWrapComp || !runKeymapComp || !editorViewModule) { + if (!view.value || !codeMirrorTheme || !fontThemeComp || !wordWrapComp || !vimModeComp || !runKeymapComp || !editorViewModule) { return; } if (!isGestureZooming.value && !zoomCommitScheduler.hasPendingCommit() && liveFontSize.value !== ss.fontSize) { @@ -2630,9 +2695,17 @@ watch( } syncEditorFontCssVars(liveFontSize.value, ss.fontFamily); const themeColors = getCurrentCustomThemeColors(); - const themeExt = await loadEditorTheme(ss.theme, editorThemeAppearance(), themeColors); + const [themeExt] = await Promise.all([loadEditorTheme(ss.theme, editorThemeAppearance(), themeColors), ss.vimModeEnabled ? ensureCodeMirrorVim() : Promise.resolve(false)]); + if (!view.value || !codeMirrorTheme || !wordWrapComp || !vimModeComp || !runKeymapComp || !editorViewModule) { + return; + } view.value.dispatch({ - effects: [codeMirrorTheme.reconfigure(themeExt), wordWrapComp.reconfigure(props.forceWordWrap || ss.wordWrap ? editorViewModule.EditorView.lineWrapping : []), runKeymapComp.reconfigure(runKeymapExtension(editorViewModule.keymap))], + effects: [ + codeMirrorTheme.reconfigure(themeExt), + wordWrapComp.reconfigure(props.forceWordWrap || ss.wordWrap ? editorViewModule.EditorView.lineWrapping : []), + vimModeComp.reconfigure(vimModeExtension(settingsStore.editorSettings.vimModeEnabled)), + runKeymapComp.reconfigure(runKeymapExtension(editorViewModule.keymap)), + ], }); }, { deep: true }, diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index 790fa3dc25..8865271793 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -2767,6 +2767,8 @@ export default { showExecutionTargetPickerDescription: "When enabled, running without a selection lets you choose between the current statement and all SQL.", wordWrap: "Word wrap", wordWrapDescription: "Wrap long SQL lines within the editor width", + vimMode: "Vim mode", + vimModeDescription: "Use Vim-style modal editing in the SQL editor", 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 88cac57266..d6f51a1d84 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -2770,6 +2770,8 @@ export default withEnglishFallback({ showExecutionTargetPickerDescription: "Al activarlo, ejecutar sin selección permite elegir entre la sentencia actual y todo el SQL.", wordWrap: "Ajuste de línea", 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", 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 25b0c83357..d3bfa16c78 100644 --- a/apps/desktop/src/i18n/locales/it.ts +++ b/apps/desktop/src/i18n/locales/it.ts @@ -2768,6 +2768,8 @@ export default withEnglishFallback({ showExecutionTargetPickerDescription: "Se attivo, l'esecuzione senza selezione permette di scegliere tra istruzione corrente e tutto l'SQL.", wordWrap: "A capo automatico", 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", 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 b90eac4434..fed1772f15 100644 --- a/apps/desktop/src/i18n/locales/ja.ts +++ b/apps/desktop/src/i18n/locales/ja.ts @@ -2759,6 +2759,8 @@ export default withEnglishFallback({ showExecutionTargetPickerDescription: "有効にすると、選択なしで実行するときに現在の文とすべてのSQLを一時的に選べます。", wordWrap: "折り返し", wordWrapDescription: "長いSQL行をエディタ幅内で折り返します", + vimMode: "Vimモード", + vimModeDescription: "SQLエディタでVim形式のモーダル編集を使用します", 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 2e38cb59bc..ef182b6bee 100644 --- a/apps/desktop/src/i18n/locales/pt-BR.ts +++ b/apps/desktop/src/i18n/locales/pt-BR.ts @@ -2769,6 +2769,8 @@ export default withEnglishFallback({ showExecutionTargetPickerDescription: "Quando ativado, executar sem seleção permite escolher entre a instrução atual e todo o SQL.", wordWrap: "Quebra de linha", wordWrapDescription: "Quebrar linhas SQL longas dentro da largura do editor", + vimMode: "Modo Vim", + vimModeDescription: "Usar edição modal no estilo Vim no editor SQL", 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 2fce7ea498..62b74a3aa9 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -2769,6 +2769,8 @@ export default withEnglishFallback({ showExecutionTargetPickerDescription: "开启后,无选区执行时可在当前语句和全部 SQL 之间临时选择。", wordWrap: "自动换行", wordWrapDescription: "长 SQL 在编辑器宽度内自动折行显示", + vimMode: "Vim 模式", + vimModeDescription: "在 SQL 编辑器中使用 Vim 风格的模态编辑", 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 43f84d89da..aa4f4cafd7 100644 --- a/apps/desktop/src/i18n/locales/zh-TW.ts +++ b/apps/desktop/src/i18n/locales/zh-TW.ts @@ -2666,6 +2666,8 @@ export default withEnglishFallback({ showExecutionTargetPickerDescription: "啟用後,無選取執行時可在目前語句與全部 SQL 之間臨時選擇。", wordWrap: "自動換行", wordWrapDescription: "長 SQL 在編輯器寬度內自動折行顯示", + vimMode: "Vim 模式", + vimModeDescription: "在 SQL 編輯器中使用 Vim 風格的模態編輯", sqlSemanticDiagnosticsEnabled: "SQL 語意診斷", sqlSemanticDiagnosticsEnabledDescription: "開啟後,編輯器會提示未知資料表、欄位等語意問題;關閉可減少 SQL 解析和中繼資料檢查的效能負擔。", confirmDangerousSqlExecution: "執行危險 SQL 前彈出確認", diff --git a/apps/desktop/src/stores/settingsStore.ts b/apps/desktop/src/stores/settingsStore.ts index 34dc43494d..064e3c51ee 100644 --- a/apps/desktop/src/stores/settingsStore.ts +++ b/apps/desktop/src/stores/settingsStore.ts @@ -335,6 +335,7 @@ export interface EditorSettings { showExecutionTargetPicker: boolean; autoAliasTables: boolean; wordWrap: boolean; + vimModeEnabled: boolean; sqlSemanticDiagnosticsMode: SqlSemanticDiagnosticsMode; sqlSemanticDiagnosticsEnabled: boolean; confirmDangerousSqlExecution: boolean; @@ -447,6 +448,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = { showExecutionTargetPicker: false, autoAliasTables: true, wordWrap: false, + vimModeEnabled: false, sqlSemanticDiagnosticsMode: "auto", sqlSemanticDiagnosticsEnabled: SQL_SEMANTIC_DIAGNOSTICS_AUTO_ENABLED, confirmDangerousSqlExecution: true, @@ -661,6 +663,7 @@ export function normalizeEditorSettings(settings: Partial, exist showExecutionTargetPicker: settings.showExecutionTargetPicker ?? DEFAULT_EDITOR_SETTINGS.showExecutionTargetPicker, 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, sqlSemanticDiagnosticsMode, sqlSemanticDiagnosticsEnabled: sqlSemanticDiagnosticsEnabledForMode(sqlSemanticDiagnosticsMode), confirmDangerousSqlExecution: settings.confirmDangerousSqlExecution ?? DEFAULT_EDITOR_SETTINGS.confirmDangerousSqlExecution, @@ -878,6 +881,7 @@ export const useSettingsStore = defineStore("settings", () => { if (partial.showExecutionTargetPicker !== undefined) editorSettings.value.showExecutionTargetPicker = partial.showExecutionTargetPicker; 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.sqlSemanticDiagnosticsMode !== undefined || partial.sqlSemanticDiagnosticsEnabled !== undefined) { const nextMode = normalizeSqlSemanticDiagnosticsMode(partial.sqlSemanticDiagnosticsMode, partial.sqlSemanticDiagnosticsEnabled); editorSettings.value.sqlSemanticDiagnosticsMode = nextMode; diff --git a/package.json b/package.json index e4bb6e4473..e78d56144b 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "@lezer/highlight": "^1.2.3", "@lucide/vue": "1.17.0", "@msgpack/msgpack": "^3.1.3", + "@replit/codemirror-vim": "^6.3.0", "@tauri-apps/api": "^2.11.0", "@tauri-apps/plugin-clipboard-manager": "^2.3.2", "@tauri-apps/plugin-dialog": "^2.7.1", diff --git a/packages/app-tests/settingsStore.test.ts b/packages/app-tests/settingsStore.test.ts index d00a2fd5a2..a5fb0924c6 100644 --- a/packages/app-tests/settingsStore.test.ts +++ b/packages/app-tests/settingsStore.test.ts @@ -133,6 +133,13 @@ test("defaults unsaved SQL close confirmation to enabled", () => { assert.equal(normalizeEditorSettings({ confirmUnsavedSqlClose: false }).confirmUnsavedSqlClose, false); }); +test("defaults Vim mode to off and preserves saved booleans", () => { + assert.equal(DEFAULT_EDITOR_SETTINGS.vimModeEnabled, false); + assert.equal(normalizeEditorSettings({}).vimModeEnabled, false); + assert.equal(normalizeEditorSettings({ vimModeEnabled: true }).vimModeEnabled, true); + assert.equal(normalizeEditorSettings({ vimModeEnabled: "yes" as any }).vimModeEnabled, false); +}); + test("defaults update notifications to enabled", () => { assert.equal(DEFAULT_EDITOR_SETTINGS.updateNotificationsEnabled, true); assert.equal(normalizeEditorSettings({}).updateNotificationsEnabled, true); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d0c9d298b6..3ea3f57cc1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -59,6 +59,9 @@ importers: '@msgpack/msgpack': specifier: ^3.1.3 version: 3.1.3 + '@replit/codemirror-vim': + specifier: ^6.3.0 + version: 6.3.0(@codemirror/commands@6.10.3)(@codemirror/language@6.12.3)(@codemirror/search@6.7.0)(@codemirror/state@6.6.0)(@codemirror/view@6.43.0) '@tauri-apps/api': specifier: ^2.11.0 version: 2.11.0 @@ -1235,6 +1238,15 @@ packages: cpu: [x64] os: [win32] + '@replit/codemirror-vim@6.3.0': + resolution: {integrity: sha512-aTx931ULAMuJx6xLf7KQDOL7CxD+Sa05FktTDrtLaSy53uj01ll3Zf17JdKsriER248oS55GBzg0CfCTjEneAQ==} + peerDependencies: + '@codemirror/commands': 6.x.x + '@codemirror/language': 6.x.x + '@codemirror/search': 6.x.x + '@codemirror/state': 6.x.x + '@codemirror/view': 6.x.x + '@rolldown/binding-android-arm64@1.0.3': resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4947,6 +4959,14 @@ snapshots: '@oxlint/binding-win32-x64-msvc@1.68.0': optional: true + '@replit/codemirror-vim@6.3.0(@codemirror/commands@6.10.3)(@codemirror/language@6.12.3)(@codemirror/search@6.7.0)(@codemirror/state@6.6.0)(@codemirror/view@6.43.0)': + dependencies: + '@codemirror/commands': 6.10.3 + '@codemirror/language': 6.12.3 + '@codemirror/search': 6.7.0 + '@codemirror/state': 6.6.0 + '@codemirror/view': 6.43.0 + '@rolldown/binding-android-arm64@1.0.3': optional: true