1use std::iter::once;
6use std::ops::Range;
7
8use malloc_size_of_derive::MallocSizeOf;
9use rayon::iter::Either;
10use unicode_segmentation::UnicodeSegmentation;
11
12use crate::text::{AssumeUnder4GB, Utf8CodeUnits, Utf16CodeUnits, Utf32CodeUnits};
13
14fn contents_vec(contents: impl Into<String>) -> Vec<String> {
15 let mut contents: Vec<_> = contents
16 .into()
17 .split('\n')
18 .map(|line| format!("{line}\n"))
19 .collect();
20 if let Some(last_line) = contents.last_mut() {
22 last_line.truncate(last_line.len() - 1);
23 }
24 contents
25}
26
27pub enum RopeMovement {
29 Character,
30 Grapheme,
31 Word,
32 Line,
33 LineStartOrEnd,
34 RopeStartOrEnd,
35}
36
37#[derive(MallocSizeOf)]
42pub struct Rope {
43 lines: Vec<String>,
46}
47
48impl Rope {
49 pub fn new(contents: impl Into<String>) -> Self {
50 Self {
51 lines: contents_vec(contents),
52 }
53 }
54
55 pub fn contents(&self) -> String {
56 self.lines.join("")
57 }
58
59 pub fn first_index(&self) -> RopeIndex {
60 RopeIndex::new(0, 0)
61 }
62
63 pub fn last_index(&self) -> RopeIndex {
64 let line_index = self.lines.len() - 1;
65 RopeIndex::new(line_index, self.line(line_index).len())
66 }
67
68 pub fn replace_range(
71 &mut self,
72 mut range: Range<RopeIndex>,
73 string: impl Into<String>,
74 ) -> RopeIndex {
75 range.start = self.normalize_index(range.start);
76 range.end = self.normalize_index(range.end);
77 assert!(range.start <= range.end);
78
79 let start_index = range.start;
80 self.delete_range(range);
81
82 let mut new_contents = contents_vec(string);
83 let Some(first_line_of_new_contents) = new_contents.first() else {
84 return start_index;
85 };
86
87 if new_contents.len() == 1 {
88 self.line_for_index_mut(start_index)
89 .insert_str(start_index.code_point, first_line_of_new_contents);
90 return RopeIndex::new(
91 start_index.line,
92 start_index.code_point + first_line_of_new_contents.len(),
93 );
94 }
95
96 let start_line = self.line_for_index_mut(start_index);
97 let last_line = new_contents.last().expect("Should have at least one line");
98 let last_index = RopeIndex::new(
99 start_index.line + new_contents.len().saturating_sub(1),
100 last_line.len(),
101 );
102
103 let remaining_string = start_line.split_off(start_index.code_point);
104 start_line.push_str(first_line_of_new_contents);
105 new_contents
106 .last_mut()
107 .expect("Should have at least one line")
108 .push_str(&remaining_string);
109
110 let splice_index = start_index.line + 1;
111 self.lines
112 .splice(splice_index..splice_index, new_contents.into_iter().skip(1));
113 last_index
114 }
115
116 fn delete_range(&mut self, mut range: Range<RopeIndex>) {
117 range.start = self.normalize_index(range.start);
118 range.end = self.normalize_index(range.end);
119 assert!(range.start <= range.end);
120
121 if range.start.line == range.end.line {
122 self.line_for_index_mut(range.start)
123 .replace_range(range.start.code_point..range.end.code_point, "");
124 return;
125 }
126
127 let removed_lines = self.lines.splice(range.start.line..range.end.line, []);
129 let first_line = removed_lines
130 .into_iter()
131 .nth(0)
132 .expect("Should have removed at least one line");
133
134 let first_line_prefix = &first_line[0..range.start.code_point];
135 let new_end_line = range.start.line;
136 self.lines[new_end_line].replace_range(0..range.end.code_point, first_line_prefix);
137 }
138
139 pub fn slice<'a>(&'a self, start: Option<RopeIndex>, end: Option<RopeIndex>) -> RopeSlice<'a> {
142 RopeSlice {
143 rope: self,
144 start: start.unwrap_or_default(),
145 end: end.unwrap_or_else(|| self.last_index()),
146 }
147 }
148
149 pub fn chars<'a>(&'a self) -> RopeChars<'a> {
150 self.slice(None, None).chars()
151 }
152
153 pub fn is_empty(&self) -> bool {
156 self.lines.first().is_none_or(String::is_empty)
157 }
158
159 pub fn len_utf16(&self) -> Utf16CodeUnits {
161 self.lines
163 .iter()
164 .map(|line| Utf16CodeUnits::length_of(AssumeUnder4GB, line))
165 .sum()
166 }
167
168 fn line(&self, index: usize) -> &str {
169 &self.lines[index]
170 }
171
172 fn line_for_index(&self, index: RopeIndex) -> &String {
173 &self.lines[index.line]
174 }
175
176 fn line_for_index_mut(&mut self, index: RopeIndex) -> &mut String {
177 &mut self.lines[index.line]
178 }
179
180 fn last_index_in_line(&self, line: usize) -> RopeIndex {
181 if line >= self.lines.len() - 1 {
182 return self.last_index();
183 }
184 RopeIndex {
185 line,
186 code_point: self.line(line).len() - 1,
187 }
188 }
189
190 fn start_of_following_line(&self, index: RopeIndex) -> RopeIndex {
194 if index.line >= self.lines.len() - 1 {
195 return self.last_index();
196 }
197 RopeIndex::new(index.line + 1, 0)
198 }
199
200 fn end_of_preceding_line(&self, index: RopeIndex) -> RopeIndex {
204 if index.line == 0 {
205 return Default::default();
206 }
207 let line_index = index.line.saturating_sub(1);
208 RopeIndex::new(line_index, self.line(line_index).len())
209 }
210
211 pub fn move_by(&self, origin: RopeIndex, unit: RopeMovement, amount: isize) -> RopeIndex {
212 if amount == 0 {
213 return origin;
214 }
215
216 match unit {
217 RopeMovement::Character | RopeMovement::Grapheme | RopeMovement::Word => {
218 self.move_by_iterator(origin, unit, amount)
219 },
220 RopeMovement::Line => self.move_by_lines(origin, amount),
221 RopeMovement::LineStartOrEnd => {
222 if amount >= 0 {
223 self.last_index_in_line(origin.line)
224 } else {
225 RopeIndex::new(origin.line, 0)
226 }
227 },
228 RopeMovement::RopeStartOrEnd => {
229 if amount >= 0 {
230 self.last_index()
231 } else {
232 Default::default()
233 }
234 },
235 }
236 }
237
238 fn move_by_lines(&self, origin: RopeIndex, lines_to_move: isize) -> RopeIndex {
239 let new_line_index = (origin.line as isize) + lines_to_move;
240 if new_line_index < 0 {
241 return Default::default();
242 }
243 if new_line_index > (self.lines.len() - 1) as isize {
244 return self.last_index();
245 }
246
247 let new_line_index = new_line_index.unsigned_abs();
248 let char_count = self.line(origin.line)[0..origin.code_point].chars().count();
249 let new_code_point_index = self
250 .line(new_line_index)
251 .char_indices()
252 .take(char_count)
253 .last()
254 .map(|(byte_index, character)| byte_index + character.len_utf8())
255 .unwrap_or_default();
256 RopeIndex::new(new_line_index, new_code_point_index)
257 .min(self.last_index_in_line(new_line_index))
258 }
259
260 fn move_by_iterator(&self, origin: RopeIndex, unit: RopeMovement, amount: isize) -> RopeIndex {
261 assert_ne!(amount, 0);
262 let (boundary_value, slice) = if amount > 0 {
263 (self.last_index(), self.slice(Some(origin), None))
264 } else {
265 (RopeIndex::default(), self.slice(None, Some(origin)))
266 };
267
268 let iterator = match unit {
269 RopeMovement::Character => slice.char_indices(),
270 RopeMovement::Grapheme => slice.grapheme_indices(),
271 RopeMovement::Word => slice.word_indices(),
272 _ => unreachable!("Should not be called for other movement types"),
273 };
274 let iterator = if amount > 0 {
275 Either::Left(iterator)
276 } else {
277 Either::Right(iterator.rev())
278 };
279
280 let mut iterations = amount.unsigned_abs();
281 for mut index in iterator {
282 iterations = iterations.saturating_sub(1);
283 if iterations == 0 {
284 if index.code_point >= self.line_for_index(index).len() {
287 index = self.start_of_following_line(index);
288 }
289 return index;
290 }
291 }
292
293 boundary_value
294 }
295
296 pub fn normalize_index(&self, rope_index: RopeIndex) -> RopeIndex {
300 let last_line = self.lines.len().saturating_sub(1);
301 let line_index = rope_index.line.min(last_line);
302
303 let line = self.line(line_index);
311 let line_length_utf8 = if line_index == last_line {
312 line.len()
313 } else {
314 line.len() - 1
315 };
316
317 let mut code_point = rope_index.code_point.min(line_length_utf8);
318 while code_point < line.len() && !line.is_char_boundary(code_point) {
319 code_point += 1;
320 }
321
322 RopeIndex::new(line_index, code_point)
323 }
324
325 pub fn index_to_utf8_offset(&self, rope_index: RopeIndex) -> Utf8CodeUnits {
327 let rope_index = self.normalize_index(rope_index);
328 let sum = self
329 .lines
330 .iter()
331 .take(rope_index.line)
332 .map(String::len)
333 .sum::<usize>() +
334 rope_index.code_point;
335 Utf8CodeUnits(sum as u32)
337 }
338
339 pub fn index_to_utf16_offset(&self, rope_index: RopeIndex) -> Utf16CodeUnits {
340 let rope_index = self.normalize_index(rope_index);
341 let final_line = self.line(rope_index.line);
342
343 let final_line_offset =
345 Utf16CodeUnits::length_of(AssumeUnder4GB, &final_line[..rope_index.code_point]);
346
347 self.lines[..rope_index.line]
349 .iter()
350 .map(|line| Utf16CodeUnits::length_of(AssumeUnder4GB, line))
351 .sum::<Utf16CodeUnits>() +
352 final_line_offset
353 }
354
355 pub fn index_to_character_offset(&self, rope_index: RopeIndex) -> Utf32CodeUnits {
357 let rope_index = self.normalize_index(rope_index);
358
359 let final_line = self.line(rope_index.line);
361 let final_line_offset =
363 Utf32CodeUnits::length_of(AssumeUnder4GB, &final_line[..rope_index.code_point]);
364 self.lines
365 .iter()
366 .take(rope_index.line)
367 .map(|line| Utf32CodeUnits::length_of(AssumeUnder4GB, line))
368 .sum::<Utf32CodeUnits>() +
369 final_line_offset
370 }
371
372 pub fn utf8_offset_to_rope_index(&self, utf8_offset: Utf8CodeUnits) -> RopeIndex {
374 let mut current_utf8_offset = usize::from(utf8_offset);
375 for (line_index, line) in self.lines.iter().enumerate() {
376 if current_utf8_offset == 0 || current_utf8_offset < line.len() {
377 return RopeIndex::new(line_index, current_utf8_offset);
378 }
379 current_utf8_offset -= line.len();
380 }
381 self.last_index()
382 }
383
384 pub fn utf16_offset_to_utf8_offset(&self, utf16_offset: Utf16CodeUnits) -> Utf8CodeUnits {
385 utf16_offset.to_utf8_code_units_in_iter(AssumeUnder4GB, &self.lines)
387 }
388
389 pub fn relevant_word_boundaries<'a>(&'a self, index: RopeIndex) -> RopeSlice<'a> {
398 let line = self.line_for_index(index);
399 let mut result_start = 0;
400 let mut result_end = None;
401 for (word_start, word) in line.unicode_word_indices() {
402 if word_start > index.code_point {
403 result_end = result_end.or_else(|| Some(word_start + word.len()));
404 break;
405 }
406 result_start = word_start;
407 result_end = Some(word_start + word.len());
408 }
409
410 let result_end = result_end.unwrap_or(result_start);
411 self.slice(
412 Some(RopeIndex::new(index.line, result_start)),
413 Some(RopeIndex::new(index.line, result_end)),
414 )
415 }
416
417 pub fn line_boundaries<'a>(&'a self, index: RopeIndex) -> RopeSlice<'a> {
419 self.slice(
420 Some(RopeIndex::new(index.line, 0)),
421 Some(self.last_index_in_line(index.line)),
422 )
423 }
424
425 fn character_at(&self, index: RopeIndex) -> Option<char> {
426 let line = self.line_for_index(index);
427 line[index.code_point..].chars().next()
428 }
429
430 fn character_before(&self, index: RopeIndex) -> Option<char> {
431 let line = self.line_for_index(index);
432 line[..index.code_point].chars().next_back()
433 }
434}
435
436#[derive(Clone, Copy, Debug, Default, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)]
445pub struct RopeIndex {
446 pub line: usize,
448 pub code_point: usize,
454}
455
456impl RopeIndex {
457 pub fn new(line: usize, code_point: usize) -> Self {
458 Self { line, code_point }
459 }
460}
461
462pub struct RopeSlice<'a> {
465 rope: &'a Rope,
467 pub start: RopeIndex,
469 pub end: RopeIndex,
471}
472
473impl From<RopeSlice<'_>> for String {
474 fn from(slice: RopeSlice<'_>) -> Self {
475 if slice.start.line == slice.end.line {
476 slice.rope.line_for_index(slice.start)[slice.start.code_point..slice.end.code_point]
477 .into()
478 } else {
479 once(&slice.rope.line_for_index(slice.start)[slice.start.code_point..])
480 .chain(
481 (slice.start.line + 1..slice.end.line)
482 .map(|line_index| slice.rope.line(line_index)),
483 )
484 .chain(once(
485 &slice.rope.line_for_index(slice.end)[..slice.end.code_point],
486 ))
487 .collect()
488 }
489 }
490}
491
492impl<'a> RopeSlice<'a> {
493 pub fn chars(self) -> RopeChars<'a> {
494 RopeChars {
495 movement_iterator: RopeMovementIterator {
496 slice: self,
497 end_of_forward_motion: |_, string| {
498 let (offset, character) = string.char_indices().next()?;
499 Some(offset + character.len_utf8())
500 },
501 start_of_backward_motion: |_, string: &str| {
502 Some(string.char_indices().next_back()?.0)
503 },
504 },
505 }
506 }
507
508 fn char_indices(self) -> RopeMovementIterator<'a> {
509 RopeMovementIterator {
510 slice: self,
511 end_of_forward_motion: |_, string| {
512 let (offset, character) = string.char_indices().next()?;
513 Some(offset + character.len_utf8())
514 },
515 start_of_backward_motion: |_, string: &str| Some(string.char_indices().next_back()?.0),
516 }
517 }
518
519 fn grapheme_indices(self) -> RopeMovementIterator<'a> {
520 RopeMovementIterator {
521 slice: self,
522 end_of_forward_motion: |_, string| {
523 let (offset, grapheme) = string.grapheme_indices(true).next()?;
524 Some(offset + grapheme.len())
525 },
526 start_of_backward_motion: |_, string| {
527 Some(string.grapheme_indices(true).next_back()?.0)
528 },
529 }
530 }
531
532 fn word_indices(self) -> RopeMovementIterator<'a> {
533 RopeMovementIterator {
534 slice: self,
535 end_of_forward_motion: |_, string| {
536 let (offset, word) = string.unicode_word_indices().next()?;
537 Some(offset + word.len())
538 },
539 start_of_backward_motion: |_, string| {
540 Some(string.unicode_word_indices().next_back()?.0)
541 },
542 }
543 }
544
545 pub fn len_utf16(self) -> Utf16CodeUnits {
546 self.chars().map(Utf16CodeUnits::length_of_char).sum()
549 }
550}
551
552struct RopeMovementIterator<'a> {
558 slice: RopeSlice<'a>,
559 end_of_forward_motion: fn(&RopeSlice, &'a str) -> Option<usize>,
560 start_of_backward_motion: fn(&RopeSlice, &'a str) -> Option<usize>,
561}
562
563impl Iterator for RopeMovementIterator<'_> {
564 type Item = RopeIndex;
565
566 fn next(&mut self) -> Option<Self::Item> {
567 if self.slice.start >= self.slice.end {
569 return None;
570 }
571
572 assert!(self.slice.start.line < self.slice.rope.lines.len());
573 let line = self.slice.rope.line_for_index(self.slice.start);
574
575 if self.slice.start.code_point < line.len() + 1 &&
576 let Some(end_offset) =
577 (self.end_of_forward_motion)(&self.slice, &line[self.slice.start.code_point..])
578 {
579 self.slice.start.code_point += end_offset;
580 return Some(self.slice.start);
581 }
582
583 self.slice.start = self.slice.rope.start_of_following_line(self.slice.start);
585 self.next()
586 }
587}
588
589impl DoubleEndedIterator for RopeMovementIterator<'_> {
590 fn next_back(&mut self) -> Option<Self::Item> {
591 if self.slice.end <= self.slice.start {
593 return None;
594 }
595
596 let line = self.slice.rope.line_for_index(self.slice.end);
597 if self.slice.end.code_point > 0 &&
598 let Some(new_start_index) =
599 (self.start_of_backward_motion)(&self.slice, &line[..self.slice.end.code_point])
600 {
601 self.slice.end.code_point = new_start_index;
602 return Some(self.slice.end);
603 }
604
605 self.slice.end = self.slice.rope.end_of_preceding_line(self.slice.end);
607 self.next_back()
608 }
609}
610
611pub struct RopeChars<'a> {
613 movement_iterator: RopeMovementIterator<'a>,
614}
615
616impl Iterator for RopeChars<'_> {
617 type Item = char;
618 fn next(&mut self) -> Option<Self::Item> {
619 self.movement_iterator
620 .next()
621 .and_then(|index| self.movement_iterator.slice.rope.character_before(index))
622 }
623}
624
625impl DoubleEndedIterator for RopeChars<'_> {
626 fn next_back(&mut self) -> Option<Self::Item> {
627 self.movement_iterator
628 .next_back()
629 .and_then(|index| self.movement_iterator.slice.rope.character_at(index))
630 }
631}
632
633#[test]
634fn test_rope_index_conversion_to_utf8_offset() {
635 let rope = Rope::new("A\nBB\nCCC\nDDDD");
636 assert_eq!(
637 rope.index_to_utf8_offset(RopeIndex::new(0, 0)),
638 Utf8CodeUnits(0),
639 );
640 assert_eq!(
641 rope.index_to_utf8_offset(RopeIndex::new(0, 1)),
642 Utf8CodeUnits(1),
643 );
644 assert_eq!(
645 rope.index_to_utf8_offset(RopeIndex::new(0, 10)),
646 Utf8CodeUnits(1),
647 "RopeIndex with offset past the end of the line should return final offset in line",
648 );
649 assert_eq!(
650 rope.index_to_utf8_offset(RopeIndex::new(1, 0)),
651 Utf8CodeUnits(2),
652 );
653 assert_eq!(
654 rope.index_to_utf8_offset(RopeIndex::new(1, 2)),
655 Utf8CodeUnits(4),
656 );
657
658 assert_eq!(
659 rope.index_to_utf8_offset(RopeIndex::new(3, 0)),
660 Utf8CodeUnits(9),
661 );
662 assert_eq!(
663 rope.index_to_utf8_offset(RopeIndex::new(3, 3)),
664 Utf8CodeUnits(12),
665 );
666 assert_eq!(
667 rope.index_to_utf8_offset(RopeIndex::new(3, 4)),
668 Utf8CodeUnits(13),
669 "There should be no newline at the end of the TextInput",
670 );
671 assert_eq!(
672 rope.index_to_utf8_offset(RopeIndex::new(3, 40)),
673 Utf8CodeUnits(13),
674 "There should be no newline at the end of the TextInput",
675 );
676}
677
678#[test]
679fn test_rope_index_conversion_to_utf16_offset() {
680 let rope = Rope::new("A\nBB\nCCC\n家家");
681 assert_eq!(
682 rope.index_to_utf16_offset(RopeIndex::new(0, 0)),
683 Utf16CodeUnits(0),
684 );
685 assert_eq!(
686 rope.index_to_utf16_offset(RopeIndex::new(0, 1)),
687 Utf16CodeUnits(1),
688 );
689 assert_eq!(
690 rope.index_to_utf16_offset(RopeIndex::new(0, 10)),
691 Utf16CodeUnits(1),
692 "RopeIndex with offset past the end of the line should return final offset in line",
693 );
694 assert_eq!(
695 rope.index_to_utf16_offset(RopeIndex::new(3, 0)),
696 Utf16CodeUnits(9),
697 );
698
699 assert_eq!(
700 rope.index_to_utf16_offset(RopeIndex::new(3, 3)),
701 Utf16CodeUnits(10),
702 "3 code unit UTF-8 encodede character"
703 );
704 assert_eq!(
705 rope.index_to_utf16_offset(RopeIndex::new(3, 6)),
706 Utf16CodeUnits(11),
707 );
708 assert_eq!(
709 rope.index_to_utf16_offset(RopeIndex::new(3, 20)),
710 Utf16CodeUnits(11),
711 );
712}
713
714#[test]
715fn test_utf16_offset_to_utf8_offset() {
716 let rope = Rope::new("A\nBB\nCCC\n家家");
717 assert_eq!(
718 rope.utf16_offset_to_utf8_offset(Utf16CodeUnits(0)),
719 Utf8CodeUnits(0),
720 );
721 assert_eq!(
722 rope.utf16_offset_to_utf8_offset(Utf16CodeUnits(1)),
723 Utf8CodeUnits(1),
724 );
725 assert_eq!(
726 rope.utf16_offset_to_utf8_offset(Utf16CodeUnits(2)),
727 Utf8CodeUnits(2),
728 "Offset past the end of the line",
729 );
730 assert_eq!(
731 rope.utf16_offset_to_utf8_offset(Utf16CodeUnits(9)),
732 Utf8CodeUnits(9),
733 );
734
735 assert_eq!(
736 rope.utf16_offset_to_utf8_offset(Utf16CodeUnits(10)),
737 Utf8CodeUnits(12),
738 "3 code unit UTF-8 encodede character"
739 );
740 assert_eq!(
741 rope.utf16_offset_to_utf8_offset(Utf16CodeUnits(11)),
742 Utf8CodeUnits(15),
743 );
744 assert_eq!(
745 rope.utf16_offset_to_utf8_offset(Utf16CodeUnits(300)),
746 Utf8CodeUnits(15),
747 );
748}
749
750#[test]
751fn test_rope_delete_slice() {
752 let mut rope = Rope::new("ABC\nDEF\n");
753 rope.delete_range(RopeIndex::new(0, 1)..RopeIndex::new(0, 2));
754 assert_eq!(rope.contents(), "AC\nDEF\n");
755
756 let mut rope = Rope::new("ABC\nDEF\n");
759 rope.delete_range(RopeIndex::new(0, 3)..RopeIndex::new(0, 4));
760 assert_eq!(rope.lines, ["ABC\n", "DEF\n", ""]);
761
762 let mut rope = Rope::new("ABC\nDEF\n");
763 rope.delete_range(RopeIndex::new(0, 0)..RopeIndex::new(0, 4));
764 assert_eq!(rope.lines, ["\n", "DEF\n", ""]);
765
766 let mut rope = Rope::new("A\nBB\nCCC");
767 rope.delete_range(RopeIndex::new(0, 2)..RopeIndex::new(1, 0));
768 assert_eq!(rope.lines, ["ABB\n", "CCC"]);
769}
770
771#[test]
772fn test_rope_replace_slice() {
773 let mut rope = Rope::new("AAA\nBBB\nCCC");
774 rope.replace_range(RopeIndex::new(0, 1)..RopeIndex::new(0, 2), "x");
775 assert_eq!(rope.contents(), "AxA\nBBB\nCCC",);
776
777 let mut rope = Rope::new("A\nBB\nCCC");
778 rope.replace_range(RopeIndex::new(0, 2)..RopeIndex::new(1, 0), "D");
779 assert_eq!(rope.lines, ["ADBB\n", "CCC"]);
780
781 let mut rope = Rope::new("AAA\nBBB\nCCC\nDDD");
782 rope.replace_range(RopeIndex::new(0, 2)..RopeIndex::new(2, 1), "x");
783 assert_eq!(rope.lines, ["AAxCC\n", "DDD"]);
784}
785
786#[test]
787fn test_rope_relevant_word() {
788 let rope = Rope::new("AAA BBB CCC");
789 let boundaries = rope.relevant_word_boundaries(RopeIndex::new(0, 0));
790 assert_eq!(boundaries.start, RopeIndex::new(0, 0));
791 assert_eq!(boundaries.end, RopeIndex::new(0, 3));
792
793 let boundaries = rope.relevant_word_boundaries(RopeIndex::new(0, 4));
795 assert_eq!(boundaries.start, RopeIndex::new(0, 0));
796 assert_eq!(boundaries.end, RopeIndex::new(0, 3));
797
798 let boundaries = rope.relevant_word_boundaries(RopeIndex::new(0, 7));
800 assert_eq!(boundaries.start, RopeIndex::new(0, 7));
801 assert_eq!(boundaries.end, RopeIndex::new(0, 10));
802
803 let boundaries = rope.relevant_word_boundaries(RopeIndex::new(0, 8));
805 assert_eq!(boundaries.start, RopeIndex::new(0, 7));
806 assert_eq!(boundaries.end, RopeIndex::new(0, 10));
807
808 let rope = Rope::new(" AAA BBB CCC");
810 let boundaries = rope.relevant_word_boundaries(RopeIndex::new(0, 3));
811 assert_eq!(boundaries.start, RopeIndex::new(0, 0));
812 assert_eq!(boundaries.end, RopeIndex::new(0, 12));
813
814 let rope = Rope::new("");
816 let boundaries = rope.relevant_word_boundaries(RopeIndex::new(0, 0));
817 assert_eq!(boundaries.start, RopeIndex::new(0, 0));
818 assert_eq!(boundaries.end, RopeIndex::new(0, 0));
819}
820
821#[test]
822fn test_rope_index_intersects_character() {
823 let rope = Rope::new("");
824 let rope_index = RopeIndex::new(0, 1);
825 assert_eq!(rope.normalize_index(rope_index), RopeIndex::new(0, 4));
826 assert_eq!(rope.index_to_utf16_offset(rope_index), Utf16CodeUnits(2));
827 assert_eq!(rope.index_to_utf8_offset(rope_index), Utf8CodeUnits(4));
828
829 let rope = Rope::new("abc\ndef");
830 assert_eq!(
831 rope.normalize_index(RopeIndex::new(0, 100)),
832 RopeIndex::new(0, 3),
833 "Normalizing index past end of line should just clamp to line length."
834 );
835 assert_eq!(
836 rope.normalize_index(RopeIndex::new(1, 100)),
837 RopeIndex::new(1, 3),
838 "Normalizing index past end of line should just clamp to line length."
839 );
840}