modalkit/editing/
key.rs

1//! # Macro recording and execution
2//!
3//! ## Overview
4//!
5//! This module provides a wrapper for [BindingMachine] implementors. The wrapper can then process
6//! [MacroAction] values, and handle recording and executing macros.
7//!
8//! Every [BindingMachine::input_key] call is considered to be a key actually typed at the
9//! terminal, and will be treated as part of a macro recording. These terminal keypresses will
10//! interrupt any pending macro keypresses, which are fed incrementally as needed during every
11//! [BindingMachine::pop] call. This allows creating stop-on-error behaviour when executing
12//! macros.
13//!
14//! ## Examples
15//!
16//! ```
17//! use modalkit::{
18//!     editing::application::EmptyInfo,
19//!     editing::key::KeyManager,
20//!     env::vim::keybindings::default_vim_keys,
21//!     key::TerminalKey,
22//! };
23//!
24//! let bindings = default_vim_keys::<EmptyInfo>();
25//! let bindings = KeyManager::new(bindings);
26//! ```
27use std::borrow::Cow;
28use std::collections::VecDeque;
29
30use crate::actions::MacroAction;
31use crate::errors::{EditError, EditResult};
32use crate::key::MacroError;
33use crate::keybindings::{dialog::Dialog, BindingMachine, InputKey};
34use crate::prelude::*;
35
36use super::{
37    application::ApplicationInfo,
38    context::{EditContext, Resolve},
39    rope::EditRope,
40    store::{RegisterPutFlags, Store},
41};
42
43const MAX_MACRO_EXEC_DEPTH: usize = 100;
44
45/// Wraps keybindings so that they can be fed simulated keypresses from macros.
46pub struct KeyManager<K, A, S>
47where
48    K: InputKey,
49{
50    bindings: Box<dyn BindingMachine<K, A, S, EditContext>>,
51    keystack: VecDeque<K>,
52
53    recording: Option<(Register, bool)>,
54    macro_exec_depth: usize,
55    commit_on_input: bool,
56    committed: EditRope,
57    pending: EditRope,
58}
59
60impl<K, A, S> KeyManager<K, A, S>
61where
62    K: InputKey,
63{
64    /// Create a new instance.
65    pub fn new<B: BindingMachine<K, A, S, EditContext> + 'static>(bindings: B) -> Self {
66        let bindings = Box::new(bindings);
67
68        Self {
69            bindings,
70            keystack: VecDeque::new(),
71
72            recording: None,
73            macro_exec_depth: 0,
74            commit_on_input: false,
75            committed: EditRope::from(""),
76            pending: EditRope::from(""),
77        }
78    }
79
80    /// Process a macro action.
81    pub fn macro_command<I: ApplicationInfo>(
82        &mut self,
83        act: &MacroAction,
84        ctx: &EditContext,
85        store: &mut Store<I>,
86    ) -> EditResult<EditInfo, I>
87    where
88        K: InputKey<Error = MacroError>,
89    {
90        let (mstr, count) = match act {
91            MacroAction::Execute(count) => {
92                let reg = ctx.get_register().unwrap_or(Register::UnnamedMacro);
93                let rope = store.registers.get_macro(reg)?;
94
95                (rope.to_string(), ctx.resolve(count))
96            },
97            MacroAction::Run(s, count) => (s.clone(), ctx.resolve(count)),
98            MacroAction::Repeat(count) => {
99                let rope = store.registers.get_last_macro()?;
100                (rope.to_string(), ctx.resolve(count))
101            },
102            MacroAction::ToggleRecording => {
103                if let Some((reg, append)) = &self.recording {
104                    // Save macro to register.
105                    let mut rope = EditRope::from("");
106                    std::mem::swap(&mut rope, &mut self.committed);
107
108                    let mut flags = RegisterPutFlags::NOTEXT;
109
110                    if *append {
111                        flags |= RegisterPutFlags::APPEND;
112                    }
113
114                    store.registers.put(reg, rope.into(), flags)?;
115
116                    // Stop recording.
117                    self.recording = None;
118                    self.commit_on_input = false;
119                    self.pending = EditRope::from("");
120                } else {
121                    let reg = ctx.get_register().unwrap_or(Register::UnnamedMacro);
122
123                    self.recording = Some((reg, ctx.get_register_append()));
124                }
125
126                return Ok(None);
127            },
128            act => {
129                return Err(EditError::Unimplemented(format!("unknown action: {act:?}")));
130            },
131        };
132
133        self.macro_exec_depth += 1;
134
135        if self.macro_exec_depth >= MAX_MACRO_EXEC_DEPTH {
136            let err = MacroError::LoopingMacro(self.macro_exec_depth);
137            return Err(err.into());
138        }
139
140        for _ in 0..count {
141            let mut keys = VecDeque::from(K::from_macro_str(mstr.as_ref())?);
142            keys.append(&mut self.keystack);
143            self.keystack = keys;
144        }
145
146        return Ok(None);
147    }
148}
149
150impl<K, A, S> BindingMachine<K, A, S, EditContext> for KeyManager<K, A, S>
151where
152    K: InputKey + ToString,
153{
154    fn input_key(&mut self, key: K) {
155        self.macro_exec_depth = 0;
156
157        if self.recording.is_some() {
158            let mut rope = EditRope::from(key.to_string());
159
160            if self.commit_on_input {
161                std::mem::swap(&mut self.pending, &mut rope);
162                self.committed += rope;
163                self.commit_on_input = false;
164            } else {
165                self.pending += rope;
166            }
167        }
168
169        self.keystack.clear();
170        self.bindings.input_key(key);
171    }
172
173    fn pop(&mut self) -> Option<(A, EditContext)> {
174        loop {
175            if let res @ Some(_) = self.bindings.pop() {
176                self.commit_on_input = true;
177
178                return res;
179            }
180
181            match self.keystack.pop_front() {
182                Some(key) => self.bindings.input_key(key),
183                None => return None,
184            }
185        }
186    }
187
188    fn context(&mut self) -> EditContext {
189        self.bindings.context()
190    }
191
192    fn show_dialog(&mut self, max_rows: usize, max_cols: usize) -> Vec<Cow<'_, str>> {
193        self.bindings.show_dialog(max_rows, max_cols)
194    }
195
196    fn show_mode(&self) -> Option<String> {
197        self.bindings.show_mode()
198    }
199
200    fn reset_mode(&mut self) {
201        self.bindings.reset_mode()
202    }
203
204    fn get_cursor_indicator(&self) -> Option<char> {
205        self.bindings.get_cursor_indicator()
206    }
207
208    fn repeat(&mut self, seq: S, other: Option<EditContext>) {
209        self.bindings.repeat(seq, other)
210    }
211
212    fn run_dialog(&mut self, dialog: Box<dyn Dialog<A>>) {
213        self.bindings.run_dialog(dialog)
214    }
215}
216
217#[cfg(test)]
218#[macro_use]
219mod tests {
220    use super::*;
221    use crossterm::event::{KeyCode, KeyEvent};
222
223    use crate::{
224        editing::application::EmptyInfo,
225        editing::store::{RegisterError, Store},
226        env::vim::VimState,
227        env::CommonKeyClass,
228        errors::EditError,
229        key::TerminalKey,
230        keybindings::EdgeEvent::{Class, Key},
231        keybindings::{dialog::PromptYesNo, EmptySequence, ModalMachine, Mode, ModeKeys, Step},
232    };
233
234    type TestStore = Store<EmptyInfo>;
235    type TestMachine = ModalMachine<TerminalKey, TestStep>;
236    type TestKeyManager = KeyManager<TerminalKey, TestAction, EmptySequence>;
237
238    #[derive(Clone)]
239    struct TestStep(Option<TestAction>, Option<TestMode>);
240
241    impl Step<TerminalKey> for TestStep {
242        type A = TestAction;
243        type State = VimState;
244        type Class = CommonKeyClass;
245        type M = TestMode;
246        type Sequence = EmptySequence;
247
248        fn is_unmapped(&self) -> bool {
249            self.0.is_none() && self.1.is_none()
250        }
251
252        fn step(&self, ctx: &mut VimState) -> (Vec<TestAction>, Option<TestMode>) {
253            let act = self.0.clone().into_iter().collect();
254
255            ctx.action.count = ctx.action.counting;
256
257            (act, self.1)
258        }
259    }
260
261    impl From<TestAction> for TestStep {
262        fn from(action: TestAction) -> Self {
263            TestStep(Some(action), None)
264        }
265    }
266
267    impl From<TestMode> for TestStep {
268        fn from(mode: TestMode) -> Self {
269            TestStep(None, Some(mode))
270        }
271    }
272
273    #[derive(Clone, Copy, Debug, Default, Hash, Eq, PartialEq)]
274    enum TestMode {
275        #[default]
276        Normal,
277        Insert,
278    }
279
280    impl Mode<TestAction, VimState> for TestMode {}
281
282    impl ModeKeys<TerminalKey, TestAction, VimState> for TestMode {
283        fn unmapped(
284            &self,
285            key: &TerminalKey,
286            _: &mut VimState,
287        ) -> (Vec<TestAction>, Option<TestMode>) {
288            match self {
289                TestMode::Normal => {
290                    return (vec![], None);
291                },
292                TestMode::Insert => {
293                    if let Some(c) = key.get_char() {
294                        return (vec![TestAction::Type(c)], None);
295                    }
296
297                    return (vec![], None);
298                },
299            }
300        }
301    }
302
303    #[derive(Clone, Debug, Default, Eq, PartialEq)]
304    enum TestAction {
305        Macro(MacroAction),
306        SetFlag(bool),
307        Type(char),
308        #[default]
309        NoOp,
310    }
311
312    fn setup_recursive_bindings() -> (TestKeyManager, TestStore) {
313        use crate::keybindings::EdgeRepeat::{Min, Once};
314
315        let mut bindings = TestMachine::empty();
316        let store = Store::default();
317
318        // Normal mode mappings
319        bindings.add_mapping(
320            TestMode::Normal,
321            &[(Once, Key("n".parse().unwrap()))],
322            &TestAction::NoOp.into(),
323        );
324        bindings.add_mapping(
325            TestMode::Normal,
326            &[(Once, Key("a".parse().unwrap()))],
327            &TestAction::Macro(MacroAction::Run("n".into(), Count::Contextual)).into(),
328        );
329        bindings.add_mapping(
330            TestMode::Normal,
331            &[(Once, Key("b".parse().unwrap()))],
332            &TestAction::Macro(MacroAction::Run("aa".into(), Count::Contextual)).into(),
333        );
334        bindings.add_mapping(
335            TestMode::Normal,
336            &[(Once, Key("c".parse().unwrap()))],
337            &TestAction::Macro(MacroAction::Run("c".into(), Count::Contextual)).into(),
338        );
339
340        // Normal mode prefixes
341        bindings.add_prefix(
342            TestMode::Normal,
343            &[
344                (Once, Key("\"".parse().unwrap())),
345                (Once, Class(CommonKeyClass::Register)),
346            ],
347            &None,
348        );
349        bindings.add_prefix(TestMode::Normal, &[(Min(1), Class(CommonKeyClass::Count))], &None);
350
351        (TestKeyManager::new(bindings), store)
352    }
353
354    fn setup_bindings(skip_confirm: bool) -> (TestKeyManager, TestStore) {
355        use crate::keybindings::EdgeRepeat::{Min, Once};
356
357        let mut bindings = TestMachine::empty();
358        let store = Store::default();
359
360        // Normal mode mappings
361        bindings.add_mapping(
362            TestMode::Normal,
363            &[
364                (Once, Key("q".parse().unwrap())),
365                (Once, Key("q".parse().unwrap())),
366                (Once, Key("q".parse().unwrap())),
367            ],
368            &TestAction::Macro(MacroAction::ToggleRecording).into(),
369        );
370        bindings.add_mapping(
371            TestMode::Normal,
372            &[(Once, Key("@".parse().unwrap()))],
373            &TestAction::Macro(MacroAction::Repeat(Count::Contextual)).into(),
374        );
375        bindings.add_mapping(
376            TestMode::Normal,
377            &[(Once, Key("Q".parse().unwrap()))],
378            &TestAction::Macro(MacroAction::Execute(Count::Contextual)).into(),
379        );
380        bindings.add_mapping(
381            TestMode::Normal,
382            &[(Once, Key("f".parse().unwrap()))],
383            &TestAction::SetFlag(skip_confirm).into(),
384        );
385        bindings.add_mapping(
386            TestMode::Normal,
387            &[(Once, Key("i".parse().unwrap()))],
388            &TestMode::Insert.into(),
389        );
390
391        // Normal mode prefixes
392        bindings.add_prefix(
393            TestMode::Normal,
394            &[
395                (Once, Key("\"".parse().unwrap())),
396                (Once, Class(CommonKeyClass::Register)),
397            ],
398            &None,
399        );
400        bindings.add_prefix(TestMode::Normal, &[(Min(1), Class(CommonKeyClass::Count))], &None);
401
402        // Insert mode mappings
403        bindings.add_mapping(
404            TestMode::Insert,
405            &[(Once, Key("<Esc>".parse().unwrap()))],
406            &TestMode::Normal.into(),
407        );
408
409        (TestKeyManager::new(bindings), store)
410    }
411
412    fn input(
413        key: TerminalKey,
414        bindings: &mut TestKeyManager,
415        store: &mut TestStore,
416        s: &mut String,
417        flag: &mut bool,
418        noops: &mut usize,
419        err: &mut Option<EditError<EmptyInfo>>,
420    ) {
421        *noops = 0;
422        *err = None;
423
424        bindings.input_key(key);
425
426        while let Some((act, ctx)) = bindings.pop() {
427            match act {
428                TestAction::NoOp => {
429                    *noops += 1;
430                    continue;
431                },
432                TestAction::Macro(act) => {
433                    *err = bindings.macro_command(&act, &ctx, store).err();
434
435                    if err.is_some() {
436                        return;
437                    }
438                },
439                TestAction::SetFlag(skip_confirm) => {
440                    if skip_confirm {
441                        *flag = true;
442                    } else {
443                        let act = vec![TestAction::SetFlag(true)];
444                        let msg = "Are you sure you want to set the flag";
445                        let confirm = PromptYesNo::new(msg, act);
446                        bindings.run_dialog(Box::new(confirm));
447                    }
448                },
449                TestAction::Type(c) => s.push(c),
450            }
451        }
452    }
453
454    #[test]
455    fn test_macro_run() {
456        let (mut bindings, mut store) = setup_bindings(true);
457        let ctx = EditContext::from(VimState::<EmptyInfo>::default());
458
459        // Run a sequence of key presses twice.
460        let run = MacroAction::Run("flif<Esc>fi".into(), 2.into());
461        let _ = bindings.macro_command(&run, &ctx, &mut store).unwrap();
462
463        // Run the first time, starting in Normal mode.
464        assert_pop1!(bindings, TestAction::SetFlag(true), ctx);
465        assert_pop1!(bindings, TestAction::NoOp, ctx);
466        assert_pop1!(bindings, TestAction::NoOp, ctx);
467        assert_pop1!(bindings, TestAction::Type('f'), ctx);
468        assert_pop1!(bindings, TestAction::NoOp, ctx);
469        assert_pop1!(bindings, TestAction::SetFlag(true), ctx);
470        assert_pop1!(bindings, TestAction::NoOp, ctx);
471
472        // Run the second time, but this time in Insert mode.
473        assert_pop1!(bindings, TestAction::Type('f'), ctx);
474        assert_pop1!(bindings, TestAction::Type('l'), ctx);
475        assert_pop1!(bindings, TestAction::Type('i'), ctx);
476        assert_pop1!(bindings, TestAction::Type('f'), ctx);
477        assert_pop1!(bindings, TestAction::NoOp, ctx);
478        assert_pop1!(bindings, TestAction::SetFlag(true), ctx);
479        assert_pop2!(bindings, TestAction::NoOp, ctx);
480    }
481
482    #[test]
483    fn test_record_and_execute() {
484        let (mut bindings, mut store) = setup_bindings(true);
485        let mut s = String::new();
486        let mut flag = false;
487        let mut err = None;
488        let mut noops = 0;
489
490        macro_rules! get_register {
491            ($reg: expr) => {
492                store.registers.get(&$reg).unwrap().value
493            };
494        }
495
496        macro_rules! input {
497            ($key: expr) => {
498                input($key, &mut bindings, &mut store, &mut s, &mut flag, &mut noops, &mut err)
499            };
500        }
501
502        // Register::UnnamedMacro is currently empty.
503        assert_eq!(get_register!(Register::UnnamedMacro).to_string(), "");
504
505        // No last macro to repeat.
506        input!(key!('@'));
507        assert!(matches!(err, Some(EditError::Register(RegisterError::NoLastMacro))), "{:?}", err);
508
509        // Press an unmapped key before recording.
510        input!(key!('l'));
511
512        // Start recording to "a.
513        input!(key!('"'));
514        input!(key!('a'));
515        input!(key!('q'));
516        input!(key!('q'));
517        input!(key!('q'));
518
519        // Type some unmapped keys.
520        input!(key!('n'));
521        input!(key!('z'));
522
523        // Type a mapped key.
524        input!(key!('f'));
525
526        // Move to Insert mode and type some characters.
527        input!(key!('i'));
528        input!(key!('a'));
529        input!(key!('b'));
530        input!(key!('c'));
531
532        // Move back to Normal mode.
533        input!("<Esc>".parse().unwrap());
534
535        // End recording.
536        input!(key!('q'));
537        input!(key!('q'));
538        input!(key!('q'));
539
540        // Register::Named('a') now contains macro text.
541        assert_eq!(get_register!(Register::Named('a')).to_string(), "nzfiabc<Esc>");
542        assert_eq!(s, "abc");
543        assert_eq!(flag, true);
544
545        // Reset flag before replaying.
546        flag = false;
547
548        // Replay macro twice.
549        input!(key!('"'));
550        input!(key!('a'));
551        input!(key!('2'));
552        input!(key!('Q'));
553        assert_eq!(s, "abcabcabc");
554        assert_eq!(flag, true);
555
556        // Reset flag before replaying.
557        flag = false;
558
559        // Replay last macro.
560        input!(key!('@'));
561        assert_eq!(s, "abcabcabcabc");
562        assert_eq!(flag, true);
563
564        // Reset flag before replaying.
565        flag = false;
566
567        // Record an unnamed macro that executes "a.
568        input!(key!('q'));
569        input!(key!('q'));
570        input!(key!('q'));
571        input!(key!('i'));
572        input!(key!('d'));
573        input!(key!('e'));
574        input!(key!('f'));
575        input!("<Esc>".parse().unwrap());
576        input!(key!('"'));
577        input!(key!('a'));
578        input!(key!('Q'));
579        input!(key!('q'));
580        input!(key!('q'));
581        input!(key!('q'));
582
583        // Register::UnnamedMacro now contains macro text.
584        assert_eq!(get_register!(Register::UnnamedMacro).to_string(), "idef<Esc>\"aQ");
585        assert_eq!(get_register!(Register::Named('a')).to_string(), "nzfiabc<Esc>");
586        assert_eq!(s, "abcabcabcabcdefabc");
587        assert_eq!(flag, true);
588
589        // Reset flag before replaying.
590        flag = false;
591
592        // Execute Register::UnnamedMacro.
593        input!(key!('3'));
594        input!(key!('Q'));
595        assert_eq!(s, "abcabcabcabcdefabcdefabcdefabcdefabc");
596        assert_eq!(flag, true);
597
598        // Reset flag before replaying.
599        flag = false;
600
601        // Replaying replays "a, since that was last used inside "@.
602        input!(key!('2'));
603        input!(key!('@'));
604        assert_eq!(s, "abcabcabcabcdefabcdefabcdefabcdefabcabcabc");
605        assert_eq!(flag, true);
606    }
607
608    #[test]
609    fn test_macro_dialog() {
610        let (mut bindings, mut store) = setup_bindings(false);
611        let mut s = String::new();
612        let mut flag = false;
613        let mut err = None;
614        let mut noops = 0;
615
616        macro_rules! get_register {
617            ($reg: expr) => {
618                store.registers.get(&$reg).unwrap().value
619            };
620        }
621
622        macro_rules! input {
623            ($key: expr) => {
624                input($key, &mut bindings, &mut store, &mut s, &mut flag, &mut noops, &mut err)
625            };
626        }
627
628        assert_eq!(bindings.show_dialog(10, 100).len(), 0);
629        assert_eq!(flag, false);
630
631        // Start recording to "a.
632        input!(key!('"'));
633        input!(key!('a'));
634        input!(key!('q'));
635        input!(key!('q'));
636        input!(key!('q'));
637
638        // Typing `f` starts a new dialog.
639        input!(key!('f'));
640        assert_eq!(bindings.show_dialog(10, 100).len(), 1);
641        assert_eq!(flag, false);
642
643        // Answer `y` to the prompt to clear dialog and set flag.
644        input!(key!('y'));
645        assert_eq!(bindings.show_dialog(10, 100).len(), 0);
646        assert_eq!(flag, true);
647
648        // End recording.
649        input!(key!('q'));
650        input!(key!('q'));
651        input!(key!('q'));
652
653        // Register::Named('a') now contains macro text.
654        assert_eq!(get_register!(Register::Named('a')).to_string(), "fy");
655
656        // Now reset the flag.
657        flag = false;
658
659        // Replay macro.
660        input!(key!('"'));
661        input!(key!('a'));
662        input!(key!('Q'));
663
664        // Flag is now true, and we end with the dialog cleared.
665        assert_eq!(bindings.show_dialog(10, 100).len(), 0);
666        assert_eq!(flag, true);
667    }
668
669    #[test]
670    fn test_macro_limit_depth() {
671        let (mut bindings, mut store) = setup_recursive_bindings();
672        let mut s = String::new();
673        let mut flag = false;
674        let mut err = None;
675        let mut noops = 0;
676
677        macro_rules! input {
678            ($key: expr) => {
679                input($key, &mut bindings, &mut store, &mut s, &mut flag, &mut noops, &mut err)
680            };
681        }
682
683        // It's okay to run a MacroAction::Run with a count >100.
684        input!(key!('1'));
685        input!(key!('1'));
686        input!(key!('0'));
687        input!(key!('a'));
688        assert!(err.is_none());
689        assert_eq!(noops, 110);
690
691        // It's okay to run a macro that calls other macros multiple times.
692        input!(key!('4'));
693        input!(key!('9'));
694        input!(key!('b'));
695        assert!(err.is_none());
696        assert_eq!(noops, 98);
697
698        // But if they repeatedly call other macros enough times, we abort.
699        input!(key!('5'));
700        input!(key!('0'));
701        input!(key!('b'));
702        println!("err = {err:?}");
703        assert!(matches!(
704            err.take().unwrap(),
705            EditError::MacroFailure(MacroError::LoopingMacro(100))
706        ));
707        assert_eq!(noops, 98);
708
709        // And a key that just always triggers itself should error.
710        input!(key!('c'));
711        println!("err = {err:?}");
712        assert!(matches!(
713            err.take().unwrap(),
714            EditError::MacroFailure(MacroError::LoopingMacro(100))
715        ));
716        assert_eq!(noops, 0);
717    }
718}