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