modalkit/env/emacs/
keybindings.rs

1//! # Emacs Keybindings
2//!
3//! ## Overview
4//!
5//! This module handles mapping the keybindings used in Emacs onto the
6//! [Action] type.
7//!
8//! ## Divergences
9//!
10//! The keybindings here diverge from the defaults in Emacs in the following ways:
11//!
12//! - `C-_` and `C-x u` behave like `M-x undo-only`
13//!
14use bitflags::bitflags;
15
16use crate::{
17    actions::{
18        Action,
19        CommandAction,
20        CommandBarAction,
21        EditAction,
22        EditorAction,
23        HistoryAction,
24        InsertTextAction,
25        PromptAction,
26        SelectionAction,
27        WindowAction,
28    },
29    editing::application::{ApplicationInfo, EmptyInfo},
30    prelude::*,
31};
32
33use super::{
34    super::{keyparse::parse, CommonKeyClass, ShellBindings},
35    EmacsMode,
36    EmacsState,
37};
38
39use crate::key::TerminalKey;
40use crate::keybindings::{InputBindings, ModalMachine, Step};
41
42bitflags! {
43    #[derive(Debug, Clone, Copy, Eq, PartialEq)]
44    struct MappedModes: u32 {
45        const I = 0b0000000000000001;
46        const C = 0b0000000000000010;
47        const S = 0b0000000000000100;
48
49        const IC = MappedModes::I.bits() | MappedModes::C.bits();
50        const ICS = MappedModes::IC.bits() | MappedModes::S.bits();
51    }
52}
53
54const MAP: MappedModes = MappedModes::ICS;
55const ICMAP: MappedModes = MappedModes::IC;
56const IMAP: MappedModes = MappedModes::I;
57const CMAP: MappedModes = MappedModes::C;
58const SMAP: MappedModes = MappedModes::S;
59
60impl MappedModes {
61    pub fn split(&self) -> Vec<EmacsMode> {
62        let mut modes = Vec::new();
63
64        if self.contains(MappedModes::I) {
65            modes.push(EmacsMode::Insert);
66        }
67
68        if self.contains(MappedModes::C) {
69            modes.push(EmacsMode::Command);
70        }
71
72        if self.contains(MappedModes::S) {
73            modes.push(EmacsMode::Search);
74        }
75
76        return modes;
77    }
78}
79
80#[derive(Clone, Debug)]
81enum InternalAction {
82    ClearTargetShape(bool),
83    SaveCounting(Option<usize>),
84    SetInsertStyle(InsertStyle),
85    SetRegister(Register),
86    SetSearchRegexParams(MoveDir1D, bool),
87    SetTargetShape(TargetShape, bool),
88}
89
90impl InternalAction {
91    pub fn run<I: ApplicationInfo>(&self, ctx: &mut EmacsState<I>) {
92        match self {
93            InternalAction::ClearTargetShape(shiftreq) => {
94                if *shiftreq {
95                    if ctx.persist.shift {
96                        ctx.persist.shape = None;
97                        ctx.persist.shift = false;
98                    }
99                } else {
100                    ctx.persist.shape = None;
101                }
102            },
103            InternalAction::SaveCounting(optn) => {
104                let counting = match (optn, ctx.action.counting) {
105                    (Some(n1), Some(n2)) => n1.saturating_mul(10).saturating_add(n2),
106                    (Some(n), None) => *n,
107                    (None, Some(n)) => n,
108                    (None, None) => 4,
109                };
110
111                ctx.action.count = Some(match ctx.action.count {
112                    None => counting,
113                    Some(prev) => prev.saturating_mul(counting),
114                });
115
116                ctx.action.counting = None;
117            },
118            InternalAction::SetInsertStyle(style) => {
119                if style == &ctx.persist.insert {
120                    ctx.persist.insert = !*style;
121                } else {
122                    ctx.persist.insert = *style;
123                }
124            },
125            InternalAction::SetRegister(reg) => {
126                ctx.action.register = Some(reg.clone());
127            },
128            InternalAction::SetSearchRegexParams(dir, incremental) => {
129                ctx.persist.regexsearch_dir = *dir;
130                ctx.persist.regexsearch_inc = *incremental;
131            },
132            InternalAction::SetTargetShape(shape, shifted) => {
133                ctx.persist.shape = Some(*shape);
134                ctx.persist.shift = *shifted;
135            },
136        }
137    }
138}
139
140#[derive(Debug)]
141enum ExternalAction<I: ApplicationInfo> {
142    Something(Action<I>),
143    Repeat(bool),
144}
145
146impl<I: ApplicationInfo> ExternalAction<I> {
147    fn resolve(&self, ctx: &mut EmacsState<I>) -> Vec<Action<I>> {
148        match self {
149            ExternalAction::Something(act) => {
150                ctx.persist.repeating = false;
151
152                vec![act.clone()]
153            },
154            ExternalAction::Repeat(reqrep) => {
155                if *reqrep && !ctx.persist.repeating {
156                    let ch = Char::Single('z').into();
157                    let it = InsertTextAction::Type(ch, MoveDir1D::Previous, Count::Contextual);
158
159                    return vec![it.into()];
160                }
161
162                ctx.persist.repeating = true;
163
164                return vec![Action::Repeat(RepeatType::LastAction)];
165            },
166        }
167    }
168}
169
170impl<I: ApplicationInfo> Clone for ExternalAction<I> {
171    fn clone(&self) -> Self {
172        match self {
173            ExternalAction::Something(act) => ExternalAction::Something(act.clone()),
174            ExternalAction::Repeat(reqrep) => ExternalAction::Repeat(*reqrep),
175        }
176    }
177}
178
179impl<I: ApplicationInfo> From<Action<I>> for ExternalAction<I> {
180    fn from(act: Action<I>) -> Self {
181        ExternalAction::Something(act)
182    }
183}
184
185/// Description of actions to take after an input sequence.
186#[derive(Debug, Default)]
187pub struct InputStep<I: ApplicationInfo> {
188    internal: Vec<InternalAction>,
189    external: Vec<ExternalAction<I>>,
190    nextm: Option<EmacsMode>,
191}
192
193impl<I: ApplicationInfo> InputStep<I> {
194    /// Create a new step that input keys can map to.
195    pub fn new() -> Self {
196        InputStep { internal: vec![], external: vec![], nextm: None }
197    }
198
199    /// Set the [EmacsMode] to switch to after this step.
200    pub fn goto(mut self, mode: EmacsMode) -> Self {
201        self.nextm = Some(mode);
202        self
203    }
204
205    /// Set the [actions](Action) that this step produces.
206    pub fn actions(mut self, acts: Vec<Action<I>>) -> Self {
207        self.external = acts.into_iter().map(ExternalAction::Something).collect();
208        self
209    }
210}
211
212impl<I: ApplicationInfo> Clone for InputStep<I> {
213    fn clone(&self) -> Self {
214        Self {
215            internal: self.internal.clone(),
216            external: self.external.clone(),
217            nextm: self.nextm,
218        }
219    }
220}
221
222impl<I: ApplicationInfo> Step<TerminalKey> for InputStep<I> {
223    type A = Action<I>;
224    type State = EmacsState<I>;
225    type M = EmacsMode;
226    type Class = CommonKeyClass;
227    type Sequence = RepeatType;
228
229    fn is_unmapped(&self) -> bool {
230        match self {
231            InputStep { internal, external, nextm: None } => {
232                internal.is_empty() && external.is_empty()
233            },
234            _ => false,
235        }
236    }
237
238    fn fallthrough(&self) -> Option<Self::M> {
239        None
240    }
241
242    fn step(&self, ctx: &mut EmacsState<I>) -> (Vec<Action<I>>, Option<Self::M>) {
243        for iact in self.internal.iter() {
244            iact.run(ctx);
245        }
246
247        let external: Vec<Action<I>> =
248            self.external.iter().flat_map(|act| act.resolve(ctx)).collect();
249
250        return (external, self.nextm);
251    }
252}
253
254macro_rules! act {
255    ($ext: expr) => {
256        isv!(vec![], vec![ExternalAction::Something($ext)])
257    };
258    ($ext: expr, $ns: expr) => {
259        isv!(vec![], vec![ExternalAction::Something($ext)], $ns)
260    };
261}
262
263macro_rules! iact {
264    ($int: expr) => {
265        isv!(vec![$int], vec![])
266    };
267    ($int: expr, $ns: expr) => {
268        isv!(vec![$int], vec![], $ns)
269    };
270}
271
272macro_rules! isv {
273    () => {
274        InputStep { internal: vec![], external: vec![], nextm: None }
275    };
276    ($ints: expr, $exts: expr) => {
277        InputStep { internal: $ints, external: $exts, nextm: None }
278    };
279    ($ints: expr, $exts: expr, $ns: expr) => {
280        InputStep { internal: $ints, external: $exts, nextm: Some($ns) }
281    };
282}
283
284macro_rules! is {
285    ($int: expr, $ext: expr) => {
286        isv!(vec![$int], vec![ExternalAction::Something($ext.into())])
287    };
288    ($int: expr, $ext: expr, $ns: expr) => {
289        isv!(vec![$int], vec![ExternalAction::Something($ext.into())], $ns)
290    };
291}
292
293macro_rules! blackhole {
294    ($act: expr) => {
295        is!(InternalAction::SetRegister(Register::Blackhole), $act)
296    };
297    ($act: expr, $nm: expr) => {
298        is!(InternalAction::SetRegister(Register::Blackhole), $act, $nm)
299    };
300}
301
302macro_rules! motion {
303    ($mt: expr) => {
304        is!(
305            InternalAction::ClearTargetShape(true),
306            EditorAction::Edit(
307                Specifier::Exact(EditAction::Motion),
308                EditTarget::Motion($mt, Count::Contextual)
309            )
310        )
311    };
312    ($mt: expr, $c: literal) => {
313        is!(
314            InternalAction::ClearTargetShape(true),
315            EditorAction::Edit(
316                Specifier::Exact(EditAction::Motion),
317                EditTarget::Motion($mt, Count::Exact($c))
318            )
319        )
320    };
321    ($mt: expr, $c: expr) => {
322        is!(
323            InternalAction::ClearTargetShape(true),
324            EditorAction::Edit(Specifier::Exact(EditAction::Motion), EditTarget::Motion($mt, $c))
325        )
326    };
327}
328
329macro_rules! yank_target {
330    ($target: expr) => {
331        edit_target!(EditAction::Yank, $target)
332    };
333    ($target: expr, $c: expr) => {
334        edit_target!(EditAction::Yank, $target, $c)
335    };
336}
337
338macro_rules! kill_target {
339    ($target: expr) => {
340        edit_target!(EditAction::Delete, $target)
341    };
342    ($target: expr, $c: expr) => {
343        edit_target!(EditAction::Delete, $target, $c)
344    };
345}
346
347macro_rules! kill {
348    ($mt: expr) => {
349        edit!(EditAction::Delete, $mt)
350    };
351    ($mt: expr, $c: expr) => {
352        edit!(EditAction::Delete, $mt, $c)
353    };
354}
355
356macro_rules! just_one_space {
357    () => {
358        isv!(vec![InternalAction::SetRegister(Register::Blackhole)], vec![
359            Action::from(EditorAction::Edit(
360                EditAction::Delete.into(),
361                RangeType::Word(WordStyle::Whitespace(false)).into()
362            ))
363            .into(),
364            Action::from(InsertTextAction::Type(
365                Char::Single(' ').into(),
366                MoveDir1D::Previous,
367                Count::Contextual
368            ))
369            .into()
370        ])
371    };
372}
373
374macro_rules! cmdbar_focus {
375    ($type: expr, $nm: expr, $act: expr) => {
376        cmdbar!(CommandBarAction::Focus(":".into(), $type, Box::new(Action::from($act))), $nm)
377    };
378}
379
380macro_rules! cmdbar_search {
381    ($dir: expr) => {
382        is!(
383            InternalAction::SetSearchRegexParams($dir, true),
384            Action::CommandBar(CommandBarAction::Focus(
385                match $dir {
386                    MoveDir1D::Next => "/".into(),
387                    MoveDir1D::Previous => "?".into(),
388                },
389                CommandType::Search,
390                Box::new(
391                    EditorAction::Edit(
392                        Specifier::Contextual,
393                        EditTarget::Search(SearchType::Regex, MoveDirMod::Same, Count::Contextual)
394                    )
395                    .into()
396                )
397            )),
398            EmacsMode::Search
399        )
400    };
401}
402
403macro_rules! window {
404    ($act: expr) => {
405        act!(Action::Window($act))
406    };
407}
408
409macro_rules! window_focus {
410    ($fc: expr) => {
411        window!(WindowAction::Focus($fc))
412    };
413}
414
415macro_rules! window_switch {
416    ($st: expr) => {
417        window!(WindowAction::Switch($st))
418    };
419}
420
421macro_rules! window_split {
422    ($axis: expr) => {
423        window!(WindowAction::Split(
424            OpenTarget::Current,
425            $axis,
426            MoveDir1D::Previous,
427            Count::Contextual
428        ))
429    };
430}
431
432macro_rules! window_close {
433    ($style: expr, $fc: expr, $flags: expr) => {
434        window!(WindowAction::Close($style($fc), $flags))
435    };
436}
437
438macro_rules! window_quit {
439    ($style: expr, $f: expr) => {
440        window_close!($style, $f, CloseFlags::QUIT)
441    };
442}
443
444macro_rules! search {
445    ($dir: expr) => {
446        is!(
447            InternalAction::SetSearchRegexParams($dir, true),
448            Action::Search(MoveDirMod::Exact($dir), Count::Exact(1))
449        )
450    };
451}
452
453macro_rules! search_word {
454    ($style: expr) => {
455        edit_target!(
456            EditAction::Motion,
457            EditTarget::Search(SearchType::Word($style, true), MoveDirMod::Same, Count::Exact(1))
458        )
459    };
460}
461
462macro_rules! start_selection {
463    ($shape: expr) => {
464        is!(
465            InternalAction::SetTargetShape($shape, false),
466            EditorAction::Selection(SelectionAction::Resize(
467                SelectionResizeStyle::Restart,
468                EditTarget::CurrentPosition
469            ))
470        )
471    };
472    ($shape: expr, $target: expr) => {
473        is!(
474            InternalAction::SetTargetShape($shape, false),
475            EditorAction::Selection(SelectionAction::Resize(
476                SelectionResizeStyle::Restart,
477                $target
478            ))
479        )
480    };
481}
482
483macro_rules! start_shift_selection {
484    ($target: expr) => {
485        is!(
486            InternalAction::SetTargetShape(TargetShape::CharWise, true),
487            EditorAction::Edit(Specifier::Exact(EditAction::Motion), $target)
488        )
489    };
490}
491
492#[rustfmt::skip]
493fn default_keys<I: ApplicationInfo>() -> Vec<(MappedModes, &'static str, InputStep<I>)> {
494    [
495        // Insert, Command and Search mode keybindings.
496        ( MAP, "<C-Y>", paste!(PasteStyle::Cursor) ),
497        ( MAP, "<M-BS>", kill!(MoveType::WordBegin(WordStyle::Little, MoveDir1D::Previous)) ),
498        ( MAP, "<M-Del>", kill!(MoveType::WordBegin(WordStyle::Little, MoveDir1D::Previous)) ),
499        ( MAP, "<BS>", erase!(MoveType::Column(MoveDir1D::Previous, true)) ),
500
501        // Insert and Command mode keybindings.
502        ( ICMAP, "<C-A>", motion!(MoveType::LinePos(MovePosition::Beginning), Count::MinusOne) ),
503        ( ICMAP, "<C-B>", motion!(MoveType::Column(MoveDir1D::Previous, true)) ),
504        ( ICMAP, "<C-D>", erase!(MoveType::Column(MoveDir1D::Next, true)) ),
505        ( ICMAP, "<C-E>", motion!(MoveType::LinePos(MovePosition::End), Count::MinusOne) ),
506        ( ICMAP, "<C-F>", motion!(MoveType::Column(MoveDir1D::Next, true)) ),
507        ( ICMAP, "<C-K>", kill!(MoveType::LinePos(MovePosition::End), Count::MinusOne) ),
508        ( ICMAP, "<C-X>z", isv!(vec![], vec![ExternalAction::Repeat(false)]) ),
509        ( ICMAP, "<C-W>", kill_target!(EditTarget::Selection) ),
510        ( ICMAP, "<C-Left>", motion!(MoveType::WordBegin(WordStyle::Little, MoveDir1D::Previous)) ),
511        ( ICMAP, "<C-Right>", motion!(MoveType::WordBegin(WordStyle::NonAlphaNum, MoveDir1D::Next)) ),
512        ( ICMAP, "<C-S-Left>", start_shift_selection!(MoveType::WordBegin(WordStyle::Little, MoveDir1D::Previous).into()) ),
513        ( ICMAP, "<C-S-Right>", start_shift_selection!(MoveType::WordBegin(WordStyle::NonAlphaNum, MoveDir1D::Next).into()) ),
514        ( ICMAP, "<M-b>", motion!(MoveType::WordBegin(WordStyle::Little, MoveDir1D::Previous)) ),
515        ( ICMAP, "<M-B>", start_shift_selection!(MoveType::WordBegin(WordStyle::Little, MoveDir1D::Previous).into()) ),
516        ( ICMAP, "<M-c>", edit!(EditAction::ChangeCase(Case::Title), MoveType::WordEnd(WordStyle::Little, MoveDir1D::Next)) ),
517        ( ICMAP, "<M-d>", kill!(MoveType::WordEnd(WordStyle::Little, MoveDir1D::Next)) ),
518        ( ICMAP, "<M-f>", motion!(MoveType::WordBegin(WordStyle::NonAlphaNum, MoveDir1D::Next)) ),
519        ( ICMAP, "<M-F>", start_shift_selection!(MoveType::WordBegin(WordStyle::NonAlphaNum, MoveDir1D::Next).into()) ),
520        ( ICMAP, "<M-l>", edit!(EditAction::ChangeCase(Case::Lower), MoveType::WordEnd(WordStyle::Little, MoveDir1D::Next)) ),
521        ( ICMAP, "<M-u>", edit!(EditAction::ChangeCase(Case::Upper), MoveType::WordEnd(WordStyle::Little, MoveDir1D::Next)) ),
522        ( ICMAP, "<M-w>", yank_target!(EditTarget::Selection) ),
523        ( ICMAP, "<M-W>", yank_target!(EditTarget::Selection) ),
524        ( ICMAP, "<M-Left>", motion!(MoveType::WordBegin(WordStyle::Little, MoveDir1D::Previous)) ),
525        ( ICMAP, "<M-Right>", motion!(MoveType::WordBegin(WordStyle::NonAlphaNum, MoveDir1D::Next)) ),
526        ( ICMAP, "<M-S-Left>", start_shift_selection!(MoveType::WordBegin(WordStyle::Little, MoveDir1D::Previous).into()) ),
527        ( ICMAP, "<M-S-Right>", start_shift_selection!(MoveType::WordBegin(WordStyle::NonAlphaNum, MoveDir1D::Next).into()) ),
528        ( ICMAP, "<S-End>", start_shift_selection!(MoveType::LinePos(MovePosition::End).into()) ),
529        ( ICMAP, "<S-Home>", start_shift_selection!(MoveType::LinePos(MovePosition::Beginning).into()) ),
530        ( ICMAP, "<S-Left>", start_shift_selection!(MoveType::Column(MoveDir1D::Previous, true).into()) ),
531        ( ICMAP, "<S-Right>", start_shift_selection!(MoveType::Column(MoveDir1D::Next, true).into()) ),
532        ( ICMAP, "<Del>", erase!(MoveType::Column(MoveDir1D::Next, true)) ),
533        ( ICMAP, "<End>", motion!(MoveType::LinePos(MovePosition::End), Count::MinusOne) ),
534        ( ICMAP, "<Home>", motion!(MoveType::LinePos(MovePosition::Beginning), Count::MinusOne) ),
535        ( ICMAP, "<Left>", motion!(MoveType::Column(MoveDir1D::Previous, true)) ),
536        ( ICMAP, "<Right>", motion!(MoveType::Column(MoveDir1D::Next, true)) ),
537        ( ICMAP, "z", isv!(vec![], vec![ExternalAction::Repeat(true)]) ),
538
539        // Insert mode keybindings.
540        ( IMAP, "<C-G>", is!(InternalAction::ClearTargetShape(false), EditorAction::Edit(EditAction::Motion.into(), EditTarget::CurrentPosition), EmacsMode::Insert) ),
541        ( IMAP, "<C-J>", chartype!(Char::Single('\n')) ),
542        ( IMAP, "<C-L>", scrollcp!(MovePosition::Middle, Axis::Vertical) ),
543        ( IMAP, "<C-N>", motion!(MoveType::Line(MoveDir1D::Next)) ),
544        ( IMAP, "<C-O>", insert_text!(InsertTextAction::OpenLine(TargetShape::CharWise, MoveDir1D::Previous, Count::Contextual)) ),
545        ( IMAP, "<C-P>", motion!(MoveType::Line(MoveDir1D::Previous)) ),
546        ( IMAP, "<C-Q>{oct<=3}", chartype!() ),
547        ( IMAP, "<C-Q>{any}", chartype!() ),
548        ( IMAP, "<C-R>", cmdbar_search!(MoveDir1D::Previous) ),
549        ( IMAP, "<C-S>", cmdbar_search!(MoveDir1D::Next) ),
550        ( IMAP, "<C-T>", unmapped!() ),
551        ( IMAP, "<C-U><C-X>s", unmapped!() ),
552        ( IMAP, "<C-V>", scroll2d!(MoveDir2D::Down, ScrollSize::Page) ),
553        ( IMAP, "<C-X><C-C>", window!(WindowAction::Close(WindowTarget::All, CloseFlags::QUIT)) ),
554        ( IMAP, "<C-X><C-O>", unmapped!() ),
555        ( IMAP, "<C-X><C-Q>", unmapped!() ),
556        ( IMAP, "<C-X><C-S>", unmapped!() ),
557        ( IMAP, "<C-X><C-T>", unmapped!() ),
558        ( IMAP, "<C-X><C-W>", unmapped!() ),
559        ( IMAP, "<C-X><C-X>", selection!(SelectionAction::CursorSet(SelectionCursorChange::SwapAnchor)) ),
560        ( IMAP, "<C-X><C-Z>", act!(Action::Suspend) ),
561        ( IMAP, "<C-X><Space>", unmapped!() ),
562        ( IMAP, "<C-X><Left>", window_switch!(OpenTarget::Offset(MoveDir1D::Previous, Count::Contextual)) ),
563        ( IMAP, "<C-X><Right>", window_switch!(OpenTarget::Offset(MoveDir1D::Next, Count::Contextual)) ),
564        ( IMAP, "<C-X>b", unmapped!() ),
565        ( IMAP, "<C-X>h", start_selection!(TargetShape::CharWise, RangeType::Buffer.into()) ),
566        ( IMAP, "<C-X>k", unmapped!() ),
567        ( IMAP, "<C-X>o", window_focus!(FocusChange::Direction1D(MoveDir1D::Next, Count::Exact(1), true)) ),
568        ( IMAP, "<C-X>r<M-w>", unmapped!() ),
569        ( IMAP, "<C-X>rb", unmapped!() ),
570        ( IMAP, "<C-X>rc", unmapped!() ),
571        ( IMAP, "<C-X>rd", unmapped!() ),
572        ( IMAP, "<C-X>rk", unmapped!() ),
573        ( IMAP, "<C-X>rl", unmapped!() ),
574        ( IMAP, "<C-X>rm", unmapped!() ),
575        ( IMAP, "<C-X>ro", unmapped!() ),
576        ( IMAP, "<C-X>ry", unmapped!() ),
577        ( IMAP, "<C-X>rN", unmapped!() ),
578        ( IMAP, "<C-X>s", unmapped!() ),
579        ( IMAP, "<C-X>u", history!(HistoryAction::Undo(Count::Contextual)) ),
580        ( IMAP, "<C-X>0", window_quit!(WindowTarget::Single, FocusChange::Current) ),
581        ( IMAP, "<C-X>1", window_quit!(WindowTarget::AllBut, FocusChange::Current) ),
582        ( IMAP, "<C-X>2", window_split!(Axis::Horizontal) ),
583        ( IMAP, "<C-X>3", window_split!(Axis::Vertical) ),
584        ( IMAP, "<C-X><", scroll2d!(MoveDir2D::Left, ScrollSize::Page) ),
585        ( IMAP, "<C-X>>", scroll2d!(MoveDir2D::Right, ScrollSize::Page) ),
586        ( IMAP, "<C-Z>", act!(Action::Suspend) ),
587        ( IMAP, "<C-@>", start_selection!(TargetShape::CharWise) ),
588        ( IMAP, "<C-_>", history!(HistoryAction::Undo(Count::Contextual)) ),
589        ( IMAP, "<C-Del>", kill!(MoveType::LinePos(MovePosition::End), Count::MinusOne) ),
590        ( IMAP, "<C-Space>", start_selection!(TargetShape::CharWise) ),
591        ( IMAP, "<M-s>.", search_word!(WordStyle::Big) ),
592        ( IMAP, "<M-t>", unmapped!() ),
593        ( IMAP, "<M-v>", scroll2d!(MoveDir2D::Up, ScrollSize::Page) ),
594        ( IMAP, "<M-x>", cmdbar_focus!(CommandType::Command, EmacsMode::Command, CommandAction::Execute(1.into())) ),
595        ( IMAP, "<M-z>{char}", kill_target!(EditTarget::Search(SearchType::Char(true), MoveDirMod::Same, Count::Contextual)) ),
596        ( IMAP, "<M-S>.", search_word!(WordStyle::Big) ),
597        ( IMAP, "<M-\\>", erase_range!(RangeType::Word(WordStyle::Whitespace(false))) ),
598        ( IMAP, "<M-^>", edit!(EditAction::Join(JoinStyle::OneSpace), MoveType::Line(MoveDir1D::Previous)) ),
599        ( IMAP, "<M-<>", edit_buffer!(EditAction::Motion, MoveTerminus::Beginning) ),
600        ( IMAP, "<M->>", edit_buffer!(EditAction::Motion, MoveTerminus::End) ),
601        ( IMAP, "<M-Space>", just_one_space!() ),
602        ( IMAP, "<S-Up>", start_shift_selection!(MoveType::Line(MoveDir1D::Previous).into()) ),
603        ( IMAP, "<S-Down>", start_shift_selection!(MoveType::Line(MoveDir1D::Next).into()) ),
604        ( IMAP, "<Insert>", iact!(InternalAction::SetInsertStyle(InsertStyle::Replace)) ),
605        ( IMAP, "<Up>", motion!(MoveType::Line(MoveDir1D::Previous)) ),
606        ( IMAP, "<Down>", motion!(MoveType::Line(MoveDir1D::Next)) ),
607        ( IMAP, "<PageDown>", scroll2d!(MoveDir2D::Down, ScrollSize::Page) ),
608        ( IMAP, "<PageUp>", scroll2d!(MoveDir2D::Up, ScrollSize::Page) ),
609
610        // Command mode keybindings.
611        ( CMAP, "<C-G>", prompt!(PromptAction::Abort(false), EmacsMode::Insert) ),
612        ( CMAP, "<C-I>", editor!(EditorAction::Complete(CompletionStyle::Single, CompletionType::Auto, CompletionDisplay::Bar)) ),
613        ( CMAP, "<Up>", prompt!(PromptAction::Recall(RecallFilter::All, MoveDir1D::Previous, Count::Contextual)) ),
614        ( CMAP, "<Down>", prompt!(PromptAction::Recall(RecallFilter::All, MoveDir1D::Next, Count::Contextual)) ),
615        ( CMAP, "<Space>", editor!(EditorAction::Complete(CompletionStyle::Prefix, CompletionType::Auto, CompletionDisplay::Bar)) ),
616        ( CMAP, "<Tab>", editor!(EditorAction::Complete(CompletionStyle::Single, CompletionType::Auto, CompletionDisplay::Bar)) ),
617
618        // Search mode keybindings.
619        ( SMAP, "<C-G>", prompt!(PromptAction::Abort(false), EmacsMode::Insert) ),
620        ( SMAP, "<C-R>", search!(MoveDir1D::Previous) ),
621        ( SMAP, "<C-S>", search!(MoveDir1D::Next) ),
622    ].to_vec()
623}
624
625#[rustfmt::skip]
626fn default_pfxs<I: ApplicationInfo>() -> Vec<(MappedModes, &'static str, Option<InputStep<I>>)> {
627    [
628        // Insert and Command mode allow count arguments.
629        ( ICMAP, "<C-U>{count*}", Some(iact!(InternalAction::SaveCounting(None))) ),
630        ( ICMAP, "<M-1>{count*}", Some(iact!(InternalAction::SaveCounting(1.into()))) ),
631        ( ICMAP, "<M-2>{count*}", Some(iact!(InternalAction::SaveCounting(2.into()))) ),
632        ( ICMAP, "<M-3>{count*}", Some(iact!(InternalAction::SaveCounting(3.into()))) ),
633        ( ICMAP, "<M-4>{count*}", Some(iact!(InternalAction::SaveCounting(4.into()))) ),
634        ( ICMAP, "<M-5>{count*}", Some(iact!(InternalAction::SaveCounting(5.into()))) ),
635        ( ICMAP, "<M-6>{count*}", Some(iact!(InternalAction::SaveCounting(6.into()))) ),
636        ( ICMAP, "<M-7>{count*}", Some(iact!(InternalAction::SaveCounting(7.into()))) ),
637        ( ICMAP, "<M-8>{count*}", Some(iact!(InternalAction::SaveCounting(8.into()))) ),
638        ( ICMAP, "<M-9>{count*}", Some(iact!(InternalAction::SaveCounting(9.into()))) ),
639        ( ICMAP, "<M-0>{count*}", Some(iact!(InternalAction::SaveCounting(0.into()))) ),
640    ].to_vec()
641}
642
643#[rustfmt::skip]
644fn default_enter<I: ApplicationInfo>() -> Vec<(MappedModes, &'static str, InputStep<I>)> {
645    [
646        // <Enter> in Insert mode types a newline character.
647        ( IMAP, "<Enter>", chartype!(Char::Single('\n')) ),
648
649        // <Enter> in Command mode submits the commands.
650        ( CMAP, "<Enter>", prompt!(PromptAction::Submit, EmacsMode::Insert) ),
651    ].to_vec()
652}
653
654#[rustfmt::skip]
655fn submit_on_enter<I: ApplicationInfo>() -> Vec<(MappedModes, &'static str, InputStep<I>)> {
656    [
657        // <Enter> in Insert and Command mode submits the command.
658        ( ICMAP, "<Enter>", prompt!(PromptAction::Submit, EmacsMode::Insert) ),
659    ].to_vec()
660}
661
662#[inline]
663fn add_prefix<I: ApplicationInfo>(
664    machine: &mut EmacsMachine<TerminalKey, I>,
665    modes: &MappedModes,
666    keys: &str,
667    action: &Option<InputStep<I>>,
668) {
669    if let Ok((_, evs)) = parse(keys) {
670        for mode in modes.split() {
671            machine.add_prefix(mode, &evs, action);
672        }
673    } else {
674        panic!("invalid Emacs keybinding: {}", keys);
675    }
676}
677
678#[inline]
679fn add_mapping<I: ApplicationInfo>(
680    machine: &mut EmacsMachine<TerminalKey, I>,
681    modes: &MappedModes,
682    keys: &str,
683    action: &InputStep<I>,
684) {
685    if let Ok((_, evs)) = parse(keys) {
686        for mode in modes.split() {
687            machine.add_mapping(mode, &evs, action);
688        }
689    } else {
690        panic!("invalid Emacs keybinding: {}", keys);
691    }
692}
693
694/// A configurable collection of Emacs bindings that can be added to a [ModalMachine].
695#[derive(Debug)]
696pub struct EmacsBindings<I: ApplicationInfo> {
697    prefixes: Vec<(MappedModes, &'static str, Option<InputStep<I>>)>,
698    mappings: Vec<(MappedModes, &'static str, InputStep<I>)>,
699    enter: Vec<(MappedModes, &'static str, InputStep<I>)>,
700}
701
702impl<I: ApplicationInfo> EmacsBindings<I> {
703    /// Remap the Enter key to [submit](PromptAction::Submit) instead.
704    pub fn submit_on_enter(mut self) -> Self {
705        self.enter = submit_on_enter();
706        self
707    }
708}
709
710impl<I: ApplicationInfo> ShellBindings for EmacsBindings<I> {
711    fn shell(self) -> Self {
712        self.submit_on_enter()
713    }
714}
715
716impl<I: ApplicationInfo> Default for EmacsBindings<I> {
717    fn default() -> Self {
718        EmacsBindings {
719            prefixes: default_pfxs(),
720            mappings: default_keys(),
721            enter: default_enter(),
722        }
723    }
724}
725
726impl<I: ApplicationInfo> InputBindings<TerminalKey, InputStep<I>> for EmacsBindings<I> {
727    fn setup(&self, machine: &mut EmacsMachine<TerminalKey, I>) {
728        for (modes, keys, action) in self.prefixes.iter() {
729            add_prefix(machine, modes, keys, action);
730        }
731
732        for (modes, keys, action) in self.mappings.iter() {
733            add_mapping(machine, modes, keys, action);
734        }
735
736        for (modes, keys, action) in self.enter.iter() {
737            add_mapping(machine, modes, keys, action);
738        }
739    }
740}
741
742/// Manage Emacs keybindings and modes.
743pub type EmacsMachine<Key, T = EmptyInfo> = ModalMachine<Key, InputStep<T>>;
744
745/// Create a new [EmacsMachine] populated with standard Emacs keys.
746pub fn default_emacs_keys<I: ApplicationInfo>() -> EmacsMachine<TerminalKey, I> {
747    ModalMachine::from_bindings::<EmacsBindings<I>>()
748}
749
750#[cfg(test)]
751mod tests {
752    use super::*;
753    use crate::editing::context::EditContext;
754    use crate::keybindings::BindingMachine;
755    use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
756
757    macro_rules! mv {
758        ($mt: expr) => {
759            Action::from(EditorAction::Edit(
760                EditAction::Motion.into(),
761                EditTarget::Motion($mt, Count::Contextual),
762            ))
763        };
764        ($mt: expr, $c: literal) => {
765            Action::from(EditorAction::Edit(
766                EditAction::Motion.into(),
767                EditTarget::Motion($mt, Count::Exact($c)),
768            ))
769        };
770        ($mt: expr, $c: expr) => {
771            Action::from(EditorAction::Edit(EditAction::Motion.into(), EditTarget::Motion($mt, $c)))
772        };
773    }
774
775    macro_rules! typechar {
776        ($c: literal) => {
777            Action::from(EditorAction::InsertText(InsertTextAction::Type(
778                Char::Single($c).into(),
779                MoveDir1D::Previous,
780                Count::Contextual,
781            )))
782        };
783    }
784
785    const CMDBAR_ABORT: Action = Action::Prompt(PromptAction::Abort(false));
786
787    fn cmdbar_focus(s: &str, ct: CommandType, act: Action) -> Action {
788        Action::CommandBar(CommandBarAction::Focus(s.into(), ct, act.into()))
789    }
790
791    fn cmdbar_search(s: &str) -> Action {
792        let search = EditTarget::Search(SearchType::Regex, MoveDirMod::Same, Count::Contextual);
793        let search = EditorAction::Edit(Specifier::Contextual, search);
794        cmdbar_focus(s, CommandType::Search, search.into())
795    }
796
797    fn mkctx() -> EmacsState {
798        EmacsState::default()
799    }
800
801    #[test]
802    fn test_selection_shift() {
803        let mut vm: EmacsMachine<TerminalKey> = default_emacs_keys();
804        let mut ctx = mkctx();
805
806        // Start out in Insert mode.
807        assert_eq!(vm.mode(), EmacsMode::Insert);
808
809        // <S-Left> begins shift selection.
810        let mov = mv!(MoveType::Column(MoveDir1D::Previous, true));
811        ctx.persist.shape = Some(TargetShape::CharWise);
812
813        vm.input_key(key!(KeyCode::Left, KeyModifiers::SHIFT));
814        assert_pop2!(vm, mov, ctx);
815        assert_eq!(vm.state().persist.shift, true);
816        assert_eq!(vm.mode(), EmacsMode::Insert);
817
818        // <Left> ends shift selection
819        let mov = mv!(MoveType::Column(MoveDir1D::Previous, true));
820        ctx.persist.shape = None;
821
822        vm.input_key(key!(KeyCode::Left));
823        assert_pop2!(vm, mov, ctx);
824        assert_eq!(vm.state().persist.shift, false);
825        assert_eq!(vm.mode(), EmacsMode::Insert);
826    }
827
828    #[test]
829    fn test_selection_no_shift() {
830        let mut vm: EmacsMachine<TerminalKey> = default_emacs_keys();
831        let mut ctx = mkctx();
832
833        ctx.persist.shape = Some(TargetShape::CharWise);
834
835        // Start out in Insert mode.
836        assert_eq!(vm.mode(), EmacsMode::Insert);
837
838        // <C-Space> begins selection.
839        let act =
840            SelectionAction::Resize(SelectionResizeStyle::Restart, EditTarget::CurrentPosition);
841        vm.input_key(ctl!(' '));
842        assert_pop2!(vm, Action::from(act), ctx);
843
844        // <Left> continues to select.
845        let mov = mv!(MoveType::Column(MoveDir1D::Previous, true));
846        vm.input_key(key!(KeyCode::Left));
847        assert_pop2!(vm, mov, ctx);
848        assert_eq!(vm.mode(), EmacsMode::Insert);
849    }
850
851    #[test]
852    fn test_search() {
853        let mut vm: EmacsMachine<TerminalKey> = default_emacs_keys();
854        let mut ctx = mkctx();
855
856        // Start out in Insert mode.
857        assert_eq!(vm.mode(), EmacsMode::Insert);
858
859        // ^R moves to Search mode.
860        ctx.persist.regexsearch_dir = MoveDir1D::Previous;
861        vm.input_key(ctl!('r'));
862        assert_pop2!(vm, cmdbar_search("?"), ctx);
863        assert_eq!(vm.mode(), EmacsMode::Search);
864
865        // Unmapped, typable character types a character.
866        let it =
867            InsertTextAction::Type(Char::from('r').into(), MoveDir1D::Previous, Count::Contextual);
868        vm.input_key(key!('r'));
869        assert_pop2!(vm, Action::from(it), ctx);
870        assert_eq!(vm.mode(), EmacsMode::Search);
871
872        // ^R in Search mode results in Action::Search.
873        let act = Action::Search(MoveDirMod::Exact(MoveDir1D::Previous), 1.into());
874        vm.input_key(ctl!('r'));
875        assert_pop2!(vm, act, ctx);
876        assert_eq!(vm.mode(), EmacsMode::Search);
877
878        // ^S in Search mode results in Action::Search.
879        ctx.persist.regexsearch_dir = MoveDir1D::Next;
880        let act = Action::Search(MoveDirMod::Exact(MoveDir1D::Next), 1.into());
881        vm.input_key(ctl!('s'));
882        assert_pop2!(vm, act, ctx);
883        assert_eq!(vm.mode(), EmacsMode::Search);
884
885        // Unmapped, non-typable character moves back to Insert mode.
886        vm.input_key(key!(KeyCode::Left));
887        assert_pop2!(vm, CMDBAR_ABORT, ctx);
888        assert_eq!(vm.mode(), EmacsMode::Insert);
889
890        // ^S moves to Search mode.
891        vm.input_key(ctl!('s'));
892        assert_pop2!(vm, cmdbar_search("/"), ctx);
893        assert_eq!(vm.mode(), EmacsMode::Search);
894
895        // ^G leaves Search mode.
896        vm.input_key(ctl!('g'));
897        assert_pop2!(vm, CMDBAR_ABORT, ctx);
898        assert_eq!(vm.mode(), EmacsMode::Insert);
899    }
900
901    #[test]
902    fn test_count() {
903        let mut vm: EmacsMachine<TerminalKey> = default_emacs_keys();
904        let mut ctx = mkctx();
905
906        // Start out in Insert mode.
907        assert_eq!(vm.mode(), EmacsMode::Insert);
908
909        // Typing a number by default types a character.
910        vm.input_key(key!('2'));
911        assert_pop2!(vm, typechar!('2'), ctx);
912
913        // Adding Alt will make it a count.
914        vm.input_key(key!('2', KeyModifiers::ALT));
915        assert_eq!(vm.pop(), None);
916
917        // Now we can type w/o Alt and it is still a count.
918        vm.input_key(key!('2'));
919        assert_eq!(vm.pop(), None);
920
921        // Type space 22 times.
922        ctx.action.count = Some(22);
923        vm.input_key(key!(' '));
924        assert_pop2!(vm, typechar!(' '), ctx);
925
926        // We can also type C-U 2 2 SPC.
927        ctx.action.count = Some(22);
928        vm.input_key(ctl!('u'));
929        vm.input_key(key!('2'));
930        vm.input_key(key!('2'));
931        vm.input_key(key!(' '));
932        assert_pop2!(vm, typechar!(' '), ctx);
933
934        // Typing C-U multiple times multiplies by 4 each time.
935        ctx.action.count = Some(64);
936        vm.input_key(ctl!('u'));
937        vm.input_key(ctl!('u'));
938        vm.input_key(ctl!('u'));
939        vm.input_key(key!(' '));
940        assert_pop2!(vm, typechar!(' '), ctx);
941    }
942
943    #[test]
944    fn test_repeat_action() {
945        let mut vm: EmacsMachine<TerminalKey> = default_emacs_keys();
946        let ctx = mkctx();
947
948        // Start out in Insert mode.
949        assert_eq!(vm.mode(), EmacsMode::Insert);
950
951        // Type several characters.
952        vm.input_key(key!('a'));
953        vm.input_key(key!('b'));
954        vm.input_key(key!('c'));
955        assert_pop1!(vm, typechar!('a'), ctx);
956        assert_pop1!(vm, typechar!('b'), ctx);
957        assert_pop2!(vm, typechar!('c'), ctx);
958
959        // Press C-X z to repeat typing 'c'.
960        vm.input_key(ctl!('x'));
961        vm.input_key(key!('z'));
962        assert_eq!(vm.state().persist.repeating, true);
963        assert_pop2!(vm, Action::Repeat(RepeatType::LastAction), ctx);
964
965        // Only 'c' comes back.
966        vm.repeat(RepeatType::LastAction, Some(ctx.clone().into()));
967        assert_pop2!(vm, typechar!('c'), ctx);
968
969        // Pressing 'z' keeps repeating.
970        vm.input_key(key!('z'));
971        assert_pop2!(vm, Action::Repeat(RepeatType::LastAction), ctx);
972        assert_eq!(vm.state().persist.repeating, true);
973
974        // Get back 'c' again.
975        vm.repeat(RepeatType::LastAction, Some(ctx.clone().into()));
976        assert_pop2!(vm, typechar!('c'), ctx);
977
978        // Pressing 'z' keeps repeating.
979        vm.input_key(key!('z'));
980        assert_pop2!(vm, Action::Repeat(RepeatType::LastAction), ctx);
981        assert_eq!(vm.state().persist.repeating, true);
982
983        // Get back 'c' again.
984        vm.repeat(RepeatType::LastAction, Some(ctx.clone().into()));
985        assert_pop2!(vm, typechar!('c'), ctx);
986
987        // Pressing 'd', an unmapped key, interrupts the sequence.
988        vm.input_key(key!('d'));
989        assert_pop2!(vm, typechar!('d'), ctx);
990        assert_eq!(vm.state().persist.repeating, false);
991
992        // Pressing 'z' doesn't repeat anymore.
993        vm.input_key(key!('z'));
994        assert_pop2!(vm, typechar!('z'), ctx);
995        assert_eq!(vm.state().persist.repeating, false);
996
997        // Press C-X z to repeat typing the 'z' character.
998        vm.input_key(ctl!('x'));
999        vm.input_key(key!('z'));
1000        assert_pop2!(vm, Action::Repeat(RepeatType::LastAction), ctx);
1001        assert_eq!(vm.state().persist.repeating, true);
1002
1003        // Get back 'z'.
1004        vm.repeat(RepeatType::LastAction, Some(ctx.clone().into()));
1005        assert_pop2!(vm, typechar!('z'), ctx);
1006
1007        // Pressing 'z' keeps repeating.
1008        vm.input_key(key!('z'));
1009        assert_pop2!(vm, Action::Repeat(RepeatType::LastAction), ctx);
1010        assert_eq!(vm.state().persist.repeating, true);
1011
1012        // Get back 'z'.
1013        vm.repeat(RepeatType::LastAction, Some(ctx.clone().into()));
1014        assert_pop2!(vm, typechar!('z'), ctx);
1015
1016        // Pressing <Right>, a mapped key, interrupts the sequence.
1017        vm.input_key(key!(KeyCode::Right));
1018        assert_pop2!(vm, mv!(MoveType::Column(MoveDir1D::Next, true)), ctx);
1019        assert_eq!(vm.state().persist.repeating, false);
1020
1021        // Pressing 'z' doesn't repeat anymore.
1022        vm.input_key(key!('z'));
1023        assert_pop2!(vm, typechar!('z'), ctx);
1024        assert_eq!(vm.state().persist.repeating, false);
1025    }
1026}