keybind

package module
v0.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 1, 2026 License: MIT Imports: 5 Imported by: 0

README

go-keybind

Key-binding config for Go TUI apps. Parse and normalize key strings, load/save JSON keybind files with merge-with-defaults semantics, validate for intra-area conflicts.

Go Version Go Reference GitHub release (latest by date) CI

go-keybind provides two focused utilities for Go TUI applications:

  1. A Key type — parse and normalize keyboard shortcut strings. Catches typos ("crtl+c"), canonicalizes aliases ("escape""esc", "return""enter", "meta""alt"), fixes modifier order, and produces a comparable value you can switch on.

  2. A generic JSON config loaderLoad[T], Save[T], and Validate work with any app-defined struct. Fields the user didn't touch keep their compiled-in defaults, the file is written on first run, and Validate catches intra-area conflicts without false-positives for intentional cross-area duplicates.

Extracted from matcha's config package. Zero external dependencies.

Install

go get github.com/floatpane/go-keybind

Requires Go 1.26+.

Usage

Parse and compare key strings
k, err := keybind.Parse("ctrl+shift+a")
// k.Ctrl = true, k.Shift = true, k.Base = "a"
// k.String() = "ctrl+shift+a"

// Aliases normalize to the same Key:
a, _ := keybind.Parse("escape")
b, _ := keybind.Parse("esc")
fmt.Println(a == b) // true

// Modifier order doesn't matter in input:
c, _ := keybind.Parse("shift+ctrl+a")
d, _ := keybind.Parse("ctrl+shift+a")
fmt.Println(c == d) // true
Load a keybind config from disk
type MyKeys struct {
    Quit   string `json:"quit"`
    Reload string `json:"reload"`
    NavUp  string `json:"nav_up"`
}

cfg, err := keybind.Load(cfgDir, "keybinds.json", MyKeys{
    Quit:   "ctrl+c",
    Reload: "r",
    NavUp:  "k",
})
// If keybinds.json didn't exist it was created with the defaults.
// Fields absent from an existing file keep their default values.
Validate for conflicts
conflicts := keybind.Validate(map[string]map[string]string{
    "global": {"quit": cfg.Quit, "reload": cfg.Reload},
    "nav":    {"up": cfg.NavUp, "down": cfg.NavDown},
})
// Cross-area duplicates ("d" for delete in two different views) are fine.
// Only intra-area conflicts (two actions bound to the same key) are reported.
Use in a TUI event loop
quitKey := keybind.MustParse(cfg.Quit) // panics on bad config value

// In your event handler:
if event.String() == quitKey.String() {
    return tea.Quit
}

Supported keys

Modifiers: ctrl, alt (= meta = opt), shift

Special keys:

Canonical Aliases
enter return
esc escape
tab
backspace bs
space
up, down, left, right
delete del
insert
home, end
pgup pageup
pgdown pagedown, pgdn
f1f12

Any single printable character is also accepted (a, A, 1, /, […).

Documentation

Full API reference: pkg.go.dev/github.com/floatpane/go-keybind

Guides: see docs/.

Sister projects

Project Role
floatpane/matcha Reference consumer — inbox, email, composer, folder keybinds.

Contributing

PRs welcome. See CONTRIBUTING.md.

Security

Report vulnerabilities privately via SECURITY.md.

License

MIT. See LICENSE.

Documentation

Overview

Package keybind provides two things for Go TUI applications:

1. A Key type — a parsed, normalized keyboard shortcut. Parse validates a key string (catching typos like "crtl+c" or "delte"), canonicalizes modifier order and special-key aliases ("escape" → "esc", "return" → "enter"), and produces a value that can be compared with == or used in a switch.

2. A generic JSON keybind-config loader (Load, Save, Validate) that any TUI app can use with its own config struct. It reads a JSON file from a config directory, merges with defaults so unknown fields survive upgrades, writes defaults on first run, and validates intra-area key conflicts.

The package is framework-agnostic and has no external dependencies.

Typical usage:

// Define your app's keybind config once.
type MyKeys struct {
    Quit   string `json:"quit"`
    Reload string `json:"reload"`
}

// Load from ~/.config/myapp/keybinds.json, writing defaults if missing.
cfg, err := keybind.Load(cfgDir, "keybinds.json", MyKeys{
    Quit:   "ctrl+c",
    Reload: "r",
})

// Validate for intra-area conflicts.
conflicts := keybind.Validate(map[string]map[string]string{
    "global": {"quit": cfg.Quit, "reload": cfg.Reload},
})

// Parse a key string from the config and compare against incoming events.
k, err := keybind.Parse(cfg.Quit)
if err != nil { /* bad value in config */ }
if incomingEvent == k.String() { /* handle quit */ }

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrEmptyKey is returned when the input string is empty or all whitespace.
	ErrEmptyKey = fmt.Errorf("keybind: empty key string")
	// ErrUnknownKey is returned when the base key or a modifier is not recognized.
	ErrUnknownKey = fmt.Errorf("keybind: unknown key")
)

Sentinel errors returned by Parse.

Functions

func Load

func Load[T any](cfgDir, filename string, defaults T) (T, error)

Load reads a JSON keybind config file from cfgDir/filename into a value of type T, starting from defaults so that any key absent from the file keeps its default value (merge semantics). If the file does not exist it is created with the JSON representation of defaults.

T must be JSON-marshalable. A typical call looks like:

cfg, err := keybind.Load(cfgDir, "keybinds.json", MyKeys{
    Quit:   "ctrl+c",
    Reload: "r",
})

func Save

func Save[T any](cfgDir, filename string, cfg T) error

Save writes cfg as indented JSON to cfgDir/filename, creating cfgDir if needed. It is the write-side counterpart to Load.

func Validate

func Validate(areas map[string]map[string]string) []string

Validate checks for intra-area conflicts: two different actions within the same area mapped to the same (non-empty) key. Cross-area duplicates are intentional and not reported — "d" for delete in both an inbox view and an email view is normal.

areas maps area name → (action name → key string). The shape mirrors a struct-per-area keybind config, and is easily built from one:

keybind.Validate(map[string]map[string]string{
    "global": {"quit": cfg.Global.Quit, "cancel": cfg.Global.Cancel},
    "inbox":  {"delete": cfg.Inbox.Delete, "archive": cfg.Inbox.Archive},
})

Each returned string is a human-readable conflict description.

Types

type Key

type Key struct {
	Ctrl  bool
	Alt   bool
	Shift bool
	// Base is the normalized base key name. For printable characters it is the
	// literal character (e.g. "c", "1", "/"). For special keys it is one of the
	// canonical names listed below.
	Base string
}

Key is a parsed, normalized keyboard shortcut.

The zero value is not a valid binding; Parse always returns a non-zero Key on success.

Key is comparable — two Keys are equal (==) when they represent the same shortcut, regardless of how the original strings were written ("esc" and "escape" produce the same Key).

func MustParse

func MustParse(s string) Key

MustParse parses a key string and panics on error. Useful for package-level var initialization and test helpers.

func Parse

func Parse(s string) (Key, error)

Parse parses and normalizes a key string such as "ctrl+c", "shift+tab", "ctrl+shift+a", "f1", "esc", or "k".

Modifier names (case-insensitive): ctrl, alt, meta, opt (alt aliases), shift. Base key names: any single printable character, or a special name from the table below. Modifier order in the input does not matter; Key.String always emits ctrl, alt, shift in that order.

Accepted special key names (and their aliases):

enter (return)  esc (escape)  tab  backspace (bs)  space
up  down  left  right  delete (del)  insert
home  end  pgup (pageup)  pgdown (pagedown, pgdn)
f1 – f12

Returns ErrEmptyKey for an empty string, and ErrUnknownKey for an unrecognized base or modifier.

func (Key) String

func (k Key) String() string

String returns the canonical form of the key, suitable for comparison with key-event strings produced by TUI frameworks. Modifiers appear in ctrl, alt, shift order followed by the base: e.g. "ctrl+alt+shift+a", "ctrl+c", "esc".

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL