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(_)
65 | Action::ColumnSelect { .. }
66 | Action::Undo
67 | Action::Redo
68 | Action::Fold
69 | Action::Unfold
70 | Action::ToggleFold
71 | Action::FoldAll
72 | Action::UnfoldAll
73 | Action::FoldLevel(_)
74 | Action::UnfoldLevel(_)
75 )
76 }
77}
78
79type MacroState = (u64, crate::selection::SelectionSet, bool);
81
82fn without_wrap(action: &Action) -> Action {
84 let mut action = action.clone();
85 match &mut action {
86 Action::Find { query, .. } | Action::Replace { query, .. } => query.options.wrap = false,
87 _ => {}
88 }
89 action
90}
91
92impl Editor {
93 pub fn play_macro(
100 &mut self,
101 view: ViewId,
102 actions: &[Action],
103 repeat: Repeat,
104 ) -> Result<usize, MacroError> {
105 let fail = |error| MacroError {
106 run: 1,
107 step: 1,
108 action: actions.first().cloned().unwrap_or(Action::Undo),
109 error,
110 };
111 self.begin_undo_group(view).map_err(fail)?;
112 let recording = self.take_recording();
113 let result = self.run_macro(view, actions, repeat);
114 self.restore_recording(recording);
115 self.end_undo_group(view).map_err(fail)?;
116 result
117 }
118
119 fn run_macro(
120 &mut self,
121 view: ViewId,
122 actions: &[Action],
123 repeat: Repeat,
124 ) -> Result<usize, MacroError> {
125 if actions.is_empty() {
126 return Ok(0);
127 }
128 let limit = match repeat {
129 Repeat::Times(n) => n,
130 Repeat::UntilEnd => MAX_UNTIL_END_RUNS,
131 };
132 let until_end = repeat == Repeat::UntilEnd;
133 let actions: Vec<Action> = if until_end {
137 actions.iter().map(without_wrap).collect()
138 } else {
139 actions.to_vec()
140 };
141 let mut runs = 0;
142 while runs < limit {
143 let before = self.macro_state(view);
144 for (i, action) in actions.iter().enumerate() {
145 let line_before = until_end.then(|| self.caret_line(view)).flatten();
148 if let Err(error) = self.apply(view, action.clone()) {
149 if until_end {
150 return Ok(runs);
151 }
152 return Err(MacroError {
153 run: runs + 1,
154 step: i + 1,
155 action: action.clone(),
156 error,
157 });
158 }
159 let wrapped = match (action, line_before, self.caret_line(view)) {
160 (Action::NextBookmark, Some(before), Some(after)) => after <= before,
161 (Action::PrevBookmark, Some(before), Some(after)) => after >= before,
162 _ => false,
163 };
164 if wrapped {
165 return Ok(runs);
166 }
167 }
168 runs += 1;
169 if until_end {
170 let after = self.macro_state(view);
171 let at_end = |s: &Option<MacroState>| s.as_ref().is_some_and(|s| s.2);
172 if after == before || (at_end(&before) && at_end(&after)) {
173 break;
174 }
175 }
176 }
177 Ok(runs)
178 }
179
180 fn caret_line(&self, view: ViewId) -> Option<usize> {
182 let v = self.view(view)?;
183 let b = self.buffer(v.buffer())?;
184 Some(b.doc().line_of(v.selections.primary().head))
185 }
186
187 fn macro_state(&self, view: ViewId) -> Option<MacroState> {
190 let v = self.view(view)?;
191 let b = self.buffer(v.buffer())?;
192 let at_end = v.selections.primary().head >= b.doc().len();
193 Some((b.revision(), v.selections.clone(), at_end))
194 }
195}