zng_ext_undo/
lib.rs

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