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            if matches!(undo, UndoOp::SetStyles { .. }) {
529                return true;
530            }
531        }
532
533        false
534    }
535
536    fn try_merge(&mut self, undo: UndoOp) -> Option<UndoOp> {
537        if let Some(UndoEntry {
538            sequence,
539            operation: last,
540            ..
541        }) = self.buf.pop()
542        {
543            let (last, undo) = Self::merge_undo(last, undo);
544            // re-add last if it survived merge
545            if let Some(last) = last {
546                self.buf.push(UndoEntry {
547                    sequence,
548                    operation: last,
549                    non_exhaustive: NonExhaustive,
550                });
551            }
552            undo
553        } else {
554            Some(undo)
555        }
556    }
557
558    fn trim_undo(&mut self) {
559        // Dump redo.
560        while self.idx < self.buf.len() {
561            self.buf.pop();
562        }
563
564        // cap undo at capacity.
565        // uses the sequence count instead of the size.
566        let count_uniq = self
567            .buf
568            .iter()
569            .fold((0, 0), |mut f, v| {
570                if v.sequence != f.0 {
571                    f.0 = v.sequence;
572                    f.1 += 1;
573                }
574                f
575            })
576            .1;
577
578        if count_uniq > self.undo_count as usize {
579            // don't drop parts of current sequence at all.
580            if self.buf[0].sequence != self.sequence {
581                let drop_sequence = self.buf[0].sequence;
582                loop {
583                    if self.buf[0].sequence == drop_sequence {
584                        self.buf.remove(0);
585                    } else {
586                        break;
587                    }
588                }
589            }
590        }
591    }
592}
593
594impl UndoBuffer for UndoVec {
595    fn undo_count(&self) -> u32 {
596        self.undo_count
597    }
598
599    fn set_undo_count(&mut self, n: u32) {
600        self.undo_count = n;
601    }
602
603    /// Begin a sequence of changes that should be undone at once.
604    fn begin_seq(&mut self) {
605        self.begin += 1;
606        if self.begin == 1 {
607            self.sequence += 1;
608        }
609    }
610
611    /// End a sequence of changes.
612    /// Unbalanced end_seq calls panic.
613    fn end_seq(&mut self) {
614        self.begin -= 1;
615    }
616
617    fn append(&mut self, undo: UndoOp) {
618        let track_undo = if self.track_replay {
619            Some(undo.clone())
620        } else {
621            None
622        };
623
624        // try merge
625        let add_undo = if let Some(last) = self.buf.last() {
626            // first begin starts a new sequence.
627            // so this shouldn't cross that boundary.
628            if last.sequence == self.sequence {
629                self.try_merge(undo)
630            } else {
631                Some(undo)
632            }
633        } else {
634            Some(undo)
635        };
636
637        // New separate undo.
638        if add_undo.is_some() {
639            // auto begin+end
640            if self.begin == 0 {
641                self.sequence += 1;
642            }
643        }
644
645        // Store in tracking.
646        // Sequence number is new if the merge failed, otherwise the
647        // same as the last. This fact will be used when replaying.
648        if let Some(track_undo) = track_undo {
649            self.replay.push(UndoEntry {
650                sequence: self.sequence,
651                operation: track_undo,
652                non_exhaustive: NonExhaustive,
653            });
654        }
655
656        // Add if not merged.
657        if let Some(add_undo) = add_undo {
658            // Not everything is undo.
659            if self.filter(&add_undo) {
660                return;
661            }
662            // Drop redo and trim undo.
663            self.trim_undo();
664
665            // add new undo if it survived merge
666            self.buf.push(UndoEntry {
667                sequence: self.sequence,
668                operation: add_undo,
669                non_exhaustive: NonExhaustive,
670            });
671
672            self.idx = self.buf.len();
673        }
674    }
675
676    fn append_from_replay(&mut self, undo: UndoEntry) {
677        let UndoEntry {
678            sequence,
679            operation: undo,
680            ..
681        } = undo;
682
683        // try merge
684        let add_undo = if let Some(last) = self.buf.last() {
685            // merges act just like sequences, so this
686            // works out for both.
687            if last.sequence == sequence {
688                self.try_merge(undo)
689            } else {
690                Some(undo)
691            }
692        } else {
693            Some(undo)
694        };
695
696        // sync sequence
697        self.sequence = sequence;
698
699        // Add if not merged.
700        if let Some(add_undo) = add_undo {
701            // Not everything is undo.
702            if self.filter(&add_undo) {
703                return;
704            }
705            // Drop redo and trim undo.
706            self.trim_undo();
707
708            // add new undo if it survived merge
709            self.buf.push(UndoEntry {
710                sequence,
711                operation: add_undo,
712                non_exhaustive: NonExhaustive,
713            });
714
715            self.idx = self.buf.len();
716        }
717    }
718
719    fn clear(&mut self) {
720        self.buf.clear();
721        self.idx = 0;
722        self.begin = 0;
723        self.sequence = 0;
724        self.replay.clear();
725    }
726
727    fn open_undo(&self) -> usize {
728        self.idx
729    }
730
731    fn open_redo(&self) -> usize {
732        self.buf.len() - self.idx
733    }
734
735    /// Get next undo
736    fn undo(&mut self) -> Vec<&UndoOp> {
737        if self.idx > 0 {
738            let sequence = self.buf[self.idx - 1].sequence;
739            let mut undo = Vec::new();
740            loop {
741                if self.buf[self.idx - 1].sequence == sequence {
742                    undo.push(&self.buf[self.idx - 1].operation);
743                    self.idx -= 1;
744                } else {
745                    break;
746                }
747                if self.idx == 0 {
748                    break;
749                }
750            }
751            undo
752        } else {
753            Vec::default()
754        }
755    }
756
757    /// Get next redo
758    fn redo(&mut self) -> Vec<&UndoOp> {
759        if self.idx < self.buf.len() {
760            let sequence = self.buf[self.idx].sequence;
761            let mut redo = Vec::new();
762            loop {
763                if self.buf[self.idx].sequence == sequence {
764                    redo.push(&self.buf[self.idx].operation);
765                    self.idx += 1;
766                } else {
767                    break;
768                }
769                if self.idx == self.buf.len() {
770                    break;
771                }
772            }
773            redo
774        } else {
775            Vec::default()
776        }
777    }
778
779    /// Enable replay functionality.
780    ///
781    /// This keeps track of all changes to a textarea.
782    /// These changes can be copied to another textarea with
783    /// the replay() function.
784    fn enable_replay_log(&mut self, replay: bool) {
785        if self.track_replay != replay {
786            self.replay.clear();
787        }
788        self.track_replay = replay;
789    }
790
791    fn has_replay_log(&self) -> bool {
792        self.track_replay
793    }
794
795    /// Get all new replay entries.
796    fn recent_replay_log(&mut self) -> Vec<UndoEntry> {
797        mem::take(&mut self.replay)
798    }
799
800    fn undo_styles_enabled(&self) -> bool {
801        self.undo_styles
802    }
803}