Commit a31780675a
Verified · cmc
Layout: unified · split
docs/plans/2026-10-05-keymaps.md added +65
| @@ -0,0 +1,65 @@ | ||
| 1 | # Keymaps and Command Palette Implementation Plan | |
| 2 | ||
| 3 | > **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. | |
| 4 | ||
| 5 | **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. | |
| 6 | ||
| 7 | **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. | |
| 8 | ||
| 9 | **Tech Stack:** Swift 6.2 tools, AppKit, SwiftUI, Swift Testing. | |
| 10 | ||
| 11 | **Spec:** `docs/design.md`, "Commands and keymaps" (keymap format, presets). | |
| 12 | ||
| 13 | ## Global Constraints | |
| 14 | ||
| 15 | - `⌘` shortcuts from the menus (save, undo, find, quick open) work in every preset. | |
| 16 | - While the text view has marked text (IME composition), keys go to the input method untouched. | |
| 17 | - `mode` is parsed and kept; bindings with a `mode` are inactive until the modal engine (Doom preset). | |
| 18 | - The user's keymap lives at `<data dir>/keymap.toml`; problems in it are reported, and the entries that parse still apply. | |
| 19 | ||
| 20 | ## Defaults chosen (user may change) | |
| 21 | ||
| 22 | - Option as Meta: left Option is Meta, right Option types characters. | |
| 23 | - Mac preset org keys: ⌃⌘T TODO, ⌃⌘↑/↓ priority, ⌃⌘←/→ promote/demote. | |
| 24 | - An active region is replaced by typing (Cocoa), as `delete-selection-mode`. | |
| 25 | - which-key appears 0.6 s after a prefix. | |
| 26 | ||
| 27 | ## File structure | |
| 28 | ||
| 29 | | File | Responsibility | | |
| 30 | | --- | --- | | |
| 31 | | `Sources/OrgCore/Keymap/Keys.swift` | `KeyChord`, `KeySequence` | | |
| 32 | | `Sources/OrgCore/Keymap/TOML.swift` | TOML subset parser | | |
| 33 | | `Sources/OrgCore/Keymap/Keymap.swift` | `KeyBinding`, `Keymap`, `KeyContext`, `KeyDispatcher` | | |
| 34 | | `Sources/OrgCore/Keymap/Presets.swift` | `KeymapPreset` (Emacs, Mac) | | |
| 35 | | `Sources/OrgEditorAppKit/Keys.swift` | `OrgTextView`, event-to-chord, `OptionAsMeta` | | |
| 36 | | `Sources/OrgEditorAppKit/EditorCommands.swift` | Command registry: ids, titles, how each runs | | |
| 37 | | `Sources/OrgEditorAppKit/OrgEditor.swift` | Dispatch, mark, callbacks for pending keys and app commands | | |
| 38 | | `Sources/Orgstar/*.swift` | Keymap loading, echo area, which-key, palette, Org menu, Settings | | |
| 39 | ||
| 40 | --- | |
| 41 | ||
| 42 | ### Task 1: Keymap core | |
| 43 | ||
| 44 | `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`. | |
| 45 | ||
| 46 | - [ ] 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. | |
| 47 | - [ ] Commit "Add keymaps". | |
| 48 | ||
| 49 | ### Task 2: Keys in the editor | |
| 50 | ||
| 51 | - `OrgTextView.keyDown` and `performKeyEquivalent` feed the dispatcher (except during IME composition); `.unbound` goes to the text system. | |
| 52 | - 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. | |
| 53 | - `edit.set-mark` starts a region that movement extends; C-g, editing, kill and copy end it. | |
| 54 | - Option as Meta per side, from the device-dependent modifier bits. | |
| 55 | - `onKeysPending(String?, [(key, title)])`, `onAppCommand(String)`, and `run(_ id:)` for menus and the palette. | |
| 56 | - [ ] 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. | |
| 57 | - [ ] Commit "Run commands from keys". | |
| 58 | ||
| 59 | ### Task 3: App | |
| 60 | ||
| 61 | - Settings: preset, Option as Meta; `keymap.toml` loaded at launch and with "Reload Keymap". | |
| 62 | - Echo area at the bottom of the editor: pending keys and command messages. which-key popup lists the continuations with command titles. | |
| 63 | - Command palette (M-x, ⇧⌘P): fuzzy search over command titles, showing each command's keys; runs on the editor. | |
| 64 | - Org menu with the org commands. | |
| 65 | - [ ] Verify with the snapshot hook; commit "Keymaps, echo area and palette in the app". | |