Skip to main content

hjkl_keymap/
trie.rs

1//! Trie (prefix tree) data structure backing [`Keymap`].
2//!
3//! Each node can optionally carry a [`Binding`] (terminal action) and has
4//! zero or more children keyed by the next [`KeyEvent`] in the chord.
5
6use std::collections::HashMap;
7use std::sync::Arc;
8
9use crate::key::KeyEvent;
10
11/// A predicate closure that gates a binding.
12///
13/// When the predicate returns `false` the binding is treated as if it does
14/// not exist: `lookup` skips it and `has_prefix` still reports deeper
15/// children so the chord can extend.  The closure is stored in an `Arc` so
16/// that `Binding<A>: Clone` even when the closure is not `Clone`.
17pub type Predicate = Arc<dyn Fn() -> bool + Send + Sync>;
18
19/// The action and metadata stored at a terminal trie node.
20#[derive(Clone)]
21pub struct Binding<A> {
22    /// The user-defined action associated with this chord.
23    pub action: A,
24    /// Human-readable description (shown in which-key).
25    pub desc: String,
26    /// `true` = recursive (`:map`), `false` = non-recursive (`:noremap`).
27    /// Ignored in v1 dispatch — reserved for future expansion.
28    pub recursive: bool,
29    /// Optional runtime gate: when `Some(pred)` and `pred()` returns `false`,
30    /// this binding is treated as `Unbound` for resolution purposes.
31    /// The trie node stays present so longer chords extending this prefix
32    /// remain reachable.
33    pub condition: Option<Predicate>,
34}
35
36impl<A: std::fmt::Debug> std::fmt::Debug for Binding<A> {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        f.debug_struct("Binding")
39            .field("action", &self.action)
40            .field("desc", &self.desc)
41            .field("recursive", &self.recursive)
42            .field("condition", &self.condition.as_ref().map(|_| "<predicate>"))
43            .finish()
44    }
45}
46
47impl<A> Binding<A> {
48    /// Evaluate the condition. Returns `true` when the binding should fire
49    /// (no predicate, or predicate returns `true`).
50    pub fn is_active(&self) -> bool {
51        self.condition.as_ref().is_none_or(|pred| pred())
52    }
53}
54
55/// A single node in the trie.
56pub(crate) struct TrieNode<A> {
57    /// Action bound at this node (if this is a terminal chord).
58    pub(crate) action: Option<Binding<A>>,
59    /// Children keyed by next key event.
60    pub(crate) children: HashMap<KeyEvent, TrieNode<A>>,
61}
62
63impl<A> Default for TrieNode<A> {
64    fn default() -> Self {
65        Self {
66            action: None,
67            children: HashMap::new(),
68        }
69    }
70}
71
72impl<A: Clone> TrieNode<A> {
73    /// Insert a binding at the given chord path (relative to this node).
74    pub(crate) fn insert(&mut self, events: &[KeyEvent], binding: Binding<A>) {
75        if events.is_empty() {
76            self.action = Some(binding);
77            return;
78        }
79        let child = self.children.entry(events[0]).or_default();
80        child.insert(&events[1..], binding);
81    }
82
83    /// Remove the binding for the given chord path.
84    /// Returns `true` if something was removed.
85    pub(crate) fn remove(&mut self, events: &[KeyEvent]) -> bool {
86        if events.is_empty() {
87            let had = self.action.is_some();
88            self.action = None;
89            return had;
90        }
91        if let Some(child) = self.children.get_mut(&events[0]) {
92            let removed = child.remove(&events[1..]);
93            // Prune empty leaf nodes.
94            if child.action.is_none() && child.children.is_empty() {
95                self.children.remove(&events[0]);
96            }
97            removed
98        } else {
99            false
100        }
101    }
102
103    /// Exact-match lookup: returns the binding if the chord terminates here
104    /// **and** the binding's condition (if any) evaluates to `true`.
105    ///
106    /// A binding with a false predicate is invisible to callers: the trie
107    /// node remains present so that longer chords extending this prefix are
108    /// still reachable, but the binding itself is treated as absent.
109    pub(crate) fn lookup(&self, events: &[KeyEvent]) -> Option<&Binding<A>> {
110        if events.is_empty() {
111            return self.action.as_ref().filter(|b| b.is_active());
112        }
113        self.children.get(&events[0])?.lookup(&events[1..])
114    }
115
116    /// Returns `true` if `events` is a proper prefix of at least one chord in this trie.
117    pub(crate) fn has_prefix(&self, events: &[KeyEvent]) -> bool {
118        if events.is_empty() {
119            // We are at the node reached by the prefix — it has a prefix if
120            // it has any children (deeper chords exist).
121            return !self.children.is_empty();
122        }
123        match self.children.get(&events[0]) {
124            Some(child) => child.has_prefix(&events[1..]),
125            None => false,
126        }
127    }
128
129    /// Iterate over direct-child *terminal* bindings reachable from `prefix`.
130    /// Each item is `(key_event, &Binding)` for each child node that has an action.
131    pub(crate) fn children_of<'a>(
132        &'a self,
133        prefix: &[KeyEvent],
134    ) -> Vec<(&'a KeyEvent, &'a Binding<A>)> {
135        if prefix.is_empty() {
136            // Return direct children that are terminals.
137            return self
138                .children
139                .iter()
140                .filter_map(|(k, node)| node.action.as_ref().map(|b| (k, b)))
141                .collect();
142        }
143        match self.children.get(&prefix[0]) {
144            Some(child) => child.children_of(&prefix[1..]),
145            None => vec![],
146        }
147    }
148
149    /// Iterate over **all** direct children reachable from `prefix` —
150    /// both terminal nodes (with a bound action) and pure-prefix nodes
151    /// (no action but with deeper children).
152    ///
153    /// Returns `(key_event, Option<&Binding>)` where `None` signals a
154    /// prefix-only entry (a submenu with no action of its own).
155    pub(crate) fn all_children_of<'a>(
156        &'a self,
157        prefix: &[KeyEvent],
158    ) -> Vec<(&'a KeyEvent, Option<&'a Binding<A>>)> {
159        if prefix.is_empty() {
160            return self
161                .children
162                .iter()
163                .filter(|(_, node)| node.action.is_some() || !node.children.is_empty())
164                .map(|(k, node)| (k, node.action.as_ref()))
165                .collect();
166        }
167        match self.children.get(&prefix[0]) {
168            Some(child) => child.all_children_of(&prefix[1..]),
169            None => vec![],
170        }
171    }
172}