rat_text/
undo_buffer.rs

1//! Undo functionality.
2
3use crate::_private::NonExhaustive;
4use crate::TextPosition;
5use crate::range_map::expand_range_by;
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
202    /// For replay only. Complete content was replaced.
203    SetText {
204        /// New text
205        txt: String,
206    },
207    /// For replay only. Undo one operation.
208    Undo,
209    /// For replay only. Redo one operation.
210    Redo,
211}
212
213/// Standard implementation for undo.
214#[derive(Debug, Clone)]
215pub struct UndoVec {
216    undo_styles: bool,
217    track_replay: bool,
218    undo_count: u32,
219
220    begin: u8,
221    sequence: u32,
222    buf: Vec<UndoEntry>,
223    replay: Vec<UndoEntry>,
224
225    // undo/redo split
226    idx: usize,
227}
228
229impl Default for UndoVec {
230    fn default() -> Self {
231        Self {
232            undo_styles: false,
233            track_replay: false,
234            undo_count: 99,
235            begin: 0,
236            sequence: 0,
237            buf: Vec::default(),
238            replay: Vec::default(),
239            idx: 0,
240        }
241    }
242}
243
244impl UndoVec {
245    /// New undo.
246    pub fn new(undo_count: u32) -> Self {
247        Self {
248            undo_count,
249            ..Default::default()
250        }
251    }
252
253    /// Enable undo for style changes.
254    ///
255    /// Usually not what you want.
256    /// Unless you allow your users to set styles manually.
257    /// If your styling is done by a parser, don't activate this.
258    ///
259    /// Changes to the range of styles and removal of styles
260    /// caused by text edits *will* be undone anyway.
261    ///
262    /// Recording those operations for *replay* will not be affected
263    /// by this setting.
264    pub fn enable_undo_styles(&mut self, undo_styles: bool) {
265        self.undo_styles = undo_styles;
266    }
267
268    /// Undo for styles are enabled.
269    pub fn undo_styles(&self) -> bool {
270        self.undo_styles
271    }
272
273    fn merge_undo(mut last: UndoOp, mut curr: UndoOp) -> (Option<UndoOp>, Option<UndoOp>) {
274        match &mut curr {
275            UndoOp::InsertChar {
276                bytes: curr_bytes,
277                cursor: curr_cursor,
278                anchor: curr_anchor,
279                txt: curr_txt,
280            } => match &mut last {
281                UndoOp::InsertChar {
282                    bytes: last_bytes,
283                    cursor: last_cursor,
284                    anchor: last_anchor,
285                    txt: last_txt,
286                } => {
287                    if last_bytes.end == curr_bytes.start {
288                        let mut last_txt = mem::take(last_txt);
289                        last_txt.push_str(curr_txt);
290                        (
291                            Some(UndoOp::InsertChar {
292                                bytes: last_bytes.start..curr_bytes.end,
293                                cursor: TextPositionChange {
294                                    before: last_cursor.before,
295                                    after: curr_cursor.after,
296                                },
297                                anchor: TextPositionChange {
298                                    before: last_anchor.before,
299                                    after: curr_anchor.after,
300                                },
301                                txt: last_txt,
302                            }),
303                            None,
304                        )
305                    } else {
306                        (Some(last), Some(curr))
307                    }
308                }
309                _ => (Some(last), Some(curr)),
310            },
311            UndoOp::RemoveChar {
312                bytes: curr_bytes,
313                cursor: curr_cursor,
314                anchor: curr_anchor,
315                txt: curr_txt,
316                styles: curr_styles,
317            } => match &mut last {
318                UndoOp::RemoveChar {
319                    bytes: last_bytes,
320                    cursor: last_cursor,
321                    anchor: last_anchor,
322                    txt: last_txt,
323                    styles: last_styles,
324                } => {
325                    if curr_bytes.end == last_bytes.start {
326                        // backspace
327                        let mut txt = mem::take(curr_txt);
328                        txt.push_str(last_txt);
329
330                        // merge into last_styles
331                        let mut styles = mem::take(last_styles);
332                        Self::merge_remove_style(last_bytes.clone(), &mut styles, curr_styles);
333
334                        (
335                            Some(UndoOp::RemoveChar {
336                                bytes: curr_bytes.start..last_bytes.end,
337                                cursor: TextPositionChange {
338                                    before: last_cursor.before,
339                                    after: curr_cursor.after,
340                                },
341                                anchor: TextPositionChange {
342                                    before: last_anchor.before,
343                                    after: curr_anchor.after,
344                                },
345                                txt,
346                                styles,
347                            }),
348                            None,
349                        )
350                    } else if curr_bytes.start == last_bytes.start {
351                        // delete
352                        let mut txt = mem::take(last_txt);
353                        txt.push_str(curr_txt);
354
355                        let curr_byte_len = curr_bytes.end - curr_bytes.start;
356
357                        // merge into last_styles
358                        let mut styles = mem::take(last_styles);
359                        Self::merge_remove_style(last_bytes.clone(), &mut styles, curr_styles);
360
361                        (
362                            Some(UndoOp::RemoveChar {
363                                bytes: last_bytes.start..last_bytes.end + curr_byte_len,
364                                cursor: TextPositionChange {
365                                    before: last_cursor.before,
366                                    after: curr_cursor.after,
367                                },
368                                anchor: TextPositionChange {
369                                    before: last_anchor.before,
370                                    after: curr_anchor.after,
371                                },
372                                txt,
373                                styles,
374                            }),
375                            None,
376                        )
377                    } else {
378                        (Some(last), Some(curr))
379                    }
380                }
381                _ => (Some(last), Some(curr)),
382            },
383
384            UndoOp::Cursor {
385                cursor: curr_cursor,
386                anchor: curr_anchor,
387            } => match &mut last {
388                UndoOp::InsertChar {
389                    bytes: last_bytes,
390                    cursor: last_cursor,
391                    anchor: last_anchor,
392                    txt: last_txt,
393                } => (
394                    Some(UndoOp::InsertChar {
395                        bytes: mem::take(last_bytes),
396                        cursor: TextPositionChange {
397                            before: last_cursor.before,
398                            after: curr_cursor.after,
399                        },
400                        anchor: TextPositionChange {
401                            before: last_anchor.before,
402                            after: curr_anchor.after,
403                        },
404                        txt: mem::take(last_txt),
405                    }),
406                    None,
407                ),
408                UndoOp::InsertStr {
409                    bytes: last_bytes,
410                    cursor: last_cursor,
411                    anchor: last_anchor,
412                    txt: last_txt,
413                } => (
414                    Some(UndoOp::InsertStr {
415                        bytes: mem::take(last_bytes),
416                        cursor: TextPositionChange {
417                            before: last_cursor.before,
418                            after: curr_cursor.after,
419                        },
420                        anchor: TextPositionChange {
421                            before: last_anchor.before,
422                            after: curr_anchor.after,
423                        },
424                        txt: mem::take(last_txt),
425                    }),
426                    None,
427                ),
428                UndoOp::RemoveChar {
429                    bytes: last_bytes,
430                    cursor: last_cursor,
431                    anchor: last_anchor,
432                    txt: last_txt,
433                    styles: last_styles,
434                } => (
435                    Some(UndoOp::RemoveChar {
436                        bytes: mem::take(last_bytes),
437                        cursor: TextPositionChange {
438                            before: last_cursor.before,
439                            after: curr_cursor.after,
440                        },
441                        anchor: TextPositionChange {
442                            before: last_anchor.before,
443                            after: curr_anchor.after,
444                        },
445                        txt: mem::take(last_txt),
446                        styles: mem::take(last_styles),
447                    }),
448                    None,
449                ),
450                UndoOp::RemoveStr {
451                    bytes: last_bytes,
452                    cursor: last_cursor,
453                    anchor: last_anchor,
454                    txt: last_txt,
455                    styles: last_styles,
456                } => (
457                    Some(UndoOp::RemoveChar {
458                        bytes: mem::take(last_bytes),
459                        cursor: TextPositionChange {
460                            before: last_cursor.before,
461                            after: curr_cursor.after,
462                        },
463                        anchor: TextPositionChange {
464                            before: last_anchor.before,
465                            after: curr_anchor.after,
466                        },
467                        txt: mem::take(last_txt),
468                        styles: mem::take(last_styles),
469                    }),
470                    None,
471                ),
472                UndoOp::Cursor {
473                    cursor: last_cursor,
474                    anchor: last_anchor,
475                } => (
476                    Some(UndoOp::Cursor {
477                        cursor: TextPositionChange {
478                            before: last_cursor.before,
479                            after: curr_cursor.after,
480                        },
481                        anchor: TextPositionChange {
482                            before: last_anchor.before,
483                            after: curr_anchor.after,
484                        },
485                    }),
486                    None,
487                ),
488                _ => (Some(last), Some(curr)),
489            },
490            _ => (Some(last), Some(curr)),
491        }
492    }
493
494    /// Merge styles from two deletes.
495    fn merge_remove_style(
496        last_range: Range<usize>,
497        last: &mut Vec<StyleChange>,
498        curr: &mut Vec<StyleChange>,
499    ) {
500        for i in (0..last.len()).rev() {
501            for j in (0..curr.len()).rev() {
502                if last[i].style == curr[j].style {
503                    if last[i].after == curr[j].before {
504                        last[i].after = curr[j].after.clone();
505                        curr.remove(j);
506                    }
507                }
508            }
509        }
510
511        // expand before and add
512        for mut curr in curr.drain(..) {
513            curr.before = expand_range_by(last_range.clone(), curr.before);
514            last.push(curr);
515        }
516    }
517}
518
519impl UndoVec {
520    fn filter(&self, undo: &UndoOp) -> bool {
521        // only useful for tracking
522        if matches!(undo, UndoOp::Undo | UndoOp::Redo | UndoOp::SetText { .. }) {
523            return true;
524        }
525
526        // style changes may/may not be undone
527        if !self.undo_styles {
528            match &undo {
529                UndoOp::SetStyles { .. } => {
530                    return true;
531                }
532                _ => {}
533            }
534        }
535
536        false
537    }
538
539    fn try_merge(&mut self, undo: UndoOp) -> Option<UndoOp> {
540        if let Some(UndoEntry {
541            sequence,
542            operation: last,
543            ..
544        }) = self.buf.pop()
545        {
546            let (last, undo) = Self::merge_undo(last, undo);
547            // re-add last if it survived merge
548            if let Some(last) = last {
549                self.buf.push(UndoEntry {
550                    sequence,
551                    operation: last,
552                    non_exhaustive: NonExhaustive,
553                });
554            }
555            undo
556        } else {
557            Some(undo)
558        }
559    }
560
561    fn trim_undo(&mut self) {
562        // Dump redo.
563        while self.idx < self.buf.len() {
564            self.buf.pop();
565        }
566
567        // cap undo at capacity.
568        // uses the sequence count instead of the size.
569        let count_uniq = self
570            .buf
571            .iter()
572            .fold((0, 0), |mut f, v| {
573                if v.sequence != f.0 {
574                    f.0 = v.sequence;
575                    f.1 += 1;
576                }
577                f
578            })
579            .1;
580
581        if count_uniq > self.undo_count as usize {
582            // don't drop parts of current sequence at all.
583            if self.buf[0].sequence != self.sequence {
584                let drop_sequence = self.buf[0].sequence;
585                loop {
586                    if self.buf[0].sequence == drop_sequence {
587                        self.buf.remove(0);
588                    } else {
589                        break;
590                    }
591                }
592            }
593        }
594    }
595}
596
597impl UndoBuffer for UndoVec {
598    fn undo_count(&self) -> u32 {
599        self.undo_count
600    }
601
602    fn set_undo_count(&mut self, n: u32) {
603        self.undo_count = n;
604    }
605
606    /// Begin a sequence of changes that should be undone at once.
607    fn begin_seq(&mut self) {
608        self.begin += 1;
609        if self.begin == 1 {
610            self.sequence += 1;
611        }
612    }
613
614    /// End a sequence of changes.
615    /// Unbalanced end_seq calls panic.
616    fn end_seq(&mut self) {
617        self.begin -= 1;
618    }
619
620    fn append(&mut self, undo: UndoOp) {
621        let track_undo = if self.track_replay {
622            Some(undo.clone())
623        } else {
624            None
625        };
626
627        // try merge
628        let add_undo = if let Some(last) = self.buf.last() {
629            // first begin starts a new sequence.
630            // so this shouldn't cross that boundary.
631            if last.sequence == self.sequence {
632                self.try_merge(undo)
633            } else {
634                Some(undo)
635            }
636        } else {
637            Some(undo)
638        };
639
640        // New separate undo.
641        if add_undo.is_some() {
642            // auto begin+end
643            if self.begin == 0 {
644                self.sequence += 1;
645            }
646        }
647
648        // Store in tracking.
649        // Sequence number is new if the merge failed, otherwise the
650        // same as the last. This fact will be used when replaying.
651        if let Some(track_undo) = track_undo {
652            self.replay.push(UndoEntry {
653                sequence: self.sequence,
654                operation: track_undo,
655                non_exhaustive: NonExhaustive,
656            });
657        }
658
659        // Add if not merged.
660        if let Some(add_undo) = add_undo {
661            // Not everything is undo.
662            if self.filter(&add_undo) {
663                return;
664            }
665            // Drop redo and trim undo.
666            self.trim_undo();
667
668            // add new undo if it survived merge
669            self.buf.push(UndoEntry {
670                sequence: self.sequence,
671                operation: add_undo,
672                non_exhaustive: NonExhaustive,
673            });
674
675            self.idx = self.buf.len();
676        }
677    }
678
679    fn append_from_replay(&mut self, undo: UndoEntry) {
680        let UndoEntry {
681            sequence,
682            operation: undo,
683            ..
684        } = undo;
685
686        // try merge
687        let add_undo = if let Some(last) = self.buf.last() {
688            // merges act just like sequences, so this
689            // works out for both.
690            if last.sequence == sequence {
691                self.try_merge(undo)
692            } else {
693                Some(undo)
694            }
695        } else {
696            Some(undo)
697        };
698
699        // sync sequence
700        self.sequence = sequence;
701
702        // Add if not merged.
703        if let Some(add_undo) = add_undo {
704            // Not everything is undo.
705            if self.filter(&add_undo) {
706                return;
707            }
708            // Drop redo and trim undo.
709            self.trim_undo();
710
711            // add new undo if it survived merge
712            self.buf.push(UndoEntry {
713                sequence,
714                operation: add_undo,
715                non_exhaustive: NonExhaustive,
716            });
717
718            self.idx = self.buf.len();
719        }
720    }
721
722    fn clear(&mut self) {
723        self.buf.clear();
724        self.idx = 0;
725        self.begin = 0;
726        self.sequence = 0;
727        self.replay.clear();
728    }
729
730    fn open_undo(&self) -> usize {
731        self.idx
732    }
733
734    fn open_redo(&self) -> usize {
735        self.buf.len() - self.idx
736    }
737
738    /// Get next undo
739    fn undo(&mut self) -> Vec<&UndoOp> {
740        if self.idx > 0 {
741            let sequence = self.buf[self.idx - 1].sequence;
742            let mut undo = Vec::new();
743            loop {
744                if self.buf[self.idx - 1].sequence == sequence {
745                    undo.push(&self.buf[self.idx - 1].operation);
746                    self.idx -= 1;
747                } else {
748                    break;
749                }
750                if self.idx == 0 {
751                    break;
752                }
753            }
754            undo
755        } else {
756            Vec::default()
757        }
758    }
759
760    /// Get next redo
761    fn redo(&mut self) -> Vec<&UndoOp> {
762        if self.idx < self.buf.len() {
763            let sequence = self.buf[self.idx].sequence;
764            let mut redo = Vec::new();
765            loop {
766                if self.buf[self.idx].sequence == sequence {
767                    redo.push(&self.buf[self.idx].operation);
768                    self.idx += 1;
769                } else {
770                    break;
771                }
772                if self.idx == self.buf.len() {
773                    break;
774                }
775            }
776            redo
777        } else {
778            Vec::default()
779        }
780    }
781
782    /// Enable replay functionality.
783    ///
784    /// This keeps track of all changes to a textarea.
785    /// These changes can be copied to another textarea with
786    /// the replay() function.
787    fn enable_replay_log(&mut self, replay: bool) {
788        if self.track_replay != replay {
789            self.replay.clear();
790        }
791        self.track_replay = replay;
792    }
793
794    fn has_replay_log(&self) -> bool {
795        self.track_replay
796    }
797
798    /// Get all new replay entries.
799    fn recent_replay_log(&mut self) -> Vec<UndoEntry> {
800        mem::take(&mut self.replay)
801    }
802
803    fn undo_styles_enabled(&self) -> bool {
804        self.undo_styles
805    }
806}