reedline/core_editor/
editor.rs

1use super::{edit_stack::EditStack, Clipboard, ClipboardMode, LineBuffer};
2use crate::enums::{EditType, UndoBehavior};
3use crate::{core_editor::get_default_clipboard, EditCommand};
4
5/// Stateful editor executing changes to the underlying [`LineBuffer`]
6///
7/// In comparison to the state-less [`LineBuffer`] the [`Editor`] keeps track of
8/// the undo/redo history and has facilities for cut/copy/yank/paste
9pub struct Editor {
10    line_buffer: LineBuffer,
11    cut_buffer: Box<dyn Clipboard>,
12
13    edit_stack: EditStack<LineBuffer>,
14    last_undo_behavior: UndoBehavior,
15}
16
17impl Default for Editor {
18    fn default() -> Self {
19        Editor {
20            line_buffer: LineBuffer::new(),
21            cut_buffer: Box::new(get_default_clipboard()),
22            edit_stack: EditStack::new(),
23            last_undo_behavior: UndoBehavior::CreateUndoPoint,
24        }
25    }
26}
27
28impl Editor {
29    /// Get the current [`LineBuffer`]
30    pub const fn line_buffer(&self) -> &LineBuffer {
31        &self.line_buffer
32    }
33
34    /// Set the current [`LineBuffer`].
35    /// [`UndoBehavior`] specifies how this change should be reflected on the undo stack.
36    pub(crate) fn set_line_buffer(&mut self, line_buffer: LineBuffer, undo_behavior: UndoBehavior) {
37        self.line_buffer = line_buffer;
38        self.update_undo_state(undo_behavior);
39    }
40
41    pub(crate) fn run_edit_command(&mut self, command: &EditCommand) {
42        match command {
43            EditCommand::MoveToStart => self.line_buffer.move_to_start(),
44            EditCommand::MoveToLineStart => self.line_buffer.move_to_line_start(),
45            EditCommand::MoveToEnd => self.line_buffer.move_to_end(),
46            EditCommand::MoveToLineEnd => self.line_buffer.move_to_line_end(),
47            EditCommand::MoveToPosition(pos) => self.line_buffer.set_insertion_point(*pos),
48            EditCommand::MoveLeft => self.line_buffer.move_left(),
49            EditCommand::MoveRight => self.line_buffer.move_right(),
50            EditCommand::MoveWordLeft => self.line_buffer.move_word_left(),
51            EditCommand::MoveBigWordLeft => self.line_buffer.move_big_word_left(),
52            EditCommand::MoveWordRight => self.line_buffer.move_word_right(),
53            EditCommand::MoveWordRightStart => self.line_buffer.move_word_right_start(),
54            EditCommand::MoveBigWordRightStart => self.line_buffer.move_big_word_right_start(),
55            EditCommand::MoveWordRightEnd => self.line_buffer.move_word_right_end(),
56            EditCommand::MoveBigWordRightEnd => self.line_buffer.move_big_word_right_end(),
57            EditCommand::InsertChar(c) => self.line_buffer.insert_char(*c),
58            EditCommand::Complete => {}
59            EditCommand::InsertString(str) => self.line_buffer.insert_str(str),
60            EditCommand::InsertNewline => self.line_buffer.insert_newline(),
61            EditCommand::ReplaceChar(chr) => self.replace_char(*chr),
62            EditCommand::ReplaceChars(n_chars, str) => self.replace_chars(*n_chars, str),
63            EditCommand::Backspace => self.line_buffer.delete_left_grapheme(),
64            EditCommand::Delete => self.line_buffer.delete_right_grapheme(),
65            EditCommand::CutChar => self.cut_char(),
66            EditCommand::BackspaceWord => self.line_buffer.delete_word_left(),
67            EditCommand::DeleteWord => self.line_buffer.delete_word_right(),
68            EditCommand::Clear => self.line_buffer.clear(),
69            EditCommand::ClearToLineEnd => self.line_buffer.clear_to_line_end(),
70            EditCommand::CutCurrentLine => self.cut_current_line(),
71            EditCommand::CutFromStart => self.cut_from_start(),
72            EditCommand::CutFromLineStart => self.cut_from_line_start(),
73            EditCommand::CutToEnd => self.cut_from_end(),
74            EditCommand::CutToLineEnd => self.cut_to_line_end(),
75            EditCommand::CutWordLeft => self.cut_word_left(),
76            EditCommand::CutBigWordLeft => self.cut_big_word_left(),
77            EditCommand::CutWordRight => self.cut_word_right(),
78            EditCommand::CutBigWordRight => self.cut_big_word_right(),
79            EditCommand::CutWordRightToNext => self.cut_word_right_to_next(),
80            EditCommand::CutBigWordRightToNext => self.cut_big_word_right_to_next(),
81            EditCommand::PasteCutBufferBefore => self.insert_cut_buffer_before(),
82            EditCommand::PasteCutBufferAfter => self.insert_cut_buffer_after(),
83            EditCommand::UppercaseWord => self.line_buffer.uppercase_word(),
84            EditCommand::LowercaseWord => self.line_buffer.lowercase_word(),
85            EditCommand::SwitchcaseChar => self.line_buffer.switchcase_char(),
86            EditCommand::CapitalizeChar => self.line_buffer.capitalize_char(),
87            EditCommand::SwapWords => self.line_buffer.swap_words(),
88            EditCommand::SwapGraphemes => self.line_buffer.swap_graphemes(),
89            EditCommand::Undo => self.undo(),
90            EditCommand::Redo => self.redo(),
91            EditCommand::CutRightUntil(c) => self.cut_right_until_char(*c, false, true),
92            EditCommand::CutRightBefore(c) => self.cut_right_until_char(*c, true, true),
93            EditCommand::MoveRightUntil(c) => self.move_right_until_char(*c, false, true),
94            EditCommand::MoveRightBefore(c) => self.move_right_until_char(*c, true, true),
95            EditCommand::CutLeftUntil(c) => self.cut_left_until_char(*c, false, true),
96            EditCommand::CutLeftBefore(c) => self.cut_left_until_char(*c, true, true),
97            EditCommand::MoveLeftUntil(c) => self.move_left_until_char(*c, false, true),
98            EditCommand::MoveLeftBefore(c) => self.move_left_until_char(*c, true, true),
99        }
100
101        let new_undo_behavior = match (command, command.edit_type()) {
102            (_, EditType::MoveCursor) => UndoBehavior::MoveCursor,
103            (EditCommand::InsertChar(c), EditType::EditText) => UndoBehavior::InsertCharacter(*c),
104            (EditCommand::Delete, EditType::EditText) => {
105                let deleted_char = self.edit_stack.current().grapheme_right().chars().next();
106                UndoBehavior::Delete(deleted_char)
107            }
108            (EditCommand::Backspace, EditType::EditText) => {
109                let deleted_char = self.edit_stack.current().grapheme_left().chars().next();
110                UndoBehavior::Backspace(deleted_char)
111            }
112            (_, EditType::UndoRedo) => UndoBehavior::UndoRedo,
113            (_, _) => UndoBehavior::CreateUndoPoint,
114        };
115        self.update_undo_state(new_undo_behavior);
116    }
117
118    pub(crate) fn move_line_up(&mut self) {
119        self.line_buffer.move_line_up();
120        self.update_undo_state(UndoBehavior::MoveCursor);
121    }
122
123    pub(crate) fn move_line_down(&mut self) {
124        self.line_buffer.move_line_down();
125        self.update_undo_state(UndoBehavior::MoveCursor);
126    }
127
128    /// Get the text of the current [`LineBuffer`]
129    pub fn get_buffer(&self) -> &str {
130        self.line_buffer.get_buffer()
131    }
132
133    /// Edit the [`LineBuffer`] in an undo-safe manner.
134    pub fn edit_buffer<F>(&mut self, func: F, undo_behavior: UndoBehavior)
135    where
136        F: FnOnce(&mut LineBuffer),
137    {
138        self.update_undo_state(undo_behavior);
139        func(&mut self.line_buffer);
140    }
141
142    /// Set the text of the current [`LineBuffer`] given the specified [`UndoBehavior`]
143    /// Insertion point update to the end of the buffer.
144    pub(crate) fn set_buffer(&mut self, buffer: String, undo_behavior: UndoBehavior) {
145        self.line_buffer.set_buffer(buffer);
146        self.update_undo_state(undo_behavior);
147    }
148
149    pub(crate) fn insertion_point(&self) -> usize {
150        self.line_buffer.insertion_point()
151    }
152
153    pub(crate) fn is_empty(&self) -> bool {
154        self.line_buffer.is_empty()
155    }
156
157    pub(crate) fn is_cursor_at_first_line(&self) -> bool {
158        self.line_buffer.is_cursor_at_first_line()
159    }
160
161    pub(crate) fn is_cursor_at_last_line(&self) -> bool {
162        self.line_buffer.is_cursor_at_last_line()
163    }
164
165    pub(crate) fn is_cursor_at_buffer_end(&self) -> bool {
166        self.line_buffer.insertion_point() == self.get_buffer().len()
167    }
168
169    pub(crate) fn reset_undo_stack(&mut self) {
170        self.edit_stack.reset();
171    }
172
173    pub(crate) fn move_to_start(&mut self, undo_behavior: UndoBehavior) {
174        self.line_buffer.move_to_start();
175        self.update_undo_state(undo_behavior);
176    }
177
178    pub(crate) fn move_to_end(&mut self, undo_behavior: UndoBehavior) {
179        self.line_buffer.move_to_end();
180        self.update_undo_state(undo_behavior);
181    }
182
183    #[allow(dead_code)]
184    pub(crate) fn move_to_line_start(&mut self, undo_behavior: UndoBehavior) {
185        self.line_buffer.move_to_line_start();
186        self.update_undo_state(undo_behavior);
187    }
188
189    pub(crate) fn move_to_line_end(&mut self, undo_behavior: UndoBehavior) {
190        self.line_buffer.move_to_line_end();
191        self.update_undo_state(undo_behavior);
192    }
193
194    fn undo(&mut self) {
195        let val = self.edit_stack.undo();
196        self.line_buffer = val.clone();
197    }
198
199    fn redo(&mut self) {
200        let val = self.edit_stack.redo();
201        self.line_buffer = val.clone();
202    }
203
204    fn update_undo_state(&mut self, undo_behavior: UndoBehavior) {
205        if matches!(undo_behavior, UndoBehavior::UndoRedo) {
206            self.last_undo_behavior = UndoBehavior::UndoRedo;
207            return;
208        }
209        if !undo_behavior.create_undo_point_after(&self.last_undo_behavior) {
210            self.edit_stack.undo();
211        }
212        self.edit_stack.insert(self.line_buffer.clone());
213        self.last_undo_behavior = undo_behavior;
214    }
215
216    fn cut_current_line(&mut self) {
217        let deletion_range = self.line_buffer.current_line_range();
218
219        let cut_slice = &self.line_buffer.get_buffer()[deletion_range.clone()];
220        if !cut_slice.is_empty() {
221            self.cut_buffer.set(cut_slice, ClipboardMode::Lines);
222            self.line_buffer.set_insertion_point(deletion_range.start);
223            self.line_buffer.clear_range(deletion_range);
224        }
225    }
226
227    fn cut_from_start(&mut self) {
228        let insertion_offset = self.line_buffer.insertion_point();
229        if insertion_offset > 0 {
230            self.cut_buffer.set(
231                &self.line_buffer.get_buffer()[..insertion_offset],
232                ClipboardMode::Normal,
233            );
234            self.line_buffer.clear_to_insertion_point();
235        }
236    }
237
238    fn cut_from_line_start(&mut self) {
239        let previous_offset = self.line_buffer.insertion_point();
240        self.line_buffer.move_to_line_start();
241        let deletion_range = self.line_buffer.insertion_point()..previous_offset;
242        let cut_slice = &self.line_buffer.get_buffer()[deletion_range.clone()];
243        if !cut_slice.is_empty() {
244            self.cut_buffer.set(cut_slice, ClipboardMode::Normal);
245            self.line_buffer.clear_range(deletion_range);
246        }
247    }
248
249    fn cut_from_end(&mut self) {
250        let cut_slice = &self.line_buffer.get_buffer()[self.line_buffer.insertion_point()..];
251        if !cut_slice.is_empty() {
252            self.cut_buffer.set(cut_slice, ClipboardMode::Normal);
253            self.line_buffer.clear_to_end();
254        }
255    }
256
257    fn cut_to_line_end(&mut self) {
258        let cut_slice = &self.line_buffer.get_buffer()
259            [self.line_buffer.insertion_point()..self.line_buffer.find_current_line_end()];
260        if !cut_slice.is_empty() {
261            self.cut_buffer.set(cut_slice, ClipboardMode::Normal);
262            self.line_buffer.clear_to_line_end();
263        }
264    }
265
266    fn cut_word_left(&mut self) {
267        let insertion_offset = self.line_buffer.insertion_point();
268        let left_index = self.line_buffer.word_left_index();
269        if left_index < insertion_offset {
270            let cut_range = left_index..insertion_offset;
271            self.cut_buffer.set(
272                &self.line_buffer.get_buffer()[cut_range.clone()],
273                ClipboardMode::Normal,
274            );
275            self.line_buffer.clear_range(cut_range);
276            self.line_buffer.set_insertion_point(left_index);
277        }
278    }
279
280    fn cut_big_word_left(&mut self) {
281        let insertion_offset = self.line_buffer.insertion_point();
282        let left_index = self.line_buffer.big_word_left_index();
283        if left_index < insertion_offset {
284            let cut_range = left_index..insertion_offset;
285            self.cut_buffer.set(
286                &self.line_buffer.get_buffer()[cut_range.clone()],
287                ClipboardMode::Normal,
288            );
289            self.line_buffer.clear_range(cut_range);
290            self.line_buffer.set_insertion_point(left_index);
291        }
292    }
293
294    fn cut_word_right(&mut self) {
295        let insertion_offset = self.line_buffer.insertion_point();
296        let right_index = self.line_buffer.word_right_index();
297        if right_index > insertion_offset {
298            let cut_range = insertion_offset..right_index;
299            self.cut_buffer.set(
300                &self.line_buffer.get_buffer()[cut_range.clone()],
301                ClipboardMode::Normal,
302            );
303            self.line_buffer.clear_range(cut_range);
304        }
305    }
306
307    fn cut_big_word_right(&mut self) {
308        let insertion_offset = self.line_buffer.insertion_point();
309        let right_index = self.line_buffer.next_whitespace();
310        if right_index > insertion_offset {
311            let cut_range = insertion_offset..right_index;
312            self.cut_buffer.set(
313                &self.line_buffer.get_buffer()[cut_range.clone()],
314                ClipboardMode::Normal,
315            );
316            self.line_buffer.clear_range(cut_range);
317        }
318    }
319
320    fn cut_word_right_to_next(&mut self) {
321        let insertion_offset = self.line_buffer.insertion_point();
322        let right_index = self.line_buffer.word_right_start_index();
323        if right_index > insertion_offset {
324            let cut_range = insertion_offset..right_index;
325            self.cut_buffer.set(
326                &self.line_buffer.get_buffer()[cut_range.clone()],
327                ClipboardMode::Normal,
328            );
329            self.line_buffer.clear_range(cut_range);
330        }
331    }
332
333    fn cut_big_word_right_to_next(&mut self) {
334        let insertion_offset = self.line_buffer.insertion_point();
335        let right_index = self.line_buffer.big_word_right_start_index();
336        if right_index > insertion_offset {
337            let cut_range = insertion_offset..right_index;
338            self.cut_buffer.set(
339                &self.line_buffer.get_buffer()[cut_range.clone()],
340                ClipboardMode::Normal,
341            );
342            self.line_buffer.clear_range(cut_range);
343        }
344    }
345
346    fn cut_char(&mut self) {
347        let insertion_offset = self.line_buffer.insertion_point();
348        let right_index = self.line_buffer.grapheme_right_index();
349        if right_index > insertion_offset {
350            let cut_range = insertion_offset..right_index;
351            self.cut_buffer.set(
352                &self.line_buffer.get_buffer()[cut_range.clone()],
353                ClipboardMode::Normal,
354            );
355            self.line_buffer.clear_range(cut_range);
356        }
357    }
358
359    fn insert_cut_buffer_before(&mut self) {
360        match self.cut_buffer.get() {
361            (content, ClipboardMode::Normal) => {
362                self.line_buffer.insert_str(&content);
363            }
364            (mut content, ClipboardMode::Lines) => {
365                // TODO: Simplify that?
366                self.line_buffer.move_to_line_start();
367                self.line_buffer.move_line_up();
368                if !content.ends_with('\n') {
369                    // TODO: Make sure platform requirements are met
370                    content.push('\n');
371                }
372                self.line_buffer.insert_str(&content);
373            }
374        }
375    }
376
377    fn insert_cut_buffer_after(&mut self) {
378        match self.cut_buffer.get() {
379            (content, ClipboardMode::Normal) => {
380                self.line_buffer.move_right();
381                self.line_buffer.insert_str(&content);
382            }
383            (mut content, ClipboardMode::Lines) => {
384                // TODO: Simplify that?
385                self.line_buffer.move_to_line_start();
386                self.line_buffer.move_line_down();
387                if !content.ends_with('\n') {
388                    // TODO: Make sure platform requirements are met
389                    content.push('\n');
390                }
391                self.line_buffer.insert_str(&content);
392            }
393        }
394    }
395
396    fn move_right_until_char(&mut self, c: char, before_char: bool, current_line: bool) {
397        if before_char {
398            self.line_buffer.move_right_before(c, current_line);
399        } else {
400            self.line_buffer.move_right_until(c, current_line);
401        }
402    }
403
404    fn move_left_until_char(&mut self, c: char, before_char: bool, current_line: bool) {
405        if before_char {
406            self.line_buffer.move_left_before(c, current_line);
407        } else {
408            self.line_buffer.move_left_until(c, current_line);
409        }
410    }
411
412    fn cut_right_until_char(&mut self, c: char, before_char: bool, current_line: bool) {
413        if let Some(index) = self.line_buffer.find_char_right(c, current_line) {
414            // Saving the section of the string that will be deleted to be
415            // stored into the buffer
416            let extra = if before_char { 0 } else { c.len_utf8() };
417            let cut_slice =
418                &self.line_buffer.get_buffer()[self.line_buffer.insertion_point()..index + extra];
419
420            if !cut_slice.is_empty() {
421                self.cut_buffer.set(cut_slice, ClipboardMode::Normal);
422
423                if before_char {
424                    self.line_buffer.delete_right_before_char(c, current_line);
425                } else {
426                    self.line_buffer.delete_right_until_char(c, current_line);
427                }
428            }
429        }
430    }
431
432    fn cut_left_until_char(&mut self, c: char, before_char: bool, current_line: bool) {
433        if let Some(index) = self.line_buffer.find_char_left(c, current_line) {
434            // Saving the section of the string that will be deleted to be
435            // stored into the buffer
436            let extra = if before_char { c.len_utf8() } else { 0 };
437            let cut_slice =
438                &self.line_buffer.get_buffer()[index + extra..self.line_buffer.insertion_point()];
439
440            if !cut_slice.is_empty() {
441                self.cut_buffer.set(cut_slice, ClipboardMode::Normal);
442
443                if before_char {
444                    self.line_buffer.delete_left_before_char(c, current_line);
445                } else {
446                    self.line_buffer.delete_left_until_char(c, current_line);
447                }
448            }
449        }
450    }
451
452    fn replace_char(&mut self, character: char) {
453        self.line_buffer.delete_right_grapheme();
454
455        self.line_buffer.insert_char(character);
456    }
457
458    fn replace_chars(&mut self, n_chars: usize, string: &str) {
459        for _ in 0..n_chars {
460            self.line_buffer.delete_right_grapheme();
461        }
462
463        self.line_buffer.insert_str(string);
464    }
465}
466
467#[cfg(test)]
468mod test {
469    use super::*;
470    use pretty_assertions::assert_eq;
471    use rstest::rstest;
472
473    fn editor_with(buffer: &str) -> Editor {
474        let mut editor = Editor::default();
475        editor.set_buffer(buffer.to_string(), UndoBehavior::CreateUndoPoint);
476        editor
477    }
478
479    #[rstest]
480    #[case("abc def ghi", 11, "abc def ")]
481    #[case("abc def-ghi", 11, "abc def-")]
482    #[case("abc def.ghi", 11, "abc ")]
483    fn test_cut_word_left(#[case] input: &str, #[case] position: usize, #[case] expected: &str) {
484        let mut editor = editor_with(input);
485        editor.line_buffer.set_insertion_point(position);
486
487        editor.cut_word_left();
488
489        assert_eq!(editor.get_buffer(), expected);
490    }
491
492    #[rstest]
493    #[case("abc def ghi", 11, "abc def ")]
494    #[case("abc def-ghi", 11, "abc ")]
495    #[case("abc def.ghi", 11, "abc ")]
496    #[case("abc def gh ", 11, "abc def ")]
497    fn test_cut_big_word_left(
498        #[case] input: &str,
499        #[case] position: usize,
500        #[case] expected: &str,
501    ) {
502        let mut editor = editor_with(input);
503        editor.line_buffer.set_insertion_point(position);
504
505        editor.cut_big_word_left();
506
507        assert_eq!(editor.get_buffer(), expected);
508    }
509
510    #[rstest]
511    #[case("hello world", 0, 'l', 1, false, "lo world")]
512    #[case("hello world", 0, 'l', 1, true, "llo world")]
513    #[ignore = "Deleting two consecutives is not implemented correctly and needs the multiplier explicitly."]
514    #[case("hello world", 0, 'l', 2, false, "o world")]
515    #[case("hello world", 0, 'h', 1, false, "hello world")]
516    #[case("hello world", 0, 'l', 3, true, "ld")]
517    #[case("hello world", 4, 'o', 1, true, "hellorld")]
518    #[case("hello world", 4, 'w', 1, false, "hellorld")]
519    #[case("hello world", 4, 'o', 1, false, "hellrld")]
520    fn test_cut_right_until_char(
521        #[case] input: &str,
522        #[case] position: usize,
523        #[case] search_char: char,
524        #[case] repeat: usize,
525        #[case] before_char: bool,
526        #[case] expected: &str,
527    ) {
528        let mut editor = editor_with(input);
529        editor.line_buffer.set_insertion_point(position);
530        for _ in 0..repeat {
531            editor.cut_right_until_char(search_char, before_char, true);
532        }
533        assert_eq!(editor.get_buffer(), expected);
534    }
535
536    #[rstest]
537    #[case("abc", 1, 'X', "aXc")]
538    #[case("abc", 1, '🔄', "a🔄c")]
539    #[case("a🔄c", 1, 'X', "aXc")]
540    #[case("a🔄c", 1, '🔀', "a🔀c")]
541    fn test_replace_char(
542        #[case] input: &str,
543        #[case] position: usize,
544        #[case] replacement: char,
545        #[case] expected: &str,
546    ) {
547        let mut editor = editor_with(input);
548        editor.line_buffer.set_insertion_point(position);
549
550        editor.replace_char(replacement);
551
552        assert_eq!(editor.get_buffer(), expected);
553    }
554
555    fn str_to_edit_commands(s: &str) -> Vec<EditCommand> {
556        s.chars().map(EditCommand::InsertChar).collect()
557    }
558
559    #[test]
560    fn test_undo_insert_works_on_work_boundaries() {
561        let mut editor = editor_with("This is  a");
562        for cmd in str_to_edit_commands(" test") {
563            editor.run_edit_command(&cmd);
564        }
565        assert_eq!(editor.get_buffer(), "This is  a test");
566        editor.run_edit_command(&EditCommand::Undo);
567        assert_eq!(editor.get_buffer(), "This is  a");
568        editor.run_edit_command(&EditCommand::Redo);
569        assert_eq!(editor.get_buffer(), "This is  a test");
570    }
571
572    #[test]
573    fn test_undo_backspace_works_on_word_boundaries() {
574        let mut editor = editor_with("This is  a test");
575        for _ in 0..6 {
576            editor.run_edit_command(&EditCommand::Backspace);
577        }
578        assert_eq!(editor.get_buffer(), "This is  ");
579        editor.run_edit_command(&EditCommand::Undo);
580        assert_eq!(editor.get_buffer(), "This is  a");
581        editor.run_edit_command(&EditCommand::Undo);
582        assert_eq!(editor.get_buffer(), "This is  a test");
583    }
584
585    #[test]
586    fn test_undo_delete_works_on_word_boundaries() {
587        let mut editor = editor_with("This  is a test");
588        editor.line_buffer.set_insertion_point(0);
589        for _ in 0..7 {
590            editor.run_edit_command(&EditCommand::Delete);
591        }
592        assert_eq!(editor.get_buffer(), "s a test");
593        editor.run_edit_command(&EditCommand::Undo);
594        assert_eq!(editor.get_buffer(), "is a test");
595        editor.run_edit_command(&EditCommand::Undo);
596        assert_eq!(editor.get_buffer(), "This  is a test");
597    }
598
599    #[test]
600    fn test_undo_insert_with_newline() {
601        let mut editor = editor_with("This is a");
602        for cmd in str_to_edit_commands(" \n test") {
603            editor.run_edit_command(&cmd);
604        }
605        assert_eq!(editor.get_buffer(), "This is a \n test");
606        editor.run_edit_command(&EditCommand::Undo);
607        assert_eq!(editor.get_buffer(), "This is a \n");
608        editor.run_edit_command(&EditCommand::Undo);
609        assert_eq!(editor.get_buffer(), "This is a");
610    }
611
612    #[test]
613    fn test_undo_backspace_with_newline() {
614        let mut editor = editor_with("This is a \n test");
615        for _ in 0..8 {
616            editor.run_edit_command(&EditCommand::Backspace);
617        }
618        assert_eq!(editor.get_buffer(), "This is ");
619        editor.run_edit_command(&EditCommand::Undo);
620        assert_eq!(editor.get_buffer(), "This is a");
621        editor.run_edit_command(&EditCommand::Undo);
622        assert_eq!(editor.get_buffer(), "This is a \n");
623        editor.run_edit_command(&EditCommand::Undo);
624        assert_eq!(editor.get_buffer(), "This is a \n test");
625    }
626
627    #[test]
628    fn test_undo_backspace_with_crlf() {
629        let mut editor = editor_with("This is a \r\n test");
630        for _ in 0..8 {
631            editor.run_edit_command(&EditCommand::Backspace);
632        }
633        assert_eq!(editor.get_buffer(), "This is ");
634        editor.run_edit_command(&EditCommand::Undo);
635        assert_eq!(editor.get_buffer(), "This is a");
636        editor.run_edit_command(&EditCommand::Undo);
637        assert_eq!(editor.get_buffer(), "This is a \r\n");
638        editor.run_edit_command(&EditCommand::Undo);
639        assert_eq!(editor.get_buffer(), "This is a \r\n test");
640    }
641
642    #[test]
643    fn test_undo_delete_with_newline() {
644        let mut editor = editor_with("This \n is a test");
645        editor.line_buffer.set_insertion_point(0);
646        for _ in 0..8 {
647            editor.run_edit_command(&EditCommand::Delete);
648        }
649        assert_eq!(editor.get_buffer(), "s a test");
650        editor.run_edit_command(&EditCommand::Undo);
651        assert_eq!(editor.get_buffer(), "is a test");
652        editor.run_edit_command(&EditCommand::Undo);
653        assert_eq!(editor.get_buffer(), "\n is a test");
654        editor.run_edit_command(&EditCommand::Undo);
655        assert_eq!(editor.get_buffer(), "This \n is a test");
656    }
657
658    #[test]
659    fn test_undo_delete_with_crlf() {
660        // CLRF delete is a special case, since the first character of the
661        // grapheme is \r rather than \n
662        let mut editor = editor_with("This \r\n is a test");
663        editor.line_buffer.set_insertion_point(0);
664        for _ in 0..8 {
665            editor.run_edit_command(&EditCommand::Delete);
666        }
667        assert_eq!(editor.get_buffer(), "s a test");
668        editor.run_edit_command(&EditCommand::Undo);
669        assert_eq!(editor.get_buffer(), "is a test");
670        editor.run_edit_command(&EditCommand::Undo);
671        assert_eq!(editor.get_buffer(), "\r\n is a test");
672        editor.run_edit_command(&EditCommand::Undo);
673        assert_eq!(editor.get_buffer(), "This \r\n is a test");
674    }
675}