diff --git a/client/client.ts b/client/client.ts index 081b95e8..e51c410d 100644 --- a/client/client.ts +++ b/client/client.ts @@ -121,7 +121,7 @@ export class Client { // CodeMirror editor editorView!: EditorView; - keyHandlerCompartment?: Compartment; + commandKeyHandlerCompartment?: Compartment; indentUnitCompartment?: Compartment; undoHistoryCompartment?: Compartment; diff --git a/client/client_system.ts b/client/client_system.ts index cff4f2c7..69e09fdf 100644 --- a/client/client_system.ts +++ b/client/client_system.ts @@ -33,7 +33,7 @@ import { languageSyscalls } from "./plugos/syscalls/language.ts"; import { codeWidgetSyscalls } from "./plugos/syscalls/code_widget.ts"; import { clientCodeWidgetSyscalls } from "./plugos/syscalls/client_code_widget.ts"; import { KVPrimitivesManifestCache } from "./plugos/manifest_cache.ts"; -import { createKeyBindings } from "./codemirror/editor_state.ts"; +import { createCommandKeyBindings } from "./codemirror/editor_state.ts"; import type { DataStoreMQ } from "./data/mq.datastore.ts"; import { jsonschemaSyscalls } from "./plugos/syscalls/jsonschema.ts"; import { luaSyscalls } from "./plugos/syscalls/lua.ts"; @@ -125,8 +125,8 @@ export class ClientSystem { }); // Replace the key mapping compartment (keybindings) this.client.editorView.dispatch({ - effects: this.client.keyHandlerCompartment?.reconfigure( - createKeyBindings(this.client), + effects: this.client.commandKeyHandlerCompartment?.reconfigure( + createCommandKeyBindings(this.client), ), }); }, diff --git a/client/codemirror/editor_state.ts b/client/codemirror/editor_state.ts index f366fe28..5a42802c 100644 --- a/client/codemirror/editor_state.ts +++ b/client/codemirror/editor_state.ts @@ -60,11 +60,15 @@ export function createEditorState( ): EditorState { let touchCount = 0; - // Ugly: keep the keyhandler compartment in the client, to be replaced later once more commands are loaded - client.keyHandlerCompartment = new Compartment(); - const keyBindings = client.keyHandlerCompartment.of( - createKeyBindings(client), + // Ugly: keep the commandKeyHandler compartment in the client, to be replaced + // later once more commands are loaded + client.commandKeyHandlerCompartment = new Compartment(); + const commandKeyBindings = client.commandKeyHandlerCompartment.of( + createCommandKeyBindings(client), ); + // Regular key bindings are not dynamically updated and do not require a + // compartment. + const regularKeyBindings = createRegularKeyBindings(client); client.indentUnitCompartment = new Compartment(); const indentUnits = client.indentUnitCompartment.of( @@ -82,6 +86,12 @@ export function createEditorState( dark: client.ui.viewState.uiOptions.darkMode, }), + // Insert our command key bindings *before* vim mode. Vim in normal-mode is + // greedy and captures all key events, preventing them from reaching our + // own handlers to trigger commands. This will mean some vim-mode + // bindings wont trigger if they have the same keys. + commandKeyBindings, + // Enable vim mode, or not [ ...client.ui.viewState.uiOptions.vimMode @@ -183,7 +193,7 @@ export function createEditorState( }, ]), disableSpellcheck(["InlineCode", "CodeText", "CodeInfo", "FrontMatter"]), - keyBindings, + regularKeyBindings, EditorView.domEventHandlers({ // This may result in duplicated touch events on mobile devices touchmove: () => { @@ -331,7 +341,7 @@ export function isValidEditor( (currentEditor !== undefined && requiredEditor === "notpage"); } -export function createCommandKeyBindings(client: Client): KeyBinding[] { +export function createCommandKeyBindings(client: Client): Extension { const commandKeyBindings: KeyBinding[] = []; // Then add bindings for plug commands @@ -386,17 +396,16 @@ export function createCommandKeyBindings(client: Client): KeyBinding[] { } } - return commandKeyBindings; + return keymap.of([ + ...commandKeyBindings, + ]); } -export function createKeyBindings(client: Client): Extension { +export function createRegularKeyBindings(client: Client): Extension { if (client.isDocumentEditor()) { - return keymap.of([ - ...createCommandKeyBindings(client), - ]); + return keymap.of([]); } else { return keymap.of([ - ...createCommandKeyBindings(client), ...createSmartQuoteKeyBindings(client), ...closeBracketsKeymap, ...client.ui.viewState.uiOptions.vimMode diff --git a/client/components/mini_editor.tsx b/client/components/mini_editor.tsx index 7bda8bfa..3b554a4d 100644 --- a/client/components/mini_editor.tsx +++ b/client/components/mini_editor.tsx @@ -148,6 +148,9 @@ export function MiniEditor( doc: text, extensions: [ EditorView.theme({}, { dark: darkMode }), + // Insert command bindings before vim-mode to ensure they're available + // in normal mode. See editor_state.ts for more details. + createCommandKeyBindings(globalThis.client), // Enable vim mode, or not [...vimMode ? [vim()] : []], [ @@ -182,7 +185,6 @@ export function MiniEditor( }, ...standardKeymap, ...historyKeymap, - ...createCommandKeyBindings(globalThis.client), ]), EditorView.domEventHandlers({ click: (e) => {