rat_text/
undo_buffer.rs

1//! Undo functionality.
2
3use crate::range_map::expand_range_by;
4use crate::TextPosition;
5use crate::_private::NonExhaustive;
6use dyn_clone::DynClone;
7use std::fmt::Debug;
8use std::mem;
9use std::ops::Range;
10
11/// Undo buffer.
12///
13/// Keeps up to undo_count operations that can be undone/redone.
14/// Additionally, it can provide a change-log which can be used
15/// to sync other text-widgets.
16///
17pub trait UndoBuffer: DynClone + Debug {
18    /// How many undoes are stored?
19    fn undo_count(&self) -> u32;
20
21    /// How many undoes are stored?
22    fn set_undo_count(&mut self, n: u32);
23
24    /// Begin a sequence of changes that should be undone at once.
25    ///
26    /// begin/end calls can be nested, but only the outer one
27    /// will define the actual scope of the undo.
28    ///
29    /// A call to begin_seq must be matched with a call to end_seq.
30    fn begin_seq(&mut self);
31
32    /// End a sequence of changes that should be undone at once.
33    fn end_seq(&mut self);
34
35    /// Appends a new operation at the current undo-position.
36    ///
37    /// Redoes will be truncated by this call.
38    ///
39    /// This call tries merge InsertChar/RemoveChar operations,
40    /// if they lie next to each other. InsertStr/RemoveStr
41    /// will never be merged.
42    fn append(&mut self, undo: UndoOp);
43
44    /// Appends a new operation but doesn't fill the replay-log.
45    ///
46    /// Used to add to the undo-buffer during replay from another
47    /// text-widget.
48    fn append_from_replay(&mut self, undo: UndoEntry);
49
50    /// Clear the undo and the replay buffer.
51    fn clear(&mut self);
52
53    /// Get the number of possible undo operations.
54    fn open_undo(&self) -> usize;
55
56    /// Get the number of possible redo operations.
57    fn open_redo(&self) -> usize;
58
59    /// Get the list of the next undo operations.
60    fn undo(&mut self) -> Vec<&UndoOp>;
61
62    /// Get the list of the next redo operations.
63    fn redo(&mut self) -> Vec<&UndoOp>;
64
65    /// Enable/disable replay recording.
66    ///
67    /// __Attention__:
68    /// For this to work the widget state must be in a 'cleared' state,
69    /// or you must *create a clone* of the widget-state *immediately* after
70    /// activating the replay-log.
71    ///
72    /// Only then the replay operations obtained by recent_replay()
73    /// will make sense to the clone.
74    ///
75    /// __Info__:
76    /// How you identify the widgets that should receive the replay-log
77    /// and other distribution problems are in the domain of the user
78    /// of this feature.
79    fn enable_replay_log(&mut self, replay: bool);
80
81    /// Is the replay-log active?
82    fn has_replay_log(&self) -> bool;
83
84    /// Get the replay-log to sync with another textarea.
85    /// This empties the replay buffer.
86    fn recent_replay_log(&mut self) -> Vec<UndoEntry>;
87
88    /// Is there undo for setting/removing styles.
89    fn undo_styles_enabled(&self) -> bool;
90}
91
92/// Stores one style change.
93#[derive(Debug, Default, Clone)]
94pub struct StyleChange {
95    pub before: Range<usize>,
96    pub after: Range<usize>,
97    pub style: usize,
98}
99
100/// Stores a text position change.
101#[derive(Debug, Default, Clone)]
102pub struct TextPositionChange {
103    pub before: TextPosition,
104    pub after: TextPosition,
105}
106
107/// Storage for undo.
108#[derive(Debug, Clone)]
109pub struct UndoEntry {
110    pub sequence: u32,
111    pub operation: UndoOp,
112    pub non_exhaustive: NonExhaustive,
113}
114
115/// Storage for undo.
116#[non_exhaustive]
117#[derive(Debug, Clone)]
118pub enum UndoOp {
119    /// Insert a single char/grapheme.
120    ///
121    /// This can contain a longer text, if consecutive InsertChar have
122    /// been merged.
123    InsertChar {
124        /// byte range for the insert.
125        bytes: Range<usize>,
126        /// cursor position change
127        cursor: TextPositionChange,
128        /// anchor position change
129        anchor: TextPositionChange,
130        /// inserted text
131        txt: String,
132    },
133    /// Insert a longer text.
134    InsertStr {
135        /// byte range for the insert.
136        bytes: Range<usize>,
137        /// cursor position change
138        cursor: TextPositionChange,
139        /// anchor position change
140        anchor: TextPositionChange,
141        /// inserted text
142        txt: String,
143    },
144    /// Remove a single char/grapheme range.
145    ///
146    /// This can be a longer range, if consecutive RemoveChar have been
147    /// merged.
148    ///
149    /// styles contains only styles whose range __intersects__ the
150    /// removed range. Styles that lie after the bytes-range will be
151    /// shifted left.
152    RemoveChar {
153        /// byte range for the remove.
154        bytes: Range<usize>,
155        /// cursor position change
156        cursor: TextPositionChange,
157        /// anchor position change
158        anchor: TextPositionChange,
159        /// removed text
160        txt: String,
161        /// removed styles
162        styles: Vec<StyleChange>,
163    },
164    /// Remove longer text range.
165    ///
166    /// styles contains only styles whose range __intersects__ the
167    /// removed range. Styles that lie after the bytes-range will be
168    /// shifted left.
169    RemoveStr {
170        /// byte range for the remove.
171        bytes: Range<usize>,
172        /// cursor position change
173        cursor: TextPositionChange,
174        /// anchor position change
175        anchor: TextPositionChange,
176        /// removed text
177        txt: String,
178        /// removed styles
179        styles: Vec<StyleChange>,
180    },
181
182    /// Cursor/anchor changed.
183    ///
184    /// This will be merged with a cursor-change immediately before.
185    /// And it will merge with both removes and inserts.
186    Cursor {
187        /// cursor position change
188        cursor: TextPositionChange,
189        /// anchor position change
190        anchor: TextPositionChange,
191    },
192
193    /// Set of styles was replaced.
194    SetStyles {
195        /// old styles
196        styles_before: Vec<(Range<usize>, usize)>,
197        /// new styles
198        styles_after: Vec<(Range<usize>, usize)>,
199    },
200    /// Add one style.
201    AddStyle {
202        /// style range
203        range: Range<usize>,
204        /// style
205        style: usize,
206    },
207    /// Remove one style.
208    RemoveStyle {
209        /// style range
210        range: Range<usize>,
211        /// style
212        style: usize,
213    },
214
215    /// For replay only. Complete content was replaced.
216    SetText {
217        /// New text
218        txt: String,
219    },
220    /// For replay only. Undo one operation.
221    Undo,
222    /// For replay only. Redo one operation.
223    Redo,
224}
225
226/// Standard implementation for undo.
227#[derive(Debug, Clone)]
228pub struct UndoVec {
229    undo_styles: bool,
230    track_replay: bool,
231    undo_count: u32,
232
233    begin: u8,
234    sequence: u32,
235    buf: Vec<UndoEntry>,
236    replay: Vec<UndoEntry>,
237
238    // undo/redo split
239    idx: usize,
240}
241
242impl Default for UndoVec {
243    fn default() -> Self {
244        Self {
245            undo_styles: false,
246            track_replay: false,
247            undo_count: 99,
248            begin: 0,
249            sequence: 0,
250            buf: Vec::default(),
251            replay: Vec::default(),
252            idx: 0,
253        }
254    }
255}
256
257impl UndoVec {
258    /// New undo.
259    pub fn new(undo_count: u32) -> Self {
260        Self {
261            undo_count,
262            ..Default::default()
263        }
264    }
265
266    /// Enable undo for style changes.
267    ///
268    /// Usually not what you want.
269    /// Unless you allow your users to set styles manually.
270    /// If your styling is done by a parser, don't activate this.
271    ///
272    /// Changes to the range of styles and removal of styles
273    /// caused by text edits *will* be undone anyway.
274    ///
275    /// Recording those operations for *replay* will not be affected
276    /// by this setting.
277    pub fn enable_undo_styles(&mut self, undo_styles: bool) {
278        self.undo_styles = undo_styles;
279    }
280
281    /// Undo for styles are enabled.
282    pub fn undo_styles(&self) -> bool {
283        self.undo_styles
284    }
285
286    fn merge_undo(mut last: UndoOp, mut curr: UndoOp) -> (Option<UndoOp>, Option<UndoOp>) {
287        match &mut curr {
288            UndoOp::InsertChar {
289                bytes: curr_bytes,
290                cursor: curr_cursor,
291                anchor: curr_anchor,
292                txt: curr_txt,
293            } => match &mut last {
294                UndoOp::InsertChar {
295                    bytes: last_bytes,
296                    cursor: last_cursor,
297                    anchor: last_anchor,
298                    txt: last_txt,
299                } => {
300                    if last_bytes.end == curr_bytes.start {
301                        let mut last_txt = mem::take(last_txt);
302                        last_txt.push_str(curr_txt);
303                        (
304                            Some(UndoOp::InsertChar {
305                                bytes: last_bytes.start..curr_bytes.end,
306                                cursor: TextPositionChange {
307                                    before: last_cursor.before,
308                                    after: curr_cursor.after,
309                                },
310                                anchor: TextPositionChange {
311                                    before: last_anchor.before,
312                                    after: curr_anchor.after,
313                                },
314                                txt: last_txt,
315                            }),
316                            None,
317                        )
318                    } else {
319                        (Some(last), Some(curr))
320                    }
321                }
322                _ => (Some(last), Some(curr)),
323            },
324            UndoOp::RemoveChar {
325                bytes: curr_bytes,
326                cursor: curr_cursor,
327                anchor: curr_anchor,
328                txt: curr_txt,
329                styles: curr_styles,
330            } => match &mut last {
331                UndoOp::RemoveChar {
332                    bytes: last_bytes,
333                    cursor: last_cursor,
334                    anchor: last_anchor,
335                    txt: last_txt,
336                    styles: last_styles,
337                } => {
338                    if curr_bytes.end == last_bytes.start {
339                        // backspace
340                        let mut txt = mem::take(curr_txt);
341                        txt.push_str(last_txt);
342
343                        // merge into last_styles
344                        let mut styles = mem::take(last_styles);
345                        Self::merge_remove_style(last_bytes.clone(), &mut styles, curr_styles);
346
347                        (
348                            Some(UndoOp::RemoveChar {
349                                bytes: curr_bytes.start..last_bytes.end,
350                                cursor: TextPositionChange {
351                                    before: last_cursor.before,
352                                    after: curr_cursor.after,
353                                },
354                                anchor: TextPositionChange {
355                                    before: last_anchor.before,
356                                    after: curr_anchor.after,
357                                },
358                                txt,
359                                styles,
360                            }),
361                            None,
362                        )
363                    } else if curr_bytes.start == last_bytes.start {
364                        // delete
365                        let mut txt = mem::take(last_txt);
366                        txt.push_str(curr_txt);
367
368                        let curr_byte_len = curr_bytes.end - curr_bytes.start;
369
370                        // merge into last_styles
371                        let mut styles = mem::take(last_styles);
372                        Self::merge_remove_style(last_bytes.clone(), &mut styles, curr_styles);
373
374                        (
375                            Some(UndoOp::RemoveChar {
376                                bytes: last_bytes.start..last_bytes.end + curr_byte_len,
377                                cursor: TextPositionChange {
378                                    before: last_cursor.before,
379                                    after: curr_cursor.after,
380                                },
381                                anchor: TextPositionChange {
382                                    before: last_anchor.before,
383                                    after: curr_anchor.after,
384                                },
385                                txt,
386                                styles,
387                            }),
388                            None,
389                        )
390                    } else {
391                        (Some(last), Some(curr))
392                    }
393                }
394                _ => (Some(last), Some(curr)),
395            },
396
397            UndoOp::Cursor {
398                cursor: curr_cursor,
399                anchor: curr_anchor,
400            } => match &mut last {
401                UndoOp::InsertChar {
402                    bytes: last_bytes,
403                    cursor: last_cursor,
404                    anchor: last_anchor,
405                    txt: last_txt,
406                } => (
407                    Some(UndoOp::InsertChar {
408                        bytes: mem::take(last_bytes),
409                        cursor: TextPositionChange {
410                            before: last_cursor.before,
411                            after: curr_cursor.after,
412                        },
413                        anchor: TextPositionChange {
414                            before: last_anchor.before,
415                            after: curr_anchor.after,
416                        },
417                        txt: mem::take(last_txt),
418                    }),
419                    None,
420                ),
421                UndoOp::InsertStr {
422                    bytes: last_bytes,
423                    cursor: last_cursor,
424                    anchor: last_anchor,
425                    txt: last_txt,
426                } => (
427                    Some(UndoOp::InsertStr {
428                        bytes: mem::take(last_bytes),
429                        cursor: TextPositionChange {
430                            before: last_cursor.before,
431                            after: curr_cursor.after,
432                        },
433                        anchor: TextPositionChange {
434                            before: last_anchor.before,
435                            after: curr_anchor.after,
436                        },
437                        txt: mem::take(last_txt),
438                    }),
439                    None,
440                ),
441                UndoOp::RemoveChar {
442                    bytes: last_bytes,
443                    cursor: last_cursor,
444                    anchor: last_anchor,
445                    txt: last_txt,
446                    styles: last_styles,
447                } => (
448                    Some(UndoOp::RemoveChar {
449                        bytes: mem::take(last_bytes),
450                        cursor: TextPositionChange {
451                            before: last_cursor.before,
452                            after: curr_cursor.after,
453                        },
454                        anchor: TextPositionChange {
455                            before: last_anchor.before,
456                            after: curr_anchor.after,
457                        },
458                        txt: mem::take(last_txt),
459                        styles: mem::take(last_styles),
460                    }),
461                    None,
462                ),
463                UndoOp::RemoveStr {
464                    bytes: last_bytes,
465                    cursor: last_cursor,
466                    anchor: last_anchor,
467                    txt: last_txt,
468                    styles: last_styles,
469                } => (
470                    Some(UndoOp::RemoveChar {
471                        bytes: mem::take(last_bytes),
472                        cursor: TextPositionChange {
473                            before: last_cursor.before,
474                            after: curr_cursor.after,
475                        },
476                        anchor: TextPositionChange {
477                            before: last_anchor.before,
478                            after: curr_anchor.after,
479                        },
480                        txt: mem::take(last_txt),
481                        styles: mem::take(last_styles),
482                    }),
483                    None,
484                ),
485                UndoOp::Cursor {
486                    cursor: last_cursor,
487                    anchor: last_anchor,
488                } => (
489                    Some(UndoOp::Cursor {
490                        cursor: TextPositionChange {
491                            before: last_cursor.before,
492                            after: curr_cursor.after,
493                        },
494                        anchor: TextPositionChange {
495                            before: last_anchor.before,
496                            after: curr_anchor.after,
497                        },
498                    }),
499                    None,
500                ),
501                _ => (Some(last), Some(curr)),
502            },
503            _ => (Some(last), Some(curr)),
504        }
505    }
506
507    /// Merge styles from two deletes.
508    fn merge_remove_style(
509        last_range: Range<usize>,
510        last: &mut Vec<StyleChange>,
511        curr: &mut Vec<StyleChange>,
512    ) {
513        for i in (0..last.len()).rev() {
514            for j in (0..curr.len()).rev() {
515                if last[i].style == curr[j].style {
516                    if last[i].after == curr[j].before {
517                        last[i].after = curr[j].after.clone();
518                        curr.remove(j);
519                    }
520                }
521            }
522        }
523
524        // expand before and add
525        for mut curr in curr.drain(..) {
526            curr.before = expand_range_by(last_range.clone(), curr.before);
527            last.push(curr);
528        }
529    }
530}
531
532impl UndoVec {
533    fn filter(&self, undo: &UndoOp) -> bool {
534        // only useful for tracking
535        if matches!(undo, UndoOp::Undo | UndoOp::Redo | UndoOp::SetText { .. }) {
536            return true;
537        }
538
539        // style changes may/may not be undone
540        if !self.undo_styles {
541            match &undo {
542                UndoOp::SetStyles { .. } | UndoOp::AddStyle { .. } | UndoOp::RemoveStyle { .. } => {
543                    return true;
544                }
545                _ => {}
546            }
547        }
548
549        false
550    }
551
552    fn try_merge(&mut self, undo: UndoOp) -> Option<UndoOp> {
553        if let Some(UndoEntry {
554            sequence,
555            operation: last,
556            ..
557        }) = self.buf.pop()
558        {
559            let (last, undo) = Self::merge_undo(last, undo);
560            // re-add last if it survived merge
561            if let Some(last) = last {
562                self.buf.push(UndoEntry {
563                    sequence,
564                    operation: last,
565                    non_exhaustive: NonExhaustive,
566                });
567            }
568            undo
569        } else {
570            Some(undo)
571        }
572    }
573
574    fn trim_undo(&mut self) {
575        // Dump redo.
576        while self.idx < self.buf.len() {
577            self.buf.pop();
578        }
579
580        // cap undo at capacity.
581        // uses the sequence count instead of the size.
582        let count_uniq = self
583            .buf
584            .iter()
585            .fold((0, 0), |mut f, v| {
586                if v.sequence != f.0 {
587                    f.0 = v.sequence;
588                    f.1 += 1;
589                }
590                f
591            })
592            .1;
593
594        if count_uniq > self.undo_count as usize {
595            // don't drop parts of current sequence at all.
596            if self.buf[0].sequence != self.sequence {
597                let drop_sequence = self.buf[0].sequence;
598                loop {
599                    if self.buf[0].sequence == drop_sequence {
600                        self.buf.remove(0);
601                    } else {
602                        break;
603                    }
604                }
605            }
606        }
607    }
608}
609
610impl UndoBuffer for UndoVec {
611    fn undo_count(&self) -> u32 {
612        self.undo_count
613    }
614
615    fn set_undo_count(&mut self, n: u32) {
616        self.undo_count = n;
617    }
618
619    /// Begin a sequence of changes that should be undone at once.
620    fn begin_seq(&mut self) {
621        self.begin += 1;
622        if self.begin == 1 {
623            self.sequence += 1;
624        }
625    }
626
627    /// End a sequence of changes.
628    /// Unbalanced end_seq calls panic.
629    fn end_seq(&mut self) {
630        self.begin -= 1;
631    }
632
633    fn append(&mut self, undo: UndoOp) {
634        let track_undo = if self.track_replay {
635            Some(undo.clone())
636        } else {
637            None
638        };
639
640        // try merge
641        let add_undo = if let Some(last) = self.buf.last() {
642            // first begin starts a new sequence.
643            // so this shouldn't cross that boundary.
644            if last.sequence == self.sequence {
645                self.try_merge(undo)
646            } else {
647                Some(undo)
648            }
649        } else {
650            Some(undo)
651        };
652
653        // New separate undo.
654        if add_undo.is_some() {
655            // auto begin+end
656            if self.begin == 0 {
657                self.sequence += 1;
658            }
659        }
660
661        // Store in tracking.
662        // Sequence number is new if the merge failed, otherwise the
663        // same as the last. This fact will be used when replaying.
664        if let Some(track_undo) = track_undo {
665            self.replay.push(UndoEntry {
666                sequence: self.sequence,
667                operation: track_undo,
668                non_exhaustive: NonExhaustive,
669            });
670        }
671
672        // Add if not merged.
673        if let Some(add_undo) = add_undo {
674            // Not everything is undo.
675            if self.filter(&add_undo) {
676                return;
677            }
678            // Drop redo and trim undo.
679            self.trim_undo();
680
681            // add new undo if it survived merge
682            self.buf.push(UndoEntry {
683                sequence: self.sequence,
684                operation: add_undo,
685                non_exhaustive: NonExhaustive,
686            });
687
688            self.idx = self.buf.len();
689        }
690    }
691
692    fn append_from_replay(&mut self, undo: UndoEntry) {
693        let UndoEntry {
694            sequence,
695            operation: undo,
696            ..
697        } = undo;
698
699        // try merge
700        let add_undo = if let Some(last) = self.buf.last() {
701            // merges act just like sequences, so this
702            // works out for both.
703            if last.sequence == sequence {
704                self.try_merge(undo)
705            } else {
706                Some(undo)
707            }
708        } else {
709            Some(undo)
710        };
711
712        // sync sequence
713        self.sequence = sequence;
714
715        // Add if not merged.
716        if let Some(add_undo) = add_undo {
717            // Not everything is undo.
718            if self.filter(&add_undo) {
719                return;
720            }
721            // Drop redo and trim undo.
722            self.trim_undo();
723
724            // add new undo if it survived merge
725            self.buf.push(UndoEntry {
726                sequence,
727                operation: add_undo,
728                non_exhaustive: NonExhaustive,
729            });
730
731            self.idx = self.buf.len();
732        }
733    }
734
735    fn clear(&mut self) {
736        self.buf.clear();
737        self.idx = 0;
738        self.begin = 0;
739        self.sequence = 0;
740        self.replay.clear();
741    }
742
743    fn open_undo(&self) -> usize {
744        self.idx
745    }
746
747    fn open_redo(&self) -> usize {
748        self.buf.len() - self.idx
749    }
750
751    /// Get next undo
752    fn undo(&mut self) -> Vec<&UndoOp> {
753        if self.idx > 0 {
754            let sequence = self.buf[self.idx - 1].sequence;
755            let mut undo = Vec::new();
756            loop {
757                if self.buf[self.idx - 1].sequence == sequence {
758                    undo.push(&self.buf[self.idx - 1].operation);
759                    self.idx -= 1;
760                } else {
761                    break;
762                }
763                if self.idx == 0 {
764                    break;
765                }
766            }
767            undo
768        } else {
769            Vec::default()
770        }
771    }
772
773    /// Get next redo
774    fn redo(&mut self) -> Vec<&UndoOp> {
775        if self.idx < self.buf.len() {
776            let sequence = self.buf[self.idx].sequence;
777            let mut redo = Vec::new();
778            loop {
779                if self.buf[self.idx].sequence == sequence {
780                    redo.push(&self.buf[self.idx].operation);
781                    self.idx += 1;
782                } else {
783                    break;
784                }
785                if self.idx == self.buf.len() {
786                    break;
787                }
788            }
789            redo
790        } else {
791            Vec::default()
792        }
793    }
794
795    /// Enable replay functionality.
796    ///
797    /// This keeps track of all changes to a textarea.
798    /// These changes can be copied to another textarea with
799    /// the replay() function.
800    fn enable_replay_log(&mut self, replay: bool) {
801        if self.track_replay != replay {
802            self.replay.clear();
803        }
804        self.track_replay = replay;
805    }
806
807    fn has_replay_log(&self) -> bool {
808        self.track_replay
809    }
810
811    /// Get all new replay entries.
812    fn recent_replay_log(&mut self) -> Vec<UndoEntry> {
813        mem::take(&mut self.replay)
814    }
815
816    fn undo_styles_enabled(&self) -> bool {
817        self.undo_styles
818    }
819}