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}