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