Skip to main content

zsh/zle/
keymaps.rs

1//! ZLE Keymap management
2
3use parking_lot::ReentrantMutex;
4use std::cell::RefCell;
5use std::collections::HashMap;
6use std::collections::VecDeque;
7use std::sync::Mutex;
8
9/// ZLE state for widget execution
10#[derive(Debug)]
11pub struct ZleState {
12    /// Current line buffer
13    pub buffer: String,
14    /// Cursor position (in characters)
15    pub cursor: usize,
16    /// Mark position
17    pub mark: usize,
18    /// Numeric argument
19    pub numeric_arg: Option<i32>,
20    /// In insert mode (vs overwrite)
21    pub insert_mode: bool,
22    /// Last character for find commands
23    pub last_find_char: Option<char>,
24    /// Find direction (true = forward)
25    pub find_forward: bool,
26    /// Undo history
27    undo_history: Vec<(String, usize)>,
28    /// Redo stack
29    pub undo_stack: Vec<(String, usize)>,
30    /// Kill ring
31    kill_ring: VecDeque<String>,
32    /// Max kill ring size
33    kill_ring_max: usize,
34    /// Vi command mode flag
35    pub vi_cmd_mode: bool,
36    /// Current keymap
37    pub keymap: KeymapName,
38    /// Last yank position for yank-pop
39    last_yank_pos: Option<(usize, usize)>,
40    /// Region is active (for visual selection)
41    pub region_active: bool,
42}
43
44impl Default for ZleState {
45    fn default() -> Self {
46        Self::new()
47    }
48}
49
50impl ZleState {
51    pub fn new() -> Self {
52        ZleState {
53            buffer: String::new(),
54            cursor: 0,
55            mark: 0,
56            numeric_arg: None,
57            insert_mode: true,
58            last_find_char: None,
59            find_forward: true,
60            undo_history: Vec::new(),
61            undo_stack: Vec::new(),
62            kill_ring: VecDeque::new(),
63            kill_ring_max: 8,
64            vi_cmd_mode: false,
65            keymap: KeymapName::Emacs,
66            last_yank_pos: None,
67            region_active: false,
68        }
69    }
70
71    /// Save current state for undo
72    pub fn save_undo(&mut self) {
73        self.undo_history.push((self.buffer.clone(), self.cursor));
74        if self.undo_history.len() > 100 {
75            self.undo_history.remove(0);
76        }
77    }
78
79    /// Undo last change
80    pub fn undo(&mut self) -> bool {
81        if let Some((buffer, cursor)) = self.undo_history.pop() {
82            self.undo_stack.push((self.buffer.clone(), self.cursor));
83            self.buffer = buffer;
84            self.cursor = cursor;
85            true
86        } else {
87            false
88        }
89    }
90
91    /// Redo last undone change
92    pub fn redo(&mut self) -> bool {
93        if let Some((buffer, cursor)) = self.undo_stack.pop() {
94            self.undo_history.push((self.buffer.clone(), self.cursor));
95            self.buffer = buffer;
96            self.cursor = cursor;
97            true
98        } else {
99            false
100        }
101    }
102
103    /// Add text to kill ring
104    pub fn kill_add(&mut self, text: &str) {
105        self.kill_ring.push_front(text.to_string());
106        if self.kill_ring.len() > self.kill_ring_max {
107            self.kill_ring.pop_back();
108        }
109    }
110
111    /// Yank from kill ring
112    pub fn yank(&mut self) -> Option<String> {
113        if let Some(text) = self.kill_ring.front().cloned() {
114            let start = self.cursor;
115            // Insert text at cursor
116            let chars: Vec<char> = self.buffer.chars().collect();
117            let mut new_buffer = String::new();
118            for (i, c) in chars.iter().enumerate() {
119                if i == self.cursor {
120                    new_buffer.push_str(&text);
121                }
122                new_buffer.push(*c);
123            }
124            if self.cursor >= chars.len() {
125                new_buffer.push_str(&text);
126            }
127            self.buffer = new_buffer;
128            self.cursor += text.chars().count();
129            self.last_yank_pos = Some((start, self.cursor));
130            Some(text)
131        } else {
132            None
133        }
134    }
135
136    /// Yank-pop: replace last yank with next kill ring entry
137    pub fn yank_pop(&mut self) -> Option<String> {
138        if let Some((start, end)) = self.last_yank_pos {
139            // Remove the previous yank
140            let chars: Vec<char> = self.buffer.chars().collect();
141            let mut new_buffer = String::new();
142            for (i, c) in chars.iter().enumerate() {
143                if i < start || i >= end {
144                    new_buffer.push(*c);
145                }
146            }
147            self.buffer = new_buffer;
148            self.cursor = start;
149
150            // Rotate kill ring
151            if let Some(front) = self.kill_ring.pop_front() {
152                self.kill_ring.push_back(front);
153            }
154
155            // Yank the new top
156            self.yank()
157        } else {
158            None
159        }
160    }
161
162    /// Get text from kill ring (without inserting)
163    pub fn kill_yank(&self) -> Option<&str> {
164        self.kill_ring.front().map(|s| s.as_str())
165    }
166
167    /// Rotate kill ring
168    pub fn kill_rotate(&mut self) {
169        if let Some(front) = self.kill_ring.pop_front() {
170            self.kill_ring.push_back(front);
171        }
172    }
173}
174
175/// Global ZLE manager (accessed via zle() function)
176pub struct ZleManager {
177    /// Keymaps
178    pub keymaps: HashMap<KeymapName, Keymap>,
179    /// User-defined widgets
180    pub user_widgets: HashMap<String, String>,
181    /// Currently-active keymap — written by `bindkey -A NAME main`
182    /// and `zle -K NAME`. Distinct from per-line vi-mode state on
183    /// ZleState. Default Emacs to match zsh's startup state.
184    pub active_keymap: KeymapName,
185}
186
187impl Default for ZleManager {
188    fn default() -> Self {
189        Self::new()
190    }
191}
192
193impl ZleManager {
194    pub fn new() -> Self {
195        let mut mgr = ZleManager {
196            keymaps: HashMap::new(),
197            user_widgets: HashMap::new(),
198            active_keymap: KeymapName::Emacs,
199        };
200
201        mgr.keymaps
202            .insert(KeymapName::Main, Keymap::emacs_default());
203        mgr.keymaps
204            .insert(KeymapName::Emacs, Keymap::emacs_default());
205        mgr.keymaps
206            .insert(KeymapName::ViInsert, Keymap::viins_default());
207        mgr.keymaps
208            .insert(KeymapName::ViCommand, Keymap::vicmd_default());
209        mgr.keymaps.insert(KeymapName::Isearch, Keymap::new());
210        mgr.keymaps.insert(KeymapName::Command, Keymap::new());
211        mgr.keymaps.insert(KeymapName::MenuSelect, Keymap::new());
212
213        mgr
214    }
215
216    /// Define a user widget
217    pub fn define_widget(&mut self, name: &str, func: &str) {
218        self.user_widgets.insert(name.to_string(), func.to_string());
219    }
220
221    /// Delete a user widget — port of zle -D from zsh/Src/Zle/zle_main.c
222    /// bin_zle case 'D'. Returns true iff a user widget by that name
223    /// existed; built-in widgets cannot be deleted.
224    pub fn delete_widget(&mut self, name: &str) -> bool {
225        self.user_widgets.remove(name).is_some()
226    }
227
228    /// Alias one widget to another — port of zle -A from
229    /// zsh/Src/Zle/zle_main.c bin_zle case 'A'. The new name dispatches
230    /// to the existing target's function. Returns true iff the target
231    /// resolves (built-in or already-user-defined).
232    pub fn alias_widget(&mut self, new_name: &str, target: &str) -> bool {
233        // If target is a user widget, copy its function name.
234        if let Some(func) = self.user_widgets.get(target).cloned() {
235            self.user_widgets.insert(new_name.to_string(), func);
236            return true;
237        }
238        // If target is a built-in widget, register the alias as a user
239        // widget that maps to the built-in name.
240        if BUILTIN_WIDGETS.contains(&target) {
241            self.user_widgets
242                .insert(new_name.to_string(), target.to_string());
243            return true;
244        }
245        false
246    }
247
248    /// Select the active keymap by name (zle -K NAME). Direct port of
249    /// zle_main.c bin_zle case 'K'. Returns true iff the named keymap
250    /// exists; canonical zsh names: main / emacs / viins / vicmd /
251    /// isearch / command / menuselect (case-sensitive). Unknown name
252    /// leaves the active keymap unchanged.
253    pub fn select_keymap(&mut self, name: &str) -> bool {
254        let target = match name {
255            "main" => KeymapName::Main,
256            "emacs" => KeymapName::Emacs,
257            "viins" => KeymapName::ViInsert,
258            "vicmd" => KeymapName::ViCommand,
259            "isearch" => KeymapName::Isearch,
260            "command" => KeymapName::Command,
261            "menuselect" => KeymapName::MenuSelect,
262            _ => return false,
263        };
264        if !self.keymaps.contains_key(&target) {
265            return false;
266        }
267        self.active_keymap = target;
268        true
269    }
270
271    /// Get a widget by name (returns the function name if user-defined)
272    pub fn get_widget<'a>(&'a self, name: &'a str) -> Option<&'a str> {
273        // Check user widgets first
274        if let Some(func) = self.user_widgets.get(name) {
275            return Some(func);
276        }
277        // Check builtin widgets
278        if BUILTIN_WIDGETS.contains(&name) {
279            return Some(name);
280        }
281        None
282    }
283
284    /// Bind a key in a keymap
285    pub fn bind_key(&mut self, keymap: KeymapName, key: &str, widget: &str) {
286        if let Some(km) = self.keymaps.get_mut(&keymap) {
287            km.bind(key, widget);
288        }
289    }
290
291    /// Unbind a key from a keymap
292    pub fn unbind_key(&mut self, keymap: KeymapName, key: &str) {
293        if let Some(km) = self.keymaps.get_mut(&keymap) {
294            km.unbind(key);
295        }
296    }
297
298    /// Execute a widget (stub - actual execution handled elsewhere)
299    pub fn execute_widget(
300        &mut self,
301        name: &str,
302        _key: Option<char>,
303    ) -> super::widgets::WidgetResult {
304        if self.get_widget(name).is_some() {
305            super::widgets::WidgetResult::Ok
306        } else {
307            super::widgets::WidgetResult::Error(format!("Unknown widget: {}", name))
308        }
309    }
310
311    /// List all widget names
312    pub fn list_widgets(&self) -> Vec<&str> {
313        let mut widgets: Vec<&str> = BUILTIN_WIDGETS.to_vec();
314
315        for name in self.user_widgets.keys() {
316            widgets.push(name.as_str());
317        }
318
319        widgets
320    }
321}
322
323/// All builtin widget names
324const BUILTIN_WIDGETS: &[&str] = &[
325    "accept-line",
326    "accept-and-hold",
327    "backward-char",
328    "backward-delete-char",
329    "backward-kill-line",
330    "backward-kill-word",
331    "backward-word",
332    "beep",
333    "beginning-of-history",
334    "beginning-of-line",
335    "capitalize-word",
336    "clear-screen",
337    "complete-word",
338    "copy-region-as-kill",
339    "delete-char",
340    "delete-char-or-list",
341    "down-case-word",
342    "down-history",
343    "down-line-or-history",
344    "down-line-or-search",
345    "end-of-history",
346    "end-of-line",
347    "exchange-point-and-mark",
348    "execute-named-cmd",
349    "expand-or-complete",
350    "forward-char",
351    "forward-word",
352    "history-incremental-search-backward",
353    "history-incremental-search-forward",
354    "kill-buffer",
355    "kill-line",
356    "kill-region",
357    "kill-whole-line",
358    "kill-word",
359    "overwrite-mode",
360    "quoted-insert",
361    "redisplay",
362    "redo",
363    "self-insert",
364    "send-break",
365    "set-mark-command",
366    "transpose-chars",
367    "transpose-words",
368    "undo",
369    "up-case-word",
370    "up-history",
371    "up-line-or-history",
372    "up-line-or-search",
373    "vi-add-eol",
374    "vi-add-next",
375    "vi-backward-blank-word",
376    "vi-backward-char",
377    "vi-backward-delete-char",
378    "vi-backward-word",
379    "vi-change",
380    "vi-change-eol",
381    "vi-change-whole-line",
382    "vi-cmd-mode",
383    "vi-delete",
384    "vi-delete-char",
385    "vi-end-of-line",
386    "vi-find-next-char",
387    "vi-find-next-char-skip",
388    "vi-find-prev-char",
389    "vi-find-prev-char-skip",
390    "vi-first-non-blank",
391    "vi-forward-blank-word",
392    "vi-forward-char",
393    "vi-forward-word",
394    "vi-forward-word-end",
395    "vi-insert",
396    "vi-insert-bol",
397    "vi-join",
398    "vi-kill-eol",
399    "vi-open-line-above",
400    "vi-open-line-below",
401    "vi-put-after",
402    "vi-put-before",
403    "vi-repeat-change",
404    "vi-repeat-find",
405    "vi-repeat-search",
406    "vi-replace",
407    "vi-replace-chars",
408    "vi-rev-repeat-find",
409    "vi-rev-repeat-search",
410    "vi-substitute",
411    "vi-yank",
412    "vi-yank-whole-line",
413    "which-command",
414    "yank",
415    "yank-pop",
416];
417
418thread_local! {
419    static ZLE_MANAGER: RefCell<ZleManager> = RefCell::new(ZleManager::new());
420}
421
422/// Guard type for accessing ZLE manager
423pub struct ZleGuard<'a>(std::cell::RefMut<'a, ZleManager>);
424
425impl<'a> std::ops::Deref for ZleGuard<'a> {
426    type Target = ZleManager;
427    fn deref(&self) -> &Self::Target {
428        &self.0
429    }
430}
431
432impl<'a> std::ops::DerefMut for ZleGuard<'a> {
433    fn deref_mut(&mut self) -> &mut Self::Target {
434        &mut self.0
435    }
436}
437
438/// Get the global ZLE manager
439pub fn zle() -> ZleGuard<'static> {
440    ZLE_MANAGER.with(|m| {
441        // SAFETY: The RefCell is thread-local so this is safe
442        ZleGuard(unsafe { std::mem::transmute(m.borrow_mut()) })
443    })
444}
445
446/// Keymap identifier
447#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
448pub enum KeymapName {
449    Emacs,
450    ViInsert,
451    ViCommand,
452    Main,       // alias for current main keymap
453    Isearch,    // incremental search
454    Command,    // command mode
455    MenuSelect, // menu selection
456}
457
458impl KeymapName {
459    pub fn from_str(s: &str) -> Option<Self> {
460        match s {
461            "emacs" => Some(Self::Emacs),
462            "viins" => Some(Self::ViInsert),
463            "vicmd" => Some(Self::ViCommand),
464            "main" => Some(Self::Main),
465            "isearch" => Some(Self::Isearch),
466            "command" => Some(Self::Command),
467            "menuselect" => Some(Self::MenuSelect),
468            _ => None,
469        }
470    }
471
472    pub fn as_str(&self) -> &'static str {
473        match self {
474            Self::Emacs => "emacs",
475            Self::ViInsert => "viins",
476            Self::ViCommand => "vicmd",
477            Self::Main => "main",
478            Self::Isearch => "isearch",
479            Self::Command => "command",
480            Self::MenuSelect => "menuselect",
481        }
482    }
483}
484
485/// A keymap - mapping from key sequences to widget names
486#[derive(Debug, Clone)]
487pub struct Keymap {
488    bindings: HashMap<String, String>,
489}
490
491impl Default for Keymap {
492    fn default() -> Self {
493        Self::new()
494    }
495}
496
497impl Keymap {
498    pub fn new() -> Self {
499        Self {
500            bindings: HashMap::new(),
501        }
502    }
503
504    /// Create default emacs keymap
505    pub fn emacs_default() -> Self {
506        let mut km = Self::new();
507
508        // Movement
509        km.bind("^F", "forward-char");
510        km.bind("^B", "backward-char");
511        km.bind("^A", "beginning-of-line");
512        km.bind("^E", "end-of-line");
513        km.bind("\\ef", "forward-word"); // Alt-f
514        km.bind("\\eb", "backward-word"); // Alt-b
515
516        // Editing
517        km.bind("^D", "delete-char");
518        km.bind("^H", "backward-delete-char");
519        km.bind("^?", "backward-delete-char"); // Backspace
520        km.bind("^K", "kill-line");
521        km.bind("^U", "backward-kill-line");
522        km.bind("\\ed", "kill-word"); // Alt-d
523        km.bind("\\e^?", "backward-kill-word"); // Alt-Backspace
524        km.bind("^W", "backward-kill-word");
525        km.bind("^Y", "yank");
526        km.bind("\\ey", "yank-pop"); // Alt-y
527
528        // Undo
529        km.bind("^_", "undo");
530        km.bind("^X^U", "undo");
531        km.bind("\\e_", "redo"); // Alt-_
532
533        // History
534        km.bind("^P", "up-line-or-history");
535        km.bind("^N", "down-line-or-history");
536        km.bind("\\e<", "beginning-of-history");
537        km.bind("\\e>", "end-of-history");
538        km.bind("^R", "history-incremental-search-backward");
539        km.bind("^S", "history-incremental-search-forward");
540
541        // Completion
542        km.bind("^I", "expand-or-complete"); // Tab
543        km.bind("\\e\\e", "complete-word");
544
545        // Accept/misc
546        km.bind("^J", "accept-line"); // Enter
547        km.bind("^M", "accept-line"); // Enter
548        km.bind("^G", "send-break");
549        km.bind("^C", "send-break");
550        km.bind("^L", "clear-screen");
551
552        // Transpose
553        km.bind("^T", "transpose-chars");
554        km.bind("\\et", "transpose-words");
555
556        // Case
557        km.bind("\\ec", "capitalize-word");
558        km.bind("\\el", "down-case-word");
559        km.bind("\\eu", "up-case-word");
560
561        // Region
562        km.bind("^@", "set-mark-command"); // Ctrl-Space
563        km.bind("^X^X", "exchange-point-and-mark");
564        km.bind("\\ew", "copy-region-as-kill");
565
566        km
567    }
568
569    /// Create default vi insert mode keymap
570    pub fn viins_default() -> Self {
571        let mut km = Self::new();
572
573        // Enter command mode
574        km.bind("^[", "vi-cmd-mode"); // Escape
575
576        // Basic editing (same as emacs)
577        km.bind("^H", "backward-delete-char");
578        km.bind("^?", "backward-delete-char");
579        km.bind("^W", "backward-kill-word");
580        km.bind("^U", "backward-kill-line");
581
582        // Accept
583        km.bind("^J", "accept-line");
584        km.bind("^M", "accept-line");
585
586        // Completion
587        km.bind("^I", "expand-or-complete");
588
589        // History
590        km.bind("^P", "up-line-or-history");
591        km.bind("^N", "down-line-or-history");
592
593        km
594    }
595
596    /// Create default vi command mode keymap
597    pub fn vicmd_default() -> Self {
598        let mut km = Self::new();
599
600        // Enter insert mode
601        km.bind("i", "vi-insert");
602        km.bind("a", "vi-add-next");
603        km.bind("I", "vi-insert-bol");
604        km.bind("A", "vi-add-eol");
605
606        // Movement
607        km.bind("h", "backward-char");
608        km.bind("l", "forward-char");
609        km.bind("w", "forward-word");
610        km.bind("b", "backward-word");
611        km.bind("0", "beginning-of-line");
612        km.bind("^", "beginning-of-line");
613        km.bind("$", "end-of-line");
614
615        // Delete
616        km.bind("x", "delete-char");
617        km.bind("X", "backward-delete-char");
618        km.bind("dd", "kill-whole-line");
619        km.bind("dw", "kill-word");
620        km.bind("db", "backward-kill-word");
621        km.bind("d$", "kill-line");
622        km.bind("d0", "backward-kill-line");
623
624        // Yank/paste
625        km.bind("y", "vi-yank");
626        km.bind("p", "vi-put-after");
627        km.bind("P", "vi-put-before");
628
629        // History
630        km.bind("k", "up-line-or-history");
631        km.bind("j", "down-line-or-history");
632        km.bind("/", "history-incremental-search-backward");
633        km.bind("?", "history-incremental-search-forward");
634        km.bind("n", "vi-repeat-search");
635        km.bind("N", "vi-rev-repeat-search");
636
637        // Undo
638        km.bind("u", "undo");
639        km.bind("^R", "redo");
640
641        // Accept
642        km.bind("^J", "accept-line");
643        km.bind("^M", "accept-line");
644
645        km
646    }
647
648    /// Bind a key sequence to a widget
649    pub fn bind(&mut self, keys: &str, widget: &str) {
650        let normalized = Self::normalize_keys(keys);
651        self.bindings.insert(normalized, widget.to_string());
652    }
653
654    /// Unbind a key sequence
655    pub fn unbind(&mut self, keys: &str) {
656        let normalized = Self::normalize_keys(keys);
657        self.bindings.remove(&normalized);
658    }
659
660    /// Look up a key sequence
661    pub fn lookup(&self, keys: &str) -> Option<&str> {
662        let normalized = Self::normalize_keys(keys);
663        self.bindings.get(&normalized).map(|s| s.as_str())
664    }
665
666    /// Check if keys could be a prefix of a binding
667    pub fn has_prefix(&self, keys: &str) -> bool {
668        let normalized = Self::normalize_keys(keys);
669        self.bindings
670            .keys()
671            .any(|k| k.starts_with(&normalized) && k != &normalized)
672    }
673
674    /// List all bindings, sorted by key sequence. zsh emits bindkey
675    /// listings in a stable order (table-walk + iterator); HashMap
676    /// iteration in Rust is randomized, so the output flickered
677    /// between runs. Sort here so consumers (bindkey -L, the
678    /// $widgets / $keymaps introspection paths) see deterministic
679    /// output.
680    pub fn list_bindings(&self) -> impl Iterator<Item = (&str, &str)> {
681        let mut sorted: Vec<(&String, &String)> = self.bindings.iter().collect();
682        sorted.sort_by(|a, b| a.0.cmp(b.0));
683        sorted.into_iter().map(|(k, v)| (k.as_str(), v.as_str()))
684    }
685
686    /// Normalize key notation
687    /// Converts various formats to a canonical form:
688    /// ^X -> control-X
689    /// \eX -> escape X (meta)
690    /// \C-x -> control-x
691    /// \M-x -> meta-x
692    fn normalize_keys(keys: &str) -> String {
693        let mut result = String::new();
694        let mut chars = keys.chars().peekable();
695
696        while let Some(c) = chars.next() {
697            match c {
698                '^' => {
699                    // Control character
700                    if let Some(&next) = chars.peek() {
701                        chars.next();
702                        let ctrl_char = if next == '?' {
703                            '\x7f' // DEL
704                        } else if next == '@' {
705                            '\x00' // NUL
706                        } else if next == '[' {
707                            '\x1b' // ESC
708                        } else {
709                            // Ctrl-A through Ctrl-Z, etc.
710                            ((next.to_ascii_uppercase() as u8) & 0x1f) as char
711                        };
712                        result.push(ctrl_char);
713                    } else {
714                        result.push(c);
715                    }
716                }
717                '\\' => {
718                    // Escape sequence
719                    if let Some(&next) = chars.peek() {
720                        match next {
721                            'e' | 'E' => {
722                                chars.next();
723                                result.push('\x1b'); // ESC
724                            }
725                            'C' => {
726                                chars.next();
727                                if chars.peek() == Some(&'-') {
728                                    chars.next();
729                                    if let Some(&ctrl_char) = chars.peek() {
730                                        chars.next();
731                                        let ctrl =
732                                            ((ctrl_char.to_ascii_uppercase() as u8) & 0x1f) as char;
733                                        result.push(ctrl);
734                                    }
735                                }
736                            }
737                            'M' => {
738                                chars.next();
739                                if chars.peek() == Some(&'-') {
740                                    chars.next();
741                                    result.push('\x1b'); // ESC prefix for meta
742                                    if let Some(&meta_char) = chars.peek() {
743                                        chars.next();
744                                        result.push(meta_char);
745                                    }
746                                }
747                            }
748                            'n' => {
749                                chars.next();
750                                result.push('\n');
751                            }
752                            't' => {
753                                chars.next();
754                                result.push('\t');
755                            }
756                            'r' => {
757                                chars.next();
758                                result.push('\r');
759                            }
760                            '\\' => {
761                                chars.next();
762                                result.push('\\');
763                            }
764                            _ => {
765                                result.push(c);
766                            }
767                        }
768                    } else {
769                        result.push(c);
770                    }
771                }
772                _ => result.push(c),
773            }
774        }
775
776        result
777    }
778}
779
780#[cfg(test)]
781mod tests {
782    use super::*;
783
784    #[test]
785    fn test_normalize_keys() {
786        assert_eq!(Keymap::normalize_keys("^A"), "\x01");
787        assert_eq!(Keymap::normalize_keys("^?"), "\x7f");
788        assert_eq!(Keymap::normalize_keys("\\ef"), "\x1bf");
789        assert_eq!(Keymap::normalize_keys("\\C-a"), "\x01");
790        assert_eq!(Keymap::normalize_keys("\\M-x"), "\x1bx");
791    }
792
793    #[test]
794    fn test_keymap_bind_lookup() {
795        let mut km = Keymap::new();
796        km.bind("^A", "beginning-of-line");
797
798        assert_eq!(km.lookup("^A"), Some("beginning-of-line"));
799        assert_eq!(km.lookup("\x01"), Some("beginning-of-line"));
800    }
801
802    #[test]
803    fn test_has_prefix() {
804        let mut km = Keymap::new();
805        km.bind("^X^U", "undo");
806
807        assert!(km.has_prefix("^X"));
808        assert!(!km.has_prefix("^X^U"));
809        assert!(!km.has_prefix("^A"));
810    }
811}