1use {
2 itertools::Itertools,
3 std::{convert::From, ops::Range},
4 unicode_segmentation::UnicodeSegmentation,
5};
6
7#[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 pub fn new() -> LineBuffer {
25 Self::default()
26 }
27
28 pub fn is_empty(&self) -> bool {
30 self.lines.is_empty()
31 }
32
33 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 pub fn insertion_point(&self) -> usize {
65 self.insertion_point
66 }
67
68 pub fn set_insertion_point(&mut self, offset: usize) {
72 self.insertion_point = offset;
73 }
74
75 pub fn get_buffer(&self) -> &str {
77 &self.lines
78 }
79
80 pub fn set_buffer(&mut self, buffer: String) {
82 self.lines = buffer;
83 self.insertion_point = self.lines.len();
84 }
85
86 pub fn line(&self) -> usize {
90 self.lines[..self.insertion_point].matches('\n').count()
91 }
92
93 pub fn num_lines(&self) -> usize {
95 self.lines.split('\n').count()
96 }
97
98 pub fn ends_with(&self, c: char) -> bool {
100 self.lines.ends_with(c)
101 }
102
103 pub fn move_to_start(&mut self) {
105 self.insertion_point = 0;
106 }
107
108 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 }
115
116 pub fn move_to_line_end(&mut self) {
121 self.insertion_point = self.find_current_line_end();
122 }
123
124 pub fn move_to_end(&mut self) {
126 self.insertion_point = self.lines.len();
127 }
128
129 pub fn len(&self) -> usize {
131 self.lines.len()
132 }
133
134 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 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 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 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 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 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 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 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 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 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 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 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 pub fn move_right(&mut self) {
303 self.insertion_point = self.grapheme_right_index();
304 }
305
306 pub fn move_left(&mut self) {
308 self.insertion_point = self.grapheme_left_index();
309 }
310
311 pub fn move_word_left(&mut self) {
313 self.insertion_point = self.word_left_index();
314 }
315
316 pub fn move_big_word_left(&mut self) {
318 self.insertion_point = self.big_word_left_index();
319 }
320
321 pub fn move_word_right(&mut self) {
323 self.insertion_point = self.word_right_index();
324 }
325
326 pub fn move_word_right_start(&mut self) {
328 self.insertion_point = self.word_right_start_index();
329 }
330
331 pub fn move_big_word_right_start(&mut self) {
333 self.insertion_point = self.big_word_right_start_index();
334 }
335
336 pub fn move_word_right_end(&mut self) {
338 self.insertion_point = self.word_right_end_index();
339 }
340
341 pub fn move_big_word_right_end(&mut self) {
343 self.insertion_point = self.big_word_right_end_index();
344 }
345
346 pub fn insert_char(&mut self, c: char) {
348 self.lines.insert(self.insertion_point, c);
349 self.move_right();
350 }
351
352 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 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 pub fn clear(&mut self) {
376 self.lines = String::new();
377 self.insertion_point = 0;
378 }
379
380 pub fn clear_to_end(&mut self) {
383 self.lines.truncate(self.insertion_point);
384 }
385
386 pub fn clear_to_line_end(&mut self) {
389 self.clear_range(self.insertion_point..self.find_current_line_end());
390 }
391
392 pub fn clear_to_insertion_point(&mut self) {
395 self.clear_range(..self.insertion_point);
396 self.insertion_point = 0;
397 }
398
399 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 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 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 pub fn grapheme_right(&self) -> &str {
430 &self.lines[self.insertion_point..self.grapheme_right_index()]
431 }
432
433 pub fn grapheme_left(&self) -> &str {
435 &self.lines[self.grapheme_left_index()..self.insertion_point]
436 }
437
438 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn is_cursor_at_first_line(&self) -> bool {
653 !self.get_buffer()[0..self.insertion_point()].contains('\n')
654 }
655
656 pub fn is_cursor_at_last_line(&self) -> bool {
658 !self.get_buffer()[self.insertion_point()..].contains('\n')
659 }
660
661 pub fn find_char_right(&self, c: char, current_line: bool) -> Option<usize> {
663 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 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 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 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 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 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 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 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 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 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
751fn 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)] #[case("word", 0, 3)] #[case("word and another one", 0, 3)]
887 #[case("word and another one", 3, 7)] #[case("word and another one", 4, 7)] #[case("word\nline two", 0, 3)] #[case("word\nline two", 3, 8)] #[case("weirdรถ characters", 0, 5)] #[case("weirdรถ characters", 5, 17)] #[case("weirdรถ", 0, 5)] #[case("weirdรถ", 5, 5)] #[case("word๐ with emoji", 0, 3)] #[case("word๐ with emoji", 3, 4)] #[case("๐", 0, 0)] 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)] #[case("This is a test", 4, "Thi sis a test", 5)] #[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 #[case("line\n", 4, 4)]
1317 #[case("line\n", 5, 5)]
1318 #[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)] #[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}