Skip to content

feat(tui): configurable leader key via keybindings.json#718

Open
lfaoro wants to merge 5 commits into
Gitlawb:mainfrom
lfaoro:feat/configurable-keybinds
Open

feat(tui): configurable leader key via keybindings.json#718
lfaoro wants to merge 5 commits into
Gitlawb:mainfrom
lfaoro:feat/configurable-keybinds

Conversation

@lfaoro

@lfaoro lfaoro commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Makes the TUI leader prefix and slash-command chords configurable via a sibling keybindings.json file (not config.json), auto-seeded next to config when missing.

  • keybindings.json at ~/.config/zero/keybindings.json (and project .zero/keybindings.json)
    • Auto-created beside config.json on write and at TUI start; never overwrites an existing file
    • Seeded with every primary builtin slash command; unbound entries are ""
    • Schema: leaderKey + leader map as slash → letter (or "" to unassign)
    • Merge: code defaults ← user ← project
  • Leader runtime reads resolved config (arm / second-press cancel / dispatch / help / idle hints)
  • Soft-fail sanitize + startup notices (invalid/reserved leaderKey, /edit, unknown slash, duplicate letters)

Reserved chords (hardcoded)

Chord Role
Ctrl+P / Ctrl+N Modal/list up/down (not assignable)
Ctrl+G Esc synonym / emacs abort (not assignable)

Summary by CodeRabbit

  • New Features
    • Added customizable leader-key chords via keybindings.json, with support for user and project/workspace overrides.
    • Leader-key help and labels now dynamically reflect the active resolved chords.
  • Changes
    • Updated the plan toggle shortcut to Ctrl+Y (was Ctrl+P).
    • Treated Ctrl+P / Ctrl+N as reserved navigation keys (no longer assignable toggles).
    • Added Ctrl+G as an alternative to Escape for dismiss/cancel.
  • Documentation
    • Expanded leader-key documentation with chord definitions, defaults/seeding, overlay precedence, and key control clarifications.
  • Reliability
    • Improved keybindings.json initialization/merging, including seeding sibling keybindings and avoiding overwriting existing edits.

Add auto-seeded keybindings.json next to config.json for leader prefix and
slash-command chords. Reserve Ctrl+N/P for menu nav and Ctrl+G as Esc; move
togglePlan default to Ctrl+Y.
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e3ade9b9-df46-4380-bb66-cf0b47e01d68

📥 Commits

Reviewing files that changed from the base of the PR and between 87062f7 and 798dfa5.

📒 Files selected for processing (2)
  • internal/tui/leader.go
  • internal/tui/leader_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/tui/leader.go

Walkthrough

The change adds persisted, layered keybindings.json configuration for leader chords, resolves it during interactive TUI startup, supports dynamic leader dispatch and help, updates reserved shortcut behavior, changes plan toggling to Ctrl+Y, and expands documentation and tests.

Changes

Leader keybindings

Layer / File(s) Summary
Keybindings storage and merge
internal/config/keybindings_file.go, internal/config/keybindings_file_test.go, internal/config/writer.go, internal/config/types.go
Adds keybinding schema, defaults, file seeding/loading, user-project overlay resolution, atomic writes, and configuration tests.
CLI keybindings resolution
internal/cli/app.go
Ensures and loads user/project keybindings, merges them with defaults, and passes the resolved result to the TUI.
Leader configuration and dispatch
internal/tui/commands.go, internal/tui/options.go, internal/tui/leader_config.go, internal/tui/leader.go, internal/tui/model.go, internal/tui/leader_config_test.go, internal/tui/leader_test.go
Validates configured leader prefixes and slash mappings, stores resolved runtime state, dispatches dynamic chords, renders dynamic help, and records configuration notices.
Shortcut behavior and documentation
internal/tui/keybindings.go, internal/tui/input_compat.go, internal/tui/keybinding_help.go, internal/tui/model.go, internal/tui/*_test.go, README.md
Reserves Ctrl+G, Ctrl+P, and Ctrl+N, maps Ctrl+G to Escape behavior, changes plan toggling to Ctrl+Y, updates help and hints, and documents keybindings.json scopes and rules.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CLI
  participant Config
  participant TUI
  User->>CLI: Start interactive TUI
  CLI->>Config: Load user and project keybindings
  Config-->>CLI: Resolve layered keybindings
  CLI->>TUI: Provide resolved keybindings
  User->>TUI: Press leader prefix and chord
  TUI->>TUI: Execute mapped slash command
Loading

Possibly related PRs

  • Gitlawb/zero#481: Modifies TUI keybinding collision and validation behavior.
  • Gitlawb/zero#699: Modifies Ctrl+X leader-chord handling, help overlays, and related TUI flows.

Suggested reviewers: vasanthdev2004, gnanamer1990, kevincodex1

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: configurable TUI leader keys via keybindings.json.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/config/keybindings_file.go`:
- Around line 126-137: Update the keybindings creation flow around the existing
os.Stat check and writeKeybindingsFile so creation uses an atomic
create-if-absent operation, preventing concurrent processes from overwriting an
existing keybindings file. Preserve the current seed/default behavior, handle an
already-created file as a no-op, and add a test covering concurrent creation.

In `@internal/tui/leader_config.go`:
- Around line 44-53: Update the leader-key resolution flow around
leaderKeyConflictsToggle so the fallback selected after a conflict is not itself
reserved or conflicting with any global toggle. Sanitize the conflicting binding
or choose a genuinely available alternative, ensuring leader dispatch cannot
shadow a configured toggle and the existing notice behavior remains accurate.

In `@internal/tui/leader.go`:
- Around line 74-80: Update matchesLeaderKey to accept control-byte
representations of Ctrl-letter leader keys in addition to the resolved
leaderKey.Matcher() result. Preserve the existing keyCtrl(msg, 'x') fallback for
the built-in leader and ensure custom Ctrl-letter leaders also match when
terminals omit ModCtrl.

In `@internal/tui/model.go`:
- Around line 2804-2805: Update the resolved-leader hint returned by the
surrounding model-rendering method to avoid hardcoded “m model” and “p provider”
commands. Derive the displayed examples from m.leaderCommands so remapped or
unbound commands are reflected, or use a generic hint when no suitable commands
are configured; preserve the existing label and cancel/list guidance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f0d2807f-df56-4f72-b630-2662b81d7289

📥 Commits

Reviewing files that changed from the base of the PR and between 31d45d5 and 975979c.

📒 Files selected for processing (21)
  • README.md
  • internal/cli/app.go
  • internal/config/keybindings_file.go
  • internal/config/keybindings_file_test.go
  • internal/config/types.go
  • internal/config/writer.go
  • internal/tui/binding_test.go
  • internal/tui/commands.go
  • internal/tui/input_compat.go
  • internal/tui/input_compat_test.go
  • internal/tui/keybinding_help.go
  • internal/tui/keybinding_help_test.go
  • internal/tui/keybindings.go
  • internal/tui/leader.go
  • internal/tui/leader_config.go
  • internal/tui/leader_config_test.go
  • internal/tui/leader_test.go
  • internal/tui/modal_selection_test.go
  • internal/tui/model.go
  • internal/tui/options.go
  • internal/tui/plan_panel.go

Comment thread internal/config/keybindings_file.go Outdated
Comment thread internal/tui/leader_config.go Outdated
Comment thread internal/tui/leader.go
Comment thread internal/tui/model.go Outdated
lfaoro added 4 commits July 17, 2026 19:35
Use O_CREATE|O_EXCL so concurrent EnsureKeybindingsFile races cannot overwrite
an existing keybindings file via rename.
When a configured leaderKey is rejected, pick a usable fallback (default
Ctrl+X, or free it by clearing a remapped toggle that claimed it) so leader
dispatch cannot collide with reserved chords or global toggles.
Show remapped/unbound leader examples from leaderCommands instead of
hardcoded m/p shortcuts; keep ? list and Esc cancel guidance.
Some terminals emit Ctrl+letter as a C0 code without ModCtrl; accept that
form for the default and custom ctrl-letter leader keys.

@Vasanthdev2004 Vasanthdev2004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took a careful look — this is solid, well-built work. go build/vet/gofmt clean, the config suite passes, and the file handling is genuinely careful: keybindings.json is seeded with O_CREATE|O_EXCL so a concurrent process or an existing user file is never overwritten, a missing file loads as empty (no hard fail), a parse error is surfaced instead of silently resetting, and the defaults←user←project overlay handles ""-to-unassign correctly. The leader sanitize path is thorough too — modifier required, reserved-chord and toggle-conflict rejection, deterministic duplicate-letter resolution, and soft-fail-to-default with user-facing notices rather than a startup crash. No correctness blockers from me.

Two things that are really the maintainer's call, not code issues — flagging so they're conscious decisions:

  1. Contribution gate. This is a ~1,400-line feature from an external contributor with no linked approved issue. It's clearly a continuation of already-merged work (the leader chords from #699) and it's good work, so I'd lean toward welcoming it — but per the contribution process that's kevin's nod to give, not an auto-yes.
  2. User-visible default change. The plan toggle moves Ctrl+PCtrl+Y so Ctrl+P/Ctrl+N can become list navigation (and Ctrl+G an Esc synonym). Sensible and conventional, but it does change an existing default and adds a new auto-seeded config file beside config.json — both worth a deliberate yes rather than sliding in unnoticed.

One tiny, optional nit: encodeKeybindingsJSON (keybindings_file.go:222-232) manually sorts the leader map "because maps encode in insertion order" — but encoding/json already sorts map keys alphabetically, so that block is redundant (output is identical either way). Harmless; drop it if you're in the file anyway.

Net: on correctness/safety this is ready — careful, tested, and it fails safe. My only holds are the two product/process calls above, which are the maintainer's to make.

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.

2 participants