zng_ext_undo/
lib.rs

1#![doc(html_favicon_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo.png")]
3//!
4//! Undo-redo app extension, service and commands.
5//!
6//! # Crate
7//!
8#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9#![warn(unused_extern_crates)]
10#![warn(missing_docs)]
11#![recursion_limit = "256"]
12// suppress nag about very simple boxed closure signatures.
13#![expect(clippy::type_complexity)]
14
15use std::{
16    any::Any,
17    fmt, mem,
18    sync::{
19        Arc,
20        atomic::{AtomicBool, Ordering},
21    },
22    time::Duration,
23};
24
25use atomic::Atomic;
26use parking_lot::Mutex;
27use zng_app::{
28    AppExtension, DInstant, INSTANT,
29    event::CommandScope,
30    event::{AnyEventArgs, Command, CommandNameExt, command},
31    shortcut::{CommandShortcutExt, shortcut},
32    update::EventUpdate,
33    widget::{
34        WIDGET, WidgetId,
35        info::{WidgetInfo, WidgetInfoBuilder},
36    },
37};
38use zng_app_context::{RunOnDrop, app_local, context_local};
39use zng_clone_move::clmv;
40use zng_ext_input::{focus::cmd::CommandFocusExt, keyboard::KEYBOARD};
41use zng_state_map::{StateId, StateMapRef, static_id};
42use zng_txt::Txt;
43use zng_var::{BoxedVar, Var, VarHandle, VarValue, WeakVar, context_var, var};
44use zng_wgt::{CommandIconExt as _, ICONS, wgt_fn};
45
46mod private {
47    // https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed
48    pub trait Sealed {}
49}
50
51/// Undo-redo app extension.
52///
53/// # Services
54///
55/// Services provided by this extension.
56///
57/// * [`UNDO`]
58#[derive(Default)]
59pub struct UndoManager {}
60
61impl AppExtension for UndoManager {
62    fn event(&mut self, update: &mut EventUpdate) {
63        // app scope handler
64        if let Some(args) = UNDO_CMD.on_unhandled(update) {
65            args.propagation().stop();
66            if let Some(c) = args.param::<u32>() {
67                UNDO.undo_select(*c);
68            } else if let Some(i) = args.param::<Duration>() {
69                UNDO.undo_select(*i);
70            } else if let Some(t) = args.param::<DInstant>() {
71                UNDO.undo_select(*t);
72            } else {
73                UNDO.undo();
74            }
75        } else if let Some(args) = REDO_CMD.on_unhandled(update) {
76            args.propagation().stop();
77            if let Some(c) = args.param::<u32>() {
78                UNDO.redo_select(*c);
79            } else if let Some(i) = args.param::<Duration>() {
80                UNDO.redo_select(*i);
81            } else if let Some(t) = args.param::<DInstant>() {
82                UNDO.redo_select(*t);
83            } else {
84                UNDO.redo();
85            }
86        }
87    }
88}
89
90context_var! {
91    /// Contextual undo limit.
92    ///
93    /// Is [`UNDO.undo_limit`] by default.
94    ///
95    /// [`UNDO.undo_limit`]: UNDO::undo_limit
96    pub static UNDO_LIMIT_VAR: u32 = UNDO.undo_limit();
97
98    /// Contextual undo interval.
99    ///
100    /// Is [`UNDO.undo_interval`] by default.
101    ///
102    /// [`UNDO.undo_interval`]: UNDO::undo_interval
103    pub static UNDO_INTERVAL_VAR: Duration = UNDO.undo_interval();
104}
105
106/// Undo-redo service.
107pub struct UNDO;
108impl UNDO {
109    /// Gets or sets the maximum length of each undo stack of each scope.
110    ///
111    /// Is `u32::MAX` by default. If the limit is reached the oldest undo action is dropped without redo.
112    ///
113    /// Note that undo scopes get the max undo from [`UNDO_LIMIT_VAR`] in context, the context var is
114    /// set to this var by default.
115    pub fn undo_limit(&self) -> BoxedVar<u32> {
116        UNDO_SV.read().undo_limit.clone()
117    }
118
119    /// Gets or sets the time interval that [`undo`] and [`redo`] cover each call.
120    ///
121    /// This value applies to all scopes and defines the max interval between actions
122    /// that are undone in a single call.
123    ///
124    /// Is the [keyboard repeat start delay + interval] by default.
125    ///
126    /// Note that undo scopes get the interval from [`UNDO_INTERVAL_VAR`] in context, the context var is
127    /// set to this var by default.
128    ///
129    /// [`undo`]: Self::undo
130    /// [`redo`]: Self::redo
131    /// [keyboard repeat start delay + interval]: zng_ext_input::keyboard::KEYBOARD::repeat_config
132    pub fn undo_interval(&self) -> BoxedVar<Duration> {
133        UNDO_SV.read().undo_interval.clone()
134    }
135
136    /// Gets if the undo service is enabled in the current context.
137    ///
138    /// If `false` calls to [`register`] are ignored.
139    ///
140    /// [`register`]: Self::register
141    pub fn is_enabled(&self) -> bool {
142        UNDO_SCOPE_CTX.get().enabled.load(Ordering::Relaxed) && UNDO_SV.read().undo_limit.get() > 0
143    }
144
145    /// Undo a selection of actions.
146    ///
147    /// # Selectors
148    ///
149    /// These types can be used as selector:
150    ///
151    /// * `u32` - Count of actions to undo.
152    /// * `Duration` - Interval between each action.
153    /// * `DInstant` - Inclusive timestamp to undo back to.
154    pub fn undo_select(&self, selector: impl UndoSelector) {
155        UNDO_SCOPE_CTX.get().undo_select(selector);
156    }
157
158    /// Redo a selection of actions.
159    pub fn redo_select(&self, selector: impl UndoSelector) {
160        UNDO_SCOPE_CTX.get().redo_select(selector);
161    }
162
163    /// Undo all actions within the [`undo_interval`].
164    ///
165    /// [`undo_interval`]: Self::undo_interval
166    pub fn undo(&self) {
167        self.undo_select(UNDO_INTERVAL_VAR.get());
168    }
169
170    /// Redo all actions within the [`undo_interval`].
171    ///
172    /// [`undo_interval`]: Self::undo_interval
173    pub fn redo(&self) {
174        self.redo_select(UNDO_INTERVAL_VAR.get());
175    }
176
177    /// Gets the parent ID that defines an undo scope, or `None` if undo is registered globally for
178    /// the entire app.
179    pub fn scope(&self) -> Option<WidgetId> {
180        UNDO_SCOPE_CTX.get().id()
181    }
182
183    /// Register an already executed action for undo in the current scope.
184    pub fn register(&self, action: impl UndoAction) {
185        UNDO_SCOPE_CTX.get().register(Box::new(action))
186    }
187
188    /// Register an already executed action for undo in the current scope.
189    ///
190    /// The action is defined as a closure `op` that matches over [`UndoOp`] to implement undo and redo.
191    pub fn register_op(&self, info: impl UndoInfo, op: impl FnMut(UndoOp) + Send + 'static) {
192        self.register(UndoRedoOp {
193            info: info.into_dyn(),
194            op: Box::new(op),
195        })
196    }
197
198    /// Register an already executed action for undo in the current scope.
199    ///
200    /// The action is defined as a closure `op` that matches over [`UndoFullOp`] referencing `data` to implement undo and redo.
201    pub fn register_full_op<D>(&self, data: D, mut op: impl FnMut(&mut D, UndoFullOp) + Send + 'static)
202    where
203        D: Any + Send + 'static,
204    {
205        self.register(UndoRedoFullOp {
206            data: Box::new(data),
207            op: Box::new(move |d, o| {
208                op(d.downcast_mut::<D>().unwrap(), o);
209            }),
210        })
211    }
212
213    /// Run the `action` and register the undo in the current scope.
214    pub fn run(&self, action: impl RedoAction) {
215        UNDO_SCOPE_CTX.get().register(Box::new(action).redo())
216    }
217
218    /// Run the `op` once with [`UndoOp::Redo`] and register it for undo in the current scope.
219    pub fn run_op(&self, info: impl UndoInfo, op: impl FnMut(UndoOp) + Send + 'static) {
220        self.run(UndoRedoOp {
221            info: info.into_dyn(),
222            op: Box::new(op),
223        })
224    }
225
226    /// Run the `op` once with `UndoFullOp::Init { .. }` and `UndoFullOp::Op(UndoOp::Redo)` and register it for undo in the current scope.
227    pub fn run_full_op<D>(&self, mut data: D, mut op: impl FnMut(&mut D, UndoFullOp) + Send + 'static)
228    where
229        D: Any + Send + 'static,
230    {
231        let mut redo = true;
232        op(&mut data, UndoFullOp::Init { redo: &mut redo });
233
234        if redo {
235            self.run(UndoRedoFullOp {
236                data: Box::new(data),
237                op: Box::new(move |d, o| {
238                    op(d.downcast_mut::<D>().unwrap(), o);
239                }),
240            })
241        }
242    }
243
244    /// Run `actions` as a [`transaction`] and commits as a group if any undo action is captured.
245    ///
246    /// [`transaction`]: Self::transaction
247    pub fn group(&self, info: impl UndoInfo, actions: impl FnOnce()) -> bool {
248        let t = self.transaction(actions);
249        let any = !t.is_empty();
250        if any {
251            t.commit_group(info);
252        }
253        any
254    }
255
256    /// Run `actions` in a new undo scope, capturing all undo actions inside it into a new
257    /// [`UndoTransaction`].
258    ///
259    /// The transaction can be immediately undone or committed.
260    pub fn transaction(&self, actions: impl FnOnce()) -> UndoTransaction {
261        let mut scope = UndoScope::default();
262        let parent_scope = UNDO_SCOPE_CTX.get();
263        *scope.enabled.get_mut() = parent_scope.enabled.load(Ordering::Relaxed);
264        *scope.id.get_mut() = parent_scope.id.load(Ordering::Relaxed);
265
266        let t_scope = Arc::new(scope);
267        let _panic_undo = RunOnDrop::new(clmv!(t_scope, || {
268            for undo in mem::take(&mut *t_scope.undo.lock()).into_iter().rev() {
269                let _ = undo.action.undo();
270            }
271        }));
272
273        let mut scope = Some(t_scope);
274        UNDO_SCOPE_CTX.with_context(&mut scope, actions);
275
276        let scope = scope.unwrap();
277        let undo = mem::take(&mut *scope.undo.lock());
278
279        UndoTransaction { undo }
280    }
281
282    /// Run `actions` as a [`transaction`] and commits as a group if the result is `Ok(O)` and at least one
283    /// undo action was registered, or undoes all if result is `Err(E)`.
284    ///
285    /// [`transaction`]: Self::transaction
286    pub fn try_group<O, E>(&self, info: impl UndoInfo, actions: impl FnOnce() -> Result<O, E>) -> Result<O, E> {
287        let mut r = None;
288        let t = self.transaction(|| r = Some(actions()));
289        let r = r.unwrap();
290        if !t.is_empty() {
291            if r.is_ok() {
292                t.commit_group(info);
293            } else {
294                t.undo();
295            }
296        }
297        r
298    }
299
300    /// Run `actions` as a [`transaction`] and commits if the result is `Ok(O)`, or undoes all if result is `Err(E)`.
301    ///
302    /// [`transaction`]: Self::transaction
303    pub fn try_commit<O, E>(&self, actions: impl FnOnce() -> Result<O, E>) -> Result<O, E> {
304        let mut r = None;
305        let t = self.transaction(|| r = Some(actions()));
306        let r = r.unwrap();
307        if !t.is_empty() {
308            if r.is_ok() {
309                t.commit();
310            } else {
311                t.undo();
312            }
313        }
314        r
315    }
316
317    /// Runs `f` in a new `scope`. All undo actions inside `f` are registered in the `scope`.
318    pub fn with_scope<R>(&self, scope: &mut WidgetUndoScope, f: impl FnOnce() -> R) -> R {
319        UNDO_SCOPE_CTX.with_context(&mut scope.0, f)
320    }
321
322    /// Runs `f` in a disabled scope, all undo actions registered inside `f` are ignored.
323    pub fn with_disabled<R>(&self, f: impl FnOnce() -> R) -> R {
324        let mut scope = UndoScope::default();
325        let parent_scope = UNDO_SCOPE_CTX.get();
326        *scope.enabled.get_mut() = false;
327        *scope.id.get_mut() = parent_scope.id.load(Ordering::Relaxed);
328
329        UNDO_SCOPE_CTX.with_context(&mut Some(Arc::new(scope)), f)
330    }
331
332    /// Track changes on `var`, registering undo actions for it.
333    ///
334    /// The variable will be tracked until the returned handle or the var is dropped.
335    ///
336    /// Note that this will keep strong clones of previous and new value every time the variable changes, but
337    /// it will only keep weak references to the variable. Dropping the handle or the var will not remove undo/redo
338    /// entries for it, they will still try to assign the variable, failing silently if the variable is dropped too.
339    ///
340    /// Var updates caused by undo and redo are tagged with [`UndoVarModifyTag`].
341    pub fn watch_var<T: VarValue>(&self, info: impl UndoInfo, var: impl Var<T>) -> VarHandle {
342        if var.capabilities().is_always_read_only() {
343            return VarHandle::dummy();
344        }
345        let var = var.actual_var();
346        let wk_var = var.downgrade();
347
348        let mut prev_value = Some(var.get());
349        let info = info.into_dyn();
350
351        var.trace_value(move |args| {
352            if args.downcast_tags::<UndoVarModifyTag>().next().is_none() {
353                let prev = prev_value.take().unwrap();
354                let new = args.value();
355                if &prev == new {
356                    // no actual change
357                    prev_value = Some(prev);
358                    return;
359                }
360                prev_value = Some(new.clone());
361                UNDO.register_op(
362                    info.clone(),
363                    clmv!(wk_var, new, |op| if let Some(var) = wk_var.upgrade() {
364                        let _ = match op {
365                            UndoOp::Undo => var.modify(clmv!(prev, |args| {
366                                args.set(prev);
367                                args.push_tag(UndoVarModifyTag);
368                            })),
369                            UndoOp::Redo => var.modify(clmv!(new, |args| {
370                                args.set(new);
371                                args.push_tag(UndoVarModifyTag);
372                            })),
373                        };
374                    }),
375                );
376            }
377        })
378    }
379
380    /// Clear all redo actions.
381    pub fn clear_redo(&self) {
382        UNDO_SCOPE_CTX.get().redo.lock().clear();
383    }
384
385    /// Clear all undo and redo actions.
386    pub fn clear(&self) {
387        let ctx = UNDO_SCOPE_CTX.get();
388        let mut u = ctx.undo.lock();
389        u.clear();
390        ctx.redo.lock().clear();
391    }
392
393    /// If the undo stack is not empty.
394    pub fn can_undo(&self) -> bool {
395        !UNDO_SCOPE_CTX.get().undo.lock().is_empty()
396    }
397
398    /// If the redo stack is not empty.
399    pub fn can_redo(&self) -> bool {
400        !UNDO_SCOPE_CTX.get().redo.lock().is_empty()
401    }
402
403    /// Clones the timestamp and info of all entries in the current undo stack.
404    ///
405    /// The latest undo action is the last entry in the list.
406    pub fn undo_stack(&self) -> UndoStackInfo {
407        UndoStackInfo::undo(&UNDO_SCOPE_CTX.get(), UNDO_INTERVAL_VAR.get())
408    }
409
410    /// Clones the timestamp and info of all entries in the current redo stack.
411    ///
412    /// The latest undone action is the last entry in the list. Note that the
413    /// timestamp marks the moment the original undo registered the action, so the
414    /// newest timestamp is in the first entry.
415    pub fn redo_stack(&self) -> UndoStackInfo {
416        UndoStackInfo::redo(&UNDO_SCOPE_CTX.get(), UNDO_INTERVAL_VAR.get())
417    }
418}
419
420/// Snapshot of the undo or redo stacks in an [`UNDO`] scope.
421#[derive(Clone)]
422pub struct UndoStackInfo {
423    /// Clones the timestamp and info of all entries in the current undo stack.
424    ///
425    /// In an undo list the latest undo action (first to undo) is the last entry in the list and has the latest timestamp.
426    ///
427    /// In an redo list the latest undone action is the last entry (first to redo). Note that the
428    /// timestamp marks the moment the original undo registered the action, so the
429    /// newest timestamp is in the first entry for redo lists.
430    pub stack: Vec<(DInstant, Arc<dyn UndoInfo>)>,
431
432    /// Grouping interval.
433    pub undo_interval: Duration,
434}
435impl UndoStackInfo {
436    fn undo(ctx: &UndoScope, undo_interval: Duration) -> Self {
437        Self {
438            stack: ctx.undo.lock().iter_mut().map(|e| (e.timestamp, e.action.info())).collect(),
439            undo_interval,
440        }
441    }
442    fn redo(ctx: &UndoScope, undo_interval: Duration) -> Self {
443        Self {
444            stack: ctx.redo.lock().iter_mut().map(|e| (e.timestamp, e.action.info())).collect(),
445            undo_interval,
446        }
447    }
448
449    /// Iterate over the `stack`, grouped by `undo_interval`.
450    pub fn iter_groups(&self) -> impl DoubleEndedIterator<Item = &[(DInstant, Arc<dyn UndoInfo>)]> {
451        struct Iter<'a> {
452            stack: &'a [(DInstant, Arc<dyn UndoInfo>)],
453            interval: Duration,
454            ts_inverted: bool,
455        }
456        impl<'a> Iterator for Iter<'a> {
457            type Item = &'a [(DInstant, Arc<dyn UndoInfo>)];
458
459            fn next(&mut self) -> Option<Self::Item> {
460                if self.stack.is_empty() {
461                    None
462                } else {
463                    let mut older = self.stack[0].0;
464
465                    let mut r = self.stack;
466
467                    if let Some(i) = self.stack.iter().position(|(newer, _)| {
468                        let (a, b) = if self.ts_inverted { (older, *newer) } else { (*newer, older) };
469                        let break_ = a.saturating_duration_since(b) > self.interval;
470                        older = *newer;
471                        break_
472                    }) {
473                        r = &self.stack[..i];
474                        self.stack = &self.stack[i..];
475                    } else {
476                        self.stack = &[];
477                    }
478
479                    Some(r)
480                }
481            }
482        }
483        impl DoubleEndedIterator for Iter<'_> {
484            fn next_back(&mut self) -> Option<Self::Item> {
485                if self.stack.is_empty() {
486                    None
487                } else {
488                    let mut newer = self.stack[self.stack.len() - 1].0;
489
490                    let mut r = self.stack;
491
492                    if let Some(i) = self.stack.iter().rposition(|(older, _)| {
493                        let (a, b) = if self.ts_inverted { (*older, newer) } else { (newer, *older) };
494                        let break_ = a.saturating_duration_since(b) > self.interval;
495                        newer = *older;
496                        break_
497                    }) {
498                        let i = i + 1;
499                        r = &self.stack[i..];
500                        self.stack = &self.stack[..i];
501                    } else {
502                        self.stack = &[];
503                    }
504
505                    Some(r)
506                }
507            }
508        }
509        Iter {
510            stack: &self.stack,
511            interval: self.undo_interval,
512            ts_inverted: self.stack.len() > 1 && self.stack[0].0 > self.stack[self.stack.len() - 1].0,
513        }
514    }
515}
516
517/// Identifies var modify requests by undo/redo action.
518///
519/// See [`UNDO.watch_var`] for more details.
520///
521/// [`UNDO.watch_var`]: UNDO::watch_var
522#[derive(Debug, Clone, Copy, PartialEq)]
523pub struct UndoVarModifyTag;
524
525/// Metadata info about an action registered for undo action.
526pub trait UndoInfo: Send + Sync + Any {
527    /// Short display description of the action that will be undone/redone.
528    fn description(&self) -> Txt;
529
530    /// Any extra metadata associated with the item. This can be a thumbnail of an image
531    /// edit action for example, or an icon.
532    ///
533    /// Is empty by default.
534    fn meta(&self) -> StateMapRef<UNDO> {
535        StateMapRef::empty()
536    }
537
538    /// Into `Arc<dyn UndoInfo>` without double wrapping.
539    fn into_dyn(self) -> Arc<dyn UndoInfo>
540    where
541        Self: Sized,
542    {
543        Arc::new(self)
544    }
545}
546impl UndoInfo for Txt {
547    fn description(&self) -> Txt {
548        self.clone()
549    }
550}
551impl UndoInfo for BoxedVar<Txt> {
552    fn description(&self) -> Txt {
553        self.get()
554    }
555}
556impl UndoInfo for &'static str {
557    fn description(&self) -> Txt {
558        Txt::from_static(self)
559    }
560}
561impl UndoInfo for Arc<dyn UndoInfo> {
562    fn description(&self) -> Txt {
563        self.as_ref().description()
564    }
565
566    fn meta(&self) -> StateMapRef<UNDO> {
567        self.as_ref().meta()
568    }
569
570    fn into_dyn(self) -> Arc<dyn UndoInfo>
571    where
572        Self: Sized,
573    {
574        self
575    }
576}
577/// Represents a single undo action.
578pub trait UndoAction: Send + Any {
579    /// Gets display info about the action that registered this undo.
580    fn info(&mut self) -> Arc<dyn UndoInfo>;
581
582    /// Undo action and returns a [`RedoAction`] that redoes it.
583    fn undo(self: Box<Self>) -> Box<dyn RedoAction>;
584
585    /// Access `dyn Any` methods.
586    fn as_any(&mut self) -> &mut dyn Any;
587
588    /// Try merge the `next` action with the previous `self`.
589    ///
590    /// This is called when `self` is the latest registered action and `next` is registered.
591    ///
592    /// This can be used to optimize high-volume actions, but note that [`UNDO.undo`] will undo all actions
593    /// within the [`UNDO.undo_interval`] of the previous, even if not merged, and merged actions always show as one action
594    /// for [`UNDO.undo_select`].
595    ///
596    /// [`UNDO.undo_interval`]: UNDO::undo_interval
597    /// [`UNDO.undo_select`]: UNDO::undo_select
598    /// [`UNDO.undo`]: UNDO::undo
599    fn merge(self: Box<Self>, args: UndoActionMergeArgs) -> Result<Box<dyn UndoAction>, (Box<dyn UndoAction>, Box<dyn UndoAction>)>;
600}
601
602/// Arguments for [`UndoAction::merge`].
603pub struct UndoActionMergeArgs {
604    /// The action that was registered after the one receiving this arguments.
605    pub next: Box<dyn UndoAction>,
606
607    /// Timestamp of the previous action registered.
608    pub prev_timestamp: DInstant,
609
610    /// If the `prev_timestamp` is within the [`UNDO.undo_interval`]. Undo actions
611    /// can choose to ignore this and merge anyway.
612    ///
613    /// [`UNDO.undo_interval`]: UNDO::undo_interval
614    pub within_undo_interval: bool,
615}
616
617/// Represents a single redo action.
618pub trait RedoAction: Send + Any {
619    /// Gets display info about the action that will be redone.
620    fn info(&mut self) -> Arc<dyn UndoInfo>;
621
622    /// Redo action and returns a [`UndoAction`] that undoes it.
623    fn redo(self: Box<Self>) -> Box<dyn UndoAction>;
624}
625
626/// Represents an undo/redo action.
627///
628/// This can be used to implement undo and redo in a single closure. See [`UNDO.register_op`] and
629/// [`UNDO.run_op`] for more details.
630///
631/// [`UNDO.register_op`]: UNDO::register_op
632/// [`UNDO.run_op`]: UNDO::run_op
633#[derive(Debug, Clone, Copy, PartialEq, Eq)]
634pub enum UndoOp {
635    /// Undo the action.
636    Undo,
637    /// Redo the action.
638    Redo,
639}
640impl UndoOp {
641    /// Gets the command that represents the OP.
642    pub fn cmd(self) -> Command {
643        match self {
644            UndoOp::Undo => UNDO_CMD,
645            UndoOp::Redo => REDO_CMD,
646        }
647    }
648}
649
650/// Represents a full undo/redo action.
651///
652/// This can be used to implement undo and redo in a single closure. See [`UNDO.register_full_op`] and
653/// [`UNDO.run_full_op`] for more details.
654///
655/// [`UNDO.register_full_op`]: UNDO::register_full_op
656/// [`UNDO.run_full_op`]: UNDO::run_full_op
657pub enum UndoFullOp<'r> {
658    /// Initialize data in the execution context.
659    ///
660    /// This is called once before the initial `Op(UndoOp::Redo)` call, it
661    /// can be used to skip registering no-ops.
662    Init {
663        /// If the op must actually be executed.
664        ///
665        /// This is `true` by default, if set to `false` the OP will be dropped without ever executing and
666        /// will not be registered for undo.
667        redo: &'r mut bool,
668    },
669
670    /// Normal undo/redo.
671    Op(UndoOp),
672    /// Collect display info.
673    Info {
674        /// Set this to respond.
675        ///
676        /// If not set the info will be some generic "action" text.
677        info: &'r mut Option<Arc<dyn UndoInfo>>,
678    },
679    /// Try merge the `next_data` onto self data (at the undone state).
680    Merge {
681        /// Closure data for the next undo action.
682        ///
683        /// The data can be from any full undo closure action, only merge if the data
684        /// indicates that it comes from actions that can be covered by the `self` closure.
685        next_data: &'r mut dyn Any,
686
687        /// Timestamp of the previous action registered.
688        prev_timestamp: DInstant,
689
690        /// If the `prev_timestamp` is within the [`UNDO.undo_interval`]. Undo actions
691        /// can choose to ignore this and merge anyway.
692        ///
693        /// [`UNDO.undo_interval`]: UNDO::undo_interval
694        within_undo_interval: bool,
695
696        /// Set this to `true` if the next action can be dropped because the `self` closure
697        /// now also implements it.
698        merged: &'r mut bool,
699    },
700}
701impl fmt::Debug for UndoFullOp<'_> {
702    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
703        match self {
704            Self::Init { .. } => f.debug_struct("Init").finish_non_exhaustive(),
705            Self::Op(arg0) => f.debug_tuple("Op").field(arg0).finish(),
706            Self::Info { .. } => f.debug_struct("Info").finish_non_exhaustive(),
707            Self::Merge { .. } => f.debug_struct("Merge").finish_non_exhaustive(),
708        }
709    }
710}
711
712/// Represents captured undo actions in an [`UNDO.transaction`] operation.
713///
714/// [`UNDO.transaction`]: UNDO::transaction
715#[must_use = "dropping the transaction undoes all captured actions"]
716pub struct UndoTransaction {
717    undo: Vec<UndoEntry>,
718}
719impl UndoTransaction {
720    /// If the transaction did not capture any undo action.
721    pub fn is_empty(&self) -> bool {
722        self.undo.is_empty()
723    }
724
725    /// Push all undo actions captured by the transaction into the current undo scope.
726    pub fn commit(mut self) {
727        let mut undo = mem::take(&mut self.undo);
728        let now = INSTANT.now();
729        for u in &mut undo {
730            u.timestamp = now;
731        }
732        let ctx = UNDO_SCOPE_CTX.get();
733        let mut ctx_undo = ctx.undo.lock();
734        if ctx_undo.is_empty() {
735            *ctx_undo = undo;
736        } else {
737            ctx_undo.extend(undo);
738        }
739    }
740
741    /// Push a single action in the current undo scope that undoes/redoes all the captured
742    /// actions in the transaction.
743    ///
744    /// Note that this will register a group item even if the transaction is empty.
745    pub fn commit_group(mut self, info: impl UndoInfo) {
746        UNDO.register(UndoGroup {
747            info: info.into_dyn(),
748            undo: mem::take(&mut self.undo),
749        })
750    }
751
752    /// Cancel the transaction, undoes all captured actions.
753    ///
754    /// This is the same as dropping the transaction.
755    pub fn undo(self) {
756        let _ = self;
757    }
758}
759impl Drop for UndoTransaction {
760    fn drop(&mut self) {
761        for undo in self.undo.drain(..).rev() {
762            let _ = undo.action.undo();
763        }
764    }
765}
766
767command! {
768    /// Represents the **undo** action.
769    ///
770    /// # Param
771    ///
772    /// If the command parameter is a `u32`, `Duration` or `DInstant` calls [`undo_select`], otherwise calls
773    /// [`undo`].
774    ///
775    /// [`undo_select`]: UNDO::undo_select
776    /// [`undo`]: UNDO::undo
777    ///
778    /// # Scope
779    ///
780    /// You can use [`CommandUndoExt::undo_scoped`] to get a command variable that is always scoped on the
781    /// focused undo scope.
782    pub static UNDO_CMD = {
783        l10n!: true,
784        name: "Undo",
785        shortcut: [shortcut!(CTRL+'Z')],
786        icon: wgt_fn!(|_| ICONS.get("undo")),
787    };
788
789    /// Represents the **redo** action.
790    ///
791    /// # Param
792    ///
793    /// If the command parameter is a `u32`, `Duration` or `DInstant` calls [`redo_select`], otherwise calls
794    /// [`redo`].
795    ///
796    /// [`redo_select`]: UNDO::redo_select
797    /// [`redo`]: UNDO::redo
798    pub static REDO_CMD = {
799        l10n!: true,
800        name: "Redo",
801        shortcut: [shortcut!(CTRL+'Y')],
802        icon: wgt_fn!(|_| ICONS.get("redo")),
803    };
804
805    /// Represents the **clear history** action.
806    ///
807    /// Implementers call [`clear`] in the undo scope.
808    ///
809    /// [`clear`]: UNDO::clear
810    pub static CLEAR_HISTORY_CMD = {
811        l10n!: true,
812        name: "Clear History",
813    };
814}
815
816/// Represents a widget undo scope.
817///
818/// See [`UNDO.with_scope`] for more details.
819///
820/// [`UNDO.with_scope`]: UNDO::with_scope
821pub struct WidgetUndoScope(Option<Arc<UndoScope>>);
822impl WidgetUndoScope {
823    /// New, not inited in a widget.
824    pub const fn new() -> Self {
825        Self(None)
826    }
827
828    /// if the scope is already inited in a widget.
829    pub fn is_inited(&self) -> bool {
830        self.0.is_some()
831    }
832
833    /// Init the scope in the [`WIDGET`].
834    ///
835    /// [`WIDGET`]: zng_app::widget::WIDGET
836    pub fn init(&mut self) {
837        let mut scope = UndoScope::default();
838        let id = WIDGET.id();
839        *scope.id.get_mut() = Some(id);
840
841        let scope = Arc::new(scope);
842        let wk_scope = Arc::downgrade(&scope);
843        let interval = UNDO_INTERVAL_VAR.actual_var();
844
845        UNDO_CMD
846            .scoped(id)
847            .with_meta(|m| m.set(*WEAK_UNDO_SCOPE_ID, (wk_scope.clone(), interval.clone())));
848        REDO_CMD.scoped(id).with_meta(|m| m.set(*WEAK_UNDO_SCOPE_ID, (wk_scope, interval)));
849
850        self.0 = Some(scope);
851    }
852
853    /// Sets the [`WIDGET`] info.
854    ///
855    /// [`WIDGET`]: zng_app::widget::WIDGET
856    pub fn info(&mut self, info: &mut WidgetInfoBuilder) {
857        info.flag_meta(*FOCUS_SCOPE_ID);
858    }
859
860    /// Deinit the scope in the [`WIDGET`].
861    ///
862    /// This clears the undo/redo stack of the scope.
863    ///
864    /// [`WIDGET`]: zng_app::widget::WIDGET
865    pub fn deinit(&mut self) {
866        self.0 = None;
867    }
868
869    /// Sets if the undo/redo is enabled in this scope.
870    ///
871    /// Is `true` by default.
872    pub fn set_enabled(&mut self, enabled: bool) {
873        self.0.as_ref().unwrap().enabled.store(enabled, Ordering::Relaxed);
874    }
875
876    /// Gets if the undo stack is not empty.
877    pub fn can_undo(&self) -> bool {
878        !self.0.as_ref().unwrap().undo.lock().is_empty()
879    }
880
881    /// Gets if the redo stack is not empty.
882    pub fn can_redo(&self) -> bool {
883        !self.0.as_ref().unwrap().redo.lock().is_empty()
884    }
885}
886impl Default for WidgetUndoScope {
887    fn default() -> Self {
888        Self::new()
889    }
890}
891
892struct UndoScope {
893    id: Atomic<Option<WidgetId>>,
894    undo: Mutex<Vec<UndoEntry>>,
895    redo: Mutex<Vec<RedoEntry>>,
896    enabled: AtomicBool,
897}
898impl Default for UndoScope {
899    fn default() -> Self {
900        Self {
901            id: Default::default(),
902            undo: Default::default(),
903            redo: Default::default(),
904            enabled: AtomicBool::new(true),
905        }
906    }
907}
908impl UndoScope {
909    fn with_enabled_undo_redo(&self, f: impl FnOnce(&mut Vec<UndoEntry>, &mut Vec<RedoEntry>)) {
910        let mut undo = self.undo.lock();
911        let mut redo = self.redo.lock();
912
913        let max_undo = if self.enabled.load(Ordering::Relaxed) {
914            UNDO_LIMIT_VAR.get() as usize
915        } else {
916            0
917        };
918
919        if undo.len() > max_undo {
920            undo.reverse();
921            while undo.len() > max_undo {
922                undo.pop();
923            }
924            undo.reverse();
925        }
926
927        if redo.len() > max_undo {
928            redo.reverse();
929            while redo.len() > max_undo {
930                redo.pop();
931            }
932            redo.reverse();
933        }
934
935        if max_undo > 0 {
936            f(&mut undo, &mut redo);
937        }
938    }
939
940    fn register(&self, action: Box<dyn UndoAction>) {
941        self.with_enabled_undo_redo(|undo, redo| {
942            let now = INSTANT.now();
943            if let Some(prev) = undo.pop() {
944                match prev.action.merge(UndoActionMergeArgs {
945                    next: action,
946                    prev_timestamp: prev.timestamp,
947                    within_undo_interval: now.duration_since(prev.timestamp) <= UNDO_SV.read().undo_interval.get(),
948                }) {
949                    Ok(merged) => undo.push(UndoEntry {
950                        timestamp: now,
951                        action: merged,
952                    }),
953                    Err((p, action)) => {
954                        undo.push(UndoEntry {
955                            timestamp: prev.timestamp,
956                            action: p,
957                        });
958                        undo.push(UndoEntry { timestamp: now, action });
959                    }
960                }
961            } else {
962                undo.push(UndoEntry { timestamp: now, action });
963            }
964            redo.clear();
965        });
966    }
967
968    fn undo_select(&self, selector: impl UndoSelector) {
969        let mut actions = vec![];
970
971        self.with_enabled_undo_redo(|undo, _| {
972            let mut select = selector.select(UndoOp::Undo);
973            while let Some(entry) = undo.last() {
974                if select.include(entry.timestamp) {
975                    actions.push(undo.pop().unwrap());
976                } else {
977                    break;
978                }
979            }
980        });
981
982        for undo in actions {
983            let redo = undo.action.undo();
984            self.redo.lock().push(RedoEntry {
985                timestamp: undo.timestamp,
986                action: redo,
987            });
988        }
989    }
990
991    fn redo_select(&self, selector: impl UndoSelector) {
992        let mut actions = vec![];
993
994        self.with_enabled_undo_redo(|_, redo| {
995            let mut select = selector.select(UndoOp::Redo);
996            while let Some(entry) = redo.last() {
997                if select.include(entry.timestamp) {
998                    actions.push(redo.pop().unwrap());
999                } else {
1000                    break;
1001                }
1002            }
1003        });
1004
1005        for redo in actions {
1006            let undo = redo.action.redo();
1007            self.undo.lock().push(UndoEntry {
1008                timestamp: redo.timestamp,
1009                action: undo,
1010            });
1011        }
1012    }
1013
1014    fn id(&self) -> Option<WidgetId> {
1015        self.id.load(Ordering::Relaxed)
1016    }
1017}
1018
1019struct UndoEntry {
1020    timestamp: DInstant,
1021    action: Box<dyn UndoAction>,
1022}
1023
1024struct RedoEntry {
1025    pub timestamp: DInstant,
1026    pub action: Box<dyn RedoAction>,
1027}
1028
1029struct UndoGroup {
1030    info: Arc<dyn UndoInfo>,
1031    undo: Vec<UndoEntry>,
1032}
1033impl UndoAction for UndoGroup {
1034    fn undo(self: Box<Self>) -> Box<dyn RedoAction> {
1035        let mut redo = Vec::with_capacity(self.undo.len());
1036        for undo in self.undo.into_iter().rev() {
1037            redo.push(RedoEntry {
1038                timestamp: undo.timestamp,
1039                action: undo.action.undo(),
1040            });
1041        }
1042        Box::new(RedoGroup { info: self.info, redo })
1043    }
1044
1045    fn info(&mut self) -> Arc<dyn UndoInfo> {
1046        self.info.clone()
1047    }
1048
1049    fn as_any(&mut self) -> &mut dyn Any {
1050        self
1051    }
1052
1053    fn merge(self: Box<Self>, args: UndoActionMergeArgs) -> Result<Box<dyn UndoAction>, (Box<dyn UndoAction>, Box<dyn UndoAction>)> {
1054        Err((self, args.next))
1055    }
1056}
1057struct RedoGroup {
1058    info: Arc<dyn UndoInfo>,
1059    redo: Vec<RedoEntry>,
1060}
1061impl RedoAction for RedoGroup {
1062    fn redo(self: Box<Self>) -> Box<dyn UndoAction> {
1063        let mut undo = Vec::with_capacity(self.redo.len());
1064        for redo in self.redo.into_iter().rev() {
1065            undo.push(UndoEntry {
1066                timestamp: redo.timestamp,
1067                action: redo.action.redo(),
1068            });
1069        }
1070        Box::new(UndoGroup { info: self.info, undo })
1071    }
1072
1073    fn info(&mut self) -> Arc<dyn UndoInfo> {
1074        self.info.clone()
1075    }
1076}
1077
1078struct UndoRedoOp {
1079    info: Arc<dyn UndoInfo>,
1080    op: Box<dyn FnMut(UndoOp) + Send>,
1081}
1082impl UndoAction for UndoRedoOp {
1083    fn undo(mut self: Box<Self>) -> Box<dyn RedoAction> {
1084        (self.op)(UndoOp::Undo);
1085        self
1086    }
1087
1088    fn info(&mut self) -> Arc<dyn UndoInfo> {
1089        self.info.clone()
1090    }
1091
1092    fn as_any(&mut self) -> &mut dyn Any {
1093        self
1094    }
1095
1096    fn merge(self: Box<Self>, args: UndoActionMergeArgs) -> Result<Box<dyn UndoAction>, (Box<dyn UndoAction>, Box<dyn UndoAction>)> {
1097        Err((self, args.next))
1098    }
1099}
1100impl RedoAction for UndoRedoOp {
1101    fn redo(mut self: Box<Self>) -> Box<dyn UndoAction> {
1102        (self.op)(UndoOp::Redo);
1103        self
1104    }
1105
1106    fn info(&mut self) -> Arc<dyn UndoInfo> {
1107        self.info.clone()
1108    }
1109}
1110
1111struct UndoRedoFullOp {
1112    data: Box<dyn Any + Send>,
1113    op: Box<dyn FnMut(&mut dyn Any, UndoFullOp) + Send>,
1114}
1115impl UndoAction for UndoRedoFullOp {
1116    fn info(&mut self) -> Arc<dyn UndoInfo> {
1117        let mut info = None;
1118        (self.op)(&mut self.data, UndoFullOp::Info { info: &mut info });
1119        info.unwrap_or_else(|| Arc::new("action"))
1120    }
1121
1122    fn undo(mut self: Box<Self>) -> Box<dyn RedoAction> {
1123        (self.op)(&mut self.data, UndoFullOp::Op(UndoOp::Undo));
1124        self
1125    }
1126
1127    fn merge(mut self: Box<Self>, mut args: UndoActionMergeArgs) -> Result<Box<dyn UndoAction>, (Box<dyn UndoAction>, Box<dyn UndoAction>)>
1128    where
1129        Self: Sized,
1130    {
1131        if let Some(u) = args.next.as_any().downcast_mut::<Self>() {
1132            let mut merged = false;
1133            (self.op)(
1134                &mut self.data,
1135                UndoFullOp::Merge {
1136                    next_data: &mut u.data,
1137                    prev_timestamp: args.prev_timestamp,
1138                    within_undo_interval: args.within_undo_interval,
1139                    merged: &mut merged,
1140                },
1141            );
1142            if merged { Ok(self) } else { Err((self, args.next)) }
1143        } else {
1144            Err((self, args.next))
1145        }
1146    }
1147
1148    fn as_any(&mut self) -> &mut dyn Any {
1149        self
1150    }
1151}
1152impl RedoAction for UndoRedoFullOp {
1153    fn info(&mut self) -> Arc<dyn UndoInfo> {
1154        let mut info = None;
1155        (self.op)(&mut self.data, UndoFullOp::Info { info: &mut info });
1156        info.unwrap_or_else(|| Arc::new("action"))
1157    }
1158
1159    fn redo(mut self: Box<Self>) -> Box<dyn UndoAction> {
1160        (self.op)(&mut self.data, UndoFullOp::Op(UndoOp::Redo));
1161        self
1162    }
1163}
1164
1165struct UndoService {
1166    undo_limit: BoxedVar<u32>,
1167    undo_interval: BoxedVar<Duration>,
1168}
1169
1170impl Default for UndoService {
1171    fn default() -> Self {
1172        Self {
1173            undo_limit: var(u32::MAX).boxed(),
1174            undo_interval: KEYBOARD.repeat_config().map(|c| c.start_delay + c.interval).cow().boxed(),
1175        }
1176    }
1177}
1178
1179context_local! {
1180    static UNDO_SCOPE_CTX: UndoScope = UndoScope::default();
1181}
1182app_local! {
1183    static UNDO_SV: UndoService = UndoService::default();
1184}
1185
1186/// Undo extension methods for widget info.
1187pub trait WidgetInfoUndoExt {
1188    /// Returns `true` if the widget is an undo scope.
1189    fn is_undo_scope(&self) -> bool;
1190
1191    /// Gets the first ancestor that is an undo scope.
1192    fn undo_scope(&self) -> Option<WidgetInfo>;
1193}
1194impl WidgetInfoUndoExt for WidgetInfo {
1195    fn is_undo_scope(&self) -> bool {
1196        self.meta().flagged(*FOCUS_SCOPE_ID)
1197    }
1198
1199    fn undo_scope(&self) -> Option<WidgetInfo> {
1200        self.ancestors().find(WidgetInfoUndoExt::is_undo_scope)
1201    }
1202}
1203
1204static_id! {
1205    static ref FOCUS_SCOPE_ID: StateId<()>;
1206}
1207
1208/// Undo extension methods for commands.
1209pub trait CommandUndoExt {
1210    /// Gets the command scoped in the undo scope widget that is or contains the focused widget, or
1211    /// scoped on the app if there is no focused undo scope.
1212    fn undo_scoped(self) -> BoxedVar<Command>;
1213
1214    /// Latest undo stack for the given scope, same as calling [`UNDO::undo_stack`] inside the scope.
1215    fn undo_stack(self) -> UndoStackInfo;
1216    /// Latest undo stack for the given scope, same as calling [`UNDO::redo_stack`] inside the scope.
1217    fn redo_stack(self) -> UndoStackInfo;
1218}
1219impl CommandUndoExt for Command {
1220    fn undo_scoped(self) -> BoxedVar<Command> {
1221        self.focus_scoped_with(|w| match w {
1222            Some(w) => {
1223                if w.is_undo_scope() {
1224                    CommandScope::Widget(w.id())
1225                } else if let Some(scope) = w.undo_scope() {
1226                    CommandScope::Widget(scope.id())
1227                } else {
1228                    CommandScope::App
1229                }
1230            }
1231            None => CommandScope::App,
1232        })
1233    }
1234
1235    fn undo_stack(self) -> UndoStackInfo {
1236        let scope = self.with_meta(|m| m.get(*WEAK_UNDO_SCOPE_ID));
1237        if let Some(scope) = scope {
1238            if let Some(s) = scope.0.upgrade() {
1239                return UndoStackInfo::undo(&s, scope.1.get());
1240            }
1241        }
1242
1243        if let CommandScope::App = self.scope() {
1244            let mut r = UNDO_SCOPE_CTX.with_default(|| UNDO.undo_stack());
1245            r.undo_interval = UNDO.undo_interval().get();
1246            return r;
1247        }
1248
1249        UndoStackInfo {
1250            stack: vec![],
1251            undo_interval: Duration::ZERO,
1252        }
1253    }
1254
1255    fn redo_stack(self) -> UndoStackInfo {
1256        let scope = self.with_meta(|m| m.get(*WEAK_UNDO_SCOPE_ID));
1257        if let Some(scope) = scope {
1258            if let Some(s) = scope.0.upgrade() {
1259                return UndoStackInfo::redo(&s, scope.1.get());
1260            }
1261        }
1262
1263        if let CommandScope::App = self.scope() {
1264            let mut r = UNDO_SCOPE_CTX.with_default(|| UNDO.redo_stack());
1265            r.undo_interval = UNDO.undo_interval().get();
1266            return r;
1267        }
1268
1269        UndoStackInfo {
1270            stack: vec![],
1271            undo_interval: Duration::ZERO,
1272        }
1273    }
1274}
1275
1276static_id! {
1277    static ref WEAK_UNDO_SCOPE_ID: StateId<(std::sync::Weak<UndoScope>, BoxedVar<Duration>)>;
1278}
1279
1280/// Represents a type that can select actions for undo or redo once.
1281///
1282/// This API is sealed, only core crate can implement it.
1283///
1284/// See [`UNDO::undo_select`] for more details.
1285pub trait UndoSelector: crate::private::Sealed {
1286    /// Selection collector.
1287    type Select: UndoSelect;
1288
1289    /// Start selecting action for the `op`.
1290    fn select(self, op: UndoOp) -> Self::Select;
1291}
1292
1293/// Selects actions to undo or redo.
1294pub trait UndoSelect {
1295    /// Called for each undo or redo action from the last item in the stack and back.
1296    ///
1297    /// The `timestamp` is the moment the item was pushed in the undo stack, if this
1298    /// function is called for [`UndoOp::Redo`] it will not be more recent than the next action.
1299    fn include(&mut self, timestamp: DInstant) -> bool;
1300}
1301impl crate::private::Sealed for u32 {}
1302impl UndoSelector for u32 {
1303    type Select = u32;
1304
1305    fn select(self, op: UndoOp) -> Self::Select {
1306        let _ = op;
1307        self
1308    }
1309}
1310impl UndoSelect for u32 {
1311    fn include(&mut self, _: DInstant) -> bool {
1312        let i = *self > 0;
1313        if i {
1314            *self -= 1;
1315        }
1316        i
1317    }
1318}
1319impl crate::private::Sealed for Duration {}
1320impl UndoSelector for Duration {
1321    type Select = UndoSelectInterval;
1322
1323    fn select(self, op: UndoOp) -> Self::Select {
1324        UndoSelectInterval {
1325            prev: None,
1326            interval: self,
1327            op,
1328        }
1329    }
1330}
1331#[doc(hidden)]
1332pub struct UndoSelectInterval {
1333    prev: Option<DInstant>,
1334    interval: Duration,
1335    op: UndoOp,
1336}
1337impl UndoSelect for UndoSelectInterval {
1338    fn include(&mut self, timestamp: DInstant) -> bool {
1339        if let Some(prev) = &mut self.prev {
1340            let (older, newer) = match self.op {
1341                UndoOp::Undo => (timestamp, *prev),
1342                UndoOp::Redo => (*prev, timestamp),
1343            };
1344            if newer.saturating_duration_since(older) <= self.interval {
1345                *prev = timestamp;
1346                true
1347            } else {
1348                false
1349            }
1350        } else {
1351            self.prev = Some(timestamp);
1352            true
1353        }
1354    }
1355}
1356impl crate::private::Sealed for DInstant {}
1357impl UndoSelector for DInstant {
1358    type Select = UndoSelectLtEq;
1359
1360    fn select(self, op: UndoOp) -> Self::Select {
1361        UndoSelectLtEq { instant: self, op }
1362    }
1363}
1364#[doc(hidden)]
1365pub struct UndoSelectLtEq {
1366    instant: DInstant,
1367    op: UndoOp,
1368}
1369impl UndoSelect for UndoSelectLtEq {
1370    fn include(&mut self, timestamp: DInstant) -> bool {
1371        match self.op {
1372            UndoOp::Undo => timestamp >= self.instant,
1373            UndoOp::Redo => timestamp <= self.instant,
1374        }
1375    }
1376}
1377
1378#[cfg(test)]
1379mod tests {
1380    use zng_app::APP;
1381
1382    use super::*;
1383
1384    #[test]
1385    fn register() {
1386        let _a = APP.minimal();
1387        let data = Arc::new(Mutex::new(vec![1, 2]));
1388
1389        UNDO.register(PushAction {
1390            data: data.clone(),
1391            item: 1,
1392        });
1393        UNDO.register(PushAction {
1394            data: data.clone(),
1395            item: 2,
1396        });
1397        assert_eq!(&[1, 2], &data.lock()[..]);
1398
1399        UNDO.undo_select(1);
1400        assert_eq!(&[1], &data.lock()[..]);
1401        UNDO.undo_select(1);
1402        assert_eq!(&[] as &[u8], &data.lock()[..]);
1403
1404        UNDO.redo_select(1);
1405        assert_eq!(&[1], &data.lock()[..]);
1406        UNDO.redo_select(1);
1407        assert_eq!(&[1, 2], &data.lock()[..]);
1408    }
1409
1410    fn push_1_2(data: &Arc<Mutex<Vec<u8>>>) {
1411        UNDO.run_op(
1412            "push 1",
1413            clmv!(data, |op| match op {
1414                UndoOp::Undo => assert_eq!(data.lock().pop(), Some(1)),
1415                UndoOp::Redo => data.lock().push(1),
1416            }),
1417        );
1418        UNDO.run_op(
1419            "push 2",
1420            clmv!(data, |op| match op {
1421                UndoOp::Undo => assert_eq!(data.lock().pop(), Some(2)),
1422                UndoOp::Redo => data.lock().push(2),
1423            }),
1424        );
1425    }
1426
1427    #[test]
1428    fn run_op() {
1429        let _a = APP.minimal();
1430        let data = Arc::new(Mutex::new(vec![]));
1431
1432        push_1_2(&data);
1433        assert_eq!(&[1, 2], &data.lock()[..]);
1434
1435        UNDO.undo_select(1);
1436        assert_eq!(&[1], &data.lock()[..]);
1437        UNDO.undo_select(1);
1438        assert_eq!(&[] as &[u8], &data.lock()[..]);
1439
1440        UNDO.redo_select(1);
1441        assert_eq!(&[1], &data.lock()[..]);
1442        UNDO.redo_select(1);
1443        assert_eq!(&[1, 2], &data.lock()[..]);
1444    }
1445
1446    #[test]
1447    fn transaction_undo() {
1448        let _a = APP.minimal();
1449        let data = Arc::new(Mutex::new(vec![]));
1450
1451        let t = UNDO.transaction(|| {
1452            push_1_2(&data);
1453        });
1454
1455        assert_eq!(&[1, 2], &data.lock()[..]);
1456        UNDO.undo_select(1);
1457        assert_eq!(&[1, 2], &data.lock()[..]);
1458
1459        t.undo();
1460        assert_eq!(&[] as &[u8], &data.lock()[..]);
1461    }
1462
1463    #[test]
1464    fn transaction_commit() {
1465        let _a = APP.minimal();
1466        let data = Arc::new(Mutex::new(vec![]));
1467
1468        let t = UNDO.transaction(|| {
1469            push_1_2(&data);
1470        });
1471
1472        assert_eq!(&[1, 2], &data.lock()[..]);
1473        UNDO.undo_select(1);
1474        assert_eq!(&[1, 2], &data.lock()[..]);
1475
1476        t.commit();
1477
1478        UNDO.undo_select(1);
1479        assert_eq!(&[1], &data.lock()[..]);
1480        UNDO.undo_select(1);
1481        assert_eq!(&[] as &[u8], &data.lock()[..]);
1482
1483        UNDO.redo_select(1);
1484        assert_eq!(&[1], &data.lock()[..]);
1485        UNDO.redo_select(1);
1486        assert_eq!(&[1, 2], &data.lock()[..]);
1487    }
1488
1489    #[test]
1490    fn transaction_group() {
1491        let _a = APP.minimal();
1492        let data = Arc::new(Mutex::new(vec![]));
1493
1494        let t = UNDO.transaction(|| {
1495            push_1_2(&data);
1496        });
1497
1498        assert_eq!(&[1, 2], &data.lock()[..]);
1499        UNDO.undo_select(1);
1500        assert_eq!(&[1, 2], &data.lock()[..]);
1501
1502        t.commit_group("push 1, 2");
1503
1504        UNDO.undo_select(1);
1505        assert_eq!(&[] as &[u8], &data.lock()[..]);
1506
1507        UNDO.redo_select(1);
1508        assert_eq!(&[1, 2], &data.lock()[..]);
1509    }
1510
1511    fn push_1_sleep_2(data: &Arc<Mutex<Vec<u8>>>) {
1512        UNDO.run_op(
1513            "push 1",
1514            clmv!(data, |op| match op {
1515                UndoOp::Undo => assert_eq!(data.lock().pop(), Some(1)),
1516                UndoOp::Redo => data.lock().push(1),
1517            }),
1518        );
1519        std::thread::sleep(Duration::from_millis(100));
1520        UNDO.run_op(
1521            "push 2",
1522            clmv!(data, |op| match op {
1523                UndoOp::Undo => assert_eq!(data.lock().pop(), Some(2)),
1524                UndoOp::Redo => data.lock().push(2),
1525            }),
1526        );
1527    }
1528
1529    #[test]
1530    fn undo_redo_t_zero() {
1531        let _a = APP.minimal();
1532        let data = Arc::new(Mutex::new(vec![]));
1533
1534        push_1_sleep_2(&data);
1535        assert_eq!(&[1, 2], &data.lock()[..]);
1536
1537        UNDO.undo_select(Duration::ZERO);
1538        assert_eq!(&[1], &data.lock()[..]);
1539        UNDO.undo_select(Duration::ZERO);
1540        assert_eq!(&[] as &[u8], &data.lock()[..]);
1541
1542        UNDO.redo_select(Duration::ZERO);
1543        assert_eq!(&[1], &data.lock()[..]);
1544        UNDO.redo_select(Duration::ZERO);
1545        assert_eq!(&[1, 2], &data.lock()[..]);
1546    }
1547
1548    #[test]
1549    fn undo_redo_t_max() {
1550        undo_redo_t_large(Duration::MAX);
1551    }
1552
1553    #[test]
1554    fn undo_redo_t_10s() {
1555        undo_redo_t_large(Duration::from_secs(10));
1556    }
1557
1558    fn undo_redo_t_large(t: Duration) {
1559        let _a = APP.minimal();
1560        let data = Arc::new(Mutex::new(vec![]));
1561
1562        push_1_sleep_2(&data);
1563        assert_eq!(&[1, 2], &data.lock()[..]);
1564
1565        UNDO.undo_select(t);
1566        assert_eq!(&[] as &[u8], &data.lock()[..]);
1567
1568        UNDO.redo_select(t);
1569        assert_eq!(&[1, 2], &data.lock()[..]);
1570    }
1571
1572    #[test]
1573    fn watch_var() {
1574        let mut app = APP.minimal().run_headless(false);
1575
1576        let test_var = var(0);
1577        UNDO.watch_var("set test var", test_var.clone()).perm();
1578
1579        test_var.set(10);
1580        app.update(false).assert_wait();
1581
1582        test_var.set(20);
1583        app.update(false).assert_wait();
1584
1585        assert_eq!(20, test_var.get());
1586
1587        UNDO.undo_select(1);
1588        app.update(false).assert_wait();
1589        assert_eq!(10, test_var.get());
1590
1591        UNDO.undo_select(1);
1592        app.update(false).assert_wait();
1593        assert_eq!(0, test_var.get());
1594
1595        UNDO.redo_select(1);
1596        app.update(false).assert_wait();
1597        assert_eq!(10, test_var.get());
1598
1599        UNDO.redo_select(1);
1600        app.update(false).assert_wait();
1601        assert_eq!(20, test_var.get());
1602    }
1603
1604    struct PushAction {
1605        data: Arc<Mutex<Vec<u8>>>,
1606        item: u8,
1607    }
1608    impl UndoAction for PushAction {
1609        fn undo(self: Box<Self>) -> Box<dyn RedoAction> {
1610            assert_eq!(self.data.lock().pop(), Some(self.item));
1611            self
1612        }
1613
1614        fn info(&mut self) -> Arc<dyn UndoInfo> {
1615            Arc::new("push")
1616        }
1617
1618        fn as_any(&mut self) -> &mut dyn Any {
1619            self
1620        }
1621
1622        fn merge(self: Box<Self>, args: UndoActionMergeArgs) -> Result<Box<dyn UndoAction>, (Box<dyn UndoAction>, Box<dyn UndoAction>)> {
1623            Err((self, args.next))
1624        }
1625    }
1626    impl RedoAction for PushAction {
1627        fn redo(self: Box<Self>) -> Box<dyn UndoAction> {
1628            self.data.lock().push(self.item);
1629            self
1630        }
1631
1632        fn info(&mut self) -> Arc<dyn UndoInfo> {
1633            Arc::new("push")
1634        }
1635    }
1636}