Documentation
¶
Overview ¶
Package keymap maps terminal keystrokes to named actions.
Matching does not mutate a Map. Sequence progress lives in Matcher, so a map can be shared by independent readers without sharing their partially typed sequences, provided its bindings are not changed concurrently.
Index ¶
Examples ¶
Constants ¶
const DefaultTimeout = time.Second
DefaultTimeout is how long a partially typed sequence remains current when a Map does not specify a positive Timeout.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Action ¶
type Action string
Action identifies an operation independently of the keystrokes bound to it. Empty is reserved for no action.
type Binding ¶
Binding associates a chord sequence with an action. Values returned by Map.Bindings are caller-owned snapshots, so an application may sort, present or serialize them without changing the map.
type Map ¶
type Map struct {
// Timeout controls how long a partially typed sequence remains current. Zero
// and negative values use [DefaultTimeout].
Timeout time.Duration
// Resolve decides when an exact binding that is also a prefix should run. Nil
// waits for another key: a continuation takes the longer binding, while a key
// that cannot continue it settles the exact binding first. Assign the event
// owner's one-shot scheduler to make the exact binding run after Timeout even
// when no further input arrives.
Resolve Resolver
// contains filtered or unexported fields
}
Map associates chord sequences with actions.
The zero value is an empty map. A Map must not be copied after first use: a copy would share the binding store and lookup tree while rebuilding only one of them.
Example ¶
package main
import (
"fmt"
"github.com/Tangerg/oolong/core/input"
"github.com/Tangerg/oolong/core/keymap"
)
func main() {
// A consumer names its operations; the map says which keystrokes produce each
// name. Neither the event protocol nor the map needs to know what an operation does.
keys := &keymap.Map{}
keys.Bind("delete-word-back", input.Ctrl.Rune('w'))
keys.Bind("delete-word-back", input.Alt.With(input.Backspace))
keys.Bind("submit", input.Chord{Code: input.Enter})
var matcher keymap.Matcher
for _, key := range []input.Key{
{Code: input.Character, Rune: 'w', Mods: input.Ctrl},
{Code: input.Enter},
{Code: input.Character, Rune: 'q'},
} {
var action keymap.Action
mine, _ := matcher.Handle(keys, key, func(next keymap.Action) bool {
action = next
return true
})
fmt.Printf("%-9s %-18s mine=%v\n", key, "\""+string(action)+"\"", mine)
}
}
Output: ctrl+w "delete-word-back" mine=true enter "submit" mine=true q "" mine=false
Example (Sequences) ¶
package main
import (
"fmt"
"github.com/Tangerg/oolong/core/input"
"github.com/Tangerg/oolong/core/keymap"
)
func main() {
// A binding can be more than one chord long. The first chord is the map's and
// names nothing yet, which the caller has to consume rather than pass on.
keys := &keymap.Map{}
keys.Bind("go-to-top", input.Chord{Rune: 'g'}, input.Chord{Rune: 'g'})
var matcher keymap.Matcher
for range 2 {
var action keymap.Action
mine, _ := matcher.Handle(keys, input.Key{Rune: 'g'}, func(next keymap.Action) bool {
action = next
return true
})
fmt.Printf("%q taken=%v waiting=%q\n", action, mine, matcher.Keys().String())
}
}
Output: "" taken=true waiting="g" "go-to-top" taken=true waiting=""
func (*Map) Bind ¶
Bind associates keys with action, replacing an existing binding for the same sequence. Empty actions and empty sequences are ignored.
func (*Map) Bindings ¶
Bindings returns a deep copy of every binding, in binding order. It is the complete observation surface for applications that present or persist a key map; use Map.Keys when only one action is relevant.
Example ¶
package main
import (
"fmt"
"github.com/Tangerg/oolong/core/input"
"github.com/Tangerg/oolong/core/keymap"
)
func main() {
// Bindings is the complete snapshot used by a settings or help interface. Keys is
// the narrower query when the caller already knows the action it wants.
keys := &keymap.Map{}
keys.Bind("submit", input.Chord{Code: input.Enter})
keys.Bind("cancel", input.Chord{Code: input.Esc})
for _, binding := range keys.Bindings() {
fmt.Println(binding)
}
}
Output: enter submit esc cancel
func (*Map) Keys ¶
Keys returns copies of the sequences bound to action, in binding order.
Example ¶
package main
import (
"fmt"
"github.com/Tangerg/oolong/core/input"
"github.com/Tangerg/oolong/core/keymap"
)
func main() {
// Keys reports the sequences bound to an action in insertion order, so callers do
// not have to reconstruct binding precedence.
keys := &keymap.Map{}
keys.Bind("delete-word-back", input.Ctrl.Rune('w'))
keys.Bind("delete-word-back", input.Alt.With(input.Backspace))
for _, bound := range keys.Keys("delete-word-back") {
fmt.Println(bound)
}
}
Output: ctrl+w alt+backspace
type Matcher ¶ added in v0.5.0
type Matcher struct {
// contains filtered or unexported fields
}
Matcher reads one Map for one event owner.
It owns partial sequence progress, exact-prefix resolution and action dispatch. The zero value is ready to use. A Matcher belongs to one goroutine and must not be copied after first use.
func (*Matcher) Clear ¶ added in v0.5.0
func (m *Matcher) Clear()
Clear abandons the partially typed sequence and cancels its resolver.
func (*Matcher) Handle ¶ added in v0.5.0
func (m *Matcher) Handle(bindings *Map, key input.Key, do func(Action) bool) (matched, handled bool)
Handle advances the matcher with key and runs a completed action through do. Matched reports whether this key belonged to bindings. Handled reports the callback's answer, except that an unfinished prefix is always handled.
A prefix belongs to the map before it names an action. If an earlier ambiguous exact match is settled while this key is being read, this key is still matched independently and the result reports whether the new key belongs. A nil Matcher, Map or callback handles nothing.
type Resolver ¶ added in v0.5.0
Resolver schedules resolve after wait and returns a function that cancels it.
It is the policy seam for a binding that is both an exact match and a prefix of a longer binding. An event owner's one-shot scheduler has this shape; keymap neither imports that owner nor owns a clock. Resolve must run on the same goroutine that calls Matcher.Handle. Calling it after cancellation is harmless.