duat_core/text/
history.rs

1//! The history for a [`Text`]
2//!
3//! The [`History`] is composed of [`Moment`]s, each having a list of
4//! [`Change`]s. Whenever you [`undo`]/[`redo`], you are
5//! undoing/redoing a whole [`Moment`], with all of its [`Change`]s,
6//! all at once.
7//!
8//! This permits Vim style undoing (one [`Change`] per [`Moment`]) as
9//! well as Kakoune style undoing (multiple [`Change`]s per
10//! [`Moment`]).
11//!
12//! [`undo`]: Text::undo
13//! [`redo`]: Text::redo
14use std::ops::Range;
15
16use bincode::{Decode, Encode};
17
18use super::{Point, Text};
19use crate::{add_shifts, merging_range_by_guess_and_lazy_shift};
20
21/// The history of edits, contains all moments
22#[derive(Default, Debug, Clone, Encode, Decode)]
23pub struct History {
24    moments: Vec<Moment>,
25    cur_moment: usize,
26    new_moment: Option<(Moment, (usize, [i32; 3]))>,
27    /// Used to update ranges on the File
28    unproc_moment: Option<(Moment, (usize, [i32; 3]))>,
29}
30
31impl History {
32    /// Creates a new [`History`]
33    pub fn new() -> Self {
34        Self::default()
35    }
36
37    /// Adds a [`Change`] without moving the ones ahead to comply
38    ///
39    /// # Safety
40    ///
41    /// This function should only be used by the [`EditHelper`], as it
42    /// is expected that, after using it, you will call
43    /// [`shift_range`] in order to keep the [`Change`]s ahead synced
44    /// with the one being added.
45    ///
46    /// [`EditHelper`]: crate::mode::EditHelper
47    pub fn apply_change(&mut self, guess_i: Option<usize>, change: Change<String>) -> usize {
48        let (moment, shift_state) = self.unproc_moment.get_or_insert_default();
49        moment.add_change(guess_i, change.clone(), shift_state);
50
51        let (moment, shift_state) = self.new_moment.get_or_insert_default();
52        moment.add_change(guess_i, change, shift_state)
53    }
54
55    /// Declares that the current moment is complete and starts a
56    /// new one
57    pub fn new_moment(&mut self) {
58        let Some((mut new_moment, (sh_from, shift))) = self.new_moment.take() else {
59            return;
60        };
61        if shift != [0; 3] {
62            for change in new_moment.0[sh_from..].iter_mut() {
63                change.shift_by(shift);
64            }
65        }
66
67        self.moments.truncate(self.cur_moment);
68        self.moments.push(new_moment);
69        self.cur_moment += 1;
70    }
71
72    /// Redoes the next [`Moment`], returning its [`Change`]s
73    ///
74    /// Applying these [`Change`]s in the order that they're given
75    /// will result in a correct redoing.
76    pub fn move_forward(&mut self) -> Option<Vec<Change<&str>>> {
77        self.new_moment();
78        if self.cur_moment == self.moments.len() {
79            None
80        } else {
81            self.cur_moment += 1;
82            Some(
83                self.moments[self.cur_moment - 1]
84                    .0
85                    .iter()
86                    .map(|c| c.as_ref())
87                    .collect(),
88            )
89        }
90    }
91
92    /// Undoes a [`Moment`], returning its reversed [`Change`]s
93    ///
94    /// These [`Change`]s will already be shifted corectly, such that
95    /// applying them in sequential order, without further
96    /// modifications, will result in a correct undoing.
97    pub fn move_backwards(&mut self) -> Option<Vec<Change<&str>>> {
98        self.new_moment();
99        if self.cur_moment == 0 {
100            None
101        } else {
102            self.cur_moment -= 1;
103
104            let mut shift = [0; 3];
105            let iter = self.moments[self.cur_moment].0.iter().map(move |change| {
106                let mut change = change.as_ref();
107                change.shift_by(shift);
108
109                shift = add_shifts(shift, change.reverse().shift());
110
111                change.reverse()
112            });
113            Some(iter.collect())
114        }
115    }
116
117    pub fn unprocessed_changes(&mut self) -> Option<Vec<Change<String>>> {
118        self.unproc_moment
119            .take()
120            .map(|(mut moment, (sh_from, shift))| {
121                if shift != [0; 3] {
122                    for change in moment.0[sh_from..].iter_mut() {
123                        change.shift_by(shift);
124                    }
125                }
126                moment.0
127            })
128    }
129
130    pub fn has_unprocessed_changes(&self) -> bool {
131        self.unproc_moment.as_ref().is_some()
132    }
133}
134
135/// A moment in history, which may contain changes, or may just
136/// contain selections
137///
138/// It also contains information about how to print the file, so that
139/// going back in time is less jarring.
140#[derive(Default, Debug, Clone, Encode, Decode)]
141pub struct Moment(Vec<Change<String>>);
142
143impl Moment {
144    /// First try to merge this change with as many changes as
145    /// possible, then add it in
146    fn add_change(
147        &mut self,
148        guess_i: Option<usize>,
149        mut change: Change<String>,
150        shift_state: &mut (usize, [i32; 3]),
151    ) -> usize {
152        let (sh_from, shift) = std::mem::take(shift_state);
153        let new_shift = change.shift();
154
155        // The range of changes that will be drained
156        let c_range = merging_range_by_guess_and_lazy_shift(
157            (&self.0, self.0.len()),
158            (guess_i.unwrap_or(0), [change.start(), change.taken_end()]),
159            (sh_from, shift, [0; 3], Point::shift_by),
160            (Change::start, Change::added_end),
161        );
162
163        // If sh_from < c_range.end, I need to shift the changes between the
164        // two, so that they match the shifting of the changes before sh_from
165        if sh_from < c_range.end && shift != [0; 3] {
166            for change in &mut self.0[sh_from..c_range.end] {
167                change.shift_by(shift);
168            }
169        // If sh_from > c_range.end, There are now three shifted
170        // states among ranges: The ones before c_range.start, between
171        // c_range.end and sh_from, and after c_range.end.
172        // I will update the second group so that it is shifted by
173        // shift + change.shift(), that way, it will be shifted like
174        // the first group.
175        } else if sh_from > c_range.end && new_shift != [0; 3] {
176            for change in &mut self.0[c_range.end..sh_from] {
177                change.shift_by(shift);
178            }
179        }
180
181        for c in self.0.drain(c_range.clone()).rev() {
182            change.try_merge(c);
183        }
184
185        let changes_taken = c_range.clone().count();
186        let new_sh_from = if !(change.added_text() == "" && change.taken_text() == "") {
187            self.0.insert(c_range.start, change);
188            sh_from.saturating_sub(changes_taken).max(c_range.start) + 1
189        } else {
190            sh_from.saturating_sub(changes_taken).max(c_range.start)
191        };
192        // If there are no more Changes after this, don't set the shift_state.
193        if new_sh_from < self.0.len() {
194            let shift = add_shifts(shift, new_shift);
195            *shift_state = (new_sh_from, shift);
196        }
197
198        c_range.start
199    }
200
201    pub fn changes(&self) -> &[Change<String>] {
202        &self.0
203    }
204}
205
206/// A change in a file, with a start, taken text, and added text
207#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)]
208pub struct Change<S: AsRef<str>> {
209    start: Point,
210    added: S,
211    taken: S,
212    added_end: Point,
213    taken_end: Point,
214}
215
216impl Change<String> {
217    /// Returns a new [Change].
218    pub fn new(edit: impl ToString, [p0, p1]: [Point; 2], text: &Text) -> Self {
219        let added = {
220            let edit = edit.to_string();
221            // A '\n' must be kept at the end, no matter what.
222            if p1 == text.len() && !edit.ends_with('\n') {
223                edit + "\n"
224            } else {
225                edit
226            }
227        };
228
229        let taken = text.strs(p0.byte()..p1.byte()).collect();
230        let added_end = p0 + Point::len_of(&added);
231        Change {
232            start: p0,
233            added,
234            taken,
235            added_end,
236            taken_end: p1,
237        }
238    }
239
240    /// Returns a copyable [`Change`]
241    pub fn as_ref(&self) -> Change<&str> {
242        Change {
243            start: self.start,
244            added: &self.added,
245            taken: &self.taken,
246            added_end: self.added_end,
247            taken_end: self.taken_end,
248        }
249    }
250
251    /// In this function, it is assumed that `self` happened
252    /// _after_ `newer`
253    ///
254    /// If the merger fails, the older [`Change`] will be returned;
255    pub fn try_merge(&mut self, mut older: Self) {
256        if has_start_of(older.added_range(), self.taken_range()) {
257            let fixed_end = older.added_end().min(self.taken_end());
258
259            let start = self.start - older.start;
260            let end = fixed_end - older.start;
261            let range = start.byte()..end.byte();
262            older.added.replace_range(range, &self.added);
263
264            let range = (fixed_end.byte() - self.start.byte())..;
265            older.taken.push_str(&self.taken[range]);
266
267            *self = older;
268        } else if has_start_of(self.taken_range(), older.added_range()) {
269            let fixed_end = self.taken_end().min(older.added_end());
270
271            let start = older.start - self.start;
272            let end = fixed_end - self.start;
273            let range = start.byte()..end.byte();
274            self.taken.replace_range(range, &older.taken);
275
276            let range = (fixed_end.byte() - older.start.byte())..;
277
278            self.added.push_str(&older.added[range]);
279        } else {
280            unreachable!("Changes chosen that don't interact");
281        }
282        self.added_end = self.start + Point::len_of(&self.added);
283        self.taken_end = self.start + Point::len_of(&self.taken);
284    }
285}
286
287impl<'a> Change<&'a str> {
288    /// Returns a new copyable [`Change`] from an insertion.
289    pub fn str_insert(added_text: &'a str, start: Point) -> Self {
290        Self {
291            start,
292            added: added_text,
293            taken: "",
294            added_end: start + Point::len_of(added_text),
295            taken_end: start,
296        }
297    }
298
299    /// This function should only be used with ghost text and builders
300    pub(super) fn remove_nl(p0: Point) -> Self {
301        Change {
302            start: p0,
303            added: "",
304            taken: "\n",
305            added_end: p0,
306            taken_end: p0 + Point::len_of("\n"),
307        }
308    }
309}
310
311impl<S: AsRef<str>> Change<S> {
312    /// Returns a reversed version of this [`Change`]
313    pub fn reverse(self) -> Change<S> {
314        Change {
315            start: self.start,
316            added: self.taken,
317            taken: self.added,
318            added_end: self.taken_end,
319            taken_end: self.added_end,
320        }
321    }
322
323    /// The [`Point`] at the start of the change
324    pub fn start(&self) -> Point {
325        self.start
326    }
327
328    /// Shifts the [`Change`] by a "signed point"
329    pub(crate) fn shift_by(&mut self, shift: [i32; 3]) {
330        self.start = self.start.shift_by(shift);
331        self.added_end = self.added_end.shift_by(shift);
332        self.taken_end = self.taken_end.shift_by(shift);
333    }
334
335    /// Returns the end of the [`Change`], before it was applied
336    pub fn taken_end(&self) -> Point {
337        self.taken_end
338    }
339
340    /// Returns the end of the [`Change`], after it was applied
341    pub fn added_end(&self) -> Point {
342        self.added_end
343    }
344
345    /// Returns the taken [`Range`]
346    pub fn taken_range(&self) -> Range<usize> {
347        self.start.byte()..self.taken_end().byte()
348    }
349
350    /// Returns the added [`Range`]
351    pub fn added_range(&self) -> Range<usize> {
352        self.start.byte()..self.added_end().byte()
353    }
354
355    /// The text that was taken on this [`Change`]
356    pub fn added_text(&self) -> &str {
357        self.added.as_ref()
358    }
359
360    /// The text that was added by this [`Change`]
361    pub fn taken_text(&self) -> &str {
362        self.taken.as_ref()
363    }
364
365    /// The total shift caused by this [`Change`]
366    pub fn shift(&self) -> [i32; 3] {
367        [
368            self.added_end().byte() as i32 - self.taken_end().byte() as i32,
369            self.added_end().char() as i32 - self.taken_end().char() as i32,
370            self.added_end().line() as i32 - self.taken_end().line() as i32,
371        ]
372    }
373}
374
375impl Copy for Change<&str> {}
376
377/// If `lhs` contains the start of `rhs`
378fn has_start_of(lhs: Range<usize>, rhs: Range<usize>) -> bool {
379    lhs.start <= rhs.start && rhs.start <= lhs.end
380}