Documentation
¶
Overview ¶
Package keybindings ports the user-customizable keybinding layer from src/keybindings/ in CC. MVP: single-keystroke bindings, JSON config in ~/.claude/keybindings.json, resolved against bubbletea v2 KeyPressMsg (which exposes structured Code + Mod after the M-P upgrade).
Chord support (multi-keystroke like "ctrl+x ctrl+s") is intentionally deferred — CC source has it but the vast majority of CC's own defaultBindings.ts entries are single-keystroke, and chord state machines are easy to get wrong. We can add it later behind the same Resolver API.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func UserFilePath ¶
UserFilePath returns ~/.claude/keybindings.json (or the equivalent on Windows once internal/config supports XDG/AppData). For now we keep it simple — internal/config.UserDir is the source of truth for ~/.claude.
Types ¶
type Binding ¶
Binding pairs a Keystroke with the action it triggers, scoped to a context. Action is empty when the user explicitly unbinds a default (Unbind == true).
func Defaults ¶
func Defaults() []Binding
Defaults is a convenience that returns the parsed default bindings.
func LoadAll ¶
LoadAll loads defaults + user bindings, with user entries appended last so they shadow defaults in the resolver's last-wins lookup.
func ParseBlocks ¶
ParseBlocks converts loaded JSON blocks into a flat []Binding list. The order is preserved, which matters: when multiple bindings match (e.g., user overrides default), the resolver picks the last one.
type Block ¶
type Block struct {
Context string `json:"context"`
Bindings map[string]string `json:"bindings"`
// Unbinds tracks keys explicitly set to null in JSON; we can't put
// `null` in a map[string]string, so the loader splits these out.
Unbinds []string `json:"-"`
}
Block is one entry in keybindings.json — a context plus a map of keystroke string → action (or null to unbind).
func DefaultBlocks ¶
func DefaultBlocks() []Block
DefaultBlocks ports the subset of src/keybindings/defaultBindings.ts that conduit's TUI actually has handlers for. The set is intentionally narrower than CC: features descoped in the parity plan (KAIROS Brief, teammate preview, voice, message actions, transcript pager, attachments, diff dialog, model picker, scroll context) don't appear here. We add them back as those features land.
Order matters: later blocks shadow earlier ones for the same keystroke in the same context. Within a block, map iteration order is undefined, but each block's bindings are disjoint by construction so it doesn't matter.
func LoadUserFile ¶
LoadUserFile reads keybindings.json at path and returns parsed blocks. Missing file → no error, empty result. Returns a wrapped error if the JSON is malformed or contains an unknown shape.
func ParseUserJSON ¶
ParseUserJSON is exposed for tests and for callers (like /doctor) that want to validate JSON without writing a file first.
type File ¶
type File struct {
Schema string `json:"$schema,omitempty"`
Docs string `json:"$docs,omitempty"`
Bindings []Block `json:"bindings"`
}
File is the top-level keybindings.json shape.
type Keystroke ¶
Keystroke is one parsed key chord — a key name plus modifiers. The key field is the lowercased canonical name: "a"-"z", "0"-"9", "enter", "escape", "tab", "space", "backspace", "delete", "up", "down", "left", "right", "pageup", "pagedown", "home", "end", or single printable characters like "/", "?".
func ParseKeystroke ¶
ParseKeystroke parses "ctrl+shift+k" into a Keystroke. Modifier aliases:
- ctrl, control
- alt, opt, option, meta (all collapse to Alt — terminals can't distinguish alt from meta on legacy keymaps)
- shift
- cmd, command, super, win (collapse to Super — only fires on terminals that emit kitty keyboard protocol)
Special key names: esc → escape, return → enter, space → " " (the canonical key name for spacebar in bubbletea v2 is "space", but we accept both).
type Resolver ¶
type Resolver struct {
// contains filtered or unexported fields
}
Resolver matches incoming KeyPressMsg events against parsed bindings, scoped by active contexts (e.g. ["Chat", "Global"]).
func NewResolver ¶
NewResolver wraps a flat binding list. Bindings later in the slice shadow earlier ones for the same keystroke+context — that's how user overrides win over defaults.
func (*Resolver) Bindings ¶
Bindings returns the full binding list, suitable for display in /keybindings.
func (*Resolver) Resolve ¶
func (r *Resolver) Resolve(msg tea.KeyPressMsg, activeContexts ...string) Result
Resolve looks up the action bound to msg in the given contexts. activeContexts is processed as a set; order doesn't matter.
Returns the LAST matching binding so user-supplied overrides shadow the defaults, mirroring src/keybindings/resolver.ts.
type Result ¶
type Result struct {
// Action is set when a non-unbind binding matched. Empty otherwise.
Action string
// Unbound is true when the matching binding was an explicit null —
// the caller should NOT fall through to default behavior. This is how
// a user disables a built-in shortcut.
Unbound bool
// Matched is true when any binding (action or unbind) matched.
Matched bool
}
Result is the outcome of resolving a key press.