krz/orgstar

A native macOS editor for org-mode files. editor org-mode swift

docs/plans/2026-10-05-keymaps.md

ab6ccec56eca03d0dadf2c9332aab10c4490eb62
orgstar/docs/plans/2026-10-05-keymaps.md rendered · source · history · blame · raw

65 lines · 5091 bytes

Keymaps and Command Palette Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Keys run commands: TOML keymaps in layers (preset, then keymap.toml), key sequences of any length with an echo area and a which-key popup, context dispatch through when, Emacs and Mac presets, Option as Meta per side, and a command palette (M-x, ⇧⌘P) over every command.

Architecture: Platform-free in OrgCore/Keymap: Emacs key notation (KeyChord, KeySequence), the TOML subset keymaps use, Keymap (layered bindings, unbinding with none, prefixes, continuations), KeyContext (heading, table, item, region) and KeyDispatcher (the pending-prefix state machine). In OrgEditorAppKit, OrgTextView turns key events into chords before the text system sees them and the editor runs the winning binding: org.* text commands through perform, view commands (org.cycle, org.cycle-global), edit.* commands as text-system selectors with an Emacs-style active mark, and app.* commands handed to the app. The app loads the keymap, shows the echo area and which-key, and hosts the palette and an Org menu.

Tech Stack: Swift 6.2 tools, AppKit, SwiftUI, Swift Testing.

Spec: docs/design.md, "Commands and keymaps" (keymap format, presets).

Global Constraints

  • ⌘ shortcuts from the menus (save, undo, find, quick open) work in every preset.
  • While the text view has marked text (IME composition), keys go to the input method untouched.
  • mode is parsed and kept; bindings with a mode are inactive until the modal engine (Doom preset).
  • The user's keymap lives at <data dir>/keymap.toml; problems in it are reported, and the entries that parse still apply.

Defaults chosen (user may change)

  • Option as Meta: left Option is Meta, right Option types characters.
  • Mac preset org keys: ⌃⌘T TODO, ⌃⌘↑/↓ priority, ⌃⌘←/→ promote/demote.
  • An active region is replaced by typing (Cocoa), as delete-selection-mode.
  • which-key appears 0.6 s after a prefix.

File structure

File Responsibility
Sources/OrgCore/Keymap/Keys.swift KeyChord, KeySequence
Sources/OrgCore/Keymap/TOML.swift TOML subset parser
Sources/OrgCore/Keymap/Keymap.swift KeyBinding, Keymap, KeyContext, KeyDispatcher
Sources/OrgCore/Keymap/Presets.swift KeymapPreset (Emacs, Mac)
Sources/OrgEditorAppKit/Keys.swift OrgTextView, event-to-chord, OptionAsMeta
Sources/OrgEditorAppKit/EditorCommands.swift Command registry: ids, titles, how each runs
Sources/OrgEditorAppKit/OrgEditor.swift Dispatch, mark, callbacks for pending keys and app commands
Sources/Orgstar/*.swift Keymap loading, echo area, which-key, palette, Org menu, Settings

Task 1: Keymap core

KeyChord.parse, KeySequence.parse/format, TOML.parse, Keymap(toml:problems:), Keymap.layered, candidates(for:mode:), isPrefix, continuations(of:), keys(for:), KeyContext.holds, KeyDispatcher.feed returning .pending, .complete, .undefined, .unbound or .cancelled.

  • Tests: notation round trips and shift folding; TOML tables, escapes and error lines; bad entries skipped with problems; later layers win and none unbinds; prefix dispatch, undefined sequences and C-g; continuations; contexts; presets parse.
  • Commit "Add keymaps".

Task 2: Keys in the editor

  • OrgTextView.keyDown and performKeyEquivalent feed the dispatcher (except during IME composition); .unbound goes to the text system.
  • Context dispatch: the first candidate whose when holds and whose command applies runs; for a single key with none, the text system gets the key; for a longer sequence the first candidate runs and reports why it can't.
  • edit.set-mark starts a region that movement extends; C-g, editing, kill and copy end it.
  • Option as Meta per side, from the device-dependent modifier bits.
  • onKeysPending(String?, [(key, title)]), onAppCommand(String), and run(_ id:) for menus and the palette.
  • Tests with synthesized key events: C-c C-t cycles TODO; TAB folds on a heading and inserts a tab in a paragraph; C-SPC C-f C-w kills one character; M-f moves a word with left Option and types with right Option; C-x C-s reaches onAppCommand; an undefined sequence reports a message; pending keys are reported.
  • Commit "Run commands from keys".

Task 3: App

  • Settings: preset, Option as Meta; keymap.toml loaded at launch and with "Reload Keymap".
  • Echo area at the bottom of the editor: pending keys and command messages. which-key popup lists the continuations with command titles.
  • Command palette (M-x, ⇧⌘P): fuzzy search over command titles, showing each command's keys; runs on the editor.
  • Org menu with the org commands.
  • Verify with the snapshot hook; commit "Keymaps, echo area and palette in the app".