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/// A macro that turns a shorthand command DSL into an [Action].
40pub use editor_types_macros::action;
41
42/// The various actions that can be taken on text.
43#[derive(Clone, Debug, Default, Eq, PartialEq)]
44pub enum EditAction {
45    /// Move the cursor.
46    ///
47    /// If a shape is [specified contextually](EditContext::get_target_shape), then visually select
48    /// text while moving, as if using [SelectionAction::Resize] with
49    /// [SelectionResizeStyle::Extend].
50    #[default]
51    Motion,
52
53    /// Delete the targeted text.
54    Delete,
55
56    /// Yank the targeted text into a [Register].
57    Yank,
58
59    /// Replace characters within the targeted text with a new character.
60    ///
61    /// If [bool] is true, virtually replace characters by how many columns they occupy.
62    Replace(bool),
63
64    /// Automatically format the targeted text.
65    Format,
66
67    /// Change the first number on each line within the targeted text.
68    ///
69    /// The [bool] argument controls whether to increment by an additional count on each line.
70    ChangeNumber(NumberChange, bool),
71
72    /// Join the lines within the targeted text together.
73    Join(JoinStyle),
74
75    /// Change the indent level of the targeted text.
76    Indent(IndentChange),
77
78    /// Change the case of the targeted text.
79    ChangeCase(Case),
80}
81
82impl EditAction {
83    /// Returns true if this [EditAction] doesn't modify a buffer's text.
84    pub fn is_readonly(&self) -> bool {
85        match self {
86            EditAction::Motion => true,
87            EditAction::Yank => true,
88
89            EditAction::ChangeCase(_) => false,
90            EditAction::ChangeNumber(_, _) => false,
91            EditAction::Delete => false,
92            EditAction::Format => false,
93            EditAction::Indent(_) => false,
94            EditAction::Join(_) => false,
95            EditAction::Replace(_) => false,
96        }
97    }
98
99    /// Returns true if the value is [EditAction::Motion].
100    pub fn is_motion(&self) -> bool {
101        matches!(self, EditAction::Motion)
102    }
103
104    /// Returns true if this [EditAction] is allowed to trigger a [WindowAction::Switch] after an
105    /// error.
106    pub fn is_switchable(&self, _: &EditContext) -> bool {
107        self.is_motion()
108    }
109}
110
111/// Actions for manipulating text selections.
112#[derive(Clone, Debug, Eq, PartialEq)]
113#[non_exhaustive]
114pub enum SelectionAction {
115    /// Duplicate selections [*n* times](Count) to adjacent lines in [MoveDir1D] direction.
116    ///
117    /// If the column positions are too large to fit on the adjacent lines, then the next line
118    /// large enough to hold the selection is used instead.
119    ///
120    /// ## Example: Using `action!`
121    ///
122    /// ```
123    /// use editor_types::prelude::*;
124    /// use editor_types::{action, Action, SelectionAction};
125    ///
126    /// let count = Count::Contextual;
127    /// let act: Action = SelectionAction::Duplicate(MoveDir1D::Next, count.clone()).into();
128    ///
129    /// // All of these are equivalent:
130    /// assert_eq!(action!("selection duplicate -d next"), act);
131    /// assert_eq!(action!("selection duplicate -d next -c ctx"), act);
132    /// assert_eq!(action!("selection duplicate -d next -c {count}"), act);
133    /// ```
134    Duplicate(MoveDir1D, Count),
135
136    /// Change the placement of the cursor and anchor of a visual selection.
137    ///
138    /// ## Example: Using `action!`
139    ///
140    /// ```
141    /// use editor_types::prelude::*;
142    /// use editor_types::{action, Action, SelectionAction};
143    ///
144    /// let change = SelectionCursorChange::End;
145    /// let act: Action = action!("selection cursor-set -f end");
146    /// assert_eq!(act, SelectionAction::CursorSet(change).into());
147    /// ```
148    CursorSet(SelectionCursorChange),
149
150    /// Expand a selection by repositioning its cursor and anchor such that they are placed on the
151    /// specified boundary.
152    ///
153    /// Be aware that since this repositions the start and end of the selection, this may not do
154    /// what you want with [TargetShape::BlockWise] selections.
155    ///
156    /// ## Example: Using `action!`
157    ///
158    /// ```
159    /// use editor_types::prelude::*;
160    /// use editor_types::{action, Action, SelectionAction};
161    ///
162    /// let style = SelectionBoundary::Line;
163    /// let split: Action = action!("selection expand -b line -t all");
164    /// assert_eq!(split, SelectionAction::Expand(style, TargetShapeFilter::ALL).into());
165    /// ```
166    Expand(SelectionBoundary, TargetShapeFilter),
167
168    /// Filter selections using the last regular expression entered for [CommandType::Search].
169    ///
170    /// ## Example: Using `action!`
171    ///
172    /// ```
173    /// use editor_types::prelude::*;
174    /// use editor_types::{action, Action, SelectionAction};
175    ///
176    /// let act = SelectionAction::Filter(MatchAction::Keep);
177    /// let split: Action = action!("selection filter -F keep");
178    /// assert_eq!(split, act.into());
179    /// ```
180    Filter(MatchAction),
181
182    /// Join adjacent selections together.
183    ///
184    /// ## Example: Using `action!`
185    ///
186    /// ```
187    /// use editor_types::prelude::*;
188    /// use editor_types::{action, Action, SelectionAction};
189    ///
190    /// let act: Action = SelectionAction::Join.into();
191    /// assert_eq!(act, action!("selection join"));
192    /// ```
193    Join,
194
195    /// Change the bounds of the current selection as described by the
196    /// [style](SelectionResizeStyle) and [target](EditTarget).
197    ///
198    /// If the context doesn't specify a selection shape, then the selection will determine its
199    /// shape from the [EditTarget].
200    ///
201    /// See the documentation for the [SelectionResizeStyle] variants for how to construct all of the
202    /// possible values using [action].
203    Resize(SelectionResizeStyle, EditTarget),
204
205    /// Split [matching selections](TargetShapeFilter) into multiple selections line.
206    ///
207    /// All of the new selections are of the same shape as the one they were split from.
208    ///
209    /// ## Example: Using `action!`
210    ///
211    /// ```
212    /// use editor_types::prelude::*;
213    /// use editor_types::{action, Action, SelectionAction};
214    ///
215    /// let style = SelectionSplitStyle::Lines;
216    /// let split: Action = action!("selection split -s lines -F all");
217    /// assert_eq!(split, SelectionAction::Split(style, TargetShapeFilter::ALL).into());
218    /// ```
219    Split(SelectionSplitStyle, TargetShapeFilter),
220
221    /// Shrink a selection by repositioning its cursor and anchor such that they are placed on the
222    /// specified boundary.
223    ///
224    /// Be aware that since this repositions the start and end of the selection, this may not do
225    /// what you want with [TargetShape::BlockWise] selections.
226    ///
227    /// ## Example: Using `action!`
228    ///
229    /// ```
230    /// use editor_types::prelude::*;
231    /// use editor_types::{action, Action, SelectionAction};
232    ///
233    /// let style = SelectionBoundary::Line;
234    /// let split: Action = action!("selection trim -b line -t all");
235    /// assert_eq!(split, SelectionAction::Trim(style, TargetShapeFilter::ALL).into());
236    /// ```
237    Trim(SelectionBoundary, TargetShapeFilter),
238}
239
240/// Actions for inserting text into a buffer.
241#[derive(Clone, Debug, Eq, PartialEq)]
242#[non_exhaustive]
243pub enum InsertTextAction {
244    /// Insert a new line [shape-wise](TargetShape) before or after the current position.
245    ///
246    /// ## Example: Using `action!`
247    ///
248    /// ```
249    /// use editor_types::prelude::*;
250    /// use editor_types::{action, Action, InsertTextAction};
251    ///
252    /// let shape = TargetShape::LineWise;
253    /// let count = Count::Contextual;
254    /// let act: Action = InsertTextAction::OpenLine(shape, MoveDir1D::Next, count).into();
255    ///
256    /// // All of these are equivalent:
257    /// assert_eq!(act, action!("insert open-line -S line -d next -c ctx"));
258    /// assert_eq!(act, action!("insert open-line -S line -d next"));
259    /// ```
260    OpenLine(TargetShape, MoveDir1D, Count),
261
262    /// Paste before or after the current cursor position [*n*](Count) times.
263    ///
264    /// ## Example: Using `action!`
265    ///
266    /// ```
267    /// use editor_types::prelude::*;
268    /// use editor_types::{action, Action, InsertTextAction};
269    ///
270    /// let count = 5;
271    /// let paste: Action = action!("insert paste -s (side -d next) -c {count}");
272    /// assert_eq!(paste, InsertTextAction::Paste(PasteStyle::Side(MoveDir1D::Next), Count::Exact(5)).into());
273    /// ```
274    Paste(PasteStyle, Count),
275
276    /// Insert the contents of a [String] on [either side](MoveDir1D) of the cursor.
277    ///
278    /// ## Example: Using `action!`
279    ///
280    /// ```
281    /// use editor_types::prelude::*;
282    /// use editor_types::{action, Action, InsertTextAction};
283    ///
284    /// let input: Action = action!(r#"insert transcribe -i "hello" -d next -c 1"#);
285    /// assert_eq!(input, InsertTextAction::Transcribe("hello".into(), MoveDir1D::Next, 1.into()).into());
286    /// ```
287    Transcribe(String, MoveDir1D, Count),
288
289    /// Type a [character](Char) on [either side](MoveDir1D) of the cursor [*n*](Count) times.
290    ///
291    /// ## Example: Using `action!`
292    ///
293    /// ```
294    /// use editor_types::prelude::*;
295    /// use editor_types::{action, Action, InsertTextAction};
296    ///
297    /// let c = Specifier::Exact(Char::from('a'));
298    /// let dir = MoveDir1D::Previous;
299    /// let count = Count::Contextual;
300    /// let act: Action = InsertTextAction::Type(c.clone(), dir, count).into();
301    ///
302    /// // All of these are equivalent:
303    /// assert_eq!(act, action!("insert type -i (exact \'a\') -d previous -c ctx"));
304    /// assert_eq!(act, action!("insert type -i (exact \'a\') -c ctx"));
305    /// assert_eq!(act, action!("insert type -i (exact \'a\') -d previous"));
306    /// assert_eq!(act, action!("insert type -i (exact \'a\')"));
307    /// assert_eq!(act, action!("insert type -i {c}"));
308    /// ```
309    Type(Specifier<Char>, MoveDir1D, Count),
310}
311
312/// Actions for manipulating a buffer's history.
313#[derive(Clone, Debug, Eq, PartialEq)]
314pub enum HistoryAction {
315    /// Create a new editing history checkpoint.
316    ///
317    /// ## Example: Using `action!`
318    ///
319    /// ```
320    /// use editor_types::prelude::*;
321    /// use editor_types::{action, Action, HistoryAction};
322    ///
323    /// let check: Action = action!("history checkpoint");
324    /// assert_eq!(check, HistoryAction::Checkpoint.into());
325    /// ```
326    Checkpoint,
327
328    /// Redo [*n*](Count) edits.
329    ///
330    /// ## Example: Using `action!`
331    ///
332    /// ```
333    /// use editor_types::prelude::*;
334    /// use editor_types::{action, Action, HistoryAction};
335    ///
336    /// let redo: Action = action!("history redo");
337    /// assert_eq!(redo, HistoryAction::Redo(Count::Contextual).into());
338    ///
339    /// let redo: Action = action!("history redo -c 1");
340    /// assert_eq!(redo, HistoryAction::Redo(Count::Exact(1)).into());
341    /// ```
342    Redo(Count),
343
344    /// Undo [*n*](Count) edits.
345    ///
346    /// ```
347    /// use editor_types::prelude::*;
348    /// use editor_types::{action, Action, HistoryAction};
349    ///
350    /// let undo: Action = action!("history undo");
351    /// assert_eq!(undo, HistoryAction::Undo(Count::Contextual).into());
352    ///
353    /// let undo: Action = action!("history undo -c 1");
354    /// assert_eq!(undo, HistoryAction::Undo(Count::Exact(1)).into());
355    /// ```
356    Undo(Count),
357}
358
359impl HistoryAction {
360    /// Returns true if this [HistoryAction] doesn't modify a buffer's text.
361    pub fn is_readonly(&self) -> bool {
362        match self {
363            HistoryAction::Redo(_) => false,
364            HistoryAction::Undo(_) => false,
365            HistoryAction::Checkpoint => true,
366        }
367    }
368}
369
370/// Actions for manipulating cursor groups.
371#[derive(Clone, Debug, Eq, PartialEq)]
372#[non_exhaustive]
373pub enum CursorAction {
374    /// Close the [targeted cursors](CursorCloseTarget) in the current cursor group.
375    ///
376    /// ## Example: Using `action!`
377    ///
378    /// ```
379    /// use editor_types::prelude::*;
380    /// use editor_types::{action, Action, CursorAction};
381    ///
382    /// let close: Action = action!("cursor close -t leader");
383    /// assert_eq!(close, CursorAction::Close(CursorCloseTarget::Leader).into());
384    ///
385    /// let close: Action = action!("cursor close -t followers");
386    /// assert_eq!(close, CursorAction::Close(CursorCloseTarget::Followers).into());
387    /// ```
388    Close(CursorCloseTarget),
389
390    /// Restore a saved cursor group.
391    ///
392    /// If a combining style is specified, then the saved group will be merged with the current one
393    /// as specified.
394    ///
395    /// ## Example: Using `action!`
396    ///
397    /// ```
398    /// use editor_types::prelude::*;
399    /// use editor_types::{action, Action, CursorAction};
400    ///
401    /// let restore: Action = action!("cursor restore -s append");
402    /// assert_eq!(restore, CursorAction::Restore(CursorGroupCombineStyle::Append).into());
403    ///
404    /// let restore: Action = action!("cursor restore -s replace");
405    /// assert_eq!(restore, CursorAction::Restore(CursorGroupCombineStyle::Replace).into());
406    ///
407    /// let restore: Action = action!("cursor restore -s (merge select-cursor -d prev)");
408    /// assert_eq!(restore, CursorAction::Restore(CursorGroupCombineStyle::Merge(CursorMergeStyle::SelectCursor(MoveDir1D::Previous))).into());
409    /// ```
410    ///
411    /// See the documentation for [CursorGroupCombineStyle] for how to construct each of its
412    /// variants with [action].
413    Restore(CursorGroupCombineStyle),
414
415    /// Rotate which cursor in the cursor group is the current leader .
416    ///
417    /// ## Example: Using `action!`
418    ///
419    /// ```
420    /// use editor_types::prelude::*;
421    /// use editor_types::{action, Action, CursorAction};
422    ///
423    /// let rotate: Action = action!("cursor rotate -d prev");
424    /// assert_eq!(rotate, CursorAction::Rotate(MoveDir1D::Previous, Count::Contextual).into());
425    ///
426    /// let rotate: Action = action!("cursor rotate -d next -c 2");
427    /// assert_eq!(rotate, CursorAction::Rotate(MoveDir1D::Next, Count::Exact(2)).into());
428    /// ```
429    Rotate(MoveDir1D, Count),
430
431    /// Save the current cursor group.
432    ///
433    /// If a combining style is specified, then the current group will be merged with any
434    /// previously saved group as specified.
435    ///
436    /// ## Example: Using `action!`
437    ///
438    /// ```
439    /// use editor_types::prelude::*;
440    /// use editor_types::{action, Action, CursorAction};
441    ///
442    /// let save: Action = action!("cursor save -s append");
443    /// assert_eq!(save, CursorAction::Save(CursorGroupCombineStyle::Append).into());
444    ///
445    /// let save: Action = action!("cursor save -s replace");
446    /// assert_eq!(save, CursorAction::Save(CursorGroupCombineStyle::Replace).into());
447    ///
448    /// let save: Action = action!("cursor save -s (merge union)");
449    /// assert_eq!(save, CursorAction::Save(CursorGroupCombineStyle::Merge(CursorMergeStyle::Union)).into());
450    /// ```
451    ///
452    /// See the documentation for [CursorGroupCombineStyle] for how to construct each of its
453    /// variants with [action].
454    Save(CursorGroupCombineStyle),
455
456    /// Split each cursor in the cursor group [*n*](Count) times.
457    ///
458    /// ## Example: Using `action!`
459    ///
460    /// ```
461    /// use editor_types::prelude::*;
462    /// use editor_types::{action, Action, CursorAction};
463    ///
464    /// let split: Action = action!("cursor split -c {}", Count::Contextual);
465    /// assert_eq!(split, CursorAction::Split(Count::Contextual).into());
466    ///
467    /// let split: Action = action!("cursor split -c {}", 5);
468    /// assert_eq!(split, CursorAction::Split(Count::Exact(5)).into());
469    /// ```
470    Split(Count),
471}
472
473impl CursorAction {
474    /// Returns true if this [CursorAction] is allowed to trigger a [WindowAction::Switch] after an
475    /// error.
476    pub fn is_switchable(&self, _: &EditContext) -> bool {
477        match self {
478            CursorAction::Restore(_) => true,
479
480            CursorAction::Close(_) => false,
481            CursorAction::Rotate(..) => false,
482            CursorAction::Save(_) => false,
483            CursorAction::Split(_) => false,
484        }
485    }
486}
487
488/// Actions for running application commands (e.g. `:w` or `:quit`).
489#[derive(Clone, Debug, Eq, PartialEq)]
490#[non_exhaustive]
491pub enum CommandAction {
492    /// Run a command string.
493    ///
494    /// This should update [Register::LastCommand].
495    ///
496    /// ## Example: Using `action!`
497    ///
498    /// ```
499    /// use editor_types::prelude::*;
500    /// use editor_types::{action, Action, CommandAction};
501    ///
502    /// let quitall: Action = action!(r#"command run -i "quitall" "#);
503    /// assert_eq!(quitall, CommandAction::Run("quitall".into()).into());
504    /// ```
505    Run(String),
506
507    /// Execute the last [CommandType::Command] entry [*n* times](Count).
508    ///
509    /// ## Example: Using `action!`
510    ///
511    /// ```
512    /// use editor_types::prelude::*;
513    /// use editor_types::{action, Action, CommandAction};
514    ///
515    /// let exec: Action = action!("command execute");
516    /// assert_eq!(exec, CommandAction::Execute(Count::Contextual).into());
517    ///
518    /// let exec5: Action = action!("command execute -c 5");
519    /// assert_eq!(exec5, CommandAction::Execute(5.into()).into());
520    /// ```
521    Execute(Count),
522}
523
524/// Actions for manipulating the application's command bar.
525#[derive(Clone, Debug, Eq, PartialEq)]
526pub enum CommandBarAction<I: ApplicationInfo> {
527    /// Focus the command bar
528    ///
529    /// ## Example: Using `action!`
530    ///
531    /// ```
532    /// use editor_types::prelude::*;
533    /// use editor_types::{action, Action, CommandBarAction};
534    ///
535    /// let focus: Action = action!(r#"cmdbar focus -p "/" -s search -a (search -d same)"#);
536    /// assert_eq!(focus, CommandBarAction::Focus(
537    ///     "/".into(),
538    ///     CommandType::Search,
539    ///     Action::Search(MoveDirMod::Same, Count::Contextual).into(),
540    /// ).into());
541    /// ```
542    Focus(String, CommandType, Box<Action<I>>),
543
544    /// Unfocus the command bar.
545    ///
546    /// ## Example: Using `action!`
547    ///
548    /// ```
549    /// use editor_types::prelude::*;
550    /// use editor_types::{action, Action, CommandBarAction};
551    ///
552    /// let unfocus: Action = action!("cmdbar unfocus");
553    /// assert_eq!(unfocus, CommandBarAction::Unfocus.into());
554    /// ```
555    Unfocus,
556}
557
558/// Actions for manipulating prompts.
559#[derive(Clone, Debug, Eq, PartialEq)]
560pub enum PromptAction {
561    /// Abort command entry.
562    ///
563    /// [bool] indicates whether this requires the prompt to be empty. (For example, how `<C-D>`
564    /// behaves in shells.)
565    ///
566    /// ## Example: Using `action!`
567    ///
568    /// ```
569    /// use editor_types::prelude::*;
570    /// use editor_types::{action, Action, PromptAction};
571    ///
572    /// let act: Action = action!("prompt abort");
573    /// let exp: Action = PromptAction::Abort(false).into();
574    /// assert_eq!(act, exp);
575    /// ```
576    Abort(bool),
577
578    /// Submit the currently entered text.
579    ///
580    /// ## Example: Using `action!`
581    ///
582    /// ```
583    /// use editor_types::prelude::*;
584    /// use editor_types::{action, Action, PromptAction};
585    ///
586    /// let act: Action = action!("prompt submit");
587    /// let exp: Action = PromptAction::Submit.into();
588    /// assert_eq!(act, exp);
589    /// ```
590    Submit,
591
592    /// Move backwards and forwards through previous entries.
593    ///
594    /// ## Example: Using `action!`
595    ///
596    /// ```
597    /// use editor_types::prelude::*;
598    /// use editor_types::{action, Action, PromptAction};
599    ///
600    /// let filter = RecallFilter::All;
601    /// let act: Action = PromptAction::Recall(filter.clone(), MoveDir1D::Next, Count::Contextual).into();
602    ///
603    /// // All of these are equivalent:
604    /// assert_eq!(act, action!("prompt recall -d next -c ctx -F all"));
605    /// assert_eq!(act, action!("prompt recall -d next -c ctx -F {filter}"));
606    /// assert_eq!(act, action!("prompt recall -d next -c ctx"));
607    /// assert_eq!(act, action!("prompt recall -d next"));
608    /// ```
609    Recall(RecallFilter, MoveDir1D, Count),
610}
611
612/// Actions for recording and running macros.
613#[derive(Clone, Debug, Eq, PartialEq)]
614#[non_exhaustive]
615pub enum MacroAction {
616    /// Execute the contents of the contextually specified Register [*n* times](Count).
617    ///
618    /// If no register is specified, then this should default to [Register::UnnamedMacro].
619    ///
620    /// ## Example: Using `action!`
621    ///
622    /// ```
623    /// use editor_types::prelude::*;
624    /// use editor_types::{action, Action, MacroAction};
625    ///
626    /// let act: Action = MacroAction::Execute(Count::Contextual).into();
627    ///
628    /// // All of these are equivalent:
629    /// assert_eq!(act, action!("macro execute -c ctx"));
630    /// assert_eq!(act, action!("macro execute"));
631    /// assert_eq!(act, action!("macro exec"));
632    /// ```
633    Execute(Count),
634
635    /// Run the given macro string [*n* times](Count).
636    ///
637    /// ## Example: Using `action!`
638    ///
639    /// ```
640    /// use editor_types::prelude::*;
641    /// use editor_types::{action, Action, MacroAction};
642    ///
643    /// let mac = "hjkl".to_string();
644    /// let act: Action = MacroAction::Run(mac, Count::Contextual).into();
645    ///
646    /// // All of these are equivalent:
647    /// assert_eq!(act, action!("macro run -i \"hjkl\" -c ctx"));
648    /// assert_eq!(act, action!("macro run -i \"hjkl\""));
649    /// ```
650    Run(String, Count),
651
652    /// Execute the contents of the previously specified register [*n* times](Count).
653    ///
654    /// ## Example: Using `action!`
655    ///
656    /// ```
657    /// use editor_types::prelude::*;
658    /// use editor_types::{action, Action, MacroAction};
659    ///
660    /// let act: Action = MacroAction::Repeat(Count::Contextual).into();
661    ///
662    /// // All of these are equivalent:
663    /// assert_eq!(act, action!("macro repeat -c ctx"));
664    /// assert_eq!(act, action!("macro repeat"));
665    /// ```
666    Repeat(Count),
667
668    /// Start or stop recording a macro.
669    ///
670    /// ## Example: Using `action!`
671    ///
672    /// ```
673    /// use editor_types::prelude::*;
674    /// use editor_types::{action, Action, MacroAction};
675    ///
676    /// let act: Action = MacroAction::ToggleRecording.into();
677    /// assert_eq!(act, action!("macro toggle-recording"));
678    /// ```
679    ToggleRecording,
680}
681
682/// Actions for manipulating application tabs.
683#[derive(Clone, Debug, Eq, PartialEq)]
684#[non_exhaustive]
685pub enum TabAction<I: ApplicationInfo> {
686    /// Close the [TabTarget] tabs with [CloseFlags] options.
687    ///
688    /// ## Example: Using `action!`
689    ///
690    /// ```
691    /// use editor_types::prelude::*;
692    /// use editor_types::{action, Action, TabAction};
693    ///
694    /// let fc = TabTarget::Single(FocusChange::Current);
695    /// let flags = CloseFlags::NONE;
696    /// let extract: Action = TabAction::Close(fc, flags).into();
697    /// assert_eq!(extract, action!("tab close -t (single current) -F none"));
698    /// ```
699    Close(TabTarget, CloseFlags),
700
701    /// Extract the currently focused window from the currently focused tab, and place it in a new
702    /// tab.
703    ///
704    /// If there is only one window in the current tab, then this does nothing.
705    ///
706    /// The new tab will be placed on [MoveDir1D] side of the tab targeted by [FocusChange]. If
707    /// [FocusChange] doesn't resolve to a valid tab, then the new tab is placed after the
708    /// currently focused tab.
709    ///
710    /// ## Example: Using `action!`
711    ///
712    /// ```
713    /// use editor_types::prelude::*;
714    /// use editor_types::{action, Action, TabAction};
715    ///
716    /// let extract: Action = TabAction::Extract(FocusChange::Current, MoveDir1D::Next).into();
717    /// assert_eq!(extract, action!("tab extract -f current -d next"));
718    /// ```
719    ///
720    /// See the documentation for [FocusChange] for how to construct each of its variants with
721    /// [action].
722    Extract(FocusChange, MoveDir1D),
723
724    /// Change the current focus to the tab targeted by [FocusChange].
725    ///
726    /// ## Example: Using `action!`
727    ///
728    /// ```
729    /// use editor_types::prelude::*;
730    /// use editor_types::{action, Action, TabAction};
731    ///
732    /// let extract: Action = TabAction::Focus(FocusChange::PreviouslyFocused).into();
733    /// assert_eq!(extract, action!("tab focus -f previously-focused"));
734    /// ```
735    ///
736    /// See the documentation for [FocusChange] for how to construct each of its variants with
737    /// [action].
738    Focus(FocusChange),
739
740    /// Move the currently focused tab to the position targeted by [FocusChange].
741    ///
742    /// ## Example: Using `action!`
743    ///
744    /// ```
745    /// use editor_types::prelude::*;
746    /// use editor_types::{action, Action, TabAction};
747    ///
748    /// let extract: Action = TabAction::Move(FocusChange::PreviouslyFocused).into();
749    /// assert_eq!(extract, action!("tab move -f previously-focused"));
750    /// ```
751    ///
752    /// See the documentation for [FocusChange] for how to construct each of its variants with
753    /// [action].
754    Move(FocusChange),
755
756    /// Open a new tab after the tab targeted by [FocusChange] that displays the requested content.
757    ///
758    /// ## Example: Using `action!`
759    ///
760    /// ```
761    /// use editor_types::prelude::*;
762    /// use editor_types::{action, Action, TabAction};
763    ///
764    /// let extract: Action = TabAction::Open(OpenTarget::Current, FocusChange::PreviouslyFocused).into();
765    /// assert_eq!(extract, action!("tab open -t current -f previously-focused"));
766    /// ```
767    ///
768    /// See the documentation for [OpenTarget] and [FocusChange] for how to construct each of their
769    /// variants with [action].
770    Open(OpenTarget<I::WindowId>, FocusChange),
771}
772
773/// Actions for manipulating application windows.
774#[derive(Clone, Debug, Eq, PartialEq)]
775#[non_exhaustive]
776pub enum WindowAction<I: ApplicationInfo> {
777    /// Close the [WindowTarget] windows with [CloseFlags] options.
778    ///
779    /// ## Example: Using `action!`
780    ///
781    /// ```
782    /// use editor_types::prelude::*;
783    /// use editor_types::{action, Action, WindowAction};
784    ///
785    /// let fc = WindowTarget::Single(FocusChange::Current);
786    /// let flags = CloseFlags::NONE;
787    /// let extract: Action = WindowAction::Close(fc, flags).into();
788    /// assert_eq!(extract, action!("window close -t (single current) -F none"));
789    /// ```
790    Close(WindowTarget, CloseFlags),
791
792    /// Exchange the currently focused window with the window targeted by [FocusChange].
793    ///
794    /// ## Example: Using `action!`
795    ///
796    /// ```
797    /// use editor_types::prelude::*;
798    /// use editor_types::{action, Action, WindowAction};
799    ///
800    /// let fc = FocusChange::PreviouslyFocused;
801    /// let act: Action = WindowAction::Exchange(fc).into();
802    /// assert_eq!(act, action!("window exchange -f previously-focused"));
803    /// ```
804    Exchange(FocusChange),
805
806    /// Change the current focus to the window targeted by [FocusChange].
807    ///
808    /// ## Example: Using `action!`
809    ///
810    /// ```
811    /// use editor_types::prelude::*;
812    /// use editor_types::{action, Action, WindowAction};
813    ///
814    /// let fc = FocusChange::PreviouslyFocused;
815    /// let act: Action = WindowAction::Focus(fc).into();
816    /// assert_eq!(act, action!("window focus -f previously-focused"));
817    /// ```
818    Focus(FocusChange),
819
820    /// Move the currently focused window to the [MoveDir2D] side of the screen.
821    ///
822    /// ## Example: Using `action!`
823    ///
824    /// ```
825    /// use editor_types::prelude::*;
826    /// use editor_types::{action, Action, WindowAction};
827    ///
828    /// let act: Action = WindowAction::MoveSide(MoveDir2D::Left).into();
829    /// assert_eq!(act, action!("window move-side -d left"));
830    /// ```
831    MoveSide(MoveDir2D),
832
833    /// Open a new window that is [*n*](Count) columns along [an axis](Axis), positioned relative to
834    /// the current window as indicated by [MoveDir1D].
835    ///
836    /// ## Example: Using `action!`
837    ///
838    /// ```
839    /// use editor_types::prelude::*;
840    /// use editor_types::{action, Action, WindowAction};
841    ///
842    /// let target = OpenTarget::Unnamed;
843    /// let axis = Axis::Horizontal;
844    /// let dir = MoveDir1D::Next;
845    /// let count = Count::Contextual;
846    /// let act: Action = WindowAction::Open(target, axis, dir, count).into();
847    ///
848    /// // All of these are equivalent:
849    /// assert_eq!(act, action!("window open -t unnamed -x horizontal -d next -c ctx"));
850    /// assert_eq!(act, action!("window open -t unnamed -x horizontal -d next"));
851    /// ```
852    Open(OpenTarget<I::WindowId>, Axis, MoveDir1D, Count),
853
854    /// Visually rotate the windows in [MoveDir2D] direction.
855    ///
856    /// ## Example: Using `action!`
857    ///
858    /// ```
859    /// use editor_types::prelude::*;
860    /// use editor_types::{action, Action, WindowAction};
861    ///
862    /// let act: Action = WindowAction::Rotate(MoveDir1D::Next).into();
863    /// assert_eq!(act, action!("window rotate -d next"));
864    /// ```
865    Rotate(MoveDir1D),
866
867    /// Split the currently focused window [*n* times](Count) along [an axis](Axis), moving
868    /// the focus in [MoveDir1D] direction after performing the split.
869    ///
870    /// ## Example: Using `action!`
871    ///
872    /// ```
873    /// use editor_types::prelude::*;
874    /// use editor_types::{action, Action, WindowAction};
875    ///
876    /// let target = OpenTarget::Current;
877    /// let axis = Axis::Vertical;
878    /// let dir = MoveDir1D::Next;
879    /// let count = Count::Contextual;
880    /// let act: Action = WindowAction::Split(target, axis, dir, count).into();
881    ///
882    /// // All of these are equivalent:
883    /// assert_eq!(act, action!("window split -t current -x vertical -d next -c ctx"));
884    /// assert_eq!(act, action!("window split -t current -x vertical -d next"));
885    /// ```
886    Split(OpenTarget<I::WindowId>, Axis, MoveDir1D, Count),
887
888    /// Switch what content the window is currently showing.
889    ///
890    /// If there are no currently open windows in the tab, then this behaves like
891    /// [WindowAction::Open].
892    ///
893    /// ## Example: Using `action!`
894    ///
895    /// ```
896    /// use editor_types::prelude::*;
897    /// use editor_types::{action, Action, WindowAction};
898    ///
899    /// let target = OpenTarget::Offset(MoveDir1D::Next, 5.into());
900    /// let switch: Action = WindowAction::Switch(target).into();
901    /// assert_eq!(switch, action!("window switch -t (offset -d next -c 5)"));
902    /// ```
903    Switch(OpenTarget<I::WindowId>),
904
905    /// Clear all of the explicitly set window sizes, and instead try to equally distribute
906    /// available rows and columns.
907    ///
908    /// ## Example: Using `action!`
909    ///
910    /// ```
911    /// use editor_types::prelude::*;
912    /// use editor_types::{action, Action, WindowAction};
913    ///
914    /// let act: Action = WindowAction::ClearSizes.into();
915    /// assert_eq!(act, action!("window clear-sizes"));
916    /// ```
917    ClearSizes,
918
919    /// Resize the window targeted by [FocusChange] according to [SizeChange].
920    ///
921    /// ## Example: Using `action!`
922    ///
923    /// ```
924    /// use editor_types::prelude::*;
925    /// use editor_types::{action, Action, WindowAction};
926    ///
927    /// let size = SizeChange::Equal;
928    /// let act: Action = WindowAction::Resize(FocusChange::Current, Axis::Vertical, size).into();
929    /// assert_eq!(act, action!("window resize -f current -x vertical -z equal"));
930    /// ```
931    Resize(FocusChange, Axis, SizeChange),
932
933    /// Write the contents of the windows targeted by [WindowTarget].
934    ///
935    /// ## Example: Using `action!`
936    ///
937    /// ```
938    /// use editor_types::prelude::*;
939    /// use editor_types::{action, Action, WindowAction};
940    ///
941    /// let target = WindowTarget::All;
942    /// let flags = WriteFlags::NONE;
943    /// let act: Action = WindowAction::Write(target, None, flags).into();
944    /// assert_eq!(act, action!("window write -t all -F none"));
945    /// ```
946    Write(WindowTarget, Option<String>, WriteFlags),
947
948    /// Zoom in on the currently focused window so that it takes up the whole screen. If there is
949    /// already a zoomed-in window, then return to showing all windows.
950    ///
951    /// ## Example: Using `action!`
952    ///
953    /// ```
954    /// use editor_types::prelude::*;
955    /// use editor_types::{action, Action, WindowAction};
956    ///
957    /// let act: Action = WindowAction::ZoomToggle.into();
958    /// assert_eq!(act, action!("window zoom-toggle"));
959    /// ```
960    ZoomToggle,
961}
962
963/// Actions for editing text within buffer.
964#[derive(Clone, Debug, Eq, PartialEq)]
965#[non_exhaustive]
966pub enum EditorAction {
967    /// Complete the text before the cursor group leader.
968    ///
969    /// See the documentation for the [CompletionStyle] variants for how to construct all of the
970    /// different [EditorAction::Complete] values using [action].
971    ///
972    /// ## Example: Using `action!`
973    ///
974    /// ```
975    /// use editor_types::prelude::*;
976    /// use editor_types::{action, Action, EditorAction};
977    ///
978    /// let ct = CompletionType::Auto;
979    /// let style = CompletionStyle::Prefix;
980    /// let display = CompletionDisplay::List;
981    /// let act: Action = EditorAction::Complete(style, ct.clone(), display).into();
982    ///
983    /// // All of these are equivalent:
984    /// assert_eq!(act, action!("complete -s prefix -T auto -D list"));
985    /// assert_eq!(act, action!("complete -s prefix -T {ct} -D list"));
986    /// assert_eq!(act, action!("complete -s prefix -D list"));
987    /// ```
988    Complete(CompletionStyle, CompletionType, CompletionDisplay),
989
990    /// Modify the current cursor group.
991    ///
992    /// See the documentation for the [CursorAction] variants for how to construct all of the
993    /// different [EditorAction::Cursor] values using [action].
994    ///
995    /// ## Example: Using `action!`
996    ///
997    /// ```
998    /// use editor_types::prelude::*;
999    /// use editor_types::{action, Action, CursorAction};
1000    ///
1001    /// let close: Action = action!("cursor close -t leader");
1002    /// assert_eq!(close, CursorAction::Close(CursorCloseTarget::Leader).into());
1003    ///
1004    /// let restore: Action = action!("cursor restore -s append");
1005    /// assert_eq!(restore, CursorAction::Restore(CursorGroupCombineStyle::Append).into());
1006    /// ```
1007    Cursor(CursorAction),
1008
1009    /// Perform the specified [action](EditAction) on [a target](EditTarget).
1010    ///
1011    /// ## Example: Using `action!`
1012    ///
1013    /// ```
1014    /// use editor_types::prelude::*;
1015    /// use editor_types::{action, Action, EditorAction};
1016    ///
1017    /// let ctx = Specifier::Contextual;
1018    /// let target = EditTarget::CurrentPosition;
1019    /// let act: Action = EditorAction::Edit(ctx, target.clone()).into();
1020    /// assert_eq!(act, action!("edit -o ctx -t {target}"));
1021    /// ```
1022    Edit(Specifier<EditAction>, EditTarget),
1023
1024    /// Perform a history operation.
1025    ///
1026    /// See the documentation for the [HistoryAction] variants for how to construct all of the
1027    /// different [EditorAction::History] values using [action].
1028    ///
1029    /// ```
1030    /// use editor_types::prelude::*;
1031    /// use editor_types::{action, Action, HistoryAction};
1032    ///
1033    /// let undo: Action = action!("history undo");
1034    /// assert_eq!(undo, HistoryAction::Undo(Count::Contextual).into());
1035    ///
1036    /// let redo: Action = action!("history redo");
1037    /// assert_eq!(redo, HistoryAction::Redo(Count::Contextual).into());
1038    /// ```
1039    History(HistoryAction),
1040
1041    /// Insert text.
1042    ///
1043    /// See the documentation for the [InsertTextAction] variants for how to construct all of the
1044    /// different [EditorAction::InsertText] values using [action].
1045    ///
1046    /// ## Example: Using `action!`
1047    ///
1048    /// ```
1049    /// use editor_types::prelude::*;
1050    /// use editor_types::{action, Action, InsertTextAction};
1051    ///
1052    /// let paste: Action = action!("insert paste -s cursor -c 10");
1053    /// assert_eq!(paste, InsertTextAction::Paste(PasteStyle::Cursor, 10.into()).into());
1054    /// ```
1055    InsertText(InsertTextAction),
1056
1057    /// Create a new [Mark] at the current leader position.
1058    ///
1059    /// ## Example: Using `action!`
1060    ///
1061    /// ```
1062    /// use editor_types::prelude::*;
1063    /// use editor_types::{action, Action, EditorAction};
1064    ///
1065    /// let mark = Mark::LastYankedBegin;
1066    /// let set_mark: Action = action!("mark -m {}", mark.clone());
1067    /// assert_eq!(set_mark, EditorAction::Mark(mark.into()).into());
1068    ///
1069    /// let set_mark: Action = action!("mark -m ctx");
1070    /// assert_eq!(set_mark, EditorAction::Mark(Specifier::Contextual).into());
1071    /// ```
1072    Mark(Specifier<Mark>),
1073
1074    /// Modify the current selection.
1075    ///
1076    /// See the documentation for the [SelectionAction] variants for how to construct all of the
1077    /// different [EditorAction::Selection] values using [action].
1078    ///
1079    /// ## Example: Using `action!`
1080    ///
1081    /// ```
1082    /// use editor_types::prelude::*;
1083    /// use editor_types::{action, Action, SelectionAction};
1084    ///
1085    /// let act: Action = SelectionAction::Duplicate(MoveDir1D::Next, Count::Contextual).into();
1086    /// assert_eq!(act, action!("selection duplicate -d next"));
1087    /// ```
1088    Selection(SelectionAction),
1089}
1090
1091impl EditorAction {
1092    /// Indicates if this is a read-only action.
1093    pub fn is_readonly(&self, ctx: &EditContext) -> bool {
1094        match self {
1095            EditorAction::Complete(_, _, _) => false,
1096            EditorAction::History(act) => act.is_readonly(),
1097            EditorAction::InsertText(_) => false,
1098
1099            EditorAction::Cursor(_) => true,
1100            EditorAction::Mark(_) => true,
1101            EditorAction::Selection(_) => true,
1102
1103            EditorAction::Edit(act, _) => ctx.resolve(act).is_readonly(),
1104        }
1105    }
1106
1107    /// Indicates how an action gets included in [RepeatType::EditSequence].
1108    ///
1109    /// `motion` indicates what to do with [EditAction::Motion].
1110    pub fn is_edit_sequence(&self, motion: SequenceStatus, ctx: &EditContext) -> SequenceStatus {
1111        match self {
1112            EditorAction::History(_) => SequenceStatus::Break,
1113            EditorAction::Mark(_) => SequenceStatus::Break,
1114            EditorAction::InsertText(_) => SequenceStatus::Track,
1115            EditorAction::Cursor(_) => SequenceStatus::Track,
1116            EditorAction::Selection(_) => SequenceStatus::Track,
1117            EditorAction::Complete(_, _, _) => SequenceStatus::Track,
1118            EditorAction::Edit(act, _) => {
1119                match ctx.resolve(act) {
1120                    EditAction::Motion => motion,
1121                    EditAction::Yank => SequenceStatus::Ignore,
1122                    _ => SequenceStatus::Track,
1123                }
1124            },
1125        }
1126    }
1127
1128    /// Indicates how an action gets included in [RepeatType::LastAction].
1129    pub fn is_last_action(&self, _: &EditContext) -> SequenceStatus {
1130        match self {
1131            EditorAction::History(HistoryAction::Checkpoint) => SequenceStatus::Ignore,
1132            EditorAction::History(HistoryAction::Undo(_)) => SequenceStatus::Atom,
1133            EditorAction::History(HistoryAction::Redo(_)) => SequenceStatus::Atom,
1134
1135            EditorAction::Complete(_, _, _) => SequenceStatus::Atom,
1136            EditorAction::Cursor(_) => SequenceStatus::Atom,
1137            EditorAction::Edit(_, _) => SequenceStatus::Atom,
1138            EditorAction::InsertText(_) => SequenceStatus::Atom,
1139            EditorAction::Mark(_) => SequenceStatus::Atom,
1140            EditorAction::Selection(_) => SequenceStatus::Atom,
1141        }
1142    }
1143
1144    /// Indicates how an action gets included in [RepeatType::LastSelection].
1145    pub fn is_last_selection(&self, ctx: &EditContext) -> SequenceStatus {
1146        match self {
1147            EditorAction::History(_) => SequenceStatus::Ignore,
1148            EditorAction::Mark(_) => SequenceStatus::Ignore,
1149            EditorAction::InsertText(_) => SequenceStatus::Ignore,
1150            EditorAction::Cursor(_) => SequenceStatus::Ignore,
1151            EditorAction::Complete(_, _, _) => SequenceStatus::Ignore,
1152
1153            EditorAction::Selection(SelectionAction::Resize(_, _)) => SequenceStatus::Track,
1154            EditorAction::Selection(_) => SequenceStatus::Ignore,
1155
1156            EditorAction::Edit(act, _) => {
1157                if let EditAction::Motion = ctx.resolve(act) {
1158                    if ctx.get_target_shape().is_some() {
1159                        SequenceStatus::Restart
1160                    } else {
1161                        SequenceStatus::Ignore
1162                    }
1163                } else {
1164                    SequenceStatus::Ignore
1165                }
1166            },
1167        }
1168    }
1169
1170    /// Returns true if this [Action] is allowed to trigger a [WindowAction::Switch] after an error.
1171    pub fn is_switchable(&self, ctx: &EditContext) -> bool {
1172        match self {
1173            EditorAction::Cursor(act) => act.is_switchable(ctx),
1174            EditorAction::Edit(act, _) => ctx.resolve(act).is_switchable(ctx),
1175            EditorAction::Complete(_, _, _) => false,
1176            EditorAction::History(_) => false,
1177            EditorAction::InsertText(_) => false,
1178            EditorAction::Mark(_) => false,
1179            EditorAction::Selection(_) => false,
1180        }
1181    }
1182}
1183
1184impl From<CursorAction> for EditorAction {
1185    fn from(act: CursorAction) -> Self {
1186        EditorAction::Cursor(act)
1187    }
1188}
1189
1190impl From<HistoryAction> for EditorAction {
1191    fn from(act: HistoryAction) -> Self {
1192        EditorAction::History(act)
1193    }
1194}
1195
1196impl From<InsertTextAction> for EditorAction {
1197    fn from(act: InsertTextAction) -> Self {
1198        EditorAction::InsertText(act)
1199    }
1200}
1201
1202impl From<SelectionAction> for EditorAction {
1203    fn from(act: SelectionAction) -> Self {
1204        EditorAction::Selection(act)
1205    }
1206}
1207
1208/// The result of either pressing a complete keybinding sequence, or parsing a command.
1209#[derive(Clone, Debug, Eq, PartialEq)]
1210#[non_exhaustive]
1211pub enum Action<I: ApplicationInfo = EmptyInfo> {
1212    /// Do nothing.
1213    ///
1214    /// ## Example: Using `action!`
1215    ///
1216    /// ```
1217    /// use editor_types::{action, Action};
1218    ///
1219    /// // All of these are equivalent:
1220    /// let noop: Action = Action::NoOp;
1221    /// assert_eq!(action!("nop"), noop);
1222    /// assert_eq!(action!("noop"), noop);
1223    /// assert_eq!(action!("no-op"), noop);
1224    /// assert_eq!(Action::default(), noop);
1225    /// ```
1226    NoOp,
1227
1228    /// Perform an editor action.
1229    ///
1230    /// See the documentation for the [EditorAction] variants for how to construct all of the
1231    /// different [Action::Editor] values using [action].
1232    Editor(EditorAction),
1233
1234    /// Perform a macro-related action.
1235    ///
1236    /// See the documentation for the [MacroAction] variants for how to construct all of the
1237    /// different [Action::Macro] values using [action].
1238    Macro(MacroAction),
1239
1240    /// Navigate through the cursor positions in [the specified list](PositionList).
1241    ///
1242    /// If the current window cannot satisfy the given [Count], then this may jump to other
1243    /// windows.
1244    ///
1245    /// ## Example: Using `action!`
1246    ///
1247    /// ```
1248    /// use editor_types::prelude::*;
1249    /// use editor_types::{action, Action, InsertTextAction};
1250    ///
1251    /// let list = PositionList::JumpList;
1252    /// let count = Count::Contextual;
1253    ///
1254    /// let act: Action = Action::Jump(list, MoveDir1D::Next, count.clone());
1255    /// assert_eq!(act, action!("jump -t jump-list -d next -c ctx"));
1256    ///
1257    /// let act: Action = Action::Jump(list, MoveDir1D::Previous, count);
1258    /// assert_eq!(act, action!("jump -t jump-list -d previous -c ctx"));
1259    /// ```
1260    Jump(PositionList, MoveDir1D, Count),
1261
1262    /// Repeat an action sequence with the current context.
1263    ///
1264    /// ## Example: Using `action!`
1265    ///
1266    /// ```
1267    /// use editor_types::prelude::*;
1268    /// use editor_types::{action, Action};
1269    ///
1270    /// let rep: Action = action!("repeat -s edit-sequence");
1271    /// assert_eq!(rep, Action::Repeat(RepeatType::EditSequence));
1272    /// ```
1273    ///
1274    /// See the [RepeatType] documentation for how to construct each of its variants.
1275    Repeat(RepeatType),
1276
1277    /// Scroll the viewport in [the specified manner](ScrollStyle).
1278    ///
1279    /// ## Example: Using `action!`
1280    ///
1281    /// ```
1282    /// use editor_types::prelude::*;
1283    /// use editor_types::{action, Action};
1284    ///
1285    /// let scroll: Action = Action::Scroll(
1286    ///     ScrollStyle::LinePos(MovePosition::Beginning, 1.into()));
1287    /// assert_eq!(scroll, action!("scroll -s (line-pos -p beginning -c 1)"));
1288    /// ```
1289    ///
1290    /// See the [ScrollStyle] documentation for how to construct each of its variants.
1291    Scroll(ScrollStyle),
1292
1293    /// Lookup the keyword under the cursor.
1294    ///
1295    /// ## Example: Using `action!`
1296    ///
1297    /// ```
1298    /// use editor_types::prelude::*;
1299    /// use editor_types::{action, Action};
1300    ///
1301    /// let kw: Action = Action::KeywordLookup(KeywordTarget::Selection);
1302    /// assert_eq!(kw, action!("keyword-lookup -t selection"));
1303    /// ```
1304    KeywordLookup(KeywordTarget),
1305
1306    /// Redraw the screen.
1307    ///
1308    /// ## Example: Using `action!`
1309    ///
1310    /// ```
1311    /// use editor_types::{action, Action};
1312    ///
1313    /// let redraw: Action = action!("redraw-screen");
1314    /// assert_eq!(redraw, Action::RedrawScreen);
1315    /// ```
1316    RedrawScreen,
1317
1318    /// Show an [InfoMessage].
1319    ShowInfoMessage(InfoMessage),
1320
1321    /// Suspend the process.
1322    ///
1323    /// ## Example: Using `action!`
1324    ///
1325    /// ```
1326    /// use editor_types::{action, Action};
1327    ///
1328    /// let suspend: Action = action!("suspend");
1329    /// assert_eq!(suspend, Action::Suspend);
1330    /// ```
1331    Suspend,
1332
1333    /// Find the [*n*<sup>th</sup>](Count) occurrence of the current application-level search.
1334    ///
1335    /// ## Example: Using `action!`
1336    ///
1337    /// ```
1338    /// use editor_types::prelude::*;
1339    /// use editor_types::{action, Action, CommandBarAction};
1340    ///
1341    /// let search: Action = action!("search -d same");
1342    /// assert_eq!(search, Action::Search(MoveDirMod::Same, Count::Contextual));
1343    /// ```
1344    ///
1345    /// See the documentation for [MoveDirMod] for how to construct all of its values using
1346    /// [action].
1347    Search(MoveDirMod, Count),
1348
1349    /// Perform a command-related action.
1350    ///
1351    /// See the documentation for the [CommandAction] variants for how to construct all of the
1352    /// different [Action::Command] values using [action].
1353    Command(CommandAction),
1354
1355    /// Perform a command bar-related action.
1356    ///
1357    /// See the documentation for the [CommandBarAction] variants for how to construct all of the
1358    /// different [Action::CommandBar] values using [action].
1359    CommandBar(CommandBarAction<I>),
1360
1361    /// Perform a prompt-related action.
1362    ///
1363    /// See the documentation for the [PromptAction] variants for how to construct all of the
1364    /// different [Action::Prompt] values using [action].
1365    Prompt(PromptAction),
1366
1367    /// Perform a tab-related action.
1368    ///
1369    /// See the documentation for the [TabAction] variants for how to construct all of the
1370    /// different [Action::Tab] values using [action].
1371    Tab(TabAction<I>),
1372
1373    /// Perform a window-related action.
1374    ///
1375    /// See the documentation for the [WindowAction] variants for how to construct all of the
1376    /// different [Action::Window] values using [action].
1377    Window(WindowAction<I>),
1378
1379    /// Application-specific command.
1380    Application(I::Action),
1381}
1382
1383impl<I: ApplicationInfo> Action<I> {
1384    /// Indicates how an action gets included in [RepeatType::EditSequence].
1385    ///
1386    /// `motion` indicates what to do with [EditAction::Motion].
1387    pub fn is_edit_sequence(&self, motion: SequenceStatus, ctx: &EditContext) -> SequenceStatus {
1388        match self {
1389            Action::Repeat(_) => SequenceStatus::Ignore,
1390
1391            Action::Application(act) => act.is_edit_sequence(ctx),
1392            Action::Editor(act) => act.is_edit_sequence(motion, ctx),
1393
1394            Action::Command(_) => SequenceStatus::Break,
1395            Action::CommandBar(_) => SequenceStatus::Break,
1396            Action::Jump(_, _, _) => SequenceStatus::Break,
1397            Action::Macro(_) => SequenceStatus::Break,
1398            Action::Prompt(_) => SequenceStatus::Break,
1399            Action::Tab(_) => SequenceStatus::Break,
1400            Action::Window(_) => SequenceStatus::Break,
1401
1402            Action::KeywordLookup(_) => SequenceStatus::Ignore,
1403            Action::NoOp => SequenceStatus::Ignore,
1404            Action::RedrawScreen => SequenceStatus::Ignore,
1405            Action::Scroll(_) => SequenceStatus::Ignore,
1406            Action::Search(_, _) => SequenceStatus::Ignore,
1407            Action::ShowInfoMessage(_) => SequenceStatus::Ignore,
1408            Action::Suspend => SequenceStatus::Ignore,
1409        }
1410    }
1411
1412    /// Indicates how an action gets included in [RepeatType::LastAction].
1413    pub fn is_last_action(&self, ctx: &EditContext) -> SequenceStatus {
1414        match self {
1415            Action::Repeat(RepeatType::EditSequence) => SequenceStatus::Atom,
1416            Action::Repeat(RepeatType::LastAction) => SequenceStatus::Ignore,
1417            Action::Repeat(RepeatType::LastSelection) => SequenceStatus::Atom,
1418
1419            Action::Application(act) => act.is_last_action(ctx),
1420            Action::Editor(act) => act.is_last_action(ctx),
1421
1422            Action::Command(_) => SequenceStatus::Atom,
1423            Action::CommandBar(_) => SequenceStatus::Atom,
1424            Action::Jump(_, _, _) => SequenceStatus::Atom,
1425            Action::Macro(_) => SequenceStatus::Atom,
1426            Action::Tab(_) => SequenceStatus::Atom,
1427            Action::Window(_) => SequenceStatus::Atom,
1428            Action::KeywordLookup(_) => SequenceStatus::Atom,
1429            Action::NoOp => SequenceStatus::Atom,
1430            Action::Prompt(_) => SequenceStatus::Atom,
1431            Action::RedrawScreen => SequenceStatus::Atom,
1432            Action::Scroll(_) => SequenceStatus::Atom,
1433            Action::Search(_, _) => SequenceStatus::Atom,
1434            Action::ShowInfoMessage(_) => SequenceStatus::Atom,
1435            Action::Suspend => SequenceStatus::Atom,
1436        }
1437    }
1438
1439    /// Indicates how an action gets included in [RepeatType::LastSelection].
1440    pub fn is_last_selection(&self, ctx: &EditContext) -> SequenceStatus {
1441        match self {
1442            Action::Repeat(_) => SequenceStatus::Ignore,
1443
1444            Action::Application(act) => act.is_last_selection(ctx),
1445            Action::Editor(act) => act.is_last_selection(ctx),
1446
1447            Action::Command(_) => SequenceStatus::Ignore,
1448            Action::CommandBar(_) => SequenceStatus::Ignore,
1449            Action::Jump(_, _, _) => SequenceStatus::Ignore,
1450            Action::Macro(_) => SequenceStatus::Ignore,
1451            Action::Tab(_) => SequenceStatus::Ignore,
1452            Action::Window(_) => SequenceStatus::Ignore,
1453            Action::KeywordLookup(_) => SequenceStatus::Ignore,
1454            Action::NoOp => SequenceStatus::Ignore,
1455            Action::Prompt(_) => SequenceStatus::Ignore,
1456            Action::RedrawScreen => SequenceStatus::Ignore,
1457            Action::Scroll(_) => SequenceStatus::Ignore,
1458            Action::Search(_, _) => SequenceStatus::Ignore,
1459            Action::ShowInfoMessage(_) => SequenceStatus::Ignore,
1460            Action::Suspend => SequenceStatus::Ignore,
1461        }
1462    }
1463
1464    /// Returns true if this [Action] is allowed to trigger a [WindowAction::Switch] after an error.
1465    pub fn is_switchable(&self, ctx: &EditContext) -> bool {
1466        match self {
1467            Action::Application(act) => act.is_switchable(ctx),
1468            Action::Editor(act) => act.is_switchable(ctx),
1469            Action::Jump(..) => true,
1470
1471            Action::CommandBar(_) => false,
1472            Action::Command(_) => false,
1473            Action::KeywordLookup(_) => false,
1474            Action::Macro(_) => false,
1475            Action::NoOp => false,
1476            Action::Prompt(_) => false,
1477            Action::RedrawScreen => false,
1478            Action::Repeat(_) => false,
1479            Action::Scroll(_) => false,
1480            Action::Search(_, _) => false,
1481            Action::ShowInfoMessage(_) => false,
1482            Action::Suspend => false,
1483            Action::Tab(_) => false,
1484            Action::Window(_) => false,
1485        }
1486    }
1487}
1488
1489#[allow(clippy::derivable_impls)]
1490impl<I: ApplicationInfo> Default for Action<I> {
1491    fn default() -> Self {
1492        Action::NoOp
1493    }
1494}
1495
1496impl<I: ApplicationInfo> From<SelectionAction> for Action<I> {
1497    fn from(act: SelectionAction) -> Self {
1498        Action::Editor(EditorAction::Selection(act))
1499    }
1500}
1501
1502impl<I: ApplicationInfo> From<InsertTextAction> for Action<I> {
1503    fn from(act: InsertTextAction) -> Self {
1504        Action::Editor(EditorAction::InsertText(act))
1505    }
1506}
1507
1508impl<I: ApplicationInfo> From<HistoryAction> for Action<I> {
1509    fn from(act: HistoryAction) -> Self {
1510        Action::Editor(EditorAction::History(act))
1511    }
1512}
1513
1514impl<I: ApplicationInfo> From<CursorAction> for Action<I> {
1515    fn from(act: CursorAction) -> Self {
1516        Action::Editor(EditorAction::Cursor(act))
1517    }
1518}
1519
1520impl<I: ApplicationInfo> From<EditorAction> for Action<I> {
1521    fn from(act: EditorAction) -> Self {
1522        Action::Editor(act)
1523    }
1524}
1525
1526impl<I: ApplicationInfo> From<MacroAction> for Action<I> {
1527    fn from(act: MacroAction) -> Self {
1528        Action::Macro(act)
1529    }
1530}
1531
1532impl<I: ApplicationInfo> From<CommandAction> for Action<I> {
1533    fn from(act: CommandAction) -> Self {
1534        Action::Command(act)
1535    }
1536}
1537
1538impl<I: ApplicationInfo> From<CommandBarAction<I>> for Action<I> {
1539    fn from(act: CommandBarAction<I>) -> Self {
1540        Action::CommandBar(act)
1541    }
1542}
1543
1544impl<I: ApplicationInfo> From<PromptAction> for Action<I> {
1545    fn from(act: PromptAction) -> Self {
1546        Action::Prompt(act)
1547    }
1548}
1549
1550impl<I: ApplicationInfo> From<WindowAction<I>> for Action<I> {
1551    fn from(act: WindowAction<I>) -> Self {
1552        Action::Window(act)
1553    }
1554}
1555
1556impl<I: ApplicationInfo> From<TabAction<I>> for Action<I> {
1557    fn from(act: TabAction<I>) -> Self {
1558        Action::Tab(act)
1559    }
1560}
1561
1562#[cfg(test)]
1563mod tests {
1564    use super::*;
1565
1566    #[test]
1567    fn test_is_readonly() {
1568        let mut ctx = EditContext::default();
1569
1570        let act = SelectionAction::Duplicate(MoveDir1D::Next, Count::Contextual);
1571        assert_eq!(EditorAction::from(act).is_readonly(&ctx), true);
1572
1573        let act = HistoryAction::Checkpoint;
1574        assert_eq!(EditorAction::from(act).is_readonly(&ctx), true);
1575
1576        let act = HistoryAction::Undo(Count::Contextual);
1577        assert_eq!(EditorAction::from(act).is_readonly(&ctx), false);
1578
1579        let act = EditorAction::Edit(Specifier::Contextual, EditTarget::CurrentPosition);
1580        ctx.operation = EditAction::Motion;
1581        assert_eq!(act.is_readonly(&ctx), true);
1582
1583        let act = EditorAction::Edit(Specifier::Contextual, EditTarget::CurrentPosition);
1584        ctx.operation = EditAction::Delete;
1585        assert_eq!(act.is_readonly(&ctx), false);
1586    }
1587}