1use std::fmt;
5
6use serde::{Deserialize, Serialize};
7
8use crate::action::{Action, ActionError};
9use crate::editor::Editor;
10use crate::view::ViewId;
11
12#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
14pub struct NamedMacro {
15 pub name: String,
16 #[serde(default)]
18 pub shortcut: Option<String>,
19 pub actions: Vec<Action>,
20}
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub enum Repeat {
24 Times(usize),
25 UntilEnd,
30}
31
32pub const MAX_UNTIL_END_RUNS: usize = 1_000_000;
34
35#[derive(Clone, Debug, PartialEq, Eq)]
37pub struct MacroError {
38 pub run: usize,
40 pub step: usize,
42 pub action: Action,
43 pub error: ActionError,
44}
45
46impl fmt::Display for MacroError {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 write!(
49 f,
50 "the macro stopped at step {} of run {}: {}",
51 self.step, self.run, self.error
52 )
53 }
54}
55
56impl std::error::Error for MacroError {}
57
58impl Action {
59 pub fn is_recordable(&self) -> bool {
62 !matches!(
63 self,
64 Action::SetSelections(_) | Action::ColumnSelect { .. } | Action::Undo | Action::Redo
65 )
66 }
67}
68
69type MacroState = (u64, crate::selection::SelectionSet, bool);
71
72impl Editor {
73 pub fn play_macro(
80 &mut self,
81 view: ViewId,
82 actions: &[Action],
83 repeat: Repeat,
84 ) -> Result<usize, MacroError> {
85 let fail = |error| MacroError {
86 run: 1,
87 step: 1,
88 action: actions.first().cloned().unwrap_or(Action::Undo),
89 error,
90 };
91 self.begin_undo_group(view).map_err(fail)?;
92 let recording = self.take_recording();
93 let result = self.run_macro(view, actions, repeat);
94 self.restore_recording(recording);
95 self.end_undo_group(view).map_err(fail)?;
96 result
97 }
98
99 fn run_macro(
100 &mut self,
101 view: ViewId,
102 actions: &[Action],
103 repeat: Repeat,
104 ) -> Result<usize, MacroError> {
105 if actions.is_empty() {
106 return Ok(0);
107 }
108 let limit = match repeat {
109 Repeat::Times(n) => n,
110 Repeat::UntilEnd => MAX_UNTIL_END_RUNS,
111 };
112 let until_end = repeat == Repeat::UntilEnd;
113 let mut runs = 0;
114 while runs < limit {
115 let before = self.macro_state(view);
116 for (i, action) in actions.iter().enumerate() {
117 if let Err(error) = self.apply(view, action.clone()) {
118 if until_end {
119 return Ok(runs);
120 }
121 return Err(MacroError {
122 run: runs + 1,
123 step: i + 1,
124 action: action.clone(),
125 error,
126 });
127 }
128 }
129 runs += 1;
130 if until_end {
131 let after = self.macro_state(view);
132 let at_end = |s: &Option<MacroState>| s.as_ref().is_some_and(|s| s.2);
133 if after == before || (at_end(&before) && at_end(&after)) {
134 break;
135 }
136 }
137 }
138 Ok(runs)
139 }
140
141 fn macro_state(&self, view: ViewId) -> Option<MacroState> {
144 let v = self.view(view)?;
145 let b = self.buffer(v.buffer())?;
146 let at_end = v.selections.primary().head >= b.doc().len();
147 Some((b.revision(), v.selections.clone(), at_end))
148 }
149}