rustyline/
undo.rs

1//! Undo API
2use std::fmt::Debug;
3
4use crate::keymap::RepeatCount;
5use crate::line_buffer::{ChangeListener, DeleteListener, Direction, LineBuffer, NoListener};
6use log::debug;
7use unicode_segmentation::UnicodeSegmentation;
8
9enum Change {
10    Begin,
11    End,
12    Insert {
13        idx: usize,
14        text: String,
15    }, // QuotedInsert, SelfInsert, Yank
16    Delete {
17        idx: usize,
18        text: String,
19    }, /* BackwardDeleteChar, BackwardKillWord, DeleteChar,
20        * KillLine, KillWholeLine, KillWord,
21        * UnixLikeDiscard, ViDeleteTo */
22    Replace {
23        idx: usize,
24        old: String,
25        new: String,
26    }, /* CapitalizeWord, Complete, DowncaseWord, Replace, TransposeChars, TransposeWords,
27        * UpcaseWord, YankPop */
28}
29
30impl Change {
31    fn undo(&self, line: &mut LineBuffer) {
32        match *self {
33            Self::Begin | Self::End => unreachable!(),
34            Self::Insert { idx, ref text } => {
35                line.delete_range(idx..idx + text.len(), &mut NoListener);
36            }
37            Self::Delete { idx, ref text } => {
38                line.insert_str(idx, text, &mut NoListener);
39                line.set_pos(idx + text.len());
40            }
41            Self::Replace {
42                idx,
43                ref old,
44                ref new,
45            } => {
46                line.replace(idx..idx + new.len(), old, &mut NoListener);
47            }
48        }
49    }
50
51    #[cfg(test)]
52    fn redo(&self, line: &mut LineBuffer) {
53        match *self {
54            Self::Begin | Self::End => unreachable!(),
55            Self::Insert { idx, ref text } => {
56                line.insert_str(idx, text, &mut NoListener);
57            }
58            Self::Delete { idx, ref text } => {
59                line.delete_range(idx..idx + text.len(), &mut NoListener);
60            }
61            Self::Replace {
62                idx,
63                ref old,
64                ref new,
65            } => {
66                line.replace(idx..idx + old.len(), new, &mut NoListener);
67            }
68        }
69    }
70
71    fn insert_seq(&self, indx: usize) -> bool {
72        if let Self::Insert { idx, ref text } = *self {
73            idx + text.len() == indx
74        } else {
75            false
76        }
77    }
78
79    fn delete_seq(&self, indx: usize, len: usize) -> bool {
80        if let Self::Delete { idx, .. } = *self {
81            // delete or backspace
82            idx == indx || idx == indx + len
83        } else {
84            false
85        }
86    }
87
88    fn replace_seq(&self, indx: usize) -> bool {
89        if let Self::Replace { idx, ref new, .. } = *self {
90            idx + new.len() == indx
91        } else {
92            false
93        }
94    }
95}
96
97/// Undo manager
98pub struct Changeset {
99    undo_group_level: u32,
100    undos: Vec<Change>, // undoable changes
101    redos: Vec<Change>, // undone changes, redoable
102}
103
104impl Changeset {
105    pub(crate) fn new() -> Self {
106        Self {
107            undo_group_level: 0,
108            undos: vec![],
109            redos: vec![],
110        }
111    }
112
113    pub(crate) fn begin(&mut self) -> usize {
114        debug!(target: "rustyline", "Changeset::begin");
115        self.redos.clear();
116        let mark = self.undos.len();
117        self.undos.push(Change::Begin);
118        self.undo_group_level += 1;
119        mark
120    }
121
122    /// Returns `true` when changes happen between the last call to `begin` and
123    /// this `end`.
124    pub(crate) fn end(&mut self) -> bool {
125        debug!(target: "rustyline", "Changeset::end");
126        self.redos.clear();
127        let mut touched = false;
128        while self.undo_group_level > 0 {
129            self.undo_group_level -= 1;
130            if let Some(&Change::Begin) = self.undos.last() {
131                // empty Begin..End
132                self.undos.pop();
133            } else {
134                self.undos.push(Change::End);
135                touched = true;
136            }
137        }
138        touched
139    }
140
141    fn insert_char(idx: usize, c: char) -> Change {
142        let mut text = String::new();
143        text.push(c);
144        Change::Insert { idx, text }
145    }
146
147    pub(crate) fn insert(&mut self, idx: usize, c: char) {
148        debug!(target: "rustyline", "Changeset::insert({idx}, {c:?})");
149        self.redos.clear();
150        if !c.is_alphanumeric() || !self.undos.last().is_some_and(|lc| lc.insert_seq(idx)) {
151            self.undos.push(Self::insert_char(idx, c));
152            return;
153        }
154        // merge consecutive char insertions when char is alphanumeric
155        let mut last_change = self.undos.pop().unwrap();
156        if let Change::Insert { ref mut text, .. } = last_change {
157            text.push(c);
158        } else {
159            unreachable!();
160        }
161        self.undos.push(last_change);
162    }
163
164    pub(crate) fn insert_str<S: AsRef<str> + Into<String> + Debug>(
165        &mut self,
166        idx: usize,
167        string: S,
168    ) {
169        debug!(target: "rustyline", "Changeset::insert_str({idx}, {string:?})");
170        self.redos.clear();
171        if string.as_ref().is_empty() {
172            return;
173        }
174        self.undos.push(Change::Insert {
175            idx,
176            text: string.into(),
177        });
178    }
179
180    pub(crate) fn delete<S: AsRef<str> + Into<String> + Debug>(&mut self, indx: usize, string: S) {
181        debug!(target: "rustyline", "Changeset::delete({indx}, {string:?})");
182        self.redos.clear();
183        if string.as_ref().is_empty() {
184            return;
185        }
186
187        if !Self::single_char(string.as_ref())
188            || !self
189                .undos
190                .last()
191                .is_some_and(|lc| lc.delete_seq(indx, string.as_ref().len()))
192        {
193            self.undos.push(Change::Delete {
194                idx: indx,
195                text: string.into(),
196            });
197            return;
198        }
199        // merge consecutive char deletions when char is alphanumeric
200        let mut last_change = self.undos.pop().unwrap();
201        if let Change::Delete {
202            ref mut idx,
203            ref mut text,
204        } = last_change
205        {
206            if *idx == indx {
207                text.push_str(string.as_ref());
208            } else {
209                text.insert_str(0, string.as_ref());
210                *idx = indx;
211            }
212        } else {
213            unreachable!();
214        }
215        self.undos.push(last_change);
216    }
217
218    fn single_char(s: &str) -> bool {
219        let mut graphemes = s.graphemes(true);
220        graphemes
221            .next()
222            .is_some_and(|grapheme| grapheme.chars().all(char::is_alphanumeric))
223            && graphemes.next().is_none()
224    }
225
226    pub(crate) fn replace<S: AsRef<str> + Into<String> + Debug>(
227        &mut self,
228        indx: usize,
229        old_: S,
230        new_: S,
231    ) {
232        debug!(target: "rustyline", "Changeset::replace({indx}, {old_:?}, {new_:?})");
233        self.redos.clear();
234
235        if !self.undos.last().is_some_and(|lc| lc.replace_seq(indx)) {
236            self.undos.push(Change::Replace {
237                idx: indx,
238                old: old_.into(),
239                new: new_.into(),
240            });
241            return;
242        }
243
244        // merge consecutive char replacements
245        let mut last_change = self.undos.pop().unwrap();
246        if let Change::Replace {
247            ref mut old,
248            ref mut new,
249            ..
250        } = last_change
251        {
252            old.push_str(old_.as_ref());
253            new.push_str(new_.as_ref());
254        } else {
255            unreachable!();
256        }
257        self.undos.push(last_change);
258    }
259
260    pub(crate) fn undo(&mut self, line: &mut LineBuffer, n: RepeatCount) -> bool {
261        debug!(target: "rustyline", "Changeset::undo");
262        let mut count = 0;
263        let mut waiting_for_begin = 0;
264        let mut undone = false;
265        while let Some(change) = self.undos.pop() {
266            match change {
267                Change::Begin => {
268                    waiting_for_begin -= 1;
269                }
270                Change::End => {
271                    waiting_for_begin += 1;
272                }
273                _ => {
274                    change.undo(line);
275                    undone = true;
276                }
277            };
278            self.redos.push(change);
279            if waiting_for_begin <= 0 {
280                count += 1;
281                if count >= n {
282                    break;
283                }
284            }
285        }
286        undone
287    }
288
289    pub(crate) fn truncate(&mut self, len: usize) {
290        debug!(target: "rustyline", "Changeset::truncate({len})");
291        self.undos.truncate(len);
292    }
293
294    #[cfg(test)]
295    pub(crate) fn redo(&mut self, line: &mut LineBuffer) -> bool {
296        let mut waiting_for_end = 0;
297        let mut redone = false;
298        while let Some(change) = self.redos.pop() {
299            match change {
300                Change::Begin => {
301                    waiting_for_end += 1;
302                }
303                Change::End => {
304                    waiting_for_end -= 1;
305                }
306                _ => {
307                    change.redo(line);
308                    redone = true;
309                }
310            };
311            self.undos.push(change);
312            if waiting_for_end <= 0 {
313                break;
314            }
315        }
316        redone
317    }
318
319    pub(crate) fn last_insert(&self) -> Option<String> {
320        for change in self.undos.iter().rev() {
321            match change {
322                Change::Insert { ref text, .. } => return Some(text.clone()),
323                Change::Replace { ref new, .. } => return Some(new.clone()),
324                Change::End => {
325                    continue;
326                }
327                _ => {
328                    return None;
329                }
330            }
331        }
332        None
333    }
334}
335
336impl DeleteListener for Changeset {
337    fn delete(&mut self, idx: usize, string: &str, _: Direction) {
338        self.delete(idx, string);
339    }
340}
341impl ChangeListener for Changeset {
342    fn insert_char(&mut self, idx: usize, c: char) {
343        self.insert(idx, c);
344    }
345
346    fn insert_str(&mut self, idx: usize, string: &str) {
347        self.insert_str(idx, string);
348    }
349
350    fn replace(&mut self, idx: usize, old: &str, new: &str) {
351        self.replace(idx, old, new);
352    }
353}
354
355#[cfg(test)]
356mod tests {
357    use super::Changeset;
358    use crate::line_buffer::{LineBuffer, NoListener};
359
360    #[test]
361    fn test_insert_chars() {
362        let mut cs = Changeset::new();
363        cs.insert(0, 'H');
364        cs.insert(1, 'i');
365        assert_eq!(1, cs.undos.len());
366        assert_eq!(0, cs.redos.len());
367        cs.insert(0, ' ');
368        assert_eq!(2, cs.undos.len());
369    }
370
371    #[test]
372    fn test_insert_strings() {
373        let mut cs = Changeset::new();
374        cs.insert_str(0, "Hello");
375        cs.insert_str(5, ", ");
376        assert_eq!(2, cs.undos.len());
377        assert_eq!(0, cs.redos.len());
378    }
379
380    #[test]
381    fn test_undo_insert() {
382        let mut buf = LineBuffer::init("", 0);
383        buf.insert_str(0, "Hello", &mut NoListener);
384        buf.insert_str(5, ", world!", &mut NoListener);
385        let mut cs = Changeset::new();
386        assert_eq!(buf.as_str(), "Hello, world!");
387
388        cs.insert_str(5, ", world!");
389
390        cs.undo(&mut buf, 1);
391        assert_eq!(0, cs.undos.len());
392        assert_eq!(1, cs.redos.len());
393        assert_eq!(buf.as_str(), "Hello");
394
395        cs.redo(&mut buf);
396        assert_eq!(1, cs.undos.len());
397        assert_eq!(0, cs.redos.len());
398        assert_eq!(buf.as_str(), "Hello, world!");
399    }
400
401    #[test]
402    fn test_undo_delete() {
403        let mut buf = LineBuffer::init("", 0);
404        buf.insert_str(0, "Hello", &mut NoListener);
405        let mut cs = Changeset::new();
406        assert_eq!(buf.as_str(), "Hello");
407
408        cs.delete(5, ", world!");
409
410        cs.undo(&mut buf, 1);
411        assert_eq!(buf.as_str(), "Hello, world!");
412
413        cs.redo(&mut buf);
414        assert_eq!(buf.as_str(), "Hello");
415    }
416
417    #[test]
418    fn test_delete_chars() {
419        let mut buf = LineBuffer::init("", 0);
420        buf.insert_str(0, "Hlo", &mut NoListener);
421
422        let mut cs = Changeset::new();
423        cs.delete(1, "e");
424        cs.delete(1, "l");
425        assert_eq!(1, cs.undos.len());
426
427        cs.undo(&mut buf, 1);
428        assert_eq!(buf.as_str(), "Hello");
429    }
430
431    #[test]
432    fn test_backspace_chars() {
433        let mut buf = LineBuffer::init("", 0);
434        buf.insert_str(0, "Hlo", &mut NoListener);
435
436        let mut cs = Changeset::new();
437        cs.delete(2, "l");
438        cs.delete(1, "e");
439        assert_eq!(1, cs.undos.len());
440
441        cs.undo(&mut buf, 1);
442        assert_eq!(buf.as_str(), "Hello");
443    }
444
445    #[test]
446    fn test_undo_replace() {
447        let mut buf = LineBuffer::init("", 0);
448        buf.insert_str(0, "Hello, world!", &mut NoListener);
449        let mut cs = Changeset::new();
450        assert_eq!(buf.as_str(), "Hello, world!");
451
452        buf.replace(1..5, "i", &mut NoListener);
453        assert_eq!(buf.as_str(), "Hi, world!");
454        cs.replace(1, "ello", "i");
455
456        cs.undo(&mut buf, 1);
457        assert_eq!(buf.as_str(), "Hello, world!");
458
459        cs.redo(&mut buf);
460        assert_eq!(buf.as_str(), "Hi, world!");
461    }
462
463    #[test]
464    fn test_last_insert() {
465        let mut cs = Changeset::new();
466        cs.begin();
467        cs.delete(0, "Hello");
468        cs.insert_str(0, "Bye");
469        cs.end();
470        let insert = cs.last_insert();
471        assert_eq!(Some("Bye".to_owned()), insert);
472    }
473
474    #[test]
475    fn test_end() {
476        let mut cs = Changeset::new();
477        cs.begin();
478        assert!(!cs.end());
479        cs.begin();
480        cs.insert_str(0, "Hi");
481        assert!(cs.end());
482    }
483}