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