using System; namespace OpenNest.Undo { /// /// Records one undoable edit. Call once the nest is (about to be) /// changed and when the edit succeeded; disposing then commits one /// history entry. Disposing without (an exception, a cancellation) /// rolls a changed nest back to the last recorded state. A scope opened while another is /// open merges into the outermost one: its changes join that entry, and leaving it /// incomplete rolls back the whole entry. A scope with nothing marked changed records and /// restores nothing. /// public sealed class ChangeScope : IDisposable { private readonly ChangeScope root; private readonly Action end; private bool completed; private bool disposed; private bool changed; private bool failed; private ChangeScope(string label, object coalesceKey, ChangeScope root, Action end) { Label = label; CoalesceKey = coalesceKey; this.root = root; this.end = end; } /// A scope with no history behind it, for views that are not a nest document. public static ChangeScope Detached() => new(null, null, null, null); /// An outermost scope; runs once when it is disposed. internal static ChangeScope Root(string label, object coalesceKey, Action end) => new(label, coalesceKey, null, end); /// A scope inside ; it merges into the outermost one. internal static ChangeScope Nested(ChangeScope outer) => new(outer.Label, null, outer.Outermost, null); public string Label { get; } /// Repeated edits with an equal key shortly after each other undo as one. public object CoalesceKey { get; } internal ChangeScope Outermost => root ?? this; /// True once this scope or any scope merged into it marked the nest changed. public bool IsChanged => Outermost.changed; /// True when the outermost scope completed and no merged scope was left incomplete. internal bool Succeeded => completed && !failed; public void MarkChanged() => Outermost.changed = true; public void Complete() => completed = true; public void Dispose() { if (disposed) return; disposed = true; if (root != null) { if (!completed) root.failed = true; return; } end?.Invoke(this); } } }