Skip to main content

zsh/zle/
keymap.rs

1//! ZLE keymap and key bindings - Direct port from zsh/Src/Zle/zle_keymap.c
2//!
3//! Keymap structures:
4//!
5//! There is a hash table of keymap names. Each name just points to a keymap.
6//! More than one name may point to the same keymap.
7//!
8//! Each keymap consists of a table of bindings for each character, and a
9//! hash table of multi-character key bindings. The keymap has no individual
10//! name, but maintains a reference count.
11
12use std::collections::HashMap;
13use std::sync::Arc;
14
15use super::thingy::Thingy;
16
17/// Flags for keymap names
18#[derive(Debug, Clone, Copy, Default)]
19pub struct KeymapNameFlags {
20    /// Can't be deleted (.safe)
21    pub immortal: bool,
22}
23
24/// A named reference to a keymap
25#[derive(Debug, Clone)]
26pub struct KeymapName {
27    pub name: String,
28    pub flags: KeymapNameFlags,
29    pub keymap: Arc<Keymap>,
30}
31
32/// Flags for keymaps
33#[derive(Debug, Clone, Copy, Default)]
34pub struct KeymapFlags {
35    /// Keymap is immutable
36    pub immutable: bool,
37}
38
39/// A keymap - binding of keys to thingies
40#[derive(Debug, Clone)]
41pub struct Keymap {
42    /// Base binding of each character (0-255)
43    pub first: [Option<Thingy>; 256],
44    /// Multi-character bindings (key sequence -> binding)
45    pub multi: HashMap<Vec<u8>, KeyBinding>,
46    /// Primary name of this keymap
47    pub primary: Option<String>,
48    /// Flags
49    pub flags: KeymapFlags,
50}
51
52/// A key binding (either a thingy or a string to send)
53#[derive(Debug, Clone)]
54pub struct KeyBinding {
55    /// The thingy this key is bound to (None for send-string)
56    pub bind: Option<Thingy>,
57    /// String to send (metafied)
58    pub str: Option<String>,
59    /// Number of sequences for which this is a prefix
60    pub prefixct: i32,
61}
62
63/// State for listing keymaps
64#[derive(Debug, Clone, Default)]
65pub struct BindState {
66    pub flags: BindStateFlags,
67    pub kmname: String,
68    pub firstseq: Vec<u8>,
69    pub lastseq: Vec<u8>,
70    pub bind: Option<Thingy>,
71    pub str: Option<String>,
72    pub prefix: Vec<u8>,
73}
74
75bitflags::bitflags! {
76    #[derive(Debug, Clone, Copy, Default)]
77    pub struct BindStateFlags: u32 {
78        const LIST = 1 << 0;
79        const ALL = 1 << 1;
80    }
81}
82
83impl Default for Keymap {
84    fn default() -> Self {
85        Keymap {
86            first: std::array::from_fn(|_| None),
87            multi: HashMap::new(),
88            primary: None,
89            flags: KeymapFlags::default(),
90        }
91    }
92}
93
94impl Keymap {
95    pub fn new() -> Self {
96        Self::default()
97    }
98
99    /// Bind a single character to a thingy
100    pub fn bind_char(&mut self, c: u8, thingy: Thingy) {
101        self.first[c as usize] = Some(thingy);
102    }
103
104    /// Unbind a single character
105    pub fn unbind_char(&mut self, c: u8) {
106        self.first[c as usize] = None;
107    }
108
109    /// Bind a key sequence to a thingy
110    pub fn bind_seq(&mut self, seq: &[u8], thingy: Thingy) {
111        if seq.len() == 1 {
112            self.bind_char(seq[0], thingy);
113        } else {
114            // Mark prefixes
115            for i in 1..seq.len() {
116                let prefix = &seq[..i];
117                self.multi
118                    .entry(prefix.to_vec())
119                    .and_modify(|kb| kb.prefixct += 1)
120                    .or_insert(KeyBinding {
121                        bind: None,
122                        str: None,
123                        prefixct: 1,
124                    });
125            }
126
127            // Add the binding
128            self.multi.insert(
129                seq.to_vec(),
130                KeyBinding {
131                    bind: Some(thingy),
132                    str: None,
133                    prefixct: 0,
134                },
135            );
136        }
137    }
138
139    /// Bind a key sequence to a string (send-string)
140    pub fn bind_str(&mut self, seq: &[u8], s: String) {
141        if seq.len() == 1 {
142            // Single char can't be send-string in first[] table
143            // Store in multi
144        }
145
146        // Mark prefixes
147        for i in 1..seq.len() {
148            let prefix = &seq[..i];
149            self.multi
150                .entry(prefix.to_vec())
151                .and_modify(|kb| kb.prefixct += 1)
152                .or_insert(KeyBinding {
153                    bind: None,
154                    str: None,
155                    prefixct: 1,
156                });
157        }
158
159        self.multi.insert(
160            seq.to_vec(),
161            KeyBinding {
162                bind: None,
163                str: Some(s),
164                prefixct: 0,
165            },
166        );
167    }
168
169    /// Unbind a key sequence
170    pub fn unbind_seq(&mut self, seq: &[u8]) {
171        if seq.len() == 1 {
172            self.unbind_char(seq[0]);
173        } else {
174            if self.multi.remove(seq).is_some() {
175                // Decrement prefix counts
176                for i in 1..seq.len() {
177                    let prefix = &seq[..i];
178                    if let Some(kb) = self.multi.get_mut(prefix) {
179                        kb.prefixct -= 1;
180                        if kb.prefixct == 0 && kb.bind.is_none() && kb.str.is_none() {
181                            // Remove empty prefix entry
182                            // (can't remove while iterating, so we'll leave it)
183                        }
184                    }
185                }
186            }
187        }
188    }
189
190    /// Look up a single character binding
191    pub fn lookup_char(&self, c: u8) -> Option<&Thingy> {
192        self.first[c as usize].as_ref()
193    }
194
195    /// Look up a key sequence binding (for multi-char sequences only)
196    pub fn lookup_seq(&self, seq: &[u8]) -> Option<&KeyBinding> {
197        if seq.len() == 1 {
198            // For single char, use lookup_char instead
199            None
200        } else {
201            self.multi.get(seq)
202        }
203    }
204
205    /// Check if a sequence is a prefix
206    pub fn is_prefix(&self, seq: &[u8]) -> bool {
207        if seq.len() == 1 {
208            // Check if this char is a prefix in multi table
209            self.multi.keys().any(|k| k.len() > 1 && k[0] == seq[0])
210        } else {
211            self.multi
212                .get(seq)
213                .map(|kb| kb.prefixct > 0)
214                .unwrap_or(false)
215        }
216    }
217}
218
219/// Manager for all keymaps
220#[derive(Debug)]
221pub struct KeymapManager {
222    /// Named keymaps
223    pub keymaps: HashMap<String, Arc<Keymap>>,
224    /// Current keymap
225    pub current: Option<Arc<Keymap>>,
226    /// Current keymap name
227    pub current_name: String,
228    /// Local keymap (temporary override)
229    pub local: Option<Arc<Keymap>>,
230    /// Key sequence buffer
231    pub keybuf: Vec<u8>,
232    /// Last named command executed
233    pub lastnamed: Option<Thingy>,
234}
235
236impl Default for KeymapManager {
237    fn default() -> Self {
238        Self::new()
239    }
240}
241
242impl KeymapManager {
243    pub fn new() -> Self {
244        let mut mgr = KeymapManager {
245            keymaps: HashMap::new(),
246            current: None,
247            current_name: "main".to_string(),
248            local: None,
249            keybuf: Vec::with_capacity(20),
250            lastnamed: None,
251        };
252
253        // Create default keymaps
254        mgr.create_default_keymaps();
255
256        mgr
257    }
258
259    /// Create the default keymaps (emacs, viins, vicmd, etc.)
260    fn create_default_keymaps(&mut self) {
261        // Create emacs keymap
262        let mut emacs = Keymap::new();
263        emacs.primary = Some("emacs".to_string());
264        self.setup_emacs_keymap(&mut emacs);
265        self.keymaps.insert("emacs".to_string(), Arc::new(emacs));
266
267        // Create viins keymap
268        let mut viins = Keymap::new();
269        viins.primary = Some("viins".to_string());
270        self.setup_viins_keymap(&mut viins);
271        self.keymaps.insert("viins".to_string(), Arc::new(viins));
272
273        // Create vicmd keymap
274        let mut vicmd = Keymap::new();
275        vicmd.primary = Some("vicmd".to_string());
276        self.setup_vicmd_keymap(&mut vicmd);
277        self.keymaps.insert("vicmd".to_string(), Arc::new(vicmd));
278
279        // Create isearch keymap
280        let isearch = Keymap::new();
281        self.keymaps
282            .insert("isearch".to_string(), Arc::new(isearch));
283
284        // Create command keymap
285        let command = Keymap::new();
286        self.keymaps
287            .insert("command".to_string(), Arc::new(command));
288
289        // "main" is initially aliased to emacs
290        let emacs = self.keymaps.get("emacs").cloned();
291        if let Some(emacs) = emacs {
292            self.keymaps.insert("main".to_string(), Arc::clone(&emacs));
293            self.current = Some(emacs);
294        }
295    }
296
297    /// Set up emacs keymap bindings
298    fn setup_emacs_keymap(&self, km: &mut Keymap) {
299        // Self-insert for printable characters
300        for c in 32u8..127 {
301            km.bind_char(c, Thingy::builtin("self-insert"));
302        }
303
304        // Basic movement
305        km.bind_char(0x01, Thingy::builtin("beginning-of-line")); // Ctrl-A
306        km.bind_char(0x02, Thingy::builtin("backward-char")); // Ctrl-B
307        km.bind_char(0x04, Thingy::builtin("delete-char-or-list")); // Ctrl-D
308        km.bind_char(0x05, Thingy::builtin("end-of-line")); // Ctrl-E
309        km.bind_char(0x06, Thingy::builtin("forward-char")); // Ctrl-F
310
311        // Editing
312        km.bind_char(0x08, Thingy::builtin("backward-delete-char")); // Ctrl-H / Backspace
313        km.bind_char(0x0B, Thingy::builtin("kill-line")); // Ctrl-K
314        km.bind_char(0x0C, Thingy::builtin("clear-screen")); // Ctrl-L
315        km.bind_char(0x0D, Thingy::builtin("accept-line")); // Ctrl-M / Enter
316        km.bind_char(0x0E, Thingy::builtin("down-line-or-history")); // Ctrl-N
317        km.bind_char(0x10, Thingy::builtin("up-line-or-history")); // Ctrl-P
318        km.bind_char(0x12, Thingy::builtin("history-incremental-search-backward")); // Ctrl-R
319        km.bind_char(0x13, Thingy::builtin("history-incremental-search-forward")); // Ctrl-S
320        km.bind_char(0x14, Thingy::builtin("transpose-chars")); // Ctrl-T
321        km.bind_char(0x15, Thingy::builtin("kill-whole-line")); // Ctrl-U
322        km.bind_char(0x17, Thingy::builtin("backward-kill-word")); // Ctrl-W
323        km.bind_char(0x19, Thingy::builtin("yank")); // Ctrl-Y
324
325        // Ctrl-C (interrupt) - mapped to send-break
326        km.bind_char(0x03, Thingy::builtin("send-break"));
327
328        // Tab completion
329        km.bind_char(0x09, Thingy::builtin("expand-or-complete")); // Tab
330
331        // Delete/Backspace
332        km.bind_char(0x7F, Thingy::builtin("backward-delete-char")); // DEL
333
334        // Escape sequences would go in multi-char bindings
335        // ESC + char sequences
336        km.bind_seq(b"\x1bb", Thingy::builtin("backward-word")); // Alt-B
337        km.bind_seq(b"\x1bf", Thingy::builtin("forward-word")); // Alt-F
338        km.bind_seq(b"\x1bd", Thingy::builtin("kill-word")); // Alt-D
339        km.bind_seq(b"\x1b\x7f", Thingy::builtin("backward-kill-word")); // Alt-Backspace
340
341        // Arrow keys (common ANSI sequences)
342        km.bind_seq(b"\x1b[A", Thingy::builtin("up-line-or-history")); // Up
343        km.bind_seq(b"\x1b[B", Thingy::builtin("down-line-or-history")); // Down
344        km.bind_seq(b"\x1b[C", Thingy::builtin("forward-char")); // Right
345        km.bind_seq(b"\x1b[D", Thingy::builtin("backward-char")); // Left
346        km.bind_seq(b"\x1b[H", Thingy::builtin("beginning-of-line")); // Home
347        km.bind_seq(b"\x1b[F", Thingy::builtin("end-of-line")); // End
348        km.bind_seq(b"\x1b[3~", Thingy::builtin("delete-char")); // Delete
349
350        // Alternative arrow key sequences
351        km.bind_seq(b"\x1bOA", Thingy::builtin("up-line-or-history"));
352        km.bind_seq(b"\x1bOB", Thingy::builtin("down-line-or-history"));
353        km.bind_seq(b"\x1bOC", Thingy::builtin("forward-char"));
354        km.bind_seq(b"\x1bOD", Thingy::builtin("backward-char"));
355    }
356
357    /// Set up viins (vi insert mode) keymap bindings
358    fn setup_viins_keymap(&self, km: &mut Keymap) {
359        // Self-insert for printable characters
360        for c in 32u8..127 {
361            km.bind_char(c, Thingy::builtin("self-insert"));
362        }
363
364        // Escape to command mode
365        km.bind_char(0x1B, Thingy::builtin("vi-cmd-mode")); // ESC
366
367        // Basic editing
368        km.bind_char(0x08, Thingy::builtin("vi-backward-delete-char")); // Ctrl-H
369        km.bind_char(0x7F, Thingy::builtin("vi-backward-delete-char")); // DEL
370        km.bind_char(0x0D, Thingy::builtin("accept-line")); // Enter
371        km.bind_char(0x09, Thingy::builtin("expand-or-complete")); // Tab
372
373        // Ctrl-C
374        km.bind_char(0x03, Thingy::builtin("send-break"));
375
376        // Ctrl-W
377        km.bind_char(0x17, Thingy::builtin("vi-backward-kill-word"));
378    }
379
380    /// Set up vicmd (vi command mode) keymap bindings
381    fn setup_vicmd_keymap(&self, km: &mut Keymap) {
382        // Movement
383        km.bind_char(b'h', Thingy::builtin("vi-backward-char"));
384        km.bind_char(b'l', Thingy::builtin("vi-forward-char"));
385        km.bind_char(b'j', Thingy::builtin("down-line-or-history"));
386        km.bind_char(b'k', Thingy::builtin("up-line-or-history"));
387        km.bind_char(b'w', Thingy::builtin("vi-forward-word"));
388        km.bind_char(b'W', Thingy::builtin("vi-forward-blank-word"));
389        km.bind_char(b'b', Thingy::builtin("vi-backward-word"));
390        km.bind_char(b'B', Thingy::builtin("vi-backward-blank-word"));
391        km.bind_char(b'e', Thingy::builtin("vi-forward-word-end"));
392        km.bind_char(b'E', Thingy::builtin("vi-forward-blank-word-end"));
393        km.bind_char(b'0', Thingy::builtin("vi-digit-or-beginning-of-line"));
394        km.bind_char(b'^', Thingy::builtin("vi-first-non-blank"));
395        km.bind_char(b'$', Thingy::builtin("vi-end-of-line"));
396
397        // Mode switching
398        km.bind_char(b'i', Thingy::builtin("vi-insert"));
399        km.bind_char(b'I', Thingy::builtin("vi-insert-bol"));
400        km.bind_char(b'a', Thingy::builtin("vi-add-next"));
401        km.bind_char(b'A', Thingy::builtin("vi-add-eol"));
402        km.bind_char(b'o', Thingy::builtin("vi-open-line-below"));
403        km.bind_char(b'O', Thingy::builtin("vi-open-line-above"));
404
405        // Editing
406        km.bind_char(b'x', Thingy::builtin("vi-delete-char"));
407        km.bind_char(b'X', Thingy::builtin("vi-backward-delete-char"));
408        km.bind_char(b'd', Thingy::builtin("vi-delete"));
409        km.bind_char(b'D', Thingy::builtin("vi-kill-eol"));
410        km.bind_char(b'c', Thingy::builtin("vi-change"));
411        km.bind_char(b'C', Thingy::builtin("vi-change-eol"));
412        km.bind_char(b'y', Thingy::builtin("vi-yank"));
413        km.bind_char(b'Y', Thingy::builtin("vi-yank-whole-line"));
414        km.bind_char(b'p', Thingy::builtin("vi-put-after"));
415        km.bind_char(b'P', Thingy::builtin("vi-put-before"));
416        km.bind_char(b'r', Thingy::builtin("vi-replace-chars"));
417        km.bind_char(b'R', Thingy::builtin("vi-replace"));
418        km.bind_char(b's', Thingy::builtin("vi-substitute"));
419        km.bind_char(b'S', Thingy::builtin("vi-change-whole-line"));
420
421        // Search
422        km.bind_char(b'/', Thingy::builtin("vi-history-search-forward"));
423        km.bind_char(b'?', Thingy::builtin("vi-history-search-backward"));
424        km.bind_char(b'n', Thingy::builtin("vi-repeat-search"));
425        km.bind_char(b'N', Thingy::builtin("vi-rev-repeat-search"));
426        km.bind_char(b'f', Thingy::builtin("vi-find-next-char"));
427        km.bind_char(b'F', Thingy::builtin("vi-find-prev-char"));
428        km.bind_char(b't', Thingy::builtin("vi-find-next-char-skip"));
429        km.bind_char(b'T', Thingy::builtin("vi-find-prev-char-skip"));
430        km.bind_char(b';', Thingy::builtin("vi-repeat-find"));
431        km.bind_char(b',', Thingy::builtin("vi-rev-repeat-find"));
432
433        // Undo
434        km.bind_char(b'u', Thingy::builtin("undo"));
435        km.bind_char(0x12, Thingy::builtin("redo")); // Ctrl-R
436
437        // Repeat
438        km.bind_char(b'.', Thingy::builtin("vi-repeat-change"));
439
440        // Digit arguments
441        for c in b'1'..=b'9' {
442            km.bind_char(c, Thingy::builtin("digit-argument"));
443        }
444
445        // Accept line
446        km.bind_char(0x0D, Thingy::builtin("accept-line"));
447
448        // Ctrl-C
449        km.bind_char(0x03, Thingy::builtin("send-break"));
450
451        // Join lines
452        km.bind_char(b'J', Thingy::builtin("vi-join"));
453
454        // Goto
455        km.bind_char(b'G', Thingy::builtin("vi-fetch-history"));
456        km.bind_char(b'g', Thingy::builtin("vi-goto-column")); // Actually prefix, but simplified
457    }
458
459    /// Get a keymap by name
460    pub fn get(&self, name: &str) -> Option<Arc<Keymap>> {
461        self.keymaps.get(name).cloned()
462    }
463
464    /// Set the current keymap
465    pub fn select(&mut self, name: &str) -> bool {
466        if let Some(km) = self.keymaps.get(name) {
467            self.current = Some(Arc::clone(km));
468            self.current_name = name.to_string();
469            true
470        } else {
471            false
472        }
473    }
474
475    /// Link a new name to an existing keymap
476    pub fn link(&mut self, oldname: &str, newname: &str) -> bool {
477        if let Some(km) = self.keymaps.get(oldname) {
478            self.keymaps.insert(newname.to_string(), Arc::clone(km));
479            true
480        } else {
481            false
482        }
483    }
484
485    /// Delete a keymap name
486    pub fn delete(&mut self, name: &str) -> bool {
487        // Don't allow deleting immortal keymaps
488        if name == "main" || name == "emacs" || name == "viins" || name == "vicmd" {
489            return false;
490        }
491        self.keymaps.remove(name).is_some()
492    }
493
494    /// Look up a key in the current keymap
495    pub fn lookup_key(&self, c: char) -> Option<Thingy> {
496        let km = self.local.as_ref().or(self.current.as_ref())?;
497
498        // For now, just look up single byte
499        if c as u32 <= 255 {
500            km.first[c as usize].clone()
501        } else {
502            None
503        }
504    }
505
506    /// Look up a key sequence in the current keymap
507    pub fn lookup_seq(&self, seq: &[u8]) -> Option<&KeyBinding> {
508        let km = self.local.as_ref().or(self.current.as_ref())?;
509        km.lookup_seq(seq)
510    }
511
512    /// Check if a sequence is a prefix in the current keymap
513    pub fn is_prefix(&self, seq: &[u8]) -> bool {
514        if let Some(km) = self.local.as_ref().or(self.current.as_ref()) {
515            km.is_prefix(seq)
516        } else {
517            false
518        }
519    }
520
521    /// List all keymap names
522    /// Port of bin_bindkey_lsmaps() from zle_keymap.c
523    pub fn list_names(&self) -> Vec<&String> {
524        self.keymaps.keys().collect()
525    }
526
527    /// Create a new empty keymap
528    /// Port of newkeymap() from zle_keymap.c
529    pub fn new_keymap(&mut self, name: &str) -> bool {
530        if self.keymaps.contains_key(name) {
531            return false;
532        }
533
534        let mut km = Keymap::new();
535        km.primary = Some(name.to_string());
536        self.keymaps.insert(name.to_string(), Arc::new(km));
537        true
538    }
539
540    /// Copy a keymap to a new name
541    /// Port of copyto from bin_bindkey_new
542    pub fn copy_keymap(&mut self, src: &str, dst: &str) -> bool {
543        if let Some(src_km) = self.keymaps.get(src) {
544            let new_km = (**src_km).clone();
545            self.keymaps.insert(dst.to_string(), Arc::new(new_km));
546            true
547        } else {
548            false
549        }
550    }
551
552    /// Set a local keymap (temporary override)
553    /// Port of selectlocalmap() from zle_keymap.c
554    pub fn select_local_map(&mut self, name: Option<&str>) {
555        self.local = name.and_then(|n| self.keymaps.get(n).cloned());
556    }
557
558    /// Re-select keymap after a widget completes
559    /// Port of reselectkeymap() from zle_keymap.c
560    pub fn reselect_keymap(&mut self) {
561        self.local = None;
562    }
563
564    /// Read a key command from the current keymap
565    /// Port of readcommand() from zle_keymap.c
566    pub fn read_command(&self, keys: &[u8]) -> Option<Thingy> {
567        let km = self.local.as_ref().or(self.current.as_ref())?;
568
569        if keys.len() == 1 {
570            km.first[keys[0] as usize].clone()
571        } else {
572            km.lookup_seq(keys).and_then(|kb| kb.bind.clone())
573        }
574    }
575
576    /// Get the key sequence from buffer
577    /// Port of getkeybuf() from zle_keymap.c
578    pub fn get_keybuf(&self) -> &[u8] {
579        &self.keybuf
580    }
581
582    /// Add to key buffer
583    /// Port of addkeybuf() from zle_keymap.c
584    pub fn add_keybuf(&mut self, c: u8) {
585        self.keybuf.push(c);
586    }
587
588    /// Clear key buffer
589    pub fn clear_keybuf(&mut self) {
590        self.keybuf.clear();
591    }
592
593    /// Check if current keymap is emacs
594    pub fn is_emacs(&self) -> bool {
595        self.current_name == "emacs" || self.current_name == "main"
596    }
597
598    /// Check if current keymap is vi insert
599    pub fn is_vi_insert(&self) -> bool {
600        self.current_name == "viins"
601    }
602
603    /// Check if current keymap is vi command
604    pub fn is_vi_cmd(&self) -> bool {
605        self.current_name == "vicmd"
606    }
607
608    /// Get keymap command for a key
609    /// Port of getkeymapcmd() from zle_keymap.c
610    pub fn get_keymap_cmd(&self, km: &Keymap, key: u8) -> Option<Thingy> {
611        km.first[key as usize].clone()
612    }
613
614    /// Check if key is prefix in keymap
615    /// Port of keyisprefix() from zle_keymap.c
616    pub fn key_is_prefix(&self, km: &Keymap, key: u8) -> bool {
617        km.multi.keys().any(|k| k.len() > 1 && k[0] == key)
618    }
619
620    /// Bind key in current keymap
621    /// Port of keybind() from zle_keymap.c  
622    pub fn keybind(&mut self, seq: &[u8], thingy: Thingy) -> bool {
623        if let Some(km) = self.keymaps.get_mut(&self.current_name) {
624            if let Some(km_mut) = Arc::get_mut(km) {
625                if seq.len() == 1 {
626                    km_mut.bind_char(seq[0], thingy);
627                } else {
628                    km_mut.bind_seq(seq, thingy);
629                }
630                return true;
631            }
632        }
633        false
634    }
635
636    /// Unbind key in current keymap
637    pub fn keyunbind(&mut self, seq: &[u8]) -> bool {
638        if let Some(km) = self.keymaps.get_mut(&self.current_name) {
639            if let Some(km_mut) = Arc::get_mut(km) {
640                km_mut.unbind_seq(seq);
641                return true;
642            }
643        }
644        false
645    }
646
647    /// Get bindings for listing
648    /// Port of scankeymap() / scanbindlist() from zle_keymap.c
649    pub fn scan_keymap(&self, name: &str) -> Vec<(Vec<u8>, String)> {
650        let mut bindings = Vec::new();
651
652        if let Some(km) = self.keymaps.get(name) {
653            // Single char bindings
654            for (i, opt) in km.first.iter().enumerate() {
655                if let Some(t) = opt {
656                    bindings.push((vec![i as u8], t.name.clone()));
657                }
658            }
659
660            // Multi-char bindings
661            for (seq, kb) in &km.multi {
662                if let Some(ref t) = kb.bind {
663                    bindings.push((seq.clone(), t.name.clone()));
664                } else if let Some(ref s) = kb.str {
665                    bindings.push((seq.clone(), format!("\"{}\"", s)));
666                }
667            }
668        }
669
670        bindings.sort_by(|a, b| a.0.cmp(&b.0));
671        bindings
672    }
673
674    /// Set keymap via ZLE (zle -K)
675    /// Port of zlesetkeymap() from zle_keymap.c
676    pub fn zle_set_keymap(&mut self, name: &str) -> bool {
677        self.select(name)
678    }
679
680    /// Reference keymap by name
681    /// Port of refkeymap_by_name() from zle_keymap.c
682    pub fn ref_keymap_by_name(&self, name: &str) -> Option<Arc<Keymap>> {
683        self.keymaps.get(name).cloned()
684    }
685
686    /// Initialize keymaps
687    /// Port of init_keymaps() from zle_keymap.c
688    pub fn init_keymaps(&mut self) {
689        self.create_default_keymaps();
690    }
691
692    /// Cleanup keymaps
693    /// Port of cleanup_keymaps() from zle_keymap.c
694    pub fn cleanup_keymaps(&mut self) {
695        self.keymaps.clear();
696        self.current = None;
697        self.local = None;
698    }
699}
700
701/// Bindkey builtin implementation
702/// Port of bin_bindkey() from zle_keymap.c
703pub fn bin_bindkey(args: &[String], opts: BindkeyOpts) -> i32 {
704    // This would be called from the shell's builtin system
705    // For now, just a stub that documents the interface
706    let _ = (args, opts);
707    0
708}
709
710/// Bindkey options
711#[derive(Debug, Default)]
712pub struct BindkeyOpts {
713    pub list: bool,             // -l
714    pub list_all: bool,         // -L
715    pub delete: bool,           // -d
716    pub remove: bool,           // -r
717    pub meta: bool,             // -m
718    pub new_keymap: bool,       // -N
719    pub keymap: Option<String>, // -M keymap
720    pub prefix: Option<String>, // -p prefix
721}