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}