reedline/core_editor/
line_buffer.rs

1use {
2    itertools::Itertools,
3    std::{convert::From, ops::Range},
4    unicode_segmentation::UnicodeSegmentation,
5};
6
7/// In memory representation of the entered line(s) including a cursor position to facilitate cursor based editing.
8#[derive(Debug, PartialEq, Eq, Clone, Default)]
9pub struct LineBuffer {
10    lines: String,
11    insertion_point: usize,
12}
13
14impl From<&str> for LineBuffer {
15    fn from(input: &str) -> Self {
16        let mut line_buffer = LineBuffer::new();
17        line_buffer.insert_str(input);
18        line_buffer
19    }
20}
21
22impl LineBuffer {
23    /// Create a line buffer instance
24    pub fn new() -> LineBuffer {
25        Self::default()
26    }
27
28    /// Check to see if the line buffer is empty
29    pub fn is_empty(&self) -> bool {
30        self.lines.is_empty()
31    }
32
33    /// Check if the line buffer is valid utf-8 and the cursor sits on a valid grapheme boundary
34    pub fn is_valid(&self) -> bool {
35        self.lines.is_char_boundary(self.insertion_point())
36            && (self
37                .lines
38                .grapheme_indices(true)
39                .any(|(i, _)| i == self.insertion_point())
40                || self.insertion_point() == self.lines.len())
41            && std::str::from_utf8(self.lines.as_bytes()).is_ok()
42    }
43
44    #[cfg(test)]
45    fn assert_valid(&self) {
46        assert!(
47            self.lines.is_char_boundary(self.insertion_point()),
48            "Not on valid char boundary"
49        );
50        assert!(
51            self.lines
52                .grapheme_indices(true)
53                .any(|(i, _)| i == self.insertion_point())
54                || self.insertion_point() == self.lines.len(),
55            "Not on valid grapheme"
56        );
57        assert!(
58            std::str::from_utf8(self.lines.as_bytes()).is_ok(),
59            "Not valid utf-8"
60        );
61    }
62
63    /// Gets the current edit position
64    pub fn insertion_point(&self) -> usize {
65        self.insertion_point
66    }
67
68    /// Sets the current edit position
69    /// ## Unicode safety:
70    /// Not checked, improper use may cause panics in following operations
71    pub fn set_insertion_point(&mut self, offset: usize) {
72        self.insertion_point = offset;
73    }
74
75    /// Output the current line in the multiline buffer
76    pub fn get_buffer(&self) -> &str {
77        &self.lines
78    }
79
80    /// Set to a single line of `buffer` and reset the `InsertionPoint` cursor to the end
81    pub fn set_buffer(&mut self, buffer: String) {
82        self.lines = buffer;
83        self.insertion_point = self.lines.len();
84    }
85
86    /// Calculates the current the user is on
87    ///
88    /// Zero-based index
89    pub fn line(&self) -> usize {
90        self.lines[..self.insertion_point].matches('\n').count()
91    }
92
93    /// Counts the number of lines in the buffer
94    pub fn num_lines(&self) -> usize {
95        self.lines.split('\n').count()
96    }
97
98    /// Checks to see if the buffer ends with a given character
99    pub fn ends_with(&self, c: char) -> bool {
100        self.lines.ends_with(c)
101    }
102
103    /// Reset the insertion point to the start of the buffer
104    pub fn move_to_start(&mut self) {
105        self.insertion_point = 0;
106    }
107
108    /// Move the cursor before the first character of the line
109    pub fn move_to_line_start(&mut self) {
110        self.insertion_point = self.lines[..self.insertion_point]
111            .rfind('\n')
112            .map_or(0, |offset| offset + 1);
113        // str is guaranteed to be utf8, thus \n is safe to assume 1 byte long
114    }
115
116    /// Move cursor position to the end of the line
117    ///
118    /// Insertion will append to the line.
119    /// Cursor on top of the potential `\n` or `\r` of `\r\n`
120    pub fn move_to_line_end(&mut self) {
121        self.insertion_point = self.find_current_line_end();
122    }
123
124    /// Set the insertion point *behind* the last character.
125    pub fn move_to_end(&mut self) {
126        self.insertion_point = self.lines.len();
127    }
128
129    /// Get the length of the buffer
130    pub fn len(&self) -> usize {
131        self.lines.len()
132    }
133
134    /// Returns where the current line terminates
135    ///
136    /// Either:
137    /// - end of buffer (`len()`)
138    /// - `\n` or `\r\n` (on the first byte)
139    pub fn find_current_line_end(&self) -> usize {
140        self.lines[self.insertion_point..].find('\n').map_or_else(
141            || self.lines.len(),
142            |i| {
143                let absolute_index = i + self.insertion_point;
144                if absolute_index > 0 && self.lines.as_bytes()[absolute_index - 1] == b'\r' {
145                    absolute_index - 1
146                } else {
147                    absolute_index
148                }
149            },
150        )
151    }
152
153    /// Cursor position *behind* the next unicode grapheme to the right
154    pub fn grapheme_right_index(&self) -> usize {
155        self.lines[self.insertion_point..]
156            .grapheme_indices(true)
157            .nth(1)
158            .map(|(i, _)| self.insertion_point + i)
159            .unwrap_or_else(|| self.lines.len())
160    }
161
162    /// Cursor position *in front of* the next unicode grapheme to the left
163    pub fn grapheme_left_index(&self) -> usize {
164        self.lines[..self.insertion_point]
165            .grapheme_indices(true)
166            .last()
167            .map(|(i, _)| i)
168            .unwrap_or(0)
169    }
170
171    /// Cursor position *behind* the next word to the right
172    pub fn word_right_index(&self) -> usize {
173        self.lines[self.insertion_point..]
174            .split_word_bound_indices()
175            .find(|(_, word)| !is_whitespace_str(word))
176            .map(|(i, word)| self.insertion_point + i + word.len())
177            .unwrap_or_else(|| self.lines.len())
178    }
179
180    /// Cursor position *behind* the next WORD to the right
181    pub fn big_word_right_index(&self) -> usize {
182        let mut found_ws = false;
183
184        self.lines[self.insertion_point..]
185            .split_word_bound_indices()
186            .find(|(_, word)| {
187                found_ws = found_ws || is_whitespace_str(word);
188                found_ws && !is_whitespace_str(word)
189            })
190            .map(|(i, word)| self.insertion_point + i + word.len())
191            .unwrap_or_else(|| self.lines.len())
192    }
193
194    /// Cursor position *at end of* the next word to the right
195    pub fn word_right_end_index(&self) -> usize {
196        self.lines[self.insertion_point..]
197            .split_word_bound_indices()
198            .find_map(|(i, word)| {
199                word.grapheme_indices(true)
200                    .next_back()
201                    .map(|x| self.insertion_point + x.0 + i)
202                    .filter(|x| !is_whitespace_str(word) && *x != self.insertion_point)
203            })
204            .unwrap_or_else(|| {
205                self.lines
206                    .grapheme_indices(true)
207                    .last()
208                    .map(|x| x.0)
209                    .unwrap_or(0)
210            })
211    }
212
213    /// Cursor position *at end of* the next WORD to the right
214    pub fn big_word_right_end_index(&self) -> usize {
215        self.lines[self.insertion_point..]
216            .split_word_bound_indices()
217            .tuple_windows()
218            .find_map(|((prev_i, prev_word), (_, word))| {
219                if is_whitespace_str(word) {
220                    prev_word
221                        .grapheme_indices(true)
222                        .next_back()
223                        .map(|x| self.insertion_point + x.0 + prev_i)
224                        .filter(|x| *x != self.insertion_point)
225                } else {
226                    None
227                }
228            })
229            .unwrap_or_else(|| {
230                self.lines
231                    .grapheme_indices(true)
232                    .last()
233                    .map(|x| x.0)
234                    .unwrap_or(0)
235            })
236    }
237
238    /// Cursor position *in front of* the next word to the right
239    pub fn word_right_start_index(&self) -> usize {
240        self.lines[self.insertion_point..]
241            .split_word_bound_indices()
242            .find(|(i, word)| *i != 0 && !is_whitespace_str(word))
243            .map(|(i, _)| self.insertion_point + i)
244            .unwrap_or_else(|| self.lines.len())
245    }
246
247    /// Cursor position *in front of* the next WORD to the right
248    pub fn big_word_right_start_index(&self) -> usize {
249        let mut found_ws = false;
250
251        self.lines[self.insertion_point..]
252            .split_word_bound_indices()
253            .find(|(i, word)| {
254                found_ws = found_ws || *i != 0 && is_whitespace_str(word);
255                found_ws && *i != 0 && !is_whitespace_str(word)
256            })
257            .map(|(i, _)| self.insertion_point + i)
258            .unwrap_or_else(|| self.lines.len())
259    }
260
261    /// Cursor position *in front of* the next word to the left
262    pub fn word_left_index(&self) -> usize {
263        self.lines[..self.insertion_point]
264            .split_word_bound_indices()
265            .filter(|(_, word)| !is_whitespace_str(word))
266            .last()
267            .map(|(i, _)| i)
268            .unwrap_or(0)
269    }
270
271    /// Cursor position *in front of* the next WORD to the left
272    pub fn big_word_left_index(&self) -> usize {
273        self.lines[..self.insertion_point]
274            .split_word_bound_indices()
275            .fold(None, |last_word_index, (i, word)| {
276                match (last_word_index, is_whitespace_str(word)) {
277                    (None, true) => None,
278                    (None, false) => Some(i),
279                    (Some(v), true) => {
280                        if is_whitespace_str(&self.lines[i..self.insertion_point]) {
281                            Some(v)
282                        } else {
283                            None
284                        }
285                    }
286                    (Some(v), false) => Some(v),
287                }
288            })
289            .unwrap_or(0)
290    }
291
292    /// Cursor position on the next whitespace
293    pub fn next_whitespace(&self) -> usize {
294        self.lines[self.insertion_point..]
295            .split_word_bound_indices()
296            .find(|(i, word)| *i != 0 && is_whitespace_str(word))
297            .map(|(i, _)| self.insertion_point + i)
298            .unwrap_or_else(|| self.lines.len())
299    }
300
301    /// Move cursor position *behind* the next unicode grapheme to the right
302    pub fn move_right(&mut self) {
303        self.insertion_point = self.grapheme_right_index();
304    }
305
306    /// Move cursor position *in front of* the next unicode grapheme to the left
307    pub fn move_left(&mut self) {
308        self.insertion_point = self.grapheme_left_index();
309    }
310
311    /// Move cursor position *in front of* the next word to the left
312    pub fn move_word_left(&mut self) {
313        self.insertion_point = self.word_left_index();
314    }
315
316    /// Move cursor position *in front of* the next WORD to the left
317    pub fn move_big_word_left(&mut self) {
318        self.insertion_point = self.big_word_left_index();
319    }
320
321    /// Move cursor position *behind* the next word to the right
322    pub fn move_word_right(&mut self) {
323        self.insertion_point = self.word_right_index();
324    }
325
326    /// Move cursor position to the start of the next word
327    pub fn move_word_right_start(&mut self) {
328        self.insertion_point = self.word_right_start_index();
329    }
330
331    /// Move cursor position to the start of the next WORD
332    pub fn move_big_word_right_start(&mut self) {
333        self.insertion_point = self.big_word_right_start_index();
334    }
335
336    /// Move cursor position to the end of the next word
337    pub fn move_word_right_end(&mut self) {
338        self.insertion_point = self.word_right_end_index();
339    }
340
341    /// Move cursor position to the end of the next WORD
342    pub fn move_big_word_right_end(&mut self) {
343        self.insertion_point = self.big_word_right_end_index();
344    }
345
346    ///Insert a single character at the insertion point and move right
347    pub fn insert_char(&mut self, c: char) {
348        self.lines.insert(self.insertion_point, c);
349        self.move_right();
350    }
351
352    /// Insert `&str` at the cursor position in the current line.
353    ///
354    /// Sets cursor to end of inserted string
355    ///
356    /// ## Unicode safety:
357    /// Does not validate the incoming string or the current cursor position
358    pub fn insert_str(&mut self, string: &str) {
359        self.lines.insert_str(self.insertion_point(), string);
360        self.insertion_point = self.insertion_point() + string.len();
361    }
362
363    /// Inserts the system specific new line character
364    ///
365    /// - On Unix systems LF (`"\n"`)
366    /// - On Windows CRLF (`"\r\n"`)
367    pub fn insert_newline(&mut self) {
368        #[cfg(target_os = "windows")]
369        self.insert_str("\r\n");
370        #[cfg(not(target_os = "windows"))]
371        self.insert_char('\n');
372    }
373
374    /// Empty buffer and reset cursor
375    pub fn clear(&mut self) {
376        self.lines = String::new();
377        self.insertion_point = 0;
378    }
379
380    /// Clear everything beginning at the cursor to the right/end.
381    /// Keeps the cursor at the end.
382    pub fn clear_to_end(&mut self) {
383        self.lines.truncate(self.insertion_point);
384    }
385
386    /// Clear beginning at the cursor up to the end of the line.
387    /// Newline character at the end remains.
388    pub fn clear_to_line_end(&mut self) {
389        self.clear_range(self.insertion_point..self.find_current_line_end());
390    }
391
392    /// Clear from the start of the buffer to the cursor.
393    /// Keeps the cursor at the beginning of the line/buffer.
394    pub fn clear_to_insertion_point(&mut self) {
395        self.clear_range(..self.insertion_point);
396        self.insertion_point = 0;
397    }
398
399    /// Clear text covered by `range` in the current line
400    ///
401    /// Safety: Does not change the insertion point/offset and is thus not unicode safe!
402    pub(crate) fn clear_range<R>(&mut self, range: R)
403    where
404        R: std::ops::RangeBounds<usize>,
405    {
406        self.replace_range(range, "");
407    }
408
409    /// Substitute text covered by `range` in the current line
410    ///
411    /// Safety: Does not change the insertion point/offset and is thus not unicode safe!
412    pub fn replace_range<R>(&mut self, range: R, replace_with: &str)
413    where
414        R: std::ops::RangeBounds<usize>,
415    {
416        self.lines.replace_range(range, replace_with);
417    }
418
419    /// Checks to see if the current edit position is pointing to whitespace
420    pub fn on_whitespace(&self) -> bool {
421        self.lines[self.insertion_point..]
422            .chars()
423            .next()
424            .map(char::is_whitespace)
425            .unwrap_or(false)
426    }
427
428    /// Get the grapheme immediately to the right of the cursor, if any
429    pub fn grapheme_right(&self) -> &str {
430        &self.lines[self.insertion_point..self.grapheme_right_index()]
431    }
432
433    /// Get the grapheme immediately to the left of the cursor, if any
434    pub fn grapheme_left(&self) -> &str {
435        &self.lines[self.grapheme_left_index()..self.insertion_point]
436    }
437
438    /// Gets the range of the word the current edit position is pointing to
439    pub fn current_word_range(&self) -> Range<usize> {
440        let right_index = self.word_right_index();
441        let left_index = self.lines[..right_index]
442            .split_word_bound_indices()
443            .filter(|(_, word)| !is_whitespace_str(word))
444            .last()
445            .map(|(i, _)| i)
446            .unwrap_or(0);
447
448        left_index..right_index
449    }
450
451    /// Range over the current line
452    ///
453    /// Starts on the first non-newline character and is an exclusive range
454    /// extending beyond the potential carriage return and line feed characters
455    /// terminating the line
456    pub fn current_line_range(&self) -> Range<usize> {
457        let left_index = self.lines[..self.insertion_point]
458            .rfind('\n')
459            .map_or(0, |offset| offset + 1);
460        let right_index = self.lines[self.insertion_point..]
461            .find('\n')
462            .map_or_else(|| self.lines.len(), |i| i + self.insertion_point + 1);
463
464        left_index..right_index
465    }
466
467    /// Uppercases the current word
468    pub fn uppercase_word(&mut self) {
469        let change_range = self.current_word_range();
470        let uppercased = self.get_buffer()[change_range.clone()].to_uppercase();
471        self.replace_range(change_range, &uppercased);
472        self.move_word_right();
473    }
474
475    /// Lowercases the current word
476    pub fn lowercase_word(&mut self) {
477        let change_range = self.current_word_range();
478        let uppercased = self.get_buffer()[change_range.clone()].to_lowercase();
479        self.replace_range(change_range, &uppercased);
480        self.move_word_right();
481    }
482
483    /// Switches the ASCII case of the current char
484    pub fn switchcase_char(&mut self) {
485        let insertion_offset = self.insertion_point();
486        let right_index = self.grapheme_right_index();
487
488        if right_index > insertion_offset {
489            let change_range = insertion_offset..right_index;
490            let swapped = self.get_buffer()[change_range.clone()]
491                .chars()
492                .map(|c| {
493                    if c.is_ascii_uppercase() {
494                        c.to_ascii_lowercase()
495                    } else {
496                        c.to_ascii_uppercase()
497                    }
498                })
499                .collect::<String>();
500            self.replace_range(change_range, &swapped);
501            self.move_right();
502        }
503    }
504
505    /// Capitalize the character at insertion point (or the first character
506    /// following the whitespace at the insertion point) and move the insertion
507    /// point right one grapheme.
508    pub fn capitalize_char(&mut self) {
509        if self.on_whitespace() {
510            self.move_word_right();
511            self.move_word_left();
512        }
513        let insertion_offset = self.insertion_point();
514        let right_index = self.grapheme_right_index();
515
516        if right_index > insertion_offset {
517            let change_range = insertion_offset..right_index;
518            let uppercased = self.get_buffer()[change_range.clone()].to_uppercase();
519            self.replace_range(change_range, &uppercased);
520            self.move_right();
521        }
522    }
523
524    /// Deletes on grapheme to the left
525    pub fn delete_left_grapheme(&mut self) {
526        let left_index = self.grapheme_left_index();
527        let insertion_offset = self.insertion_point();
528        if left_index < insertion_offset {
529            self.clear_range(left_index..insertion_offset);
530            self.insertion_point = left_index;
531        }
532    }
533
534    /// Deletes one grapheme to the right
535    pub fn delete_right_grapheme(&mut self) {
536        let right_index = self.grapheme_right_index();
537        let insertion_offset = self.insertion_point();
538        if right_index > insertion_offset {
539            self.clear_range(insertion_offset..right_index);
540        }
541    }
542
543    /// Deletes one word to the left
544    pub fn delete_word_left(&mut self) {
545        let left_word_index = self.word_left_index();
546        self.clear_range(left_word_index..self.insertion_point());
547        self.insertion_point = left_word_index;
548    }
549
550    /// Deletes one word to the right
551    pub fn delete_word_right(&mut self) {
552        let right_word_index = self.word_right_index();
553        self.clear_range(self.insertion_point()..right_word_index);
554    }
555
556    /// Swaps current word with word on right
557    pub fn swap_words(&mut self) {
558        let word_1_range = self.current_word_range();
559        self.move_word_right();
560        let word_2_range = self.current_word_range();
561
562        if word_1_range != word_2_range {
563            self.move_word_left();
564            let insertion_line = self.get_buffer();
565            let word_1 = insertion_line[word_1_range.clone()].to_string();
566            let word_2 = insertion_line[word_2_range.clone()].to_string();
567            self.replace_range(word_2_range, &word_1);
568            self.replace_range(word_1_range, &word_2);
569        }
570    }
571
572    /// Swaps current grapheme with grapheme on right
573    pub fn swap_graphemes(&mut self) {
574        let initial_offset = self.insertion_point();
575
576        if initial_offset == 0 {
577            self.move_right();
578        } else if initial_offset == self.get_buffer().len() {
579            self.move_left();
580        }
581
582        let updated_offset = self.insertion_point();
583        let grapheme_1_start = self.grapheme_left_index();
584        let grapheme_2_end = self.grapheme_right_index();
585
586        if grapheme_1_start < updated_offset && grapheme_2_end > updated_offset {
587            let grapheme_1 = self.get_buffer()[grapheme_1_start..updated_offset].to_string();
588            let grapheme_2 = self.get_buffer()[updated_offset..grapheme_2_end].to_string();
589            self.replace_range(updated_offset..grapheme_2_end, &grapheme_1);
590            self.replace_range(grapheme_1_start..updated_offset, &grapheme_2);
591            self.insertion_point = grapheme_2_end;
592        } else {
593            self.insertion_point = updated_offset;
594        }
595    }
596
597    /// Moves one line up
598    pub fn move_line_up(&mut self) {
599        if !self.is_cursor_at_first_line() {
600            let old_range = self.current_line_range();
601
602            let grapheme_col = self.lines[old_range.start..self.insertion_point()]
603                .graphemes(true)
604                .count();
605
606            // Platform independent way to jump to the previous line.
607            // Doesn't matter if `\n` or `\r\n` terminated line.
608            // Maybe replace with more explicit implementation.
609            self.set_insertion_point(old_range.start);
610            self.move_left();
611
612            let new_range = self.current_line_range();
613            let new_line = &self.lines[new_range.clone()];
614
615            self.insertion_point = new_line
616                .grapheme_indices(true)
617                .take(grapheme_col + 1)
618                .last()
619                .map_or(new_range.start, |(i, _)| i + new_range.start);
620        }
621    }
622
623    /// Moves one line down
624    pub fn move_line_down(&mut self) {
625        if !self.is_cursor_at_last_line() {
626            let old_range = self.current_line_range();
627
628            let grapheme_col = self.lines[old_range.start..self.insertion_point()]
629                .graphemes(true)
630                .count();
631
632            // Exclusive range, thus guaranteed to be in the next line
633            self.set_insertion_point(old_range.end);
634
635            let new_range = self.current_line_range();
636            let new_line = &self.lines[new_range.clone()];
637
638            // Slightly different to move_line_up to account for the special
639            // case of the last line without newline char at the end.
640            // -> use `self.find_current_line_end()`
641            self.insertion_point = new_line
642                .grapheme_indices(true)
643                .nth(grapheme_col)
644                .map_or_else(
645                    || self.find_current_line_end(),
646                    |(i, _)| i + new_range.start,
647                );
648        }
649    }
650
651    /// Checks to see if the cursor is on the first line of the buffer
652    pub fn is_cursor_at_first_line(&self) -> bool {
653        !self.get_buffer()[0..self.insertion_point()].contains('\n')
654    }
655
656    /// Checks to see if the cursor is on the last line of the buffer
657    pub fn is_cursor_at_last_line(&self) -> bool {
658        !self.get_buffer()[self.insertion_point()..].contains('\n')
659    }
660
661    /// Finds index for the first occurrence of a char to the right of offset
662    pub fn find_char_right(&self, c: char, current_line: bool) -> Option<usize> {
663        // Skip current grapheme
664        let char_offset = self.grapheme_right_index();
665        let range = if current_line {
666            char_offset..self.current_line_range().end
667        } else {
668            char_offset..self.lines.len()
669        };
670        self.lines[range].find(c).map(|index| index + char_offset)
671    }
672
673    /// Finds index for the first occurrence of a char to the left of offset
674    pub fn find_char_left(&self, c: char, current_line: bool) -> Option<usize> {
675        let range = if current_line {
676            self.current_line_range().start..self.insertion_point()
677        } else {
678            0..self.insertion_point()
679        };
680        self.lines[range.clone()].rfind(c).map(|i| i + range.start)
681    }
682
683    /// Moves the insertion point until the next char to the right
684    pub fn move_right_until(&mut self, c: char, current_line: bool) -> usize {
685        if let Some(index) = self.find_char_right(c, current_line) {
686            self.insertion_point = index;
687        }
688
689        self.insertion_point
690    }
691
692    /// Moves the insertion point before the next char to the right
693    pub fn move_right_before(&mut self, c: char, current_line: bool) -> usize {
694        if let Some(index) = self.find_char_right(c, current_line) {
695            self.insertion_point = index;
696            self.insertion_point = self.grapheme_left_index();
697        }
698
699        self.insertion_point
700    }
701
702    /// Moves the insertion point until the next char to the left of offset
703    pub fn move_left_until(&mut self, c: char, current_line: bool) -> usize {
704        if let Some(index) = self.find_char_left(c, current_line) {
705            self.insertion_point = index;
706        }
707
708        self.insertion_point
709    }
710
711    /// Moves the insertion point before the next char to the left of offset
712    pub fn move_left_before(&mut self, c: char, current_line: bool) -> usize {
713        if let Some(index) = self.find_char_left(c, current_line) {
714            self.insertion_point = index + c.len_utf8();
715        }
716
717        self.insertion_point
718    }
719
720    /// Deletes until first character to the right of offset
721    pub fn delete_right_until_char(&mut self, c: char, current_line: bool) {
722        if let Some(index) = self.find_char_right(c, current_line) {
723            self.clear_range(self.insertion_point()..index + c.len_utf8());
724        }
725    }
726
727    /// Deletes before first character to the right of offset
728    pub fn delete_right_before_char(&mut self, c: char, current_line: bool) {
729        if let Some(index) = self.find_char_right(c, current_line) {
730            self.clear_range(self.insertion_point()..index);
731        }
732    }
733
734    /// Deletes until first character to the left of offset
735    pub fn delete_left_until_char(&mut self, c: char, current_line: bool) {
736        if let Some(index) = self.find_char_left(c, current_line) {
737            self.clear_range(index..self.insertion_point());
738            self.insertion_point = index;
739        }
740    }
741
742    /// Deletes before first character to the left of offset
743    pub fn delete_left_before_char(&mut self, c: char, current_line: bool) {
744        if let Some(index) = self.find_char_left(c, current_line) {
745            self.clear_range(index + c.len_utf8()..self.insertion_point());
746            self.insertion_point = index + c.len_utf8();
747        }
748    }
749}
750
751/// Match any sequence of characters that are considered a word boundary
752fn is_whitespace_str(s: &str) -> bool {
753    s.chars().all(char::is_whitespace)
754}
755
756#[cfg(test)]
757mod test {
758    use super::*;
759    use pretty_assertions::assert_eq;
760    use rstest::rstest;
761
762    fn buffer_with(content: &str) -> LineBuffer {
763        let mut line_buffer = LineBuffer::new();
764        line_buffer.insert_str(content);
765
766        line_buffer
767    }
768
769    #[test]
770    fn test_new_buffer_is_empty() {
771        let line_buffer = LineBuffer::new();
772        assert!(line_buffer.is_empty());
773        line_buffer.assert_valid();
774    }
775
776    #[test]
777    fn test_clearing_line_buffer_resets_buffer_and_insertion_point() {
778        let mut line_buffer = buffer_with("this is a command");
779        line_buffer.clear();
780        let empty_buffer = LineBuffer::new();
781
782        assert_eq!(line_buffer, empty_buffer);
783        line_buffer.assert_valid();
784    }
785
786    #[test]
787    fn insert_str_updates_insertion_point_point_correctly() {
788        let mut line_buffer = LineBuffer::new();
789        line_buffer.insert_str("this is a command");
790
791        let expected_updated_insertion_point = 17;
792
793        assert_eq!(
794            expected_updated_insertion_point,
795            line_buffer.insertion_point()
796        );
797        line_buffer.assert_valid();
798    }
799
800    #[test]
801    fn insert_char_updates_insertion_point_point_correctly() {
802        let mut line_buffer = LineBuffer::new();
803        line_buffer.insert_char('c');
804
805        let expected_updated_insertion_point = 1;
806
807        assert_eq!(
808            expected_updated_insertion_point,
809            line_buffer.insertion_point()
810        );
811        line_buffer.assert_valid();
812    }
813
814    #[rstest]
815    #[case("new string", 10)]
816    #[case("new line1\nnew line 2", 20)]
817    fn set_buffer_updates_insertion_point_to_new_buffer_length(
818        #[case] string_to_set: &str,
819        #[case] expected_insertion_point: usize,
820    ) {
821        let mut line_buffer = buffer_with("test string");
822        let before_operation_location = 11;
823        assert_eq!(before_operation_location, line_buffer.insertion_point());
824
825        line_buffer.set_buffer(string_to_set.to_string());
826
827        assert_eq!(expected_insertion_point, line_buffer.insertion_point());
828        line_buffer.assert_valid();
829    }
830
831    #[rstest]
832    #[case("This is a test", "This is a tes")]
833    #[case("This is a test ๐Ÿ˜Š", "This is a test ")]
834    #[case("", "")]
835    fn delete_left_grapheme_works(#[case] input: &str, #[case] expected: &str) {
836        let mut line_buffer = buffer_with(input);
837        line_buffer.delete_left_grapheme();
838
839        let expected_line_buffer = buffer_with(expected);
840
841        assert_eq!(expected_line_buffer, line_buffer);
842        line_buffer.assert_valid();
843    }
844
845    #[rstest]
846    #[case("This is a test", "This is a tes")]
847    #[case("This is a test ๐Ÿ˜Š", "This is a test ")]
848    #[case("", "")]
849    fn delete_right_grapheme_works(#[case] input: &str, #[case] expected: &str) {
850        let mut line_buffer = buffer_with(input);
851        line_buffer.move_left();
852        line_buffer.delete_right_grapheme();
853
854        let expected_line_buffer = buffer_with(expected);
855
856        assert_eq!(expected_line_buffer, line_buffer);
857        line_buffer.assert_valid();
858    }
859
860    #[test]
861    fn delete_word_left_works() {
862        let mut line_buffer = buffer_with("This is a test");
863        line_buffer.delete_word_left();
864
865        let expected_line_buffer = buffer_with("This is a ");
866
867        assert_eq!(expected_line_buffer, line_buffer);
868        line_buffer.assert_valid();
869    }
870
871    #[test]
872    fn delete_word_right_works() {
873        let mut line_buffer = buffer_with("This is a test");
874        line_buffer.move_word_left();
875        line_buffer.delete_word_right();
876
877        let expected_line_buffer = buffer_with("This is a ");
878
879        assert_eq!(expected_line_buffer, line_buffer);
880        line_buffer.assert_valid();
881    }
882
883    #[rstest]
884    #[case("", 0, 0)] // Basecase
885    #[case("word", 0, 3)] // Cursor on top of the last grapheme of the word
886    #[case("word and another one", 0, 3)]
887    #[case("word and another one", 3, 7)] // repeat calling will move
888    #[case("word and another one", 4, 7)] // Starting from whitespace works
889    #[case("word\nline two", 0, 3)] // Multiline...
890    #[case("word\nline two", 3, 8)] // ... contineus to next word end
891    #[case("weirdรถ characters", 0, 5)] // Multibyte unicode at the word end (latin UTF-8 should be two bytes long)
892    #[case("weirdรถ characters", 5, 17)] // continue with unicode (latin UTF-8 should be two bytes long)
893    #[case("weirdรถ", 0, 5)] // Multibyte unicode at the buffer end is fine as well
894    #[case("weirdรถ", 5, 5)] // Multibyte unicode at the buffer end is fine as well
895    #[case("word๐Ÿ˜‡ with emoji", 0, 3)] // (Emojis are a separate word)
896    #[case("word๐Ÿ˜‡ with emoji", 3, 4)] // Moves to end of "emoji word" as it is one grapheme, on top of the first byte
897    #[case("๐Ÿ˜‡", 0, 0)] // More UTF-8 shenanigans
898    fn test_move_word_right_end(
899        #[case] input: &str,
900        #[case] in_location: usize,
901        #[case] expected: usize,
902    ) {
903        let mut line_buffer = buffer_with(input);
904        line_buffer.set_insertion_point(in_location);
905
906        line_buffer.move_word_right_end();
907
908        assert_eq!(line_buffer.insertion_point(), expected);
909        line_buffer.assert_valid();
910    }
911
912    #[rstest]
913    #[case("This is a test", 13, "This is a tesT", 14)]
914    #[case("This is a test", 10, "This is a Test", 11)]
915    #[case("This is a test", 9, "This is a Test", 11)]
916    fn capitalize_char_works(
917        #[case] input: &str,
918        #[case] in_location: usize,
919        #[case] output: &str,
920        #[case] out_location: usize,
921    ) {
922        let mut line_buffer = buffer_with(input);
923        line_buffer.set_insertion_point(in_location);
924        line_buffer.capitalize_char();
925
926        let mut expected = buffer_with(output);
927        expected.set_insertion_point(out_location);
928
929        assert_eq!(expected, line_buffer);
930        line_buffer.assert_valid();
931    }
932
933    #[rstest]
934    #[case("This is a test", 13, "This is a TEST", 14)]
935    #[case("This is a test", 10, "This is a TEST", 14)]
936    #[case("", 0, "", 0)]
937    #[case("This", 0, "THIS", 4)]
938    #[case("This", 4, "THIS", 4)]
939    fn uppercase_word_works(
940        #[case] input: &str,
941        #[case] in_location: usize,
942        #[case] output: &str,
943        #[case] out_location: usize,
944    ) {
945        let mut line_buffer = buffer_with(input);
946        line_buffer.set_insertion_point(in_location);
947        line_buffer.uppercase_word();
948
949        let mut expected = buffer_with(output);
950        expected.set_insertion_point(out_location);
951
952        assert_eq!(expected, line_buffer);
953        line_buffer.assert_valid();
954    }
955
956    #[rstest]
957    #[case("This is a TEST", 13, "This is a test", 14)]
958    #[case("This is a TEST", 10, "This is a test", 14)]
959    #[case("", 0, "", 0)]
960    #[case("THIS", 0, "this", 4)]
961    #[case("THIS", 4, "this", 4)]
962    fn lowercase_word_works(
963        #[case] input: &str,
964        #[case] in_location: usize,
965        #[case] output: &str,
966        #[case] out_location: usize,
967    ) {
968        let mut line_buffer = buffer_with(input);
969        line_buffer.set_insertion_point(in_location);
970        line_buffer.lowercase_word();
971
972        let mut expected = buffer_with(output);
973        expected.set_insertion_point(out_location);
974
975        assert_eq!(expected, line_buffer);
976        line_buffer.assert_valid();
977    }
978
979    #[rstest]
980    #[case("", 0, "", 0)]
981    #[case("a test", 2, "a Test", 3)]
982    #[case("a Test", 2, "a test", 3)]
983    #[case("test", 0, "Test", 1)]
984    #[case("Test", 0, "test", 1)]
985    #[case("test", 3, "tesT", 4)]
986    #[case("tesT", 3, "test", 4)]
987    #[case("รŸ", 0, "รŸ", 2)]
988    fn switchcase_char(
989        #[case] input: &str,
990        #[case] in_location: usize,
991        #[case] output: &str,
992        #[case] out_location: usize,
993    ) {
994        let mut line_buffer = buffer_with(input);
995        line_buffer.set_insertion_point(in_location);
996        line_buffer.switchcase_char();
997
998        let mut expected = buffer_with(output);
999        expected.set_insertion_point(out_location);
1000
1001        assert_eq!(expected, line_buffer);
1002        line_buffer.assert_valid();
1003    }
1004
1005    #[rstest]
1006    #[case("This is a test", 13, "This is a tets", 14)]
1007    #[case("This is a test", 14, "This is a tets", 14)] // NOTE: Swapping works in opposite direction at last index
1008    #[case("This is a test", 4, "Thi sis a test", 5)] // NOTE: Swaps space, moves right
1009    #[case("This is a test", 0, "hTis is a test", 2)]
1010    fn swap_graphemes_work(
1011        #[case] input: &str,
1012        #[case] in_location: usize,
1013        #[case] output: &str,
1014        #[case] out_location: usize,
1015    ) {
1016        let mut line_buffer = buffer_with(input);
1017        line_buffer.set_insertion_point(in_location);
1018
1019        line_buffer.swap_graphemes();
1020
1021        let mut expected = buffer_with(output);
1022        expected.set_insertion_point(out_location);
1023
1024        assert_eq!(line_buffer, expected);
1025        line_buffer.assert_valid();
1026    }
1027
1028    #[rstest]
1029    #[case("This is a test", 8, "This is test a", 8)]
1030    #[case("This is a test", 0, "is This a test", 0)]
1031    #[case("This is a test", 14, "This is a test", 14)]
1032    fn swap_words_works(
1033        #[case] input: &str,
1034        #[case] in_location: usize,
1035        #[case] output: &str,
1036        #[case] out_location: usize,
1037    ) {
1038        let mut line_buffer = buffer_with(input);
1039        line_buffer.set_insertion_point(in_location);
1040
1041        line_buffer.swap_words();
1042
1043        let mut expected = buffer_with(output);
1044        expected.set_insertion_point(out_location);
1045
1046        assert_eq!(line_buffer, expected);
1047        line_buffer.assert_valid();
1048    }
1049
1050    #[rstest]
1051    #[case("line 1\nline 2", 7, 0)]
1052    #[case("line 1\nline 2", 8, 1)]
1053    #[case("line 1\nline 2", 0, 0)]
1054    #[case("line\nlong line", 14, 4)]
1055    #[case("line\nlong line", 8, 3)]
1056    #[case("line 1\n๐Ÿ˜‡line 2", 11, 1)]
1057    #[case("line\n\nline", 8, 5)]
1058    fn moving_up_works(
1059        #[case] input: &str,
1060        #[case] in_location: usize,
1061        #[case] out_location: usize,
1062    ) {
1063        let mut line_buffer = buffer_with(input);
1064        line_buffer.set_insertion_point(in_location);
1065
1066        line_buffer.move_line_up();
1067
1068        let mut expected = buffer_with(input);
1069        expected.set_insertion_point(out_location);
1070
1071        assert_eq!(line_buffer, expected);
1072        line_buffer.assert_valid();
1073    }
1074
1075    #[rstest]
1076    #[case("line 1", 0, 0)]
1077    #[case("line 1\nline 2", 0, 7)]
1078    #[case("line 1\n๐Ÿ˜‡line 2", 1, 11)]
1079    #[case("line ๐Ÿ˜‡ 1\nline 2 long", 9, 18)]
1080    #[case("line 1\nline 2", 7, 7)]
1081    #[case("long line\nline", 8, 14)]
1082    #[case("long line\nline", 4, 14)]
1083    #[case("long line\nline", 3, 13)]
1084    #[case("long line\nline\nline", 8, 14)]
1085    #[case("line\n\nline", 3, 5)]
1086    fn moving_down_works(
1087        #[case] input: &str,
1088        #[case] in_location: usize,
1089        #[case] out_location: usize,
1090    ) {
1091        let mut line_buffer = buffer_with(input);
1092        line_buffer.set_insertion_point(in_location);
1093
1094        line_buffer.move_line_down();
1095
1096        let mut expected = buffer_with(input);
1097        expected.set_insertion_point(out_location);
1098
1099        assert_eq!(line_buffer, expected);
1100        line_buffer.assert_valid();
1101    }
1102
1103    #[rstest]
1104    #[case("line", 4, true)]
1105    #[case("line 1\nline 2\nline 3", 0, true)]
1106    #[case("line 1\nline 2\nline 3", 6, true)]
1107    #[case("line 1\nline 2\nline 3", 8, false)]
1108    fn test_first_line_detection(
1109        #[case] input: &str,
1110        #[case] in_location: usize,
1111        #[case] expected: bool,
1112    ) {
1113        let mut line_buffer = buffer_with(input);
1114        line_buffer.set_insertion_point(in_location);
1115        line_buffer.assert_valid();
1116
1117        assert_eq!(line_buffer.is_cursor_at_first_line(), expected);
1118    }
1119
1120    #[rstest]
1121    #[case("line", 4, true)]
1122    #[case("line\nline", 9, true)]
1123    #[case("line 1\nline 2\nline 3", 8, false)]
1124    #[case("line 1\nline 2\nline 3", 13, false)]
1125    #[case("line 1\nline 2\nline 3", 14, true)]
1126    #[case("line 1\nline 2\nline 3", 20, true)]
1127    #[case("line 1\nline 2\nline 3\n", 20, false)]
1128    #[case("line 1\nline 2\nline 3\n", 21, true)]
1129    fn test_last_line_detection(
1130        #[case] input: &str,
1131        #[case] in_location: usize,
1132        #[case] expected: bool,
1133    ) {
1134        let mut line_buffer = buffer_with(input);
1135        line_buffer.set_insertion_point(in_location);
1136        line_buffer.assert_valid();
1137
1138        assert_eq!(line_buffer.is_cursor_at_last_line(), expected);
1139    }
1140
1141    #[rstest]
1142    #[case("abc def ghi", 0, 'c', true, 2)]
1143    #[case("abc def ghi", 0, 'a', true, 0)]
1144    #[case("abc def ghi", 0, 'z', true, 0)]
1145    #[case("a๐Ÿ˜‡c", 0, 'c', true, 5)]
1146    #[case("๐Ÿ˜‡bc", 0, 'c', true, 5)]
1147    #[case("abc\ndef", 0, 'f', true, 0)]
1148    #[case("abc\ndef", 3, 'f', true, 3)]
1149    #[case("abc\ndef", 0, 'f', false, 6)]
1150    #[case("abc\ndef", 3, 'f', false, 6)]
1151    fn test_move_right_until(
1152        #[case] input: &str,
1153        #[case] position: usize,
1154        #[case] c: char,
1155        #[case] current_line: bool,
1156        #[case] expected: usize,
1157    ) {
1158        let mut line_buffer = buffer_with(input);
1159        line_buffer.set_insertion_point(position);
1160
1161        line_buffer.move_right_until(c, current_line);
1162
1163        assert_eq!(line_buffer.insertion_point(), expected);
1164        line_buffer.assert_valid();
1165    }
1166
1167    #[rstest]
1168    #[case("abc def ghi", 0, 'd', true, 3)]
1169    #[case("abc def ghi", 3, 'd', true, 3)]
1170    #[case("a๐Ÿ˜‡c", 0, 'c', true, 1)]
1171    #[case("๐Ÿ˜‡bc", 0, 'c', true, 4)]
1172    fn test_move_right_before(
1173        #[case] input: &str,
1174        #[case] position: usize,
1175        #[case] c: char,
1176        #[case] current_line: bool,
1177        #[case] expected: usize,
1178    ) {
1179        let mut line_buffer = buffer_with(input);
1180        line_buffer.set_insertion_point(position);
1181
1182        line_buffer.move_right_before(c, current_line);
1183
1184        assert_eq!(line_buffer.insertion_point(), expected);
1185        line_buffer.assert_valid();
1186    }
1187
1188    #[rstest]
1189    #[case("abc def ghi", 0, 'd', true, "ef ghi")]
1190    #[case("abc def ghi", 0, 'i', true, "")]
1191    #[case("abc def ghi", 0, 'z', true, "abc def ghi")]
1192    #[case("abc def ghi", 0, 'a', true, "abc def ghi")]
1193    fn test_delete_until(
1194        #[case] input: &str,
1195        #[case] position: usize,
1196        #[case] c: char,
1197        #[case] current_line: bool,
1198        #[case] expected: &str,
1199    ) {
1200        let mut line_buffer = buffer_with(input);
1201        line_buffer.set_insertion_point(position);
1202
1203        line_buffer.delete_right_until_char(c, current_line);
1204
1205        assert_eq!(line_buffer.lines, expected);
1206        line_buffer.assert_valid();
1207    }
1208
1209    #[rstest]
1210    #[case("abc def ghi", 0, 'b', true, "bc def ghi")]
1211    #[case("abc def ghi", 0, 'i', true, "i")]
1212    #[case("abc def ghi", 0, 'z', true, "abc def ghi")]
1213    fn test_delete_before(
1214        #[case] input: &str,
1215        #[case] position: usize,
1216        #[case] c: char,
1217        #[case] current_line: bool,
1218        #[case] expected: &str,
1219    ) {
1220        let mut line_buffer = buffer_with(input);
1221        line_buffer.set_insertion_point(position);
1222
1223        line_buffer.delete_right_before_char(c, current_line);
1224
1225        assert_eq!(line_buffer.lines, expected);
1226        line_buffer.assert_valid();
1227    }
1228
1229    #[rstest]
1230    #[case("abc def ghi", 4, 'c', true, 2)]
1231    #[case("abc def ghi", 0, 'a', true, 0)]
1232    #[case("abc def ghi", 6, 'a', true, 0)]
1233    fn test_move_left_until(
1234        #[case] input: &str,
1235        #[case] position: usize,
1236        #[case] c: char,
1237        #[case] current_line: bool,
1238        #[case] expected: usize,
1239    ) {
1240        let mut line_buffer = buffer_with(input);
1241        line_buffer.set_insertion_point(position);
1242
1243        line_buffer.move_left_until(c, current_line);
1244
1245        assert_eq!(line_buffer.insertion_point(), expected);
1246        line_buffer.assert_valid();
1247    }
1248
1249    #[rstest]
1250    #[case("abc def ghi", 4, 'c', true, 3)]
1251    #[case("abc def ghi", 0, 'a', true, 0)]
1252    #[case("abc def ghi", 6, 'a', true, 1)]
1253    fn test_move_left_before(
1254        #[case] input: &str,
1255        #[case] position: usize,
1256        #[case] c: char,
1257        #[case] current_line: bool,
1258        #[case] expected: usize,
1259    ) {
1260        let mut line_buffer = buffer_with(input);
1261        line_buffer.set_insertion_point(position);
1262
1263        line_buffer.move_left_before(c, current_line);
1264
1265        assert_eq!(line_buffer.insertion_point(), expected);
1266        line_buffer.assert_valid();
1267    }
1268
1269    #[rstest]
1270    #[case("abc def ghi", 5, 'b', true, "aef ghi")]
1271    #[case("abc def ghi", 5, 'e', true, "abc def ghi")]
1272    #[case("abc def ghi", 10, 'a', true, "i")]
1273    #[case("z\nabc def ghi", 10, 'z', true, "z\nabc def ghi")]
1274    #[case("z\nabc def ghi", 12, 'z', false, "i")]
1275    fn test_delete_until_left(
1276        #[case] input: &str,
1277        #[case] position: usize,
1278        #[case] c: char,
1279        #[case] current_line: bool,
1280        #[case] expected: &str,
1281    ) {
1282        let mut line_buffer = buffer_with(input);
1283        line_buffer.set_insertion_point(position);
1284
1285        line_buffer.delete_left_until_char(c, current_line);
1286
1287        assert_eq!(line_buffer.lines, expected);
1288        line_buffer.assert_valid();
1289    }
1290
1291    #[rstest]
1292    #[case("abc def ghi", 5, 'b', true, "abef ghi")]
1293    #[case("abc def ghi", 5, 'e', true, "abc def ghi")]
1294    #[case("abc def ghi", 10, 'a', true, "ai")]
1295    fn test_delete_before_left(
1296        #[case] input: &str,
1297        #[case] position: usize,
1298        #[case] c: char,
1299        #[case] current_line: bool,
1300        #[case] expected: &str,
1301    ) {
1302        let mut line_buffer = buffer_with(input);
1303        line_buffer.set_insertion_point(position);
1304
1305        line_buffer.delete_left_before_char(c, current_line);
1306
1307        assert_eq!(line_buffer.lines, expected);
1308        line_buffer.assert_valid();
1309    }
1310
1311    #[rstest]
1312    #[case("line", 0, 4)]
1313    #[case("line\nline", 1, 4)]
1314    #[case("line\nline", 7, 9)]
1315    // TODO: Check if this behavior is desired for full vi consistency
1316    #[case("line\n", 4, 4)]
1317    #[case("line\n", 5, 5)]
1318    // Platform agnostic
1319    #[case("\n", 0, 0)]
1320    #[case("\r\n", 0, 0)]
1321    #[case("line\r\nword", 1, 4)]
1322    #[case("line\r\nword", 7, 10)]
1323    fn test_find_current_line_end(
1324        #[case] input: &str,
1325        #[case] in_location: usize,
1326        #[case] expected: usize,
1327    ) {
1328        let mut line_buffer = buffer_with(input);
1329        line_buffer.set_insertion_point(in_location);
1330        line_buffer.assert_valid();
1331
1332        assert_eq!(line_buffer.find_current_line_end(), expected);
1333    }
1334
1335    #[rstest]
1336    #[case("", 0, 0)]
1337    #[case("\n", 0, 0)]
1338    #[case("\n", 1, 1)]
1339    #[case("a\nb", 0, 0)]
1340    #[case("a\nb", 1, 0)]
1341    #[case("a\nb", 2, 1)]
1342    #[case("a\nbc", 3, 1)]
1343    #[case("a\r\nb", 3, 1)]
1344    #[case("a\r\nbc", 4, 1)]
1345    fn test_current_line_num(
1346        #[case] input: &str,
1347        #[case] in_location: usize,
1348        #[case] expected: usize,
1349    ) {
1350        let mut line_buffer = buffer_with(input);
1351        line_buffer.set_insertion_point(in_location);
1352        line_buffer.assert_valid();
1353
1354        assert_eq!(line_buffer.line(), expected);
1355    }
1356
1357    #[rstest]
1358    #[case("", 0, 1)]
1359    #[case("line", 0, 1)]
1360    #[case("\n", 0, 2)]
1361    #[case("line\n", 0, 2)]
1362    #[case("a\nb", 0, 2)]
1363    fn test_num_lines(#[case] input: &str, #[case] in_location: usize, #[case] expected: usize) {
1364        let mut line_buffer = buffer_with(input);
1365        line_buffer.set_insertion_point(in_location);
1366        line_buffer.assert_valid();
1367
1368        assert_eq!(line_buffer.num_lines(), expected);
1369    }
1370
1371    #[rstest]
1372    #[case("", 0, 0)]
1373    #[case("line", 0, 4)]
1374    #[case("\n", 0, 0)]
1375    #[case("line\n", 0, 4)]
1376    #[case("a\nb", 2, 3)]
1377    #[case("a\nb", 0, 1)]
1378    #[case("a\r\nb", 0, 1)]
1379    fn test_move_to_line_end(
1380        #[case] input: &str,
1381        #[case] in_location: usize,
1382        #[case] expected: usize,
1383    ) {
1384        let mut line_buffer = buffer_with(input);
1385        line_buffer.set_insertion_point(in_location);
1386
1387        line_buffer.move_to_line_end();
1388
1389        assert_eq!(line_buffer.insertion_point(), expected);
1390        line_buffer.assert_valid();
1391    }
1392
1393    #[rstest]
1394    #[case("", 0, 0)]
1395    #[case("line", 3, 0)]
1396    #[case("\n", 1, 1)]
1397    #[case("\n", 0, 0)]
1398    #[case("\nline", 3, 1)]
1399    #[case("a\nb", 2, 2)]
1400    #[case("a\nb", 3, 2)]
1401    #[case("a\r\nb", 3, 3)]
1402    fn test_move_to_line_start(
1403        #[case] input: &str,
1404        #[case] in_location: usize,
1405        #[case] expected: usize,
1406    ) {
1407        let mut line_buffer = buffer_with(input);
1408        line_buffer.set_insertion_point(in_location);
1409
1410        line_buffer.move_to_line_start();
1411
1412        assert_eq!(line_buffer.insertion_point(), expected);
1413        line_buffer.assert_valid();
1414    }
1415
1416    #[rstest]
1417    #[case("", 0, 0..0)]
1418    #[case("line", 0, 0..4)]
1419    #[case("line\n", 0, 0..5)]
1420    #[case("line\n", 4, 0..5)]
1421    #[case("line\r\n", 0, 0..6)]
1422    #[case("line\r\n", 4, 0..6)] // Position 5 would be invalid from a grapheme perspective
1423    #[case("line\nsecond", 5, 5..11)]
1424    #[case("line\r\nsecond", 7, 6..12)]
1425    fn test_current_line_range(
1426        #[case] input: &str,
1427        #[case] in_location: usize,
1428        #[case] expected: Range<usize>,
1429    ) {
1430        let mut line_buffer = buffer_with(input);
1431        line_buffer.set_insertion_point(in_location);
1432        line_buffer.assert_valid();
1433
1434        assert_eq!(line_buffer.current_line_range(), expected);
1435    }
1436
1437    #[rstest]
1438    #[case("This is a test", 7, "This is", 7)]
1439    #[case("This is a test\nunrelated", 7, "This is\nunrelated", 7)]
1440    #[case("This is a test\r\nunrelated", 7, "This is\r\nunrelated", 7)]
1441    fn test_clear_to_line_end(
1442        #[case] input: &str,
1443        #[case] in_location: usize,
1444        #[case] output: &str,
1445        #[case] out_location: usize,
1446    ) {
1447        let mut line_buffer = buffer_with(input);
1448        line_buffer.set_insertion_point(in_location);
1449
1450        line_buffer.clear_to_line_end();
1451
1452        let mut expected = buffer_with(output);
1453        expected.set_insertion_point(out_location);
1454
1455        assert_eq!(expected, line_buffer);
1456        line_buffer.assert_valid();
1457    }
1458
1459    #[rstest]
1460    #[case("abc def ghi", 10, 8)]
1461    #[case("abc def-ghi", 10, 8)]
1462    #[case("abc def.ghi", 10, 4)]
1463    fn test_word_left_index(#[case] input: &str, #[case] position: usize, #[case] expected: usize) {
1464        let mut line_buffer = buffer_with(input);
1465        line_buffer.set_insertion_point(position);
1466
1467        let index = line_buffer.word_left_index();
1468
1469        assert_eq!(index, expected);
1470    }
1471
1472    #[rstest]
1473    #[case("abc def ghi", 10, 8)]
1474    #[case("abc def-ghi", 10, 4)]
1475    #[case("abc def.ghi", 10, 4)]
1476    #[case("abc def   i", 10, 4)]
1477    fn test_big_word_left_index(
1478        #[case] input: &str,
1479        #[case] position: usize,
1480        #[case] expected: usize,
1481    ) {
1482        let mut line_buffer = buffer_with(input);
1483        line_buffer.set_insertion_point(position);
1484
1485        let index = line_buffer.big_word_left_index();
1486
1487        assert_eq!(index, expected,);
1488    }
1489
1490    #[rstest]
1491    #[case("abc def ghi", 0, 4)]
1492    #[case("abc-def ghi", 0, 3)]
1493    #[case("abc.def ghi", 0, 8)]
1494    fn test_word_right_start_index(
1495        #[case] input: &str,
1496        #[case] position: usize,
1497        #[case] expected: usize,
1498    ) {
1499        let mut line_buffer = buffer_with(input);
1500        line_buffer.set_insertion_point(position);
1501
1502        let index = line_buffer.word_right_start_index();
1503
1504        assert_eq!(index, expected);
1505    }
1506
1507    #[rstest]
1508    #[case("abc def ghi", 0, 4)]
1509    #[case("abc-def ghi", 0, 8)]
1510    #[case("abc.def ghi", 0, 8)]
1511    fn test_big_word_right_start_index(
1512        #[case] input: &str,
1513        #[case] position: usize,
1514        #[case] expected: usize,
1515    ) {
1516        let mut line_buffer = buffer_with(input);
1517        line_buffer.set_insertion_point(position);
1518
1519        let index = line_buffer.big_word_right_start_index();
1520
1521        assert_eq!(index, expected);
1522    }
1523
1524    #[rstest]
1525    #[case("abc def ghi", 0, 2)]
1526    #[case("abc-def ghi", 0, 2)]
1527    #[case("abc.def ghi", 0, 6)]
1528    #[case("abc", 1, 2)]
1529    #[case("abc", 2, 2)]
1530    #[case("abc def", 2, 6)]
1531    fn test_word_right_end_index(
1532        #[case] input: &str,
1533        #[case] position: usize,
1534        #[case] expected: usize,
1535    ) {
1536        let mut line_buffer = buffer_with(input);
1537        line_buffer.set_insertion_point(position);
1538
1539        let index = line_buffer.word_right_end_index();
1540
1541        assert_eq!(index, expected);
1542    }
1543
1544    #[rstest]
1545    #[case("abc def ghi", 0, 2)]
1546    #[case("abc-def ghi", 0, 6)]
1547    #[case("abc-def ghi", 5, 6)]
1548    #[case("abc-def ghi", 6, 10)]
1549    #[case("abc.def ghi", 0, 6)]
1550    #[case("abc", 1, 2)]
1551    #[case("abc", 2, 2)]
1552    #[case("abc def", 2, 6)]
1553    #[case("abc-def", 6, 6)]
1554    fn test_big_word_right_end_index(
1555        #[case] input: &str,
1556        #[case] position: usize,
1557        #[case] expected: usize,
1558    ) {
1559        let mut line_buffer = buffer_with(input);
1560        line_buffer.set_insertion_point(position);
1561
1562        let index = line_buffer.big_word_right_end_index();
1563
1564        assert_eq!(index, expected);
1565    }
1566
1567    #[rstest]
1568    #[case("abc def", 0, 3)]
1569    #[case("abc def ghi", 3, 7)]
1570    #[case("abc", 1, 3)]
1571    fn test_next_whitespace(#[case] input: &str, #[case] position: usize, #[case] expected: usize) {
1572        let mut line_buffer = buffer_with(input);
1573        line_buffer.set_insertion_point(position);
1574
1575        let index = line_buffer.next_whitespace();
1576
1577        assert_eq!(index, expected);
1578    }
1579}