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". | ||