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    user_widgets: HashMap<String, String>,
181}
182
183impl Default for ZleManager {
184    fn default() -> Self {
185        Self::new()
186    }
187}
188
189impl ZleManager {
190    pub fn new() -> Self {
191        let mut mgr = ZleManager {
192            keymaps: HashMap::new(),
193            user_widgets: HashMap::new(),
194        };
195
196        mgr.keymaps
197            .insert(KeymapName::Main, Keymap::emacs_default());
198        mgr.keymaps
199            .insert(KeymapName::Emacs, Keymap::emacs_default());
200        mgr.keymaps
201            .insert(KeymapName::ViInsert, Keymap::viins_default());
202        mgr.keymaps
203            .insert(KeymapName::ViCommand, Keymap::vicmd_default());
204        mgr.keymaps.insert(KeymapName::Isearch, Keymap::new());
205        mgr.keymaps.insert(KeymapName::Command, Keymap::new());
206        mgr.keymaps.insert(KeymapName::MenuSelect, Keymap::new());
207
208        mgr
209    }
210
211    /// Define a user widget
212    pub fn define_widget(&mut self, name: &str, func: &str) {
213        self.user_widgets.insert(name.to_string(), func.to_string());
214    }
215
216    /// Get a widget by name (returns the function name if user-defined)
217    pub fn get_widget<'a>(&'a self, name: &'a str) -> Option<&'a str> {
218        // Check user widgets first
219        if let Some(func) = self.user_widgets.get(name) {
220            return Some(func);
221        }
222        // Check builtin widgets
223        if BUILTIN_WIDGETS.contains(&name) {
224            return Some(name);
225        }
226        None
227    }
228
229    /// Bind a key in a keymap
230    pub fn bind_key(&mut self, keymap: KeymapName, key: &str, widget: &str) {
231        if let Some(km) = self.keymaps.get_mut(&keymap) {
232            km.bind(key, widget);
233        }
234    }
235
236    /// Unbind a key from a keymap
237    pub fn unbind_key(&mut self, keymap: KeymapName, key: &str) {
238        if let Some(km) = self.keymaps.get_mut(&keymap) {
239            km.unbind(key);
240        }
241    }
242
243    /// Execute a widget (stub - actual execution handled elsewhere)
244    pub fn execute_widget(
245        &mut self,
246        name: &str,
247        _key: Option<char>,
248    ) -> super::widgets::WidgetResult {
249        if self.get_widget(name).is_some() {
250            super::widgets::WidgetResult::Ok
251        } else {
252            super::widgets::WidgetResult::Error(format!("Unknown widget: {}", name))
253        }
254    }
255
256    /// List all widget names
257    pub fn list_widgets(&self) -> Vec<&str> {
258        let mut widgets: Vec<&str> = BUILTIN_WIDGETS.to_vec();
259
260        for name in self.user_widgets.keys() {
261            widgets.push(name.as_str());
262        }
263
264        widgets
265    }
266}
267
268/// All builtin widget names
269const BUILTIN_WIDGETS: &[&str] = &[
270    "accept-line",
271    "accept-and-hold",
272    "backward-char",
273    "backward-delete-char",
274    "backward-kill-line",
275    "backward-kill-word",
276    "backward-word",
277    "beep",
278    "beginning-of-history",
279    "beginning-of-line",
280    "capitalize-word",
281    "clear-screen",
282    "complete-word",
283    "copy-region-as-kill",
284    "delete-char",
285    "delete-char-or-list",
286    "down-case-word",
287    "down-history",
288    "down-line-or-history",
289    "down-line-or-search",
290    "end-of-history",
291    "end-of-line",
292    "exchange-point-and-mark",
293    "execute-named-cmd",
294    "expand-or-complete",
295    "forward-char",
296    "forward-word",
297    "history-incremental-search-backward",
298    "history-incremental-search-forward",
299    "kill-buffer",
300    "kill-line",
301    "kill-region",
302    "kill-whole-line",
303    "kill-word",
304    "overwrite-mode",
305    "quoted-insert",
306    "redisplay",
307    "redo",
308    "self-insert",
309    "send-break",
310    "set-mark-command",
311    "transpose-chars",
312    "transpose-words",
313    "undo",
314    "up-case-word",
315    "up-history",
316    "up-line-or-history",
317    "up-line-or-search",
318    "vi-add-eol",
319    "vi-add-next",
320    "vi-backward-blank-word",
321    "vi-backward-char",
322    "vi-backward-delete-char",
323    "vi-backward-word",
324    "vi-change",
325    "vi-change-eol",
326    "vi-change-whole-line",
327    "vi-cmd-mode",
328    "vi-delete",
329    "vi-delete-char",
330    "vi-end-of-line",
331    "vi-find-next-char",
332    "vi-find-next-char-skip",
333    "vi-find-prev-char",
334    "vi-find-prev-char-skip",
335    "vi-first-non-blank",
336    "vi-forward-blank-word",
337    "vi-forward-char",
338    "vi-forward-word",
339    "vi-forward-word-end",
340    "vi-insert",
341    "vi-insert-bol",
342    "vi-join",
343    "vi-kill-eol",
344    "vi-open-line-above",
345    "vi-open-line-below",
346    "vi-put-after",
347    "vi-put-before",
348    "vi-repeat-change",
349    "vi-repeat-find",
350    "vi-repeat-search",
351    "vi-replace",
352    "vi-replace-chars",
353    "vi-rev-repeat-find",
354    "vi-rev-repeat-search",
355    "vi-substitute",
356    "vi-yank",
357    "vi-yank-whole-line",
358    "which-command",
359    "yank",
360    "yank-pop",
361];
362
363thread_local! {
364    static ZLE_MANAGER: RefCell<ZleManager> = RefCell::new(ZleManager::new());
365}
366
367/// Guard type for accessing ZLE manager
368pub struct ZleGuard<'a>(std::cell::RefMut<'a, ZleManager>);
369
370impl<'a> std::ops::Deref for ZleGuard<'a> {
371    type Target = ZleManager;
372    fn deref(&self) -> &Self::Target {
373        &self.0
374    }
375}
376
377impl<'a> std::ops::DerefMut for ZleGuard<'a> {
378    fn deref_mut(&mut self) -> &mut Self::Target {
379        &mut self.0
380    }
381}
382
383/// Get the global ZLE manager
384pub fn zle() -> ZleGuard<'static> {
385    ZLE_MANAGER.with(|m| {
386        // SAFETY: The RefCell is thread-local so this is safe
387        ZleGuard(unsafe { std::mem::transmute(m.borrow_mut()) })
388    })
389}
390
391/// Keymap identifier
392#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
393pub enum KeymapName {
394    Emacs,
395    ViInsert,
396    ViCommand,
397    Main,       // alias for current main keymap
398    Isearch,    // incremental search
399    Command,    // command mode
400    MenuSelect, // menu selection
401}
402
403impl KeymapName {
404    pub fn from_str(s: &str) -> Option<Self> {
405        match s {
406            "emacs" => Some(Self::Emacs),
407            "viins" => Some(Self::ViInsert),
408            "vicmd" => Some(Self::ViCommand),
409            "main" => Some(Self::Main),
410            "isearch" => Some(Self::Isearch),
411            "command" => Some(Self::Command),
412            "menuselect" => Some(Self::MenuSelect),
413            _ => None,
414        }
415    }
416
417    pub fn as_str(&self) -> &'static str {
418        match self {
419            Self::Emacs => "emacs",
420            Self::ViInsert => "viins",
421            Self::ViCommand => "vicmd",
422            Self::Main => "main",
423            Self::Isearch => "isearch",
424            Self::Command => "command",
425            Self::MenuSelect => "menuselect",
426        }
427    }
428}
429
430/// A keymap - mapping from key sequences to widget names
431#[derive(Debug, Clone)]
432pub struct Keymap {
433    bindings: HashMap<String, String>,
434}
435
436impl Default for Keymap {
437    fn default() -> Self {
438        Self::new()
439    }
440}
441
442impl Keymap {
443    pub fn new() -> Self {
444        Self {
445            bindings: HashMap::new(),
446        }
447    }
448
449    /// Create default emacs keymap
450    pub fn emacs_default() -> Self {
451        let mut km = Self::new();
452
453        // Movement
454        km.bind("^F", "forward-char");
455        km.bind("^B", "backward-char");
456        km.bind("^A", "beginning-of-line");
457        km.bind("^E", "end-of-line");
458        km.bind("\\ef", "forward-word"); // Alt-f
459        km.bind("\\eb", "backward-word"); // Alt-b
460
461        // Editing
462        km.bind("^D", "delete-char");
463        km.bind("^H", "backward-delete-char");
464        km.bind("^?", "backward-delete-char"); // Backspace
465        km.bind("^K", "kill-line");
466        km.bind("^U", "backward-kill-line");
467        km.bind("\\ed", "kill-word"); // Alt-d
468        km.bind("\\e^?", "backward-kill-word"); // Alt-Backspace
469        km.bind("^W", "backward-kill-word");
470        km.bind("^Y", "yank");
471        km.bind("\\ey", "yank-pop"); // Alt-y
472
473        // Undo
474        km.bind("^_", "undo");
475        km.bind("^X^U", "undo");
476        km.bind("\\e_", "redo"); // Alt-_
477
478        // History
479        km.bind("^P", "up-line-or-history");
480        km.bind("^N", "down-line-or-history");
481        km.bind("\\e<", "beginning-of-history");
482        km.bind("\\e>", "end-of-history");
483        km.bind("^R", "history-incremental-search-backward");
484        km.bind("^S", "history-incremental-search-forward");
485
486        // Completion
487        km.bind("^I", "expand-or-complete"); // Tab
488        km.bind("\\e\\e", "complete-word");
489
490        // Accept/misc
491        km.bind("^J", "accept-line"); // Enter
492        km.bind("^M", "accept-line"); // Enter
493        km.bind("^G", "send-break");
494        km.bind("^C", "send-break");
495        km.bind("^L", "clear-screen");
496
497        // Transpose
498        km.bind("^T", "transpose-chars");
499        km.bind("\\et", "transpose-words");
500
501        // Case
502        km.bind("\\ec", "capitalize-word");
503        km.bind("\\el", "down-case-word");
504        km.bind("\\eu", "up-case-word");
505
506        // Region
507        km.bind("^@", "set-mark-command"); // Ctrl-Space
508        km.bind("^X^X", "exchange-point-and-mark");
509        km.bind("\\ew", "copy-region-as-kill");
510
511        km
512    }
513
514    /// Create default vi insert mode keymap
515    pub fn viins_default() -> Self {
516        let mut km = Self::new();
517
518        // Enter command mode
519        km.bind("^[", "vi-cmd-mode"); // Escape
520
521        // Basic editing (same as emacs)
522        km.bind("^H", "backward-delete-char");
523        km.bind("^?", "backward-delete-char");
524        km.bind("^W", "backward-kill-word");
525        km.bind("^U", "backward-kill-line");
526
527        // Accept
528        km.bind("^J", "accept-line");
529        km.bind("^M", "accept-line");
530
531        // Completion
532        km.bind("^I", "expand-or-complete");
533
534        // History
535        km.bind("^P", "up-line-or-history");
536        km.bind("^N", "down-line-or-history");
537
538        km
539    }
540
541    /// Create default vi command mode keymap
542    pub fn vicmd_default() -> Self {
543        let mut km = Self::new();
544
545        // Enter insert mode
546        km.bind("i", "vi-insert");
547        km.bind("a", "vi-add-next");
548        km.bind("I", "vi-insert-bol");
549        km.bind("A", "vi-add-eol");
550
551        // Movement
552        km.bind("h", "backward-char");
553        km.bind("l", "forward-char");
554        km.bind("w", "forward-word");
555        km.bind("b", "backward-word");
556        km.bind("0", "beginning-of-line");
557        km.bind("^", "beginning-of-line");
558        km.bind("$", "end-of-line");
559
560        // Delete
561        km.bind("x", "delete-char");
562        km.bind("X", "backward-delete-char");
563        km.bind("dd", "kill-whole-line");
564        km.bind("dw", "kill-word");
565        km.bind("db", "backward-kill-word");
566        km.bind("d$", "kill-line");
567        km.bind("d0", "backward-kill-line");
568
569        // Yank/paste
570        km.bind("y", "vi-yank");
571        km.bind("p", "vi-put-after");
572        km.bind("P", "vi-put-before");
573
574        // History
575        km.bind("k", "up-line-or-history");
576        km.bind("j", "down-line-or-history");
577        km.bind("/", "history-incremental-search-backward");
578        km.bind("?", "history-incremental-search-forward");
579        km.bind("n", "vi-repeat-search");
580        km.bind("N", "vi-rev-repeat-search");
581
582        // Undo
583        km.bind("u", "undo");
584        km.bind("^R", "redo");
585
586        // Accept
587        km.bind("^J", "accept-line");
588        km.bind("^M", "accept-line");
589
590        km
591    }
592
593    /// Bind a key sequence to a widget
594    pub fn bind(&mut self, keys: &str, widget: &str) {
595        let normalized = Self::normalize_keys(keys);
596        self.bindings.insert(normalized, widget.to_string());
597    }
598
599    /// Unbind a key sequence
600    pub fn unbind(&mut self, keys: &str) {
601        let normalized = Self::normalize_keys(keys);
602        self.bindings.remove(&normalized);
603    }
604
605    /// Look up a key sequence
606    pub fn lookup(&self, keys: &str) -> Option<&str> {
607        let normalized = Self::normalize_keys(keys);
608        self.bindings.get(&normalized).map(|s| s.as_str())
609    }
610
611    /// Check if keys could be a prefix of a binding
612    pub fn has_prefix(&self, keys: &str) -> bool {
613        let normalized = Self::normalize_keys(keys);
614        self.bindings
615            .keys()
616            .any(|k| k.starts_with(&normalized) && k != &normalized)
617    }
618
619    /// List all bindings
620    pub fn list_bindings(&self) -> impl Iterator<Item = (&str, &str)> {
621        self.bindings.iter().map(|(k, v)| (k.as_str(), v.as_str()))
622    }
623
624    /// Normalize key notation
625    /// Converts various formats to a canonical form:
626    /// ^X -> control-X
627    /// \eX -> escape X (meta)
628    /// \C-x -> control-x
629    /// \M-x -> meta-x
630    fn normalize_keys(keys: &str) -> String {
631        let mut result = String::new();
632        let mut chars = keys.chars().peekable();
633
634        while let Some(c) = chars.next() {
635            match c {
636                '^' => {
637                    // Control character
638                    if let Some(&next) = chars.peek() {
639                        chars.next();
640                        let ctrl_char = if next == '?' {
641                            '\x7f' // DEL
642                        } else if next == '@' {
643                            '\x00' // NUL
644                        } else if next == '[' {
645                            '\x1b' // ESC
646                        } else {
647                            // Ctrl-A through Ctrl-Z, etc.
648                            ((next.to_ascii_uppercase() as u8) & 0x1f) as char
649                        };
650                        result.push(ctrl_char);
651                    } else {
652                        result.push(c);
653                    }
654                }
655                '\\' => {
656                    // Escape sequence
657                    if let Some(&next) = chars.peek() {
658                        match next {
659                            'e' | 'E' => {
660                                chars.next();
661                                result.push('\x1b'); // ESC
662                            }
663                            'C' => {
664                                chars.next();
665                                if chars.peek() == Some(&'-') {
666                                    chars.next();
667                                    if let Some(&ctrl_char) = chars.peek() {
668                                        chars.next();
669                                        let ctrl =
670                                            ((ctrl_char.to_ascii_uppercase() as u8) & 0x1f) as char;
671                                        result.push(ctrl);
672                                    }
673                                }
674                            }
675                            'M' => {
676                                chars.next();
677                                if chars.peek() == Some(&'-') {
678                                    chars.next();
679                                    result.push('\x1b'); // ESC prefix for meta
680                                    if let Some(&meta_char) = chars.peek() {
681                                        chars.next();
682                                        result.push(meta_char);
683                                    }
684                                }
685                            }
686                            'n' => {
687                                chars.next();
688                                result.push('\n');
689                            }
690                            't' => {
691                                chars.next();
692                                result.push('\t');
693                            }
694                            'r' => {
695                                chars.next();
696                                result.push('\r');
697                            }
698                            '\\' => {
699                                chars.next();
700                                result.push('\\');
701                            }
702                            _ => {
703                                result.push(c);
704                            }
705                        }
706                    } else {
707                        result.push(c);
708                    }
709                }
710                _ => result.push(c),
711            }
712        }
713
714        result
715    }
716}
717
718#[cfg(test)]
719mod tests {
720    use super::*;
721
722    #[test]
723    fn test_normalize_keys() {
724        assert_eq!(Keymap::normalize_keys("^A"), "\x01");
725        assert_eq!(Keymap::normalize_keys("^?"), "\x7f");
726        assert_eq!(Keymap::normalize_keys("\\ef"), "\x1bf");
727        assert_eq!(Keymap::normalize_keys("\\C-a"), "\x01");
728        assert_eq!(Keymap::normalize_keys("\\M-x"), "\x1bx");
729    }
730
731    #[test]
732    fn test_keymap_bind_lookup() {
733        let mut km = Keymap::new();
734        km.bind("^A", "beginning-of-line");
735
736        assert_eq!(km.lookup("^A"), Some("beginning-of-line"));
737        assert_eq!(km.lookup("\x01"), Some("beginning-of-line"));
738    }
739
740    #[test]
741    fn test_has_prefix() {
742        let mut km = Keymap::new();
743        km.bind("^X^U", "undo");
744
745        assert!(km.has_prefix("^X"));
746        assert!(!km.has_prefix("^X^U"));
747        assert!(!km.has_prefix("^A"));
748    }
749}