editor_types/
lib.rs

1//! # Editor Types
2//!
3//! ## Overview
4//!
5//! The types in this crate provides a defunctionalized view of a text editor. Consumers of these
6//! types should map them into text manipulation or user interface actions.
7//!
8//! ## Examples
9//!
10//! ```
11//! use editor_types::{Action, EditAction, EditorAction};
12//! use editor_types::prelude::*;
13//!
14//! // Delete the current text selection.
15//! let _: Action = EditorAction::Edit(EditAction::Delete.into(), EditTarget::Selection).into();
16//!
17//! // Copy the next three lines.
18//! let _: Action = EditorAction::Edit(EditAction::Yank.into(), EditTarget::Range(RangeType::Line, true, 3.into())).into();
19//!
20//! // Make some contextually specified number of words lowercase.
21//! let _: Action = EditorAction::Edit(
22//!     EditAction::ChangeCase(Case::Lower).into(),
23//!     EditTarget::Motion(MoveType::WordBegin(WordStyle::Big, MoveDir1D::Next), Count::Contextual)
24//! ).into();
25//!
26//! // Scroll the viewport so that line 10 is at the top of the screen.
27//! let _: Action = Action::Scroll(ScrollStyle::LinePos(MovePosition::Beginning, 10.into()));
28//! ```
29pub mod application;
30pub mod context;
31pub mod prelude;
32pub mod util;
33
34use self::application::*;
35use self::context::{EditContext, Resolve};
36use self::prelude::*;
37use keybindings::SequenceStatus;
38
39/// The various actions that can be taken on text.
40#[derive(Clone, Debug, Default, Eq, PartialEq)]
41pub enum EditAction {
42    /// Move the cursor.
43    ///
44    /// If a shape is [specified contextually](EditContext::get_target_shape), then visually select
45    /// text while moving, as if using [SelectionAction::Resize] with
46    /// [SelectionResizeStyle::Extend].
47    #[default]
48    Motion,
49
50    /// Delete the targeted text.
51    Delete,
52
53    /// Yank the targeted text into a [Register].
54    Yank,
55
56    /// Replace characters within the targeted text with a new character.
57    ///
58    /// If [bool] is true, virtually replace characters by how many columns they occupy.
59    Replace(bool),
60
61    /// Automatically format the targeted text.
62    Format,
63
64    /// Change the first number on each line within the targeted text.
65    ///
66    /// The [bool] argument controls whether to increment by an additional count on each line.
67    ChangeNumber(NumberChange, bool),
68
69    /// Join the lines within the targeted text together.
70    ///
71    /// If [bool] is true, modify spacing when joining.
72    Join(JoinStyle),
73
74    /// Change the indent level of the targeted text.
75    Indent(IndentChange),
76
77    /// Change the case of the targeted text.
78    ChangeCase(Case),
79}
80
81impl EditAction {
82    /// Returns true if this [EditAction] doesn't modify a buffer's text.
83    pub fn is_readonly(&self) -> bool {
84        match self {
85            EditAction::Motion => true,
86            EditAction::Yank => true,
87
88            EditAction::ChangeCase(_) => false,
89            EditAction::ChangeNumber(_, _) => false,
90            EditAction::Delete => false,
91            EditAction::Format => false,
92            EditAction::Indent(_) => false,
93            EditAction::Join(_) => false,
94            EditAction::Replace(_) => false,
95        }
96    }
97
98    /// Returns true if the value is [EditAction::Motion].
99    pub fn is_motion(&self) -> bool {
100        matches!(self, EditAction::Motion)
101    }
102
103    /// Returns true if this [EditAction] is allowed to trigger a [WindowAction::Switch] after an
104    /// error.
105    pub fn is_switchable(&self, _: &EditContext) -> bool {
106        self.is_motion()
107    }
108}
109
110/// Actions for manipulating text selections.
111#[derive(Clone, Debug, Eq, PartialEq)]
112#[non_exhaustive]
113pub enum SelectionAction {
114    /// Duplicate selections [*n* times](Count) to adjacent lines in [MoveDir1D] direction.
115    ///
116    /// If the column positions are too large to fit on the adjacent lines, then the next line
117    /// large enough to hold the selection is used instead.
118    Duplicate(MoveDir1D, Count),
119
120    /// Change the placement of the cursor and anchor of a visual selection.
121    CursorSet(SelectionCursorChange),
122
123    /// Expand a selection by repositioning its cursor and anchor such that they are placed on the
124    /// specified boundary.
125    ///
126    /// Be aware that since this repositions the start and end of the selection, this may not do
127    /// what you want with [TargetShape::BlockWise] selections.
128    Expand(SelectionBoundary, TargetShapeFilter),
129
130    /// Filter selections using the last regular expression entered for [CommandType::Search].
131    ///
132    /// The [bool] argument indicates whether we should drop selections that match instead of
133    /// keeping them.
134    Filter(bool),
135
136    /// Join adjacent selections together.
137    Join,
138
139    /// Change the bounds of the current selection as described by the
140    /// [style](SelectionResizeStyle) and [target](EditTarget).
141    ///
142    /// If the context doesn't specify a selection shape, then the selection will determine its
143    /// shape from the [EditTarget].
144    Resize(SelectionResizeStyle, EditTarget),
145
146    /// Split [matching selections](TargetShapeFilter) into multiple selections line.
147    ///
148    /// All of the new selections are of the same shape as the one they were split from.
149    Split(SelectionSplitStyle, TargetShapeFilter),
150
151    /// Shrink a selection by repositioning its cursor and anchor such that they are placed on the
152    /// specified boundary.
153    ///
154    /// Be aware that since this repositions the start and end of the selection, this may not do
155    /// what you want with [TargetShape::BlockWise] selections.
156    Trim(SelectionBoundary, TargetShapeFilter),
157}
158
159/// Actions for inserting text into a buffer.
160#[derive(Clone, Debug, Eq, PartialEq)]
161#[non_exhaustive]
162pub enum InsertTextAction {
163    /// Insert a new line [shape-wise](TargetShape) before or after the current position.
164    OpenLine(TargetShape, MoveDir1D, Count),
165
166    /// Paste before or after the current cursor position [*n*](Count) times.
167    Paste(PasteStyle, Count),
168
169    /// Insert the contents of a [String] on [either side](MoveDir1D) of the cursor.
170    Transcribe(String, MoveDir1D, Count),
171
172    /// Type a [character](Char) on [either side](MoveDir1D) of the cursor [*n*](Count) times.
173    Type(Specifier<Char>, MoveDir1D, Count),
174}
175
176/// Actions for manipulating a buffer's history.
177#[derive(Clone, Debug, Eq, PartialEq)]
178pub enum HistoryAction {
179    /// Create a new editing history checkpoint.
180    Checkpoint,
181
182    /// Redo [*n*](Count) edits.
183    Redo(Count),
184
185    /// Undo [*n*](Count) edits.
186    Undo(Count),
187}
188
189impl HistoryAction {
190    /// Returns true if this [HistoryAction] doesn't modify a buffer's text.
191    pub fn is_readonly(&self) -> bool {
192        match self {
193            HistoryAction::Redo(_) => false,
194            HistoryAction::Undo(_) => false,
195            HistoryAction::Checkpoint => true,
196        }
197    }
198}
199
200/// Actions for manipulating cursor groups.
201#[derive(Clone, Debug, Eq, PartialEq)]
202#[non_exhaustive]
203pub enum CursorAction {
204    /// Close the [targeted cursors](CursorCloseTarget) in the current cursor group.
205    Close(CursorCloseTarget),
206
207    /// Restore a saved cursor group.
208    ///
209    /// If a combining style is specified, then the saved group will be merged with the current one
210    /// as specified.
211    Restore(CursorGroupCombineStyle),
212
213    /// Rotate which cursor in the cursor group is the current leader .
214    Rotate(MoveDir1D, Count),
215
216    /// Save the current cursor group.
217    ///
218    /// If a combining style is specified, then the current group will be merged with any
219    /// previously saved group as specified.
220    Save(CursorGroupCombineStyle),
221
222    /// Split each cursor in the cursor group [*n*](Count) times.
223    Split(Count),
224}
225
226impl CursorAction {
227    /// Returns true if this [CursorAction] is allowed to trigger a [WindowAction::Switch] after an
228    /// error.
229    pub fn is_switchable(&self, _: &EditContext) -> bool {
230        match self {
231            CursorAction::Restore(_) => true,
232
233            CursorAction::Close(_) => false,
234            CursorAction::Rotate(..) => false,
235            CursorAction::Save(_) => false,
236            CursorAction::Split(_) => false,
237        }
238    }
239}
240
241/// Actions for running application commands (e.g. `:w` or `:quit`).
242#[derive(Clone, Debug, Eq, PartialEq)]
243#[non_exhaustive]
244pub enum CommandAction {
245    /// Run a command string.
246    ///
247    /// This should update [Register::LastCommand].
248    Run(String),
249
250    /// Execute the last [CommandType::Command] entry [*n* times](Count).
251    Execute(Count),
252}
253
254/// Actions for manipulating the application's command bar.
255#[derive(Clone, Debug, Eq, PartialEq)]
256pub enum CommandBarAction<I: ApplicationInfo> {
257    /// Focus the command bar
258    Focus(String, CommandType, Box<Action<I>>),
259
260    /// Unfocus the command bar.
261    Unfocus,
262}
263
264/// Actions for manipulating prompts.
265#[derive(Clone, Debug, Eq, PartialEq)]
266pub enum PromptAction {
267    /// Abort command entry.
268    ///
269    /// [bool] indicates whether this requires the prompt to be empty. (For example, how `<C-D>`
270    /// behaves in shells.)
271    Abort(bool),
272
273    /// Submit the currently entered text.
274    Submit,
275
276    /// Move backwards and forwards through previous entries.
277    ///
278    /// If [bool] is `true`, then this should only move through entries that share an initially
279    /// typed prefix.
280    Recall(MoveDir1D, Count, bool),
281}
282
283/// Actions for recording and running macros.
284#[derive(Clone, Debug, Eq, PartialEq)]
285#[non_exhaustive]
286pub enum MacroAction {
287    /// Execute the contents of the contextually specified Register [*n* times](Count).
288    ///
289    /// If no register is specified, then this should default to [Register::UnnamedMacro].
290    Execute(Count),
291
292    /// Run the given macro string [*n* times](Count).
293    Run(String, Count),
294
295    /// Execute the contents of the previously specified register [*n* times](Count).
296    Repeat(Count),
297
298    /// Start or stop recording a macro.
299    ToggleRecording,
300}
301
302/// Actions for manipulating application tabs.
303#[derive(Clone, Debug, Eq, PartialEq)]
304#[non_exhaustive]
305pub enum TabAction<I: ApplicationInfo> {
306    /// Close the [TabTarget] tabs with [CloseFlags] options.
307    Close(TabTarget, CloseFlags),
308
309    /// Extract the currently focused window from the currently focused tab, and place it in a new
310    /// tab.
311    ///
312    /// If there is only one window in the current tab, then this does nothing.
313    ///
314    /// The new tab will be placed on [MoveDir1D] side of the tab targeted by [FocusChange]. If
315    /// [FocusChange] doesn't resolve to a valid tab, then the new tab is placed after the
316    /// currently focused tab.
317    Extract(FocusChange, MoveDir1D),
318
319    /// Change the current focus to the tab targeted by [FocusChange].
320    Focus(FocusChange),
321
322    /// Move the currently focused tab to the position targeted by [FocusChange].
323    Move(FocusChange),
324
325    /// Open a new tab after the tab targeted by [FocusChange] that displays the requested content.
326    Open(OpenTarget<I::WindowId>, FocusChange),
327}
328
329/// Actions for manipulating application windows.
330#[derive(Clone, Debug, Eq, PartialEq)]
331#[non_exhaustive]
332pub enum WindowAction<I: ApplicationInfo> {
333    /// Close the [WindowTarget] windows with [CloseFlags] options.
334    Close(WindowTarget, CloseFlags),
335
336    /// Exchange the currently focused window with the window targeted by [FocusChange].
337    Exchange(FocusChange),
338
339    /// Change the current focus to the window targeted by [FocusChange].
340    Focus(FocusChange),
341
342    /// Move the currently focused window to the [MoveDir2D] side of the screen.
343    MoveSide(MoveDir2D),
344
345    /// Open a new window that is [*n*](Count) columns along [an axis](Axis), positioned relative to
346    /// the current window as indicated by [MoveDir1D].
347    Open(OpenTarget<I::WindowId>, Axis, MoveDir1D, Count),
348
349    /// Visually rotate the windows in [MoveDir2D] direction.
350    Rotate(MoveDir1D),
351
352    /// Split the currently focused window [*n* times](Count) along [an axis](Axis), moving
353    /// the focus in [MoveDir1D] direction after performing the split.
354    Split(OpenTarget<I::WindowId>, Axis, MoveDir1D, Count),
355
356    /// Switch what content the window is currently showing.
357    ///
358    /// If there are no currently open windows in the tab, then this behaves like
359    /// [WindowAction::Open].
360    Switch(OpenTarget<I::WindowId>),
361
362    /// Clear all of the explicitly set window sizes, and instead try to equally distribute
363    /// available rows and columns.
364    ClearSizes,
365
366    /// Resize the window targeted by [FocusChange] according to [SizeChange].
367    Resize(FocusChange, Axis, SizeChange),
368
369    /// Write the contents of the windows targeted by [WindowTarget].
370    Write(WindowTarget, Option<String>, WriteFlags),
371
372    /// Zoom in on the currently focused window so that it takes up the whole screen. If there is
373    /// already a zoomed-in window, then return to showing all windows.
374    ZoomToggle,
375}
376
377/// Actions for editing text within buffer.
378#[derive(Clone, Debug, Eq, PartialEq)]
379#[non_exhaustive]
380pub enum EditorAction {
381    /// Complete the text before the cursor group leader.
382    Complete(CompletionType, CompletionSelection, CompletionDisplay),
383
384    /// Modify the current cursor group.
385    Cursor(CursorAction),
386
387    /// Perform the specified [action](EditAction) on [a target](EditTarget).
388    Edit(Specifier<EditAction>, EditTarget),
389
390    /// Perform a history operation.
391    History(HistoryAction),
392
393    /// Insert text.
394    InsertText(InsertTextAction),
395
396    /// Create a new [Mark] at the current leader position.
397    Mark(Specifier<Mark>),
398
399    /// Modify the current selection.
400    Selection(SelectionAction),
401}
402
403impl EditorAction {
404    /// Indicates if this is a read-only action.
405    pub fn is_readonly(&self, ctx: &EditContext) -> bool {
406        match self {
407            EditorAction::Complete(_, _, _) => false,
408            EditorAction::History(act) => act.is_readonly(),
409            EditorAction::InsertText(_) => false,
410
411            EditorAction::Cursor(_) => true,
412            EditorAction::Mark(_) => true,
413            EditorAction::Selection(_) => true,
414
415            EditorAction::Edit(act, _) => ctx.resolve(act).is_readonly(),
416        }
417    }
418
419    /// Indicates how an action gets included in [RepeatType::EditSequence].
420    ///
421    /// `motion` indicates what to do with [EditAction::Motion].
422    pub fn is_edit_sequence(&self, motion: SequenceStatus, ctx: &EditContext) -> SequenceStatus {
423        match self {
424            EditorAction::History(_) => SequenceStatus::Break,
425            EditorAction::Mark(_) => SequenceStatus::Break,
426            EditorAction::InsertText(_) => SequenceStatus::Track,
427            EditorAction::Cursor(_) => SequenceStatus::Track,
428            EditorAction::Selection(_) => SequenceStatus::Track,
429            EditorAction::Complete(_, _, _) => SequenceStatus::Track,
430            EditorAction::Edit(act, _) => {
431                match ctx.resolve(act) {
432                    EditAction::Motion => motion,
433                    EditAction::Yank => SequenceStatus::Ignore,
434                    _ => SequenceStatus::Track,
435                }
436            },
437        }
438    }
439
440    /// Indicates how an action gets included in [RepeatType::LastAction].
441    pub fn is_last_action(&self, _: &EditContext) -> SequenceStatus {
442        match self {
443            EditorAction::History(HistoryAction::Checkpoint) => SequenceStatus::Ignore,
444            EditorAction::History(HistoryAction::Undo(_)) => SequenceStatus::Atom,
445            EditorAction::History(HistoryAction::Redo(_)) => SequenceStatus::Atom,
446
447            EditorAction::Complete(_, _, _) => SequenceStatus::Atom,
448            EditorAction::Cursor(_) => SequenceStatus::Atom,
449            EditorAction::Edit(_, _) => SequenceStatus::Atom,
450            EditorAction::InsertText(_) => SequenceStatus::Atom,
451            EditorAction::Mark(_) => SequenceStatus::Atom,
452            EditorAction::Selection(_) => SequenceStatus::Atom,
453        }
454    }
455
456    /// Indicates how an action gets included in [RepeatType::LastSelection].
457    pub fn is_last_selection(&self, ctx: &EditContext) -> SequenceStatus {
458        match self {
459            EditorAction::History(_) => SequenceStatus::Ignore,
460            EditorAction::Mark(_) => SequenceStatus::Ignore,
461            EditorAction::InsertText(_) => SequenceStatus::Ignore,
462            EditorAction::Cursor(_) => SequenceStatus::Ignore,
463            EditorAction::Complete(_, _, _) => SequenceStatus::Ignore,
464
465            EditorAction::Selection(SelectionAction::Resize(_, _)) => SequenceStatus::Track,
466            EditorAction::Selection(_) => SequenceStatus::Ignore,
467
468            EditorAction::Edit(act, _) => {
469                if let EditAction::Motion = ctx.resolve(act) {
470                    if ctx.get_target_shape().is_some() {
471                        SequenceStatus::Restart
472                    } else {
473                        SequenceStatus::Ignore
474                    }
475                } else {
476                    SequenceStatus::Ignore
477                }
478            },
479        }
480    }
481
482    /// Returns true if this [Action] is allowed to trigger a [WindowAction::Switch] after an error.
483    pub fn is_switchable(&self, ctx: &EditContext) -> bool {
484        match self {
485            EditorAction::Cursor(act) => act.is_switchable(ctx),
486            EditorAction::Edit(act, _) => ctx.resolve(act).is_switchable(ctx),
487            EditorAction::Complete(_, _, _) => false,
488            EditorAction::History(_) => false,
489            EditorAction::InsertText(_) => false,
490            EditorAction::Mark(_) => false,
491            EditorAction::Selection(_) => false,
492        }
493    }
494}
495
496impl From<CursorAction> for EditorAction {
497    fn from(act: CursorAction) -> Self {
498        EditorAction::Cursor(act)
499    }
500}
501
502impl From<HistoryAction> for EditorAction {
503    fn from(act: HistoryAction) -> Self {
504        EditorAction::History(act)
505    }
506}
507
508impl From<InsertTextAction> for EditorAction {
509    fn from(act: InsertTextAction) -> Self {
510        EditorAction::InsertText(act)
511    }
512}
513
514impl From<SelectionAction> for EditorAction {
515    fn from(act: SelectionAction) -> Self {
516        EditorAction::Selection(act)
517    }
518}
519
520/// The result of either pressing a complete keybinding sequence, or parsing a command.
521#[derive(Clone, Debug, Eq, PartialEq)]
522#[non_exhaustive]
523pub enum Action<I: ApplicationInfo = EmptyInfo> {
524    /// Do nothing.
525    NoOp,
526
527    /// Perform an editor action.
528    Editor(EditorAction),
529
530    /// Perform a macro-related action.
531    Macro(MacroAction),
532
533    /// Navigate through the cursor positions in [the specified list](PositionList).
534    ///
535    /// If the current window cannot satisfy the given [Count], then this may jump to other
536    /// windows.
537    Jump(PositionList, MoveDir1D, Count),
538
539    /// Repeat an action sequence with the current context.
540    Repeat(RepeatType),
541
542    /// Scroll the viewport in [the specified manner](ScrollStyle).
543    Scroll(ScrollStyle),
544
545    /// Lookup the keyword under the cursor.
546    KeywordLookup,
547
548    /// Redraw the screen.
549    RedrawScreen,
550
551    /// Show an [InfoMessage].
552    ShowInfoMessage(InfoMessage),
553
554    /// Suspend the process.
555    Suspend,
556
557    /// Find the [*n*<sup>th</sup>](Count) occurrence of the current application-level search.
558    Search(MoveDirMod, Count),
559
560    /// Perform a command-related action.
561    Command(CommandAction),
562
563    /// Perform a command bar-related action.
564    CommandBar(CommandBarAction<I>),
565
566    /// Perform a prompt-related action.
567    Prompt(PromptAction),
568
569    /// Perform a tab-related action.
570    Tab(TabAction<I>),
571
572    /// Perform a window-related action.
573    Window(WindowAction<I>),
574
575    /// Application-specific command.
576    Application(I::Action),
577}
578
579impl<I: ApplicationInfo> Action<I> {
580    /// Indicates how an action gets included in [RepeatType::EditSequence].
581    ///
582    /// `motion` indicates what to do with [EditAction::Motion].
583    pub fn is_edit_sequence(&self, motion: SequenceStatus, ctx: &EditContext) -> SequenceStatus {
584        match self {
585            Action::Repeat(_) => SequenceStatus::Ignore,
586
587            Action::Application(act) => act.is_edit_sequence(ctx),
588            Action::Editor(act) => act.is_edit_sequence(motion, ctx),
589
590            Action::Command(_) => SequenceStatus::Break,
591            Action::CommandBar(_) => SequenceStatus::Break,
592            Action::Jump(_, _, _) => SequenceStatus::Break,
593            Action::Macro(_) => SequenceStatus::Break,
594            Action::Prompt(_) => SequenceStatus::Break,
595            Action::Tab(_) => SequenceStatus::Break,
596            Action::Window(_) => SequenceStatus::Break,
597
598            Action::KeywordLookup => SequenceStatus::Ignore,
599            Action::NoOp => SequenceStatus::Ignore,
600            Action::RedrawScreen => SequenceStatus::Ignore,
601            Action::Scroll(_) => SequenceStatus::Ignore,
602            Action::Search(_, _) => SequenceStatus::Ignore,
603            Action::ShowInfoMessage(_) => SequenceStatus::Ignore,
604            Action::Suspend => SequenceStatus::Ignore,
605        }
606    }
607
608    /// Indicates how an action gets included in [RepeatType::LastAction].
609    pub fn is_last_action(&self, ctx: &EditContext) -> SequenceStatus {
610        match self {
611            Action::Repeat(RepeatType::EditSequence) => SequenceStatus::Atom,
612            Action::Repeat(RepeatType::LastAction) => SequenceStatus::Ignore,
613            Action::Repeat(RepeatType::LastSelection) => SequenceStatus::Atom,
614
615            Action::Application(act) => act.is_last_action(ctx),
616            Action::Editor(act) => act.is_last_action(ctx),
617
618            Action::Command(_) => SequenceStatus::Atom,
619            Action::CommandBar(_) => SequenceStatus::Atom,
620            Action::Jump(_, _, _) => SequenceStatus::Atom,
621            Action::Macro(_) => SequenceStatus::Atom,
622            Action::Tab(_) => SequenceStatus::Atom,
623            Action::Window(_) => SequenceStatus::Atom,
624            Action::KeywordLookup => SequenceStatus::Atom,
625            Action::NoOp => SequenceStatus::Atom,
626            Action::Prompt(_) => SequenceStatus::Atom,
627            Action::RedrawScreen => SequenceStatus::Atom,
628            Action::Scroll(_) => SequenceStatus::Atom,
629            Action::Search(_, _) => SequenceStatus::Atom,
630            Action::ShowInfoMessage(_) => SequenceStatus::Atom,
631            Action::Suspend => SequenceStatus::Atom,
632        }
633    }
634
635    /// Indicates how an action gets included in [RepeatType::LastSelection].
636    pub fn is_last_selection(&self, ctx: &EditContext) -> SequenceStatus {
637        match self {
638            Action::Repeat(_) => SequenceStatus::Ignore,
639
640            Action::Application(act) => act.is_last_selection(ctx),
641            Action::Editor(act) => act.is_last_selection(ctx),
642
643            Action::Command(_) => SequenceStatus::Ignore,
644            Action::CommandBar(_) => SequenceStatus::Ignore,
645            Action::Jump(_, _, _) => SequenceStatus::Ignore,
646            Action::Macro(_) => SequenceStatus::Ignore,
647            Action::Tab(_) => SequenceStatus::Ignore,
648            Action::Window(_) => SequenceStatus::Ignore,
649            Action::KeywordLookup => SequenceStatus::Ignore,
650            Action::NoOp => SequenceStatus::Ignore,
651            Action::Prompt(_) => SequenceStatus::Ignore,
652            Action::RedrawScreen => SequenceStatus::Ignore,
653            Action::Scroll(_) => SequenceStatus::Ignore,
654            Action::Search(_, _) => SequenceStatus::Ignore,
655            Action::ShowInfoMessage(_) => SequenceStatus::Ignore,
656            Action::Suspend => SequenceStatus::Ignore,
657        }
658    }
659
660    /// Returns true if this [Action] is allowed to trigger a [WindowAction::Switch] after an error.
661    pub fn is_switchable(&self, ctx: &EditContext) -> bool {
662        match self {
663            Action::Application(act) => act.is_switchable(ctx),
664            Action::Editor(act) => act.is_switchable(ctx),
665            Action::Jump(..) => true,
666
667            Action::CommandBar(_) => false,
668            Action::Command(_) => false,
669            Action::KeywordLookup => false,
670            Action::Macro(_) => false,
671            Action::NoOp => false,
672            Action::Prompt(_) => false,
673            Action::RedrawScreen => false,
674            Action::Repeat(_) => false,
675            Action::Scroll(_) => false,
676            Action::Search(_, _) => false,
677            Action::ShowInfoMessage(_) => false,
678            Action::Suspend => false,
679            Action::Tab(_) => false,
680            Action::Window(_) => false,
681        }
682    }
683}
684
685#[allow(clippy::derivable_impls)]
686impl<I: ApplicationInfo> Default for Action<I> {
687    fn default() -> Self {
688        Action::NoOp
689    }
690}
691
692impl<I: ApplicationInfo> From<SelectionAction> for Action<I> {
693    fn from(act: SelectionAction) -> Self {
694        Action::Editor(EditorAction::Selection(act))
695    }
696}
697
698impl<I: ApplicationInfo> From<InsertTextAction> for Action<I> {
699    fn from(act: InsertTextAction) -> Self {
700        Action::Editor(EditorAction::InsertText(act))
701    }
702}
703
704impl<I: ApplicationInfo> From<HistoryAction> for Action<I> {
705    fn from(act: HistoryAction) -> Self {
706        Action::Editor(EditorAction::History(act))
707    }
708}
709
710impl<I: ApplicationInfo> From<CursorAction> for Action<I> {
711    fn from(act: CursorAction) -> Self {
712        Action::Editor(EditorAction::Cursor(act))
713    }
714}
715
716impl<I: ApplicationInfo> From<EditorAction> for Action<I> {
717    fn from(act: EditorAction) -> Self {
718        Action::Editor(act)
719    }
720}
721
722impl<I: ApplicationInfo> From<MacroAction> for Action<I> {
723    fn from(act: MacroAction) -> Self {
724        Action::Macro(act)
725    }
726}
727
728impl<I: ApplicationInfo> From<CommandAction> for Action<I> {
729    fn from(act: CommandAction) -> Self {
730        Action::Command(act)
731    }
732}
733
734impl<I: ApplicationInfo> From<CommandBarAction<I>> for Action<I> {
735    fn from(act: CommandBarAction<I>) -> Self {
736        Action::CommandBar(act)
737    }
738}
739
740impl<I: ApplicationInfo> From<PromptAction> for Action<I> {
741    fn from(act: PromptAction) -> Self {
742        Action::Prompt(act)
743    }
744}
745
746impl<I: ApplicationInfo> From<WindowAction<I>> for Action<I> {
747    fn from(act: WindowAction<I>) -> Self {
748        Action::Window(act)
749    }
750}
751
752impl<I: ApplicationInfo> From<TabAction<I>> for Action<I> {
753    fn from(act: TabAction<I>) -> Self {
754        Action::Tab(act)
755    }
756}
757
758#[cfg(test)]
759mod tests {
760    use super::*;
761
762    #[test]
763    fn test_is_readonly() {
764        let mut ctx = EditContext::default();
765
766        let act = SelectionAction::Duplicate(MoveDir1D::Next, Count::Contextual);
767        assert_eq!(EditorAction::from(act).is_readonly(&ctx), true);
768
769        let act = HistoryAction::Checkpoint;
770        assert_eq!(EditorAction::from(act).is_readonly(&ctx), true);
771
772        let act = HistoryAction::Undo(Count::Contextual);
773        assert_eq!(EditorAction::from(act).is_readonly(&ctx), false);
774
775        let act = EditorAction::Edit(Specifier::Contextual, EditTarget::CurrentPosition);
776        ctx.operation = EditAction::Motion;
777        assert_eq!(act.is_readonly(&ctx), true);
778
779        let act = EditorAction::Edit(Specifier::Contextual, EditTarget::CurrentPosition);
780        ctx.operation = EditAction::Delete;
781        assert_eq!(act.is_readonly(&ctx), false);
782    }
783}