Prefix tree (trie) for key sequence → command bindings.
Each node in the trie can represent either an intermediate step in a
multi-key sequence (a prefix node) or a terminal binding (a command node).
Nodes may simultaneously be a prefix and a command (e.g. g could be a
command and also prefix for gg).
Key representation
A key is a {codepoint, modifiers} tuple where codepoint is the Unicode
codepoint of the key and modifiers is a bitmask of modifier keys:
0x01— Shift0x02— Ctrl0x04— Alt0x08— Super
Usage
trie = Minga.Keymap.Bindings.new()
trie = Minga.Keymap.Bindings.bind(trie, [{?j, 0}], :move_down, "Move cursor down")
trie = Minga.Keymap.Bindings.bind(trie, [{?g, 0}, {?g, 0}], :file_start, "Go to first line")
{:command, :move_down} = Minga.Keymap.Bindings.lookup(trie, {?j, 0})
{:prefix, node} = Minga.Keymap.Bindings.lookup(trie, {?g, 0})
Summary
Functions
Binds a key sequence to a command in the trie.
Sets a human-readable description on an intermediate (prefix) node without
binding a command. Useful for labelling leader-key groups like f → "+file".
Returns the direct children of a trie node for which-key display.
Formats a single key/0 tuple into a human-readable string.
Looks up a single key in the trie.
Looks up a full key sequence in the trie, walking node by node.
Merges a list of binding tuples into a trie.
Merges a list of binding tuples into a trie, excluding specific commands.
Merges a named shared group into a trie.
Merges a named shared group into a trie with exclusions.
Creates a new, empty trie root node.
Removes a key sequence binding from the trie.
Types
@type key() :: {codepoint :: non_neg_integer(), modifiers :: non_neg_integer()}
A single key event: {codepoint, modifiers}.
codepoint is the Unicode codepoint (e.g. ?j = 106).
modifiers is a bitmask: Shift=0x01, Ctrl=0x02, Alt=0x04, Super=0x08.
@type node_t() :: Minga.Keymap.Bindings.Node.t()
A trie node.
Functions
Binds a key sequence to a command in the trie.
Returns an updated trie root. Intermediate nodes are created as needed. Rebinding an existing sequence overwrites the previous binding.
Parameters
root— the trie root nodekeys— non-empty list ofkey/0values representing the sequencecommand— atom name of the command to binddescription— human-readable description for which-key display
Examples
iex> trie = Minga.Keymap.Bindings.new()
iex> trie = Minga.Keymap.Bindings.bind(trie, [{?j, 0}], :move_down, "Move cursor down")
iex> Minga.Keymap.Bindings.lookup(trie, {?j, 0})
{:command, :move_down}
iex> Minga.Keymap.Bindings.lookup(trie, {?k, 0})
:not_found
Sets a human-readable description on an intermediate (prefix) node without
binding a command. Useful for labelling leader-key groups like f → "+file".
Creates intermediate nodes as needed.
Returns the direct children of a trie node for which-key display.
Each entry is a {key, label} tuple where label is either the
description string (for a terminal binding) or the command atom (for a
prefix or unnamed node).
Examples
iex> trie = Minga.Keymap.Bindings.new()
iex> trie = Minga.Keymap.Bindings.bind(trie, [{?j, 0}], :move_down, "Move cursor down")
iex> Minga.Keymap.Bindings.children(trie)
[{{106, 0}, "Move cursor down"}]
Formats a single key/0 tuple into a human-readable string.
Examples
iex> Minga.Keymap.Bindings.format_key({32, 0})
"SPC"
iex> Minga.Keymap.Bindings.format_key({?s, 0x02})
"C-s"
iex> Minga.Keymap.Bindings.format_key({?j, 0x00})
"j"
Looks up a single key in the trie.
Returns one of:
{:command, atom()}— the key sequence is complete and maps to a command{:prefix, node_t()}— the key is a valid prefix; the returned node can be used as the new root for the next key:not_found— the key does not exist in this trie node
Examples
iex> trie = Minga.Keymap.Bindings.new()
iex> trie = Minga.Keymap.Bindings.bind(trie, [{?g, 0}, {?g, 0}], :document_start, "Go to first line")
iex> match?({:prefix, _}, Minga.Keymap.Bindings.lookup(trie, {?g, 0}))
true
iex> Minga.Keymap.Bindings.lookup(trie, {?z, 0})
:not_found
@spec lookup_sequence(node_t(), [key()]) :: {:command, atom(), String.t()} | {:prefix, node_t()} | :not_found
Looks up a full key sequence in the trie, walking node by node.
Returns one of:
{:command, atom(), String.t()}— the sequence maps to a command with its description{:prefix, node_t()}— the sequence is a valid prefix (more keys needed):not_found— no match at some point in the sequence
Examples
iex> trie = Minga.Keymap.Bindings.new()
iex> trie = Minga.Keymap.Bindings.bind(trie, [{?g, 0}, {?g, 0}], :document_start, "Go to first line")
iex> Minga.Keymap.Bindings.lookup_sequence(trie, [{?g, 0}, {?g, 0}])
{:command, :document_start, "Go to first line"}
iex> Minga.Keymap.Bindings.lookup_sequence(trie, [{?g, 0}])
{:prefix, %Minga.Keymap.Bindings.Node{children: %{{103, 0} => %Minga.Keymap.Bindings.Node{children: %{}, command: :document_start, description: "Go to first line"}}, command: nil, description: nil}}
iex> Minga.Keymap.Bindings.lookup_sequence(trie, [{?z, 0}])
:not_found
Merges a list of binding tuples into a trie.
Each binding is a {key_sequence, command, description} tuple. Bindings
are applied in order, so later entries override earlier ones on conflict.
This is the bulk registration helper for shared binding groups. Scope modules call this to include a group's bindings, then apply scope-specific bindings on top (which override group bindings on conflict).
Examples
iex> bindings = [
...> {[{?j, 0}], :move_down, "Move down"},
...> {[{?k, 0}], :move_up, "Move up"}
...> ]
iex> trie = Minga.Keymap.Bindings.merge_bindings(Minga.Keymap.Bindings.new(), bindings)
iex> {:command, :move_down} = Minga.Keymap.Bindings.lookup(trie, {?j, 0})
iex> {:command, :move_up} = Minga.Keymap.Bindings.lookup(trie, {?k, 0})
Merges a list of binding tuples into a trie, excluding specific commands.
Same as merge_bindings/2 but skips any binding whose command atom
appears in the exclude list. Use this when a scope includes a shared
group but needs to override specific commands with different semantics.
Examples
iex> bindings = [
...> {[{?j, 0}], :move_down, "Move down"},
...> {[{?q, 0}], :quit_editor, "Quit"}
...> ]
iex> trie = Minga.Keymap.Bindings.merge_bindings(Minga.Keymap.Bindings.new(), bindings, exclude: [:quit_editor])
iex> {:command, :move_down} = Minga.Keymap.Bindings.lookup(trie, {?j, 0})
iex> :not_found = Minga.Keymap.Bindings.lookup(trie, {?q, 0})
Merges a named shared group into a trie.
Convenience wrapper that calls SharedGroups.get/1 and merge_bindings/2.
Examples
trie = Bindings.new()
|> Bindings.merge_group(:cua_navigation)
|> Bindings.bind([{?q, 0}], :quit, "Quit")
Merges a named shared group into a trie with exclusions.
Examples
trie = Bindings.new()
|> Bindings.merge_group(:cua_navigation, exclude: [:move_up])
@spec new() :: node_t()
Creates a new, empty trie root node.
Examples
iex> trie = Minga.Keymap.Bindings.new()
iex> Minga.Keymap.Bindings.lookup(trie, {?j, 0})
:not_found
Removes a key sequence binding from the trie.
Clears the command and description on the terminal node. Prunes empty intermediate nodes (nodes with no command and no children) on the way back up so the trie doesn't accumulate dead branches.
Returns the updated trie. No-op if the sequence doesn't exist.
Examples
iex> trie = Minga.Keymap.Bindings.new()
iex> trie = Minga.Keymap.Bindings.bind(trie, [{?j, 0}], :move_down, "Move cursor down")
iex> trie = Minga.Keymap.Bindings.unbind(trie, [{?j, 0}])
iex> Minga.Keymap.Bindings.lookup(trie, {?j, 0})
:not_found