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};
17use parking_lot::Mutex;
18
19use super::{Point, Text};
20use crate::{add_shifts, merging_range_by_guess_and_lazy_shift};
21
22/// The history of edits, contains all moments
23#[derive(Default, Debug)]
24pub struct History {
25    moments: Vec<Moment>,
26    cur_moment: usize,
27    new_changes: Option<(Vec<Change>, (usize, [i32; 3]))>,
28    /// Used to update ranges on the File
29    unproc_changes: Mutex<Option<(Vec<Change>, (usize, [i32; 3]))>>,
30    unproc_moments: Mutex<Vec<Moment>>,
31}
32
33impl History {
34    /// Creates a new [`History`]
35    pub fn new() -> Self {
36        Self::default()
37    }
38
39    /// Adds a [`Change`] to the [`History`]
40    pub fn apply_change(&mut self, guess_i: Option<usize>, change: Change) -> usize {
41        let (changes, shift_state) = self.unproc_changes.get_mut().get_or_insert_default();
42        add_change(changes, guess_i, change.clone(), shift_state);
43
44        let (changes, shift_state) = self.new_changes.get_or_insert_default();
45        add_change(changes, guess_i, change, shift_state)
46    }
47
48    /// Declares that the current moment is complete and starts a
49    /// new one
50    pub fn new_moment(&mut self) {
51        let Some((mut new_changes, (sh_from, shift))) = self.new_changes.take() else {
52            return;
53        };
54        finish_shifting(&mut new_changes, sh_from, shift);
55
56        if let Some((mut unproc_changes, (sh_from, shift))) = self.unproc_changes.get_mut().take() {
57            finish_shifting(&mut unproc_changes, sh_from, shift);
58            self.unproc_moments.get_mut().push(Moment {
59                changes: Box::leak(Box::from(unproc_changes)),
60                is_rev: false,
61            });
62        }
63
64        self.moments.truncate(self.cur_moment);
65
66        self.moments.push(Moment {
67            changes: Box::leak(Box::from(new_changes)),
68            is_rev: false,
69        });
70        self.cur_moment += 1;
71    }
72
73    /// Redoes the next [`Moment`], returning its [`Change`]s
74    ///
75    /// Applying these [`Change`]s in the order that they're given
76    /// will result in a correct redoing.
77    pub fn move_forward(&mut self) -> Option<Moment> {
78        self.new_moment();
79        if self.cur_moment == self.moments.len() {
80            None
81        } else {
82            self.cur_moment += 1;
83
84            let moment = self.moments[self.cur_moment - 1];
85            self.unproc_moments.get_mut().push(moment);
86
87            Some(moment)
88        }
89    }
90
91    /// Undoes a [`Moment`], returning its reversed [`Change`]s
92    ///
93    /// These [`Change`]s will already be shifted corectly, such that
94    /// applying them in sequential order, without further
95    /// modifications, will result in a correct undoing.
96    pub fn move_backwards(&mut self) -> Option<Moment> {
97        self.new_moment();
98        if self.cur_moment == 0 {
99            None
100        } else {
101            self.cur_moment -= 1;
102
103            let mut moment = self.moments[self.cur_moment];
104            moment.is_rev = true;
105            self.unproc_moments.get_mut().push(moment);
106
107            Some(moment)
108        }
109    }
110
111	/// The list of moments that have yet to be processed
112    pub(crate) fn unprocessed_moments(&self) -> Vec<Moment> {
113        let fresh_moment =
114            self.unproc_changes
115                .lock()
116                .take()
117                .map(|(mut changes, (sh_from, shift))| {
118                    finish_shifting(&mut changes, sh_from, shift);
119
120                    Moment {
121                        changes: Box::leak(Box::from(changes)),
122                        is_rev: false,
123                    }
124                });
125
126        let mut unproc_moments = self.unproc_moments.lock();
127
128        unproc_moments.extend(fresh_moment);
129
130        std::mem::take(&mut unproc_moments)
131    }
132}
133
134impl Encode for History {
135    fn encode<E: bincode::enc::Encoder>(
136        &self,
137        encoder: &mut E,
138    ) -> Result<(), bincode::error::EncodeError> {
139        Encode::encode(&self.moments, encoder)?;
140        Encode::encode(&self.cur_moment, encoder)?;
141        Encode::encode(&self.new_changes, encoder)?;
142        Encode::encode(&*self.unproc_changes.lock(), encoder)?;
143        Encode::encode(&*self.unproc_moments.lock(), encoder)?;
144        Ok(())
145    }
146}
147
148impl<Context> Decode<Context> for History {
149    fn decode<D: bincode::de::Decoder<Context = Context>>(
150        decoder: &mut D,
151    ) -> Result<Self, bincode::error::DecodeError> {
152        Ok(History {
153            moments: Decode::decode(decoder)?,
154            cur_moment: Decode::decode(decoder)?,
155            new_changes: Decode::decode(decoder)?,
156            unproc_changes: Mutex::new(Decode::decode(decoder)?),
157            unproc_moments: Mutex::new(Decode::decode(decoder)?),
158        })
159    }
160}
161
162impl Clone for History {
163    fn clone(&self) -> Self {
164        Self {
165            moments: self.moments.clone(),
166            cur_moment: self.cur_moment,
167            new_changes: self.new_changes.clone(),
168            unproc_changes: Mutex::new(self.unproc_changes.lock().clone()),
169            unproc_moments: Mutex::new(self.unproc_moments.lock().clone()),
170        }
171    }
172}
173
174/// A moment in history, which may contain changes, or may just
175/// contain selections
176///
177/// It also contains information about how to print the file, so that
178/// going back in time is less jarring.
179#[derive(Clone, Copy, Default, Debug, Encode)]
180pub struct Moment {
181    changes: &'static [Change],
182    is_rev: bool,
183}
184
185impl Moment {
186    /// An [`Iterator`] over the [`Change`]s in this [`Moment`]
187    ///
188    /// These may represent forward or backwards [`Change`]s, forward
189    /// for newly introduced [`Change`]s and backwards when undoing.
190    pub fn changes(&self) -> impl ExactSizeIterator<Item = Change<&str>> + '_ {
191        let mut shift = [0; 3];
192        self.changes.iter().map(move |change| {
193            if self.is_rev {
194                let mut change = change.as_ref().reverse();
195                change.shift_by(shift);
196
197                shift = add_shifts(shift, change.shift());
198
199                change
200            } else {
201                change.as_ref()
202            }
203        })
204    }
205
206    /// Returns the number of [`Change`]s in this [`Moment`]
207    ///
208    /// Can be `0`, since, in the case of [`Parser`]s, a call for
209    /// [`parse`] is always sent, in order to update [`File`]s
210    /// every time they are printed.
211    ///
212    /// [`Parser`]: crate::file::Parser
213    /// [`parse`]: crate::file::Parser::parse
214    /// [`File`]: crate::file::File
215    pub fn len(&self) -> usize {
216        self.changes.len()
217    }
218
219    /// Wether there are any [`Change`]s in this [`Moment`]
220    ///
221    /// This can happen when creating a [`Moment::default`].
222    #[must_use]
223    pub fn is_empty(&self) -> bool {
224        self.len() == 0
225    }
226}
227
228impl<Context> Decode<Context> for Moment {
229    fn decode<D: bincode::de::Decoder<Context = Context>>(
230        decoder: &mut D,
231    ) -> Result<Self, bincode::error::DecodeError> {
232        Ok(Moment {
233            changes: Vec::decode(decoder)?.leak(),
234            is_rev: Decode::decode(decoder)?,
235        })
236    }
237}
238
239/// First try to merge this change with as many changes as
240/// possible, then add it in
241fn add_change(
242    changes: &mut Vec<Change>,
243    guess_i: Option<usize>,
244    mut change: Change,
245    shift_state: &mut (usize, [i32; 3]),
246) -> usize {
247    let (sh_from, shift) = std::mem::take(shift_state);
248    let new_shift = change.shift();
249
250    // The range of changes that will be drained
251    let c_range = merging_range_by_guess_and_lazy_shift(
252        (changes, changes.len()),
253        (guess_i.unwrap_or(0), [change.start(), change.taken_end()]),
254        (sh_from, shift, [0; 3], Point::shift_by),
255        (Change::start, Change::added_end),
256    );
257
258    // If sh_from < c_range.end, I need to shift the changes between the
259    // two, so that they match the shifting of the changes before sh_from
260    if sh_from < c_range.end && shift != [0; 3] {
261        for change in &mut changes[sh_from..c_range.end] {
262            change.shift_by(shift);
263        }
264    // If sh_from > c_range.end, There are now three shifted
265    // states among ranges: The ones before c_range.start, between
266    // c_range.end and sh_from, and after c_range.end.
267    // I will update the second group so that it is shifted by
268    // shift + change.shift(), that way, it will be shifted like
269    // the first group.
270    } else if sh_from > c_range.end && new_shift != [0; 3] {
271        let shift = change.shift();
272        for change in &mut changes[c_range.end..sh_from] {
273            change.shift_by(shift);
274        }
275    }
276
277    for c in changes.drain(c_range.clone()).rev() {
278        change.try_merge(c);
279    }
280
281    let changes_taken = c_range.clone().count();
282    let new_sh_from = if !(change.added_str() == "" && change.taken_str() == "") {
283        changes.insert(c_range.start, change);
284        sh_from.saturating_sub(changes_taken).max(c_range.start) + 1
285    } else {
286        sh_from.saturating_sub(changes_taken).max(c_range.start)
287    };
288    // If there are no more Changes after this, don't set the shift_state.
289    if new_sh_from < changes.len() {
290        let shift = add_shifts(shift, new_shift);
291        *shift_state = (new_sh_from, shift);
292    }
293
294    c_range.start
295}
296
297/// A change in a file, with a start, taken text, and added text
298#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)]
299pub struct Change<S = String> {
300    start: Point,
301    added: S,
302    taken: S,
303    added_end: Point,
304    taken_end: Point,
305}
306
307impl Change {
308    /// Returns a new [Change].
309    pub fn new(edit: impl ToString, [p0, p1]: [Point; 2], text: &Text) -> Self {
310        let added = {
311            let edit = edit.to_string();
312            // A '\n' must be kept at the end, no matter what.
313            if p1 == text.len() && !edit.ends_with('\n') {
314                edit + "\n"
315            } else {
316                edit
317            }
318        };
319
320        let taken = text.strs(p0..p1).unwrap().collect();
321        let added_end = p0 + Point::len_of(&added);
322        Change {
323            start: p0,
324            added,
325            taken,
326            added_end,
327            taken_end: p1,
328        }
329    }
330
331    /// Returns a copyable [`Change`]
332    pub fn as_ref(&self) -> Change<&str> {
333        Change {
334            start: self.start,
335            added: &self.added,
336            taken: &self.taken,
337            added_end: self.added_end,
338            taken_end: self.taken_end,
339        }
340    }
341
342    /// In this function, it is assumed that `self` happened
343    /// _after_ `newer`
344    ///
345    /// If the merger fails, the older [`Change`] will be returned;
346    pub fn try_merge(&mut self, mut older: Self) {
347        if has_start_of(older.added_range(), self.taken_range()) {
348            let fixed_end = older.added_end().min(self.taken_end());
349
350            let start = self.start - older.start;
351            let end = fixed_end - older.start;
352            let range = start.byte()..end.byte();
353            older.added.replace_range(range, &self.added);
354
355            let range = (fixed_end.byte() - self.start.byte())..;
356            older.taken.push_str(&self.taken[range]);
357
358            *self = older;
359        } else if has_start_of(self.taken_range(), older.added_range()) {
360            let fixed_end = self.taken_end().min(older.added_end());
361
362            let start = older.start - self.start;
363            let end = fixed_end - self.start;
364            let range = start.byte()..end.byte();
365            self.taken.replace_range(range, &older.taken);
366
367            let range = (fixed_end.byte() - older.start.byte())..;
368
369            self.added.push_str(&older.added[range]);
370        } else {
371            unreachable!("Changes chosen that don't interact");
372        }
373        self.added_end = self.start + Point::len_of(&self.added);
374        self.taken_end = self.start + Point::len_of(&self.taken);
375    }
376}
377
378impl<'a> Change<&'a str> {
379    /// Returns a new copyable [`Change`] from an insertion.
380    pub fn str_insert(added_str: &'a str, start: Point) -> Self {
381        Self {
382            start,
383            added: added_str,
384            taken: "",
385            added_end: start + Point::len_of(added_str),
386            taken_end: start,
387        }
388    }
389
390    pub(super) fn remove_last_nl(len: Point) -> Self {
391        Self {
392            start: len.rev('\n'),
393            added: "",
394            taken: "\n",
395            added_end: len.rev('\n'),
396            taken_end: len,
397        }
398    }
399}
400
401impl<S: std::borrow::Borrow<str>> Change<S> {
402    /// Returns a reversed version of this [`Change`]
403    pub fn reverse(self) -> Change<S> {
404        Change {
405            start: self.start,
406            added: self.taken,
407            taken: self.added,
408            added_end: self.taken_end,
409            taken_end: self.added_end,
410        }
411    }
412
413    /// The [`Point`] at the start of the change
414    pub fn start(&self) -> Point {
415        self.start
416    }
417
418    /// Shifts the [`Change`] by a "signed point"
419    pub(crate) fn shift_by(&mut self, shift: [i32; 3]) {
420        self.start = self.start.shift_by(shift);
421        self.added_end = self.added_end.shift_by(shift);
422        self.taken_end = self.taken_end.shift_by(shift);
423    }
424
425    /// Returns the end of the [`Change`], before it was applied
426    pub fn taken_end(&self) -> Point {
427        self.taken_end
428    }
429
430    /// Returns the end of the [`Change`], after it was applied
431    pub fn added_end(&self) -> Point {
432        self.added_end
433    }
434
435    /// Returns the taken [`Range`]
436    pub fn taken_range(&self) -> Range<Point> {
437        self.start..self.taken_end()
438    }
439
440    /// Returns the added [`Range`]
441    pub fn added_range(&self) -> Range<Point> {
442        self.start..self.added_end()
443    }
444
445    /// The text that was taken on this [`Change`]
446    pub fn added_str(&self) -> &str {
447        self.added.borrow()
448    }
449
450    /// The text that was added by this [`Change`]
451    pub fn taken_str(&self) -> &str {
452        self.taken.borrow()
453    }
454
455    /// The total shift caused by this [`Change`]
456    pub fn shift(&self) -> [i32; 3] {
457        [
458            self.added_end().byte() as i32 - self.taken_end().byte() as i32,
459            self.added_end().char() as i32 - self.taken_end().char() as i32,
460            self.added_end().line() as i32 - self.taken_end().line() as i32,
461        ]
462    }
463}
464
465/// If `lhs` contains the start of `rhs`
466fn has_start_of(lhs: Range<Point>, rhs: Range<Point>) -> bool {
467    lhs.start <= rhs.start && rhs.start <= lhs.end
468}
469
470fn finish_shifting(changes: &mut [Change], sh_from: usize, shift: [i32; 3]) {
471    if shift != [0; 3] {
472        for change in changes[sh_from..].iter_mut() {
473            change.shift_by(shift);
474        }
475    }
476}