| 1 | //! Per-mode key trie: pending sequences, counts and which-key candidates. |
| 2 | |
| 3 | use std::collections::BTreeMap; |
| 4 | |
| 5 | use super::keys::{Key, KeySeq}; |
| 6 | use crate::action::Action; |
| 7 | use crate::state::Mode; |
| 8 | |
| 9 | /// Bindings from one config layer, in file order. `None` is `"none"`: remove the binding. |
| 10 | pub type Bindings = Vec<(KeySeq, Option<Vec<Action>>)>; |
| 11 | |
| 12 | /// One layer's `[keys.*]` tables. |
| 13 | #[derive(Clone, Debug, Default, PartialEq)] |
| 14 | pub struct KeyTables { |
| 15 | pub global: Bindings, |
| 16 | pub image: Bindings, |
| 17 | pub grid: Bindings, |
| 18 | pub visual: Bindings, |
| 19 | } |
| 20 | |
| 21 | /// A completed binding: the actions to run and the count typed before it. |
| 22 | #[derive(Clone, Debug, PartialEq)] |
| 23 | pub struct Run { |
| 24 | pub actions: Vec<Action>, |
| 25 | pub count: Option<usize>, |
| 26 | } |
| 27 | |
| 28 | impl Run { |
| 29 | /// The actions with the count applied. |
| 30 | pub fn counted(self) -> Vec<Action> { |
| 31 | match self.count { |
| 32 | Some(n) => self.actions.into_iter().map(|a| a.with_count(n)).collect(), |
| 33 | None => self.actions, |
| 34 | } |
| 35 | } |
| 36 | } |
| 37 | |
| 38 | /// A next key that would continue the pending sequence. |
| 39 | #[allow(dead_code)] // which-key arrives in M6 |
| 40 | #[derive(Clone, Debug, PartialEq)] |
| 41 | pub struct Candidate { |
| 42 | pub key: Key, |
| 43 | pub actions: Option<Vec<Action>>, |
| 44 | /// Longer sequences continue through this key. |
| 45 | pub prefix: bool, |
| 46 | } |
| 47 | |
| 48 | #[derive(Clone, Debug, Default)] |
| 49 | struct Node { |
| 50 | actions: Option<Vec<Action>>, |
| 51 | children: BTreeMap<Key, Node>, |
| 52 | } |
| 53 | |
| 54 | impl Node { |
| 55 | fn walk(&self, keys: &[Key]) -> Option<&Node> { |
| 56 | keys.iter().try_fold(self, |n, k| n.children.get(k)) |
| 57 | } |
| 58 | |
| 59 | fn insert(&mut self, keys: &[Key], actions: Vec<Action>) { |
| 60 | let node = keys |
| 61 | .iter() |
| 62 | .fold(self, |n, k| n.children.entry(*k).or_default()); |
| 63 | node.actions = Some(actions); |
| 64 | } |
| 65 | |
| 66 | fn collect(&self, prefix: &mut Vec<Key>, out: &mut Vec<(KeySeq, Vec<Action>)>) { |
| 67 | if let Some(a) = &self.actions { |
| 68 | out.push((KeySeq(prefix.clone()), a.clone())); |
| 69 | } |
| 70 | for (k, child) in &self.children { |
| 71 | prefix.push(*k); |
| 72 | child.collect(prefix, out); |
| 73 | prefix.pop(); |
| 74 | } |
| 75 | } |
| 76 | } |
| 77 | |
| 78 | /// Largest count accepted; more digits are ignored. |
| 79 | const MAX_COUNT: usize = 9_999_999; |
| 80 | |
| 81 | #[derive(Clone, Debug, Default)] |
| 82 | pub struct Keymap { |
| 83 | /// Image, grid, visual. |
| 84 | tries: [Node; 3], |
| 85 | pending: Vec<Key>, |
| 86 | count: Option<usize>, |
| 87 | } |
| 88 | |
| 89 | fn idx(mode: Mode) -> usize { |
| 90 | match mode { |
| 91 | Mode::Image => 0, |
| 92 | Mode::Grid => 1, |
| 93 | Mode::Visual => 2, |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | impl Keymap { |
| 98 | /// Build from config layers, lowest priority first. Within each mode the levels stack |
| 99 | /// global < mode (< visual for visual mode, which sits on grid); later layers win inside a |
| 100 | /// level, and `"none"` removes whatever a lower level bound. |
| 101 | pub fn new(layers: &[&KeyTables]) -> Self { |
| 102 | type Level = fn(&KeyTables) -> &Bindings; |
| 103 | let global: Level = |t| &t.global; |
| 104 | let image: Level = |t| &t.image; |
| 105 | let grid: Level = |t| &t.grid; |
| 106 | let visual: Level = |t| &t.visual; |
| 107 | let chains: [&[Level]; 3] = [&[global, image], &[global, grid], &[global, grid, visual]]; |
| 108 | let mut km = Keymap::default(); |
| 109 | for (trie, chain) in km.tries.iter_mut().zip(chains) { |
| 110 | let mut flat: BTreeMap<&KeySeq, &Vec<Action>> = BTreeMap::new(); |
| 111 | for level in chain { |
| 112 | let mut merged: BTreeMap<&KeySeq, Option<&Vec<Action>>> = BTreeMap::new(); |
| 113 | for layer in layers { |
| 114 | for (k, v) in level(layer) { |
| 115 | merged.insert(k, v.as_ref()); |
| 116 | } |
| 117 | } |
| 118 | for (k, v) in merged { |
| 119 | match v { |
| 120 | Some(a) => flat.insert(k, a), |
| 121 | None => flat.remove(k), |
| 122 | }; |
| 123 | } |
| 124 | } |
| 125 | for (k, a) in flat { |
| 126 | trie.insert(&k.0, a.clone()); |
| 127 | } |
| 128 | } |
| 129 | km |
| 130 | } |
| 131 | |
| 132 | fn root(&self, mode: Mode) -> &Node { |
| 133 | &self.tries[idx(mode)] |
| 134 | } |
| 135 | |
| 136 | /// Feed one key press. Returns the bindings it completed (usually none or one; an |
| 137 | /// ambiguous prefix followed by a non-continuing key yields the prefix's binding first). |
| 138 | pub fn feed(&mut self, mode: Mode, key: Key) -> Vec<Run> { |
| 139 | if self.pending.is_empty() |
| 140 | && let Some(d) = key.digit() |
| 141 | && (d != 0 || self.count.is_some()) |
| 142 | { |
| 143 | let c = self.count.unwrap_or(0).saturating_mul(10) + d as usize; |
| 144 | self.count = Some(c.min(MAX_COUNT)); |
| 145 | return Vec::new(); |
| 146 | } |
| 147 | self.pending.push(key); |
| 148 | match self.root(mode).walk(&self.pending) { |
| 149 | Some(node) if node.children.is_empty() => { |
| 150 | let actions = node.actions.clone().unwrap_or_default(); |
| 151 | self.pending.clear(); |
| 152 | vec![Run { |
| 153 | actions, |
| 154 | count: self.count.take(), |
| 155 | }] |
| 156 | } |
| 157 | Some(_) => Vec::new(), |
| 158 | None => { |
| 159 | self.pending.pop(); |
| 160 | let prefix = self |
| 161 | .root(mode) |
| 162 | .walk(&self.pending) |
| 163 | .and_then(|n| n.actions.clone()) |
| 164 | .filter(|_| !self.pending.is_empty()); |
| 165 | self.pending.clear(); |
| 166 | match prefix { |
| 167 | // `g` is bound and so is `gg`: `gx` runs `g`, then `x` on its own. |
| 168 | Some(actions) => { |
| 169 | let mut out = vec![Run { |
| 170 | actions, |
| 171 | count: self.count.take(), |
| 172 | }]; |
| 173 | out.extend(self.feed(mode, key)); |
| 174 | out |
| 175 | } |
| 176 | None => { |
| 177 | self.count = None; |
| 178 | Vec::new() |
| 179 | } |
| 180 | } |
| 181 | } |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | /// The pending sequence is itself bound and also a prefix of longer ones. |
| 186 | pub fn is_ambiguous(&self, mode: Mode) -> bool { |
| 187 | !self.pending.is_empty() |
| 188 | && self |
| 189 | .root(mode) |
| 190 | .walk(&self.pending) |
| 191 | .is_some_and(|n| n.actions.is_some() && !n.children.is_empty()) |
| 192 | } |
| 193 | |
| 194 | /// The user stopped typing: an ambiguous sequence runs its own binding. |
| 195 | pub fn timeout(&mut self, mode: Mode) -> Vec<Run> { |
| 196 | if !self.is_ambiguous(mode) { |
| 197 | return Vec::new(); |
| 198 | } |
| 199 | let actions = self |
| 200 | .root(mode) |
| 201 | .walk(&self.pending) |
| 202 | .and_then(|n| n.actions.clone()) |
| 203 | .unwrap_or_default(); |
| 204 | self.pending.clear(); |
| 205 | vec![Run { |
| 206 | actions, |
| 207 | count: self.count.take(), |
| 208 | }] |
| 209 | } |
| 210 | |
| 211 | /// A key sequence (not a count) is waiting for more keys. |
| 212 | #[allow(dead_code)] // which-key arrives in M6 |
| 213 | pub fn is_pending(&self) -> bool { |
| 214 | !self.pending.is_empty() |
| 215 | } |
| 216 | |
| 217 | pub fn cancel(&mut self) { |
| 218 | self.pending.clear(); |
| 219 | self.count = None; |
| 220 | } |
| 221 | |
| 222 | /// The count and keys typed so far, for the status bar. |
| 223 | pub fn pending_display(&self) -> String { |
| 224 | let mut s = self.count.map(|c| c.to_string()).unwrap_or_default(); |
| 225 | if !self.pending.is_empty() { |
| 226 | s.push_str(&KeySeq(self.pending.clone()).to_string()); |
| 227 | } |
| 228 | s |
| 229 | } |
| 230 | |
| 231 | /// The binding of a complete sequence (mouse buttons and the wheel bypass the trie). |
| 232 | pub fn lookup(&self, mode: Mode, keys: &[Key]) -> Option<&[Action]> { |
| 233 | self.root(mode).walk(keys)?.actions.as_deref() |
| 234 | } |
| 235 | |
| 236 | /// The keys that continue the pending sequence, for which-key. |
| 237 | #[allow(dead_code)] // which-key arrives in M6 |
| 238 | pub fn candidates(&self, mode: Mode) -> Vec<Candidate> { |
| 239 | let Some(node) = self.root(mode).walk(&self.pending) else { |
| 240 | return Vec::new(); |
| 241 | }; |
| 242 | node.children |
| 243 | .iter() |
| 244 | .map(|(k, n)| Candidate { |
| 245 | key: *k, |
| 246 | actions: n.actions.clone(), |
| 247 | prefix: !n.children.is_empty(), |
| 248 | }) |
| 249 | .collect() |
| 250 | } |
| 251 | |
| 252 | /// Every binding in a mode, in key order. |
| 253 | pub fn bindings(&self, mode: Mode) -> Vec<(KeySeq, Vec<Action>)> { |
| 254 | let mut out = Vec::new(); |
| 255 | self.root(mode).collect(&mut Vec::new(), &mut out); |
| 256 | out |
| 257 | } |
| 258 | |
| 259 | /// The sequences bound to exactly `action` in a mode, shortest first. |
| 260 | #[allow(dead_code)] // the palette and help arrive in M6 |
| 261 | pub fn keys_for(&self, mode: Mode, action: &Action) -> Vec<KeySeq> { |
| 262 | let mut keys: Vec<KeySeq> = self |
| 263 | .bindings(mode) |
| 264 | .into_iter() |
| 265 | .filter(|(_, a)| a.len() == 1 && a[0] == *action) |
| 266 | .map(|(k, _)| k) |
| 267 | .collect(); |
| 268 | keys.sort_by_key(|k| k.0.len()); |
| 269 | keys |
| 270 | } |
| 271 | } |
| 272 | |
| 273 | #[cfg(test)] |
| 274 | mod tests { |
| 275 | use super::*; |
| 276 | use crate::action::Zoom; |
| 277 | |
| 278 | fn bind(pairs: &[(&str, &str)]) -> Bindings { |
| 279 | pairs |
| 280 | .iter() |
| 281 | .map(|(k, a)| { |
| 282 | let actions = (*a != "none").then(|| vec![a.parse().unwrap()]); |
| 283 | (k.parse().unwrap(), actions) |
| 284 | }) |
| 285 | .collect() |
| 286 | } |
| 287 | |
| 288 | fn keys(s: &str) -> Vec<Key> { |
| 289 | s.parse::<KeySeq>().unwrap().0 |
| 290 | } |
| 291 | |
| 292 | /// Feed a whole string and collect the counted actions. |
| 293 | fn type_(km: &mut Keymap, mode: Mode, s: &str) -> Vec<Action> { |
| 294 | keys(s) |
| 295 | .into_iter() |
| 296 | .flat_map(|k| km.feed(mode, k)) |
| 297 | .flat_map(Run::counted) |
| 298 | .collect() |
| 299 | } |
| 300 | |
| 301 | fn defaults() -> KeyTables { |
| 302 | KeyTables { |
| 303 | global: bind(&[ |
| 304 | ("n", "next"), |
| 305 | ("gg", "first"), |
| 306 | ("G", "last"), |
| 307 | ("q", "quit"), |
| 308 | ("0", "first"), |
| 309 | ("<Space>w", "run wallpaper"), |
| 310 | ("<Space>c", "run copy"), |
| 311 | ]), |
| 312 | image: bind(&[("+", "zoom in"), ("h", "pan left")]), |
| 313 | grid: bind(&[("h", "prev"), ("v", "visual")]), |
| 314 | visual: bind(&[("m", "mark-all")]), |
| 315 | } |
| 316 | } |
| 317 | |
| 318 | #[test] |
| 319 | fn sequences_and_counts() { |
| 320 | let t = defaults(); |
| 321 | let mut km = Keymap::new(&[&t]); |
| 322 | let m = Mode::Image; |
| 323 | assert_eq!(type_(&mut km, m, "n"), [Action::Next(1)]); |
| 324 | assert_eq!(type_(&mut km, m, "g"), []); |
| 325 | assert_eq!(km.pending_display(), "g"); |
| 326 | assert_eq!(type_(&mut km, m, "g"), [Action::First]); |
| 327 | assert_eq!(type_(&mut km, m, "12G"), [Action::Goto(12)]); |
| 328 | assert_eq!(type_(&mut km, m, "5gg"), [Action::Goto(5)]); |
| 329 | assert_eq!(type_(&mut km, m, "3n"), [Action::Next(3)]); |
| 330 | assert_eq!(type_(&mut km, m, "3+"), [Action::Zoom(Zoom::In(3))]); |
| 331 | // `0` is a key unless a count is pending. |
| 332 | assert_eq!(type_(&mut km, m, "0"), [Action::First]); |
| 333 | assert_eq!(type_(&mut km, m, "10n"), [Action::Next(10)]); |
| 334 | assert_eq!(km.pending_display(), ""); |
| 335 | } |
| 336 | |
| 337 | #[test] |
| 338 | fn broken_sequences_are_dropped() { |
| 339 | let t = defaults(); |
| 340 | let mut km = Keymap::new(&[&t]); |
| 341 | let m = Mode::Image; |
| 342 | assert_eq!(type_(&mut km, m, "5gx"), []); |
| 343 | assert!(!km.is_pending()); |
| 344 | assert_eq!(km.pending_display(), ""); |
| 345 | assert_eq!(type_(&mut km, m, "<Space>"), []); |
| 346 | assert_eq!(type_(&mut km, m, "<Esc>"), []); |
| 347 | assert_eq!(type_(&mut km, m, "n"), [Action::Next(1)]); |
| 348 | assert_eq!(type_(&mut km, m, "Z"), []); |
| 349 | } |
| 350 | |
| 351 | #[test] |
| 352 | fn modes_override_global() { |
| 353 | let t = defaults(); |
| 354 | let mut km = Keymap::new(&[&t]); |
| 355 | assert_eq!( |
| 356 | type_(&mut km, Mode::Image, "h"), |
| 357 | [Action::Pan(crate::state::view::Dir::Left)] |
| 358 | ); |
| 359 | assert_eq!(type_(&mut km, Mode::Grid, "h"), [Action::Prev(1)]); |
| 360 | // Visual sits on grid. |
| 361 | assert_eq!(type_(&mut km, Mode::Visual, "h"), [Action::Prev(1)]); |
| 362 | assert_eq!(type_(&mut km, Mode::Visual, "m"), [Action::MarkAll]); |
| 363 | assert_eq!(type_(&mut km, Mode::Grid, "m"), []); |
| 364 | } |
| 365 | |
| 366 | #[test] |
| 367 | fn later_layers_win_and_none_removes() { |
| 368 | let base = defaults(); |
| 369 | let user = KeyTables { |
| 370 | global: bind(&[("n", "prev"), ("q", "none")]), |
| 371 | image: bind(&[("+", "none"), ("G", "none")]), |
| 372 | ..Default::default() |
| 373 | }; |
| 374 | let mut km = Keymap::new(&[&base, &user]); |
| 375 | assert_eq!(type_(&mut km, Mode::Image, "n"), [Action::Prev(1)]); |
| 376 | assert_eq!(type_(&mut km, Mode::Image, "q"), []); |
| 377 | assert_eq!(type_(&mut km, Mode::Image, "+"), []); |
| 378 | // A mode's "none" masks the global binding in that mode only. |
| 379 | assert_eq!(type_(&mut km, Mode::Image, "G"), []); |
| 380 | assert_eq!(type_(&mut km, Mode::Grid, "G"), [Action::Last]); |
| 381 | } |
| 382 | |
| 383 | #[test] |
| 384 | fn ambiguous_prefix_waits_then_resolves() { |
| 385 | let t = KeyTables { |
| 386 | global: bind(&[("g", "first"), ("gg", "last"), ("n", "next")]), |
| 387 | ..Default::default() |
| 388 | }; |
| 389 | let mut km = Keymap::new(&[&t]); |
| 390 | let m = Mode::Image; |
| 391 | assert_eq!(type_(&mut km, m, "g"), []); |
| 392 | assert!(km.is_ambiguous(m)); |
| 393 | assert_eq!(type_(&mut km, m, "g"), [Action::Last]); |
| 394 | // A non-continuing key runs the prefix, then itself. |
| 395 | assert_eq!(type_(&mut km, m, "gn"), [Action::First, Action::Next(1)]); |
| 396 | type_(&mut km, m, "4g"); |
| 397 | let fired: Vec<Action> = km.timeout(m).into_iter().flat_map(Run::counted).collect(); |
| 398 | assert_eq!(fired, [Action::Goto(4)]); |
| 399 | assert!(!km.is_pending()); |
| 400 | } |
| 401 | |
| 402 | #[test] |
| 403 | fn which_key_candidates() { |
| 404 | let t = defaults(); |
| 405 | let mut km = Keymap::new(&[&t]); |
| 406 | type_(&mut km, Mode::Image, "<Space>"); |
| 407 | let c = km.candidates(Mode::Image); |
| 408 | assert_eq!(c.len(), 2); |
| 409 | assert_eq!(c[0].key, Key::plain('c')); |
| 410 | assert_eq!(c[0].actions, Some(vec![Action::Run("copy".into())])); |
| 411 | assert!(!c[0].prefix); |
| 412 | km.cancel(); |
| 413 | let root = km.candidates(Mode::Image); |
| 414 | assert!(root.iter().any(|c| c.key == Key::plain('g') && c.prefix)); |
| 415 | } |
| 416 | |
| 417 | #[test] |
| 418 | fn lookup_and_reverse_lookup() { |
| 419 | let t = defaults(); |
| 420 | let km = Keymap::new(&[&t]); |
| 421 | assert_eq!( |
| 422 | km.lookup(Mode::Image, &keys("n")), |
| 423 | Some(&[Action::Next(1)][..]) |
| 424 | ); |
| 425 | assert_eq!(km.lookup(Mode::Image, &keys("g")), None); |
| 426 | assert_eq!( |
| 427 | km.keys_for(Mode::Image, &Action::First), |
| 428 | vec!["0".parse().unwrap(), "gg".parse().unwrap()] |
| 429 | ); |
| 430 | assert!( |
| 431 | km.bindings(Mode::Grid) |
| 432 | .iter() |
| 433 | .any(|(k, _)| k.to_string() == "v") |
| 434 | ); |
| 435 | } |
| 436 | } |