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}