add_ed/
history.rs

1//! Module for history snapshotting and management.
2
3use crate::{EdError, Result};
4use std::fmt::Debug;
5
6/// A special type of Clone for [`History`]
7///
8/// Needed because [`History`] requires a Clone that re-uses as much memory as
9/// possible, while normal Clone is usually expected to create unrelated copies
10/// of data.
11pub trait Snapshot{
12  /// Create a memory efficient copy of self
13  ///
14  /// Beware, mutation of the created copy (if even possible) may modify the
15  /// original.
16  fn create_snapshot(&self) -> Self;
17}
18
19/// A history abstraction over generic objects used by add-ed.
20///
21/// Handles snapshotting and moving over the history of snapshots. Currently
22/// uses a revert style of undo inspired by
23/// [this reasoning.](https://github.com/zaboople/klonk/blob/master/TheGURQ.md)
24///
25/// Automatically manages snapshot creation upon mutable access to the current
26/// point in history. Further allows pausing snapshot creation via
27/// [`History.dont_snapshot`] as well as manual snapshot creation via
28/// [`History.snapshot`] (for use during script/macro execution, to make each
29/// snapshot correspond to a user action).
30#[derive(Clone, Debug)]
31pub struct History<T> where
32  T: Default + Debug + Snapshot + PartialEq,
33{
34  snapshots: Vec<(String, T)>,
35  viewed_i: usize,
36  saved_i: Option<usize>,
37  /// If true all calls to [`History::snapshot`] are ignored (including the
38  /// automatic call upon running `.current_mut()`).
39  ///
40  /// Intended for macro execution, when it would be confusing to create
41  /// multiple snapshots for what the user sees as a single action.
42  ///
43  /// (If a point in history is viewed a snapshot reverting to that point in
44  /// history will be created before mutable access no matter if this variable
45  /// is set to true.)
46  pub dont_snapshot: bool,
47}
48impl<T> Default for History<T> where
49  T: Default + Debug + Snapshot + PartialEq,
50{
51  fn default() -> Self { Self::new() }
52}
53impl <T> History<T> where
54  T: Default + Debug + Snapshot + PartialEq,
55{
56  /// Create new [`History`] instance
57  ///
58  /// - Only an empty present state exists.
59  /// - Considered saved at initial empty state.
60  pub fn new() -> Self {
61    Self{
62      snapshots: vec![("Before reading in a file (empty)".to_owned(), T::default())],
63      viewed_i: 0,
64      saved_i: Some(0),
65      dont_snapshot: false,
66    }
67  }
68
69  /// Get if the buffer is saved
70  ///
71  /// It aims to be true when the viewed buffer matches the data last saved.
72  /// If it is uncertain or difficult to track it will return false.
73  pub fn saved(&self) -> bool {
74    self.saved_i == Some(self.viewed_i)
75  }
76  /// Mark the currently viewed buffer state as saved
77  ///
78  /// If `dont_snapshot` is set this instead behaves as
79  /// `set_unsaved()`, as we cannot be sure a snapshot will exist
80  /// corresponding to the state in which the buffer was saved.
81  pub fn set_saved(&mut self) {
82    if !self.dont_snapshot {
83      self.saved_i = Some(self.viewed_i);
84    } else {
85      self.saved_i = None;
86    }
87  }
88  /// Declare that no known buffer state is saved
89  ///
90  /// Mainly useful for testing, but may be relevant when knowing that file
91  /// was changed on disk.
92  pub fn set_unsaved(&mut self) {
93    self.saved_i = None;
94  }
95
96  /// Get an immutable view into the currently viewed point in history
97  pub fn current(&self) -> &T {
98    &self.snapshots[self.viewed_i].1
99  }
100  /// Get a mutable state to make new changes
101  ///
102  /// - Takes a string describing what is causing this new snapshot. (Should
103  ///   generally be the full command, if not be as clear as possible.)
104  /// - If currently viewing history, will create a revert snapshot at end of
105  ///   history.
106  /// - Unless self.dont_snapshot, will create a new snapshot tagged with the
107  ///   given cause for modification.
108  /// - Returns mutable access to the snapshot at the end of history.
109  pub fn current_mut(&mut self,
110    modification_cause: String,
111  ) -> &mut T {
112    self.snapshot(modification_cause);
113    &mut self.snapshots[self.viewed_i].1
114  }
115
116  fn internal_create_snapshot(&mut self, label: String) {
117    // Push the current index to end of history with label
118    // (reverts if in history, snapshots if at end of history)
119    self.snapshots.push((label, self.snapshots[self.viewed_i].1.create_snapshot()));
120    // Move to end of history
121    self.viewed_i = self.snapshots.len() - 1;
122  }
123  /// Manually add a snapshot
124  ///
125  /// Takes a String as an argument that should describe what causes the
126  /// change seen in the created snapshot relative to the preceding snapshot.
127  ///
128  /// The only case this should be needed is before setting `dont_snapshot` for
129  /// a script execution. If `dont_snapshot` isn't set snapshots are created
130  /// automatically whenever [`Self.current_mut`] is executed.
131  pub fn snapshot(&mut self,
132    modification_cause: String,
133  ) {
134    // If we are in the past, create a revert snapshot
135    // This is needed even if snapshots are disabled, to not change history
136    if self.viewed_i < self.snapshots.len() - 1 {
137      self.internal_create_snapshot(format!(
138        "u{}",
139        self.snapshots.len().saturating_sub(self.viewed_i + 1),
140      ));
141    }
142    // If snapshots aren't disabled, create one
143    if !self.dont_snapshot {
144      self.internal_create_snapshot(modification_cause);
145    }
146  }
147
148  /// Checks if the last two snapshots in history are identical. If yes deletes
149  /// one of them.
150  ///
151  /// Intended for use by macros and scripts, as they have to add a snapshot
152  /// even for non-mutating scripts since they don't know if a script will
153  /// modify the buffer. By running this after macro execution the snapshot will
154  /// be deleted if extraneous and left if relevant.
155  pub fn dedup_present(&mut self) {
156    let mut last_2_iter = self.snapshots.iter().rev().take(2);
157    if last_2_iter.next().map(|x| &x.1) == last_2_iter.next().map(|x| &x.1) {
158      self.snapshots.pop();
159      self.viewed_i = self.snapshots.len() - 1;
160    }
161  }
162
163  /// Accessor to view the full list of snapshots
164  ///
165  /// - Entries are in order of creation, the first operation is first in the
166  /// list.
167  /// - The string beside the snapshot describes what caused the state in the
168  ///   snapshot (relative to the preceeding snapshot).
169  pub fn snapshots(&self) -> &Vec<(String, T)> {
170    &self.snapshots
171  }
172  /// Shorthand for `.snapshots().len()`
173  pub fn len(&self) -> usize {
174    self.snapshots.len()
175  }
176  /// Getter for what index was last saved
177  ///
178  /// Returns None if no index is believed to be saved.
179  ///
180  /// Intended to be used to enrich when listing snapshots by marking the one
181  /// considered saved.
182  pub fn saved_i(&self) -> Option<usize> {
183    self.saved_i
184  }
185  /// Getter for currently viewed snapshot index
186  pub fn viewed_i(&self) -> usize {
187    self.viewed_i
188  }
189  /// Setter for currently viewed snapshot index
190  ///
191  /// Returns the modification cause for the now viewed index.
192  ///
193  /// Will return error if given index doesn't hold a snapshot (aka. is too
194  /// big).
195  pub fn set_viewed_i(&mut self, new_i: usize) -> Result<&str> {
196    if new_i < self.len() {
197      self.viewed_i = new_i;
198      Ok(&self.snapshots[self.viewed_i].0)
199    }
200    else {
201      Err(EdError::UndoIndexTooBig{
202        index: new_i,
203        history_len: self.len(),
204        relative_redo_limit: self.len() - self.viewed_i - 1,
205      })
206    }
207  }
208}