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
82impl Editor {
83 pub fn play_macro(
90 &mut self,
91 view: ViewId,
92 actions: &[Action],
93 repeat: Repeat,
94 ) -> Result<usize, MacroError> {
95 let fail = |error| MacroError {
96 run: 1,
97 step: 1,
98 action: actions.first().cloned().unwrap_or(Action::Undo),
99 error,
100 };
101 self.begin_undo_group(view).map_err(fail)?;
102 let recording = self.take_recording();
103 let result = self.run_macro(view, actions, repeat);
104 self.restore_recording(recording);
105 self.end_undo_group(view).map_err(fail)?;
106 result
107 }
108
109 fn run_macro(
110 &mut self,
111 view: ViewId,
112 actions: &[Action],
113 repeat: Repeat,
114 ) -> Result<usize, MacroError> {
115 if actions.is_empty() {
116 return Ok(0);
117 }
118 let limit = match repeat {
119 Repeat::Times(n) => n,
120 Repeat::UntilEnd => MAX_UNTIL_END_RUNS,
121 };
122 let until_end = repeat == Repeat::UntilEnd;
123 let mut runs = 0;
124 while runs < limit {
125 let before = self.macro_state(view);
126 for (i, action) in actions.iter().enumerate() {
127 if let Err(error) = self.apply(view, action.clone()) {
128 if until_end {
129 return Ok(runs);
130 }
131 return Err(MacroError {
132 run: runs + 1,
133 step: i + 1,
134 action: action.clone(),
135 error,
136 });
137 }
138 }
139 runs += 1;
140 if until_end {
141 let after = self.macro_state(view);
142 let at_end = |s: &Option<MacroState>| s.as_ref().is_some_and(|s| s.2);
143 if after == before || (at_end(&before) && at_end(&after)) {
144 break;
145 }
146 }
147 }
148 Ok(runs)
149 }
150
151 fn macro_state(&self, view: ViewId) -> Option<MacroState> {
154 let v = self.view(view)?;
155 let b = self.buffer(v.buffer())?;
156 let at_end = v.selections.primary().head >= b.doc().len();
157 Some((b.revision(), v.selections.clone(), at_end))
158 }
159}