Skip to content

feat(keys): rebind every key under [keys] - #104

Open
benjaminwood wants to merge 1 commit into
plannotator:mainfrom
benjaminwood:feat/configurable-keys
Open

benjaminwood wants to merge 1 commit into
plannotator:mainfrom
benjaminwood:feat/configurable-keys

Conversation

@benjaminwood

Copy link
Copy Markdown

Implements the keys half of #58, to the shape described there.

[keys]
left = ["j", "Left"]   # jkl; instead of hjkl
down = ["k", "Down"]
up = ["l", "Up"]
right = [";", "Right"]
line-start = ["^", "Home"]
  • [keys] in config.toml: action name to a list of keys. An entry replaces that action's keys; [] unbinds it. Every other action keeps its default.
  • Defaults are today's bindings. The existing tests pass unchanged.
  • Strict: an unknown action, an unknown key name, or a key bound to two actions that read the same keypress is an error that names it:
    • key "j" is bound to both down and left
    • unknown variant lefty, expected one of up, down, …
    • unknown key "Lft"; expected one character, ctrl+<character>, or one of Up, Down, …
  • plannotator-tui config prints every action with its keys.
  • Hints follow the bindings: the footer, the toolbar, the Review menu rows, the quit question and the visual/move statuses.
  • README: a Keys section with the action table; the Where/Keys table notes that it lists defaults.

Shape

  • src/keys.rs: Action (kebab-case names), Key (a character, a named key, or ctrl+<character>), Keymap, and one context per mode (GLOBAL, DOCUMENT, MOTION, TREE, MENU, …) listing the actions it dispatches in the order it tries them. Call sites read as before: match self.keys.action(&key, keys::TREE) { Some(Action::Down) => … }.
  • An action keeps its name everywhere it applies, so rebinding down once covers the document, tree, notes, menu and pickers.
  • Conflicts are checked within the groups of contexts that read the same keypress (global plus the focused pane, visual plus motions, each picker). A key can still mean different things in different modes: Home is top among blocks and line-start for the cursor.
  • The bindings were spread across input.rs, menu.rs, pick.rs and archive_view.rs; all four go through the keymap now. The compose box (text entry) is untouched.

For review

  • Matching is today's: a plain binding ignores modifiers (shifted letters arrive with Shift set); ctrl+… needs Ctrl alone.
  • ctrl+c is its own action, interrupt, so rebinding quit can't lose it. Each mode closes on exactly the keys it did before (e.g. q still doesn't close the Review menu).
  • x on a note (remove, also Delete) and x on a block (clear-block) are separate actions, so Delete stays notes-only as today.
  • A config that fails to parse still doesn't stop the TUI; it falls back to defaults, as [ui] theme does, and plannotator-tui config reports the error.
  • The picker and menu borders (↑↓ … enter … esc) still name arrows and fixed keys; happy to make those follow too if you'd like.

Testing: cargo test --workspace (326 pass; new keys::tests and an app test where jkl; moves and the footer reads jkl; move), cargo clippy --workspace --all-targets clean, cargo fmt --all --check. Run by hand with the jkl; layout above.

🤖 Generated with Claude Code

Each key the viewer acts on is now an action with a name (`down`, `line-start`,
`send`, ...) and a list of keys in config.toml, defaulting to today's bindings.
A key bound to two actions that read the same keypress is a config error that
names both, and the footer, menu and toolbar hints follow the bindings.

Refs plannotator#58

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B4DEckuRs9pSTyzuvCvrXN
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant