keymap

package
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Sep 24, 2026 License: MIT Imports: 3 Imported by: 0

Documentation

Overview

Package keymap turns emacs key notation into structured keystrokes and resolves sequences of them against a prefix tree of bindings.

This package depends only on the standard library. Translating a terminal library's event type into a Key is deliberately somebody else's job, which is what keeps every rule in here unit-testable without a terminal.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func SortContinuations added in v0.5.1

func SortContinuations(cs []Continuation)

SortContinuations puts cs in the order Continuations returns, for a caller that merges the continuations of several maps.

func SpecString

func SpecString(seq []Key) string

SpecString renders a key sequence in canonical notation, the form used by config files and describe-bindings.

Types

type Continuation

type Continuation struct {
	// Key is the next keystroke, normalized as the map stores it.
	Key Key
	// Command is the bound command name. Empty when IsPrefix.
	Command string
	// IsPrefix reports that this key leads to a further keymap rather than to a
	// command, so pressing it waits for another key.
	IsPrefix bool
	// Count is how many bindings this key leads to: the recursive total beneath
	// a prefix, and 1 for a leaf. A panel reporting "+register 3" is claiming
	// three reachable commands, so counting only immediate children would lie
	// about any nesting deeper than one level.
	Count int
}

Continuation is one key that may follow a prefix.

A node in the tree is either terminal or internal — Map.Bind refuses to let a key be both a command and a prefix — so exactly one of Command and IsPrefix is meaningful on any given Continuation.

type Key

type Key struct {
	Rune    rune       // base character; 0 when Special != SpecialNone
	Special SpecialKey // non-None for keys that have no character
	Ctrl    bool
	Meta    bool // emacs Meta: Alt, or an ESC prefix
	// Shift is only meaningful when Special != SpecialNone: a rune carries its own
	// case, so S- on a character is meaningless. Normalize enforces this by
	// clearing Shift on any rune key, and ParseSpec rejects "S-a" outright.
	Shift bool
}

Key is a single keystroke.

func Normalize

func Normalize(k Key) Key

Normalize folds the ambiguities that terminals impose on control keys, so a binding matches no matter which encoding the terminal happened to send.

The folds exist because a terminal does not transmit "Ctrl" as a modifier bit for control characters: it transmits a single byte in the C0 range, and several distinct keystrokes collapse onto the same byte before the program ever sees them. Folding to one canonical spelling is therefore recovering information the terminal already destroyed, not a policy choice.

Every fold here is unconditional. The one genuinely contentious case, C-h, is opt-in per Map via Map.TreatCtrlHAsBackspace.

func ParseSpec

func ParseSpec(spec string) ([]Key, error)

ParseSpec parses emacs key notation into a key sequence. Keys are separated by whitespace, so "C-x C-f" is two keystrokes and "C-x" is one.

It never panics; malformed input always comes back as an error.

func (Key) String

func (k Key) String() string

String renders the key in canonical emacs notation. The result always parses back to the same key via ParseSpec, for every key this package can produce.

Modifiers are emitted in the fixed order C- M- S-. Shift is emitted only for special keys: a rune already carries its own case.

type Map

type Map struct {
	// TreatCtrlHAsBackspace folds C-h into <backspace>. Off by default, because
	// in emacs C-h is the help prefix; turn it on for terminals whose erase
	// character is ^H. See the comment in Map.normalize.
	TreatCtrlHAsBackspace bool
	// contains filtered or unexported fields
}

Map resolves key sequences to command names.

The zero value is not usable; call New.

func New

func New() *Map

New returns an empty Map.

func (*Map) Bind

func (m *Map) Bind(seq []Key, command string) error

Bind binds a key sequence to a command name, creating intermediate prefix maps as needed. Rebinding an existing sequence replaces it silently.

It fails if the sequence would shadow, or be shadowed by, an existing binding: binding C-x C-f when C-x is already a command, or binding C-x when C-x C-f exists. The error names both bindings.

func (*Map) Bindings

func (m *Map) Bindings() map[string]string

Bindings returns every binding as canonical spec string to command name, for describe-bindings and for M-x to show where a command lives.

func (*Map) Continuations

func (m *Map) Continuations(seq []Key) []Continuation

Continuations lists the keys that may follow seq.

This is what a which-key panel is built from: after a pending prefix, it answers "what can I press now, and what will it do".

seq is normalized the same way Map.Lookup normalizes it, including this map's opt-in folds, so a caller may pass keys straight from the terminal decoder. It returns nil when seq resolves to a complete binding (a terminal node has no children, so nothing can follow it) and when seq matches nothing at all — neither case is an error.

The result is ordered for a human reading a panel: plain runes first, since those are the quickest to press and read as menu letters; then runes carrying Ctrl or Meta; then named keys such as <f1> and <up>. Within each group, keys are sorted ascending by canonical notation. The order is total and stable, which matters because Go randomizes map iteration and an unsorted implementation would reshuffle a panel between one keystroke and the next.

func (*Map) Lookup

func (m *Map) Lookup(seq []Key) Result

Lookup resolves a key sequence.

func (*Map) Unbind

func (m *Map) Unbind(seq []Key) error

Unbind removes the binding for a sequence and prunes any prefix nodes left empty, so a former prefix stops reporting Pending.

func (*Map) Where

func (m *Map) Where(command string) []string

Where returns every key sequence bound to command, in canonical notation, sorted for determinism. It returns nil when the command has no bindings.

A command legitimately has several bindings — undo answers to both C-_ and C-x u — so this is the reverse index of Map.Bindings, which callers would otherwise have to invert themselves on every lookup.

type Result

type Result struct {
	Kind    ResultKind
	Command string // set only when Kind is Found
}

Result is the outcome of a Map.Lookup.

type ResultKind

type ResultKind int

ResultKind says what a lookup found.

const (
	// Undefined means the sequence matches no binding and cannot become one.
	Undefined ResultKind = iota
	// Pending means the sequence is a proper prefix of at least one binding, so
	// the caller should read another key. This is how C-x waits.
	Pending
	// Found means the sequence resolves to a command.
	Found
)

func (ResultKind) String

func (r ResultKind) String() string

type SpecialKey

type SpecialKey int

SpecialKey identifies a key that produces no character.

const (
	SpecialNone SpecialKey = iota
	KeyEnter
	KeyTab
	KeyBackspace
	KeyDelete
	KeyEscape
	KeyUp
	KeyDown
	KeyLeft
	KeyRight
	KeyHome
	KeyEnd
	KeyPgUp
	KeyPgDn
	KeyF1
	KeyF2
	KeyF3
	KeyF4
	KeyF5
	KeyF6
	KeyF7
	KeyF8
	KeyF9
	KeyF10
	KeyF11
	KeyF12
)

The special keys nem understands.

Jump to

Keyboard shortcuts

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