Keybindings – inksane
inksane
ExtensionsEditor features

Keybindings

Register editing and indentation keymaps, use feature shortcuts, and add your own bindings.

Two extensions provide general editing shortcuts. DefaultKeymapExtension adds standard editing and search bindings. KeybindExtension adds Tab indentation using the editor’s tabSize option.

Setup

Import both extensions from the keybinds subpath and register them with the editor:

import { Editor } from "@inksane/core";
import { DefaultKeymapExtension, KeybindExtension } from "@inksane/core/extensions/keybinds";

const editor = new Editor({
  element: document.getElementById("editor")!,
  tabSize: 4,
  extensions: [KeybindExtension, DefaultKeymapExtension],
});

Create the mount element before this code runs. Register only KeybindExtension if you want Tab indentation without the standard editing keymap. BaseKit includes both extensions.

The default keymap

The default keymap is composed from CodeMirror’s defaultKeymap, searchKeymap, and indentWithTab. Register DefaultKeymapExtension to enable it.

Standard editing keys include:

  • Word and line deletion, such as Mod-Backspace and Shift-Mod-k.
  • Line movement and selection, such as Alt-ArrowUp, Mod-], and Mod-/.
  • Home, End, page, and arrow navigation, with the usual Shift variants for selection.
  • Select all on Mod-a.

Enter inserts a plain newline and Backspace deletes a character. Core leaves out the Markdown language keymap so it does not override registered editing shortcuts with list or quote continuation.

Search keys include:

  • Mod-f opens the Find panel.
  • Mod-g and F3 find the next match, with Shift for the previous one.
  • Mod-d selects the next occurrence.
  • Mod-Alt-g jumps to a line.

Indentation

Tab indents the selected lines, or the current line when nothing is selected. Shift+Tab dedents. The KeybindExtension owns these bindings and honors the tabSize option, which defaults to 2.

The commands return false when there is nothing to indent, for example when a dedent would go past the start of a line, and the key is left to the browser. indentWithTab from the default keymap is a fallback when DefaultKeymapExtension is also registered. It uses CodeMirror’s language indentation rather than the editor’s tabSize.

Precedence

Within BaseKit, feature bindings precede the default keymap. When registering individual extensions, put feature keymaps before DefaultKeymapExtension or give them a higher priority.

Undo and redo (Mod-z, Mod-y, Mod-Shift-z) come from the history extension, which owns its own keymap.

Bindings inksane adds

Each binding is active only when its extension is registered. Mod is Cmd on macOS and Ctrl on Windows and Linux.

KeysActionFrom
Mod-bBoldBold
Mod-iItalicItalic
Mod-Shift-xStrikethroughStrikethrough
Mod-kLink, when onLinkRequest is setLink
Mod-.Inline codeCode
Mod-Shift-.Code blockCode
Mod-Shift->Code blockCode
Mod-Shift--Horizontal ruleHorizontal rule
Mod-zUndoHistory
Mod-yRedoHistory
Mod-Shift-zRedoHistory
TabIndent the selection or the linekeybinds
Shift-TabDedent the selection or the linekeybinds
ArrowUpMove into an adjacent HTML blockHTML
ArrowDownMove into an adjacent HTML blockHTML

The arrow keys only take over when the cursor would otherwise skip past an HTML block. Everything else, such as Mod-a, Mod-f, or Mod-d, comes from the default keymap described above.

Opt out

The editor no longer has a defaultKeymap option. Disable that feature in BaseKit:

import { Editor } from "@inksane/core";
import { BaseKit } from "@inksane/core/extensions/base-kit";

const editor = new Editor({
  element: document.getElementById("editor")!,
  extensions: [BaseKit({ defaultKeymap: false })],
});

The kit’s Tab indentation stays enabled. Pass BaseKit({ defaultKeymap: false, keybinds: false }) to disable both general keymap extensions. Individually registered editors can simply omit them.

Replace or extend

Disable the kit’s keymap before registering a replacement. An extension with the same name does not replace it automatically:

import { Editor } from "@inksane/core";
import { BaseKit } from "@inksane/core/extensions/base-kit";
import { defaultKeymap, selectAll } from "@codemirror/commands";

const editor = new Editor({
  element: document.getElementById("editor")!,
  extensions: [
    BaseKit({ defaultKeymap: false }),
    {
      name: "defaultKeymap",
      addKeybinds: () => [...defaultKeymap, { key: "Mod-Shift-a", run: selectAll }],
    },
  ],
});

The replacement defines the complete keymap. Add searchKeymap from @codemirror/search if you want to retain the standard search shortcuts.

Add your own

Return CodeMirror KeyBinding objects from addKeybinds. The callback returns whether it handled the key. This example inserts a timestamp using a core document command:

import { Editor, type Extension } from "@inksane/core";
import { BaseKit } from "@inksane/core/extensions/base-kit";

const TimestampShortcut: Extension = {
  name: "timestamp-shortcut",
  priority: 10,
  addKeybinds({ editor }) {
    return [
      {
        key: "Mod-Shift-t",
        run() {
          const { from, to } = editor.state.selection.main;
          return editor.commands.insertContent({
            content: new Date().toISOString(),
            from,
            to,
          });
        },
      },
    ];
  },
};

const editor = new Editor({
  element: document.getElementById("editor")!,
  extensions: [BaseKit(), TimestampShortcut],
});

See write your own extension for the full pattern.