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::{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 Utf16CodeUnits(self.chars().map(char::len_utf16).sum())
162 }
163
164 fn line(&self, index: usize) -> &str {
165 &self.lines[index]
166 }
167
168 fn line_for_index(&self, index: RopeIndex) -> &String {
169 &self.lines[index.line]
170 }
171
172 fn line_for_index_mut(&mut self, index: RopeIndex) -> &mut String {
173 &mut self.lines[index.line]
174 }
175
176 fn last_index_in_line(&self, line: usize) -> RopeIndex {
177 if line >= self.lines.len() - 1 {
178 return self.last_index();
179 }
180 RopeIndex {
181 line,
182 code_point: self.line(line).len() - 1,
183 }
184 }
185
186 fn start_of_following_line(&self, index: RopeIndex) -> RopeIndex {
190 if index.line >= self.lines.len() - 1 {
191 return self.last_index();
192 }
193 RopeIndex::new(index.line + 1, 0)
194 }
195
196 fn end_of_preceding_line(&self, index: RopeIndex) -> RopeIndex {
200 if index.line == 0 {
201 return Default::default();
202 }
203 let line_index = index.line.saturating_sub(1);
204 RopeIndex::new(line_index, self.line(line_index).len())
205 }
206
207 pub fn move_by(&self, origin: RopeIndex, unit: RopeMovement, amount: isize) -> RopeIndex {
208 if amount == 0 {
209 return origin;
210 }
211
212 match unit {
213 RopeMovement::Character | RopeMovement::Grapheme | RopeMovement::Word => {
214 self.move_by_iterator(origin, unit, amount)
215 },
216 RopeMovement::Line => self.move_by_lines(origin, amount),
217 RopeMovement::LineStartOrEnd => {
218 if amount >= 0 {
219 self.last_index_in_line(origin.line)
220 } else {
221 RopeIndex::new(origin.line, 0)
222 }
223 },
224 RopeMovement::RopeStartOrEnd => {
225 if amount >= 0 {
226 self.last_index()
227 } else {
228 Default::default()
229 }
230 },
231 }
232 }
233
234 fn move_by_lines(&self, origin: RopeIndex, lines_to_move: isize) -> RopeIndex {
235 let new_line_index = (origin.line as isize) + lines_to_move;
236 if new_line_index < 0 {
237 return Default::default();
238 }
239 if new_line_index > (self.lines.len() - 1) as isize {
240 return self.last_index();
241 }
242
243 let new_line_index = new_line_index.unsigned_abs();
244 let char_count = self.line(origin.line)[0..origin.code_point].chars().count();
245 let new_code_point_index = self
246 .line(new_line_index)
247 .char_indices()
248 .take(char_count)
249 .last()
250 .map(|(byte_index, character)| byte_index + character.len_utf8())
251 .unwrap_or_default();
252 RopeIndex::new(new_line_index, new_code_point_index)
253 .min(self.last_index_in_line(new_line_index))
254 }
255
256 fn move_by_iterator(&self, origin: RopeIndex, unit: RopeMovement, amount: isize) -> RopeIndex {
257 assert_ne!(amount, 0);
258 let (boundary_value, slice) = if amount > 0 {
259 (self.last_index(), self.slice(Some(origin), None))
260 } else {
261 (RopeIndex::default(), self.slice(None, Some(origin)))
262 };
263
264 let iterator = match unit {
265 RopeMovement::Character => slice.char_indices(),
266 RopeMovement::Grapheme => slice.grapheme_indices(),
267 RopeMovement::Word => slice.word_indices(),
268 _ => unreachable!("Should not be called for other movement types"),
269 };
270 let iterator = if amount > 0 {
271 Either::Left(iterator)
272 } else {
273 Either::Right(iterator.rev())
274 };
275
276 let mut iterations = amount.unsigned_abs();
277 for mut index in iterator {
278 iterations = iterations.saturating_sub(1);
279 if iterations == 0 {
280 if index.code_point >= self.line_for_index(index).len() {
283 index = self.start_of_following_line(index);
284 }
285 return index;
286 }
287 }
288
289 boundary_value
290 }
291
292 pub fn normalize_index(&self, rope_index: RopeIndex) -> RopeIndex {
296 let last_line = self.lines.len().saturating_sub(1);
297 let line_index = rope_index.line.min(last_line);
298
299 let line = self.line(line_index);
307 let line_length_utf8 = if line_index == last_line {
308 line.len()
309 } else {
310 line.len() - 1
311 };
312
313 let mut code_point = rope_index.code_point.min(line_length_utf8);
314 while code_point < line.len() && !line.is_char_boundary(code_point) {
315 code_point += 1;
316 }
317
318 RopeIndex::new(line_index, code_point)
319 }
320
321 pub fn index_to_utf8_offset(&self, rope_index: RopeIndex) -> Utf8CodeUnits {
323 let rope_index = self.normalize_index(rope_index);
324 Utf8CodeUnits(
325 self.lines
326 .iter()
327 .take(rope_index.line)
328 .map(String::len)
329 .sum::<usize>() +
330 rope_index.code_point,
331 )
332 }
333
334 pub fn index_to_utf16_offset(&self, rope_index: RopeIndex) -> Utf16CodeUnits {
335 let rope_index = self.normalize_index(rope_index);
336 let final_line = self.line(rope_index.line);
337
338 let final_line_offset = Utf16CodeUnits(
340 final_line[0..rope_index.code_point]
341 .chars()
342 .map(char::len_utf16)
343 .sum(),
344 );
345
346 self.lines
347 .iter()
348 .take(rope_index.line)
349 .map(|line| Utf16CodeUnits(line.chars().map(char::len_utf16).sum()))
350 .sum::<Utf16CodeUnits>() +
351 final_line_offset
352 }
353
354 pub fn index_to_character_offset(&self, rope_index: RopeIndex) -> Utf32CodeUnits {
356 let rope_index = self.normalize_index(rope_index);
357
358 let final_line = self.line(rope_index.line);
360 let final_line_offset = Utf32CodeUnits::length_of(&final_line[..rope_index.code_point]);
361 self.lines
362 .iter()
363 .take(rope_index.line)
364 .map(|line| Utf32CodeUnits::length_of(line))
365 .sum::<Utf32CodeUnits>() +
366 final_line_offset
367 }
368
369 pub fn utf8_offset_to_rope_index(&self, utf8_offset: Utf8CodeUnits) -> RopeIndex {
371 let mut current_utf8_offset = utf8_offset.0;
372 for (line_index, line) in self.lines.iter().enumerate() {
373 if current_utf8_offset == 0 || current_utf8_offset < line.len() {
374 return RopeIndex::new(line_index, current_utf8_offset);
375 }
376 current_utf8_offset -= line.len();
377 }
378 self.last_index()
379 }
380
381 pub fn utf16_offset_to_utf8_offset(&self, utf16_offset: Utf16CodeUnits) -> Utf8CodeUnits {
382 let mut current_utf16_offset = Utf16CodeUnits::zero();
383 let mut current_utf8_offset = Utf8CodeUnits::zero();
384
385 for character in self.chars() {
386 let utf16_length = character.len_utf16();
387 if current_utf16_offset + Utf16CodeUnits(utf16_length) > utf16_offset {
388 return current_utf8_offset;
389 }
390 current_utf8_offset += Utf8CodeUnits(character.len_utf8());
391 current_utf16_offset += Utf16CodeUnits(utf16_length);
392 }
393 current_utf8_offset
394 }
395
396 pub fn relevant_word_boundaries<'a>(&'a self, index: RopeIndex) -> RopeSlice<'a> {
405 let line = self.line_for_index(index);
406 let mut result_start = 0;
407 let mut result_end = None;
408 for (word_start, word) in line.unicode_word_indices() {
409 if word_start > index.code_point {
410 result_end = result_end.or_else(|| Some(word_start + word.len()));
411 break;
412 }
413 result_start = word_start;
414 result_end = Some(word_start + word.len());
415 }
416
417 let result_end = result_end.unwrap_or(result_start);
418 self.slice(
419 Some(RopeIndex::new(index.line, result_start)),
420 Some(RopeIndex::new(index.line, result_end)),
421 )
422 }
423
424 pub fn line_boundaries<'a>(&'a self, index: RopeIndex) -> RopeSlice<'a> {
426 self.slice(
427 Some(RopeIndex::new(index.line, 0)),
428 Some(self.last_index_in_line(index.line)),
429 )
430 }
431
432 fn character_at(&self, index: RopeIndex) -> Option<char> {
433 let line = self.line_for_index(index);
434 line[index.code_point..].chars().next()
435 }
436
437 fn character_before(&self, index: RopeIndex) -> Option<char> {
438 let line = self.line_for_index(index);
439 line[..index.code_point].chars().next_back()
440 }
441}
442
443#[derive(Clone, Copy, Debug, Default, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)]
452pub struct RopeIndex {
453 pub line: usize,
455 pub code_point: usize,
461}
462
463impl RopeIndex {
464 pub fn new(line: usize, code_point: usize) -> Self {
465 Self { line, code_point }
466 }
467}
468
469pub struct RopeSlice<'a> {
472 rope: &'a Rope,
474 pub start: RopeIndex,
476 pub end: RopeIndex,
478}
479
480impl From<RopeSlice<'_>> for String {
481 fn from(slice: RopeSlice<'_>) -> Self {
482 if slice.start.line == slice.end.line {
483 slice.rope.line_for_index(slice.start)[slice.start.code_point..slice.end.code_point]
484 .into()
485 } else {
486 once(&slice.rope.line_for_index(slice.start)[slice.start.code_point..])
487 .chain(
488 (slice.start.line + 1..slice.end.line)
489 .map(|line_index| slice.rope.line(line_index)),
490 )
491 .chain(once(
492 &slice.rope.line_for_index(slice.end)[..slice.end.code_point],
493 ))
494 .collect()
495 }
496 }
497}
498
499impl<'a> RopeSlice<'a> {
500 pub fn chars(self) -> RopeChars<'a> {
501 RopeChars {
502 movement_iterator: RopeMovementIterator {
503 slice: self,
504 end_of_forward_motion: |_, string| {
505 let (offset, character) = string.char_indices().next()?;
506 Some(offset + character.len_utf8())
507 },
508 start_of_backward_motion: |_, string: &str| {
509 Some(string.char_indices().next_back()?.0)
510 },
511 },
512 }
513 }
514
515 fn char_indices(self) -> RopeMovementIterator<'a> {
516 RopeMovementIterator {
517 slice: self,
518 end_of_forward_motion: |_, string| {
519 let (offset, character) = string.char_indices().next()?;
520 Some(offset + character.len_utf8())
521 },
522 start_of_backward_motion: |_, string: &str| Some(string.char_indices().next_back()?.0),
523 }
524 }
525
526 fn grapheme_indices(self) -> RopeMovementIterator<'a> {
527 RopeMovementIterator {
528 slice: self,
529 end_of_forward_motion: |_, string| {
530 let (offset, grapheme) = string.grapheme_indices(true).next()?;
531 Some(offset + grapheme.len())
532 },
533 start_of_backward_motion: |_, string| {
534 Some(string.grapheme_indices(true).next_back()?.0)
535 },
536 }
537 }
538
539 fn word_indices(self) -> RopeMovementIterator<'a> {
540 RopeMovementIterator {
541 slice: self,
542 end_of_forward_motion: |_, string| {
543 let (offset, word) = string.unicode_word_indices().next()?;
544 Some(offset + word.len())
545 },
546 start_of_backward_motion: |_, string| {
547 Some(string.unicode_word_indices().next_back()?.0)
548 },
549 }
550 }
551}
552
553struct RopeMovementIterator<'a> {
559 slice: RopeSlice<'a>,
560 end_of_forward_motion: fn(&RopeSlice, &'a str) -> Option<usize>,
561 start_of_backward_motion: fn(&RopeSlice, &'a str) -> Option<usize>,
562}
563
564impl Iterator for RopeMovementIterator<'_> {
565 type Item = RopeIndex;
566
567 fn next(&mut self) -> Option<Self::Item> {
568 if self.slice.start >= self.slice.end {
570 return None;
571 }
572
573 assert!(self.slice.start.line < self.slice.rope.lines.len());
574 let line = self.slice.rope.line_for_index(self.slice.start);
575
576 if self.slice.start.code_point < line.len() + 1 &&
577 let Some(end_offset) =
578 (self.end_of_forward_motion)(&self.slice, &line[self.slice.start.code_point..])
579 {
580 self.slice.start.code_point += end_offset;
581 return Some(self.slice.start);
582 }
583
584 self.slice.start = self.slice.rope.start_of_following_line(self.slice.start);
586 self.next()
587 }
588}
589
590impl DoubleEndedIterator for RopeMovementIterator<'_> {
591 fn next_back(&mut self) -> Option<Self::Item> {
592 if self.slice.end <= self.slice.start {
594 return None;
595 }
596
597 let line = self.slice.rope.line_for_index(self.slice.end);
598 if self.slice.end.code_point > 0 &&
599 let Some(new_start_index) =
600 (self.start_of_backward_motion)(&self.slice, &line[..self.slice.end.code_point])
601 {
602 self.slice.end.code_point = new_start_index;
603 return Some(self.slice.end);
604 }
605
606 self.slice.end = self.slice.rope.end_of_preceding_line(self.slice.end);
608 self.next_back()
609 }
610}
611
612pub struct RopeChars<'a> {
614 movement_iterator: RopeMovementIterator<'a>,
615}
616
617impl Iterator for RopeChars<'_> {
618 type Item = char;
619 fn next(&mut self) -> Option<Self::Item> {
620 self.movement_iterator
621 .next()
622 .and_then(|index| self.movement_iterator.slice.rope.character_before(index))
623 }
624}
625
626impl DoubleEndedIterator for RopeChars<'_> {
627 fn next_back(&mut self) -> Option<Self::Item> {
628 self.movement_iterator
629 .next_back()
630 .and_then(|index| self.movement_iterator.slice.rope.character_at(index))
631 }
632}
633
634#[test]
635fn test_rope_index_conversion_to_utf8_offset() {
636 let rope = Rope::new("A\nBB\nCCC\nDDDD");
637 assert_eq!(
638 rope.index_to_utf8_offset(RopeIndex::new(0, 0)),
639 Utf8CodeUnits(0),
640 );
641 assert_eq!(
642 rope.index_to_utf8_offset(RopeIndex::new(0, 1)),
643 Utf8CodeUnits(1),
644 );
645 assert_eq!(
646 rope.index_to_utf8_offset(RopeIndex::new(0, 10)),
647 Utf8CodeUnits(1),
648 "RopeIndex with offset past the end of the line should return final offset in line",
649 );
650 assert_eq!(
651 rope.index_to_utf8_offset(RopeIndex::new(1, 0)),
652 Utf8CodeUnits(2),
653 );
654 assert_eq!(
655 rope.index_to_utf8_offset(RopeIndex::new(1, 2)),
656 Utf8CodeUnits(4),
657 );
658
659 assert_eq!(
660 rope.index_to_utf8_offset(RopeIndex::new(3, 0)),
661 Utf8CodeUnits(9),
662 );
663 assert_eq!(
664 rope.index_to_utf8_offset(RopeIndex::new(3, 3)),
665 Utf8CodeUnits(12),
666 );
667 assert_eq!(
668 rope.index_to_utf8_offset(RopeIndex::new(3, 4)),
669 Utf8CodeUnits(13),
670 "There should be no newline at the end of the TextInput",
671 );
672 assert_eq!(
673 rope.index_to_utf8_offset(RopeIndex::new(3, 40)),
674 Utf8CodeUnits(13),
675 "There should be no newline at the end of the TextInput",
676 );
677}
678
679#[test]
680fn test_rope_index_conversion_to_utf16_offset() {
681 let rope = Rope::new("A\nBB\nCCC\n家家");
682 assert_eq!(
683 rope.index_to_utf16_offset(RopeIndex::new(0, 0)),
684 Utf16CodeUnits(0),
685 );
686 assert_eq!(
687 rope.index_to_utf16_offset(RopeIndex::new(0, 1)),
688 Utf16CodeUnits(1),
689 );
690 assert_eq!(
691 rope.index_to_utf16_offset(RopeIndex::new(0, 10)),
692 Utf16CodeUnits(1),
693 "RopeIndex with offset past the end of the line should return final offset in line",
694 );
695 assert_eq!(
696 rope.index_to_utf16_offset(RopeIndex::new(3, 0)),
697 Utf16CodeUnits(9),
698 );
699
700 assert_eq!(
701 rope.index_to_utf16_offset(RopeIndex::new(3, 3)),
702 Utf16CodeUnits(10),
703 "3 code unit UTF-8 encodede character"
704 );
705 assert_eq!(
706 rope.index_to_utf16_offset(RopeIndex::new(3, 6)),
707 Utf16CodeUnits(11),
708 );
709 assert_eq!(
710 rope.index_to_utf16_offset(RopeIndex::new(3, 20)),
711 Utf16CodeUnits(11),
712 );
713}
714
715#[test]
716fn test_utf16_offset_to_utf8_offset() {
717 let rope = Rope::new("A\nBB\nCCC\n家家");
718 assert_eq!(
719 rope.utf16_offset_to_utf8_offset(Utf16CodeUnits(0)),
720 Utf8CodeUnits(0),
721 );
722 assert_eq!(
723 rope.utf16_offset_to_utf8_offset(Utf16CodeUnits(1)),
724 Utf8CodeUnits(1),
725 );
726 assert_eq!(
727 rope.utf16_offset_to_utf8_offset(Utf16CodeUnits(2)),
728 Utf8CodeUnits(2),
729 "Offset past the end of the line",
730 );
731 assert_eq!(
732 rope.utf16_offset_to_utf8_offset(Utf16CodeUnits(9)),
733 Utf8CodeUnits(9),
734 );
735
736 assert_eq!(
737 rope.utf16_offset_to_utf8_offset(Utf16CodeUnits(10)),
738 Utf8CodeUnits(12),
739 "3 code unit UTF-8 encodede character"
740 );
741 assert_eq!(
742 rope.utf16_offset_to_utf8_offset(Utf16CodeUnits(11)),
743 Utf8CodeUnits(15),
744 );
745 assert_eq!(
746 rope.utf16_offset_to_utf8_offset(Utf16CodeUnits(300)),
747 Utf8CodeUnits(15),
748 );
749}
750
751#[test]
752fn test_rope_delete_slice() {
753 let mut rope = Rope::new("ABC\nDEF\n");
754 rope.delete_range(RopeIndex::new(0, 1)..RopeIndex::new(0, 2));
755 assert_eq!(rope.contents(), "AC\nDEF\n");
756
757 let mut rope = Rope::new("ABC\nDEF\n");
760 rope.delete_range(RopeIndex::new(0, 3)..RopeIndex::new(0, 4));
761 assert_eq!(rope.lines, ["ABC\n", "DEF\n", ""]);
762
763 let mut rope = Rope::new("ABC\nDEF\n");
764 rope.delete_range(RopeIndex::new(0, 0)..RopeIndex::new(0, 4));
765 assert_eq!(rope.lines, ["\n", "DEF\n", ""]);
766
767 let mut rope = Rope::new("A\nBB\nCCC");
768 rope.delete_range(RopeIndex::new(0, 2)..RopeIndex::new(1, 0));
769 assert_eq!(rope.lines, ["ABB\n", "CCC"]);
770}
771
772#[test]
773fn test_rope_replace_slice() {
774 let mut rope = Rope::new("AAA\nBBB\nCCC");
775 rope.replace_range(RopeIndex::new(0, 1)..RopeIndex::new(0, 2), "x");
776 assert_eq!(rope.contents(), "AxA\nBBB\nCCC",);
777
778 let mut rope = Rope::new("A\nBB\nCCC");
779 rope.replace_range(RopeIndex::new(0, 2)..RopeIndex::new(1, 0), "D");
780 assert_eq!(rope.lines, ["ADBB\n", "CCC"]);
781
782 let mut rope = Rope::new("AAA\nBBB\nCCC\nDDD");
783 rope.replace_range(RopeIndex::new(0, 2)..RopeIndex::new(2, 1), "x");
784 assert_eq!(rope.lines, ["AAxCC\n", "DDD"]);
785}
786
787#[test]
788fn test_rope_relevant_word() {
789 let rope = Rope::new("AAA BBB CCC");
790 let boundaries = rope.relevant_word_boundaries(RopeIndex::new(0, 0));
791 assert_eq!(boundaries.start, RopeIndex::new(0, 0));
792 assert_eq!(boundaries.end, RopeIndex::new(0, 3));
793
794 let boundaries = rope.relevant_word_boundaries(RopeIndex::new(0, 4));
796 assert_eq!(boundaries.start, RopeIndex::new(0, 0));
797 assert_eq!(boundaries.end, RopeIndex::new(0, 3));
798
799 let boundaries = rope.relevant_word_boundaries(RopeIndex::new(0, 7));
801 assert_eq!(boundaries.start, RopeIndex::new(0, 7));
802 assert_eq!(boundaries.end, RopeIndex::new(0, 10));
803
804 let boundaries = rope.relevant_word_boundaries(RopeIndex::new(0, 8));
806 assert_eq!(boundaries.start, RopeIndex::new(0, 7));
807 assert_eq!(boundaries.end, RopeIndex::new(0, 10));
808
809 let rope = Rope::new(" AAA BBB CCC");
811 let boundaries = rope.relevant_word_boundaries(RopeIndex::new(0, 3));
812 assert_eq!(boundaries.start, RopeIndex::new(0, 0));
813 assert_eq!(boundaries.end, RopeIndex::new(0, 12));
814
815 let rope = Rope::new("");
817 let boundaries = rope.relevant_word_boundaries(RopeIndex::new(0, 0));
818 assert_eq!(boundaries.start, RopeIndex::new(0, 0));
819 assert_eq!(boundaries.end, RopeIndex::new(0, 0));
820}
821
822#[test]
823fn test_rope_index_intersects_character() {
824 let rope = Rope::new("");
825 let rope_index = RopeIndex::new(0, 1);
826 assert_eq!(rope.normalize_index(rope_index), RopeIndex::new(0, 4));
827 assert_eq!(rope.index_to_utf16_offset(rope_index), Utf16CodeUnits(2));
828 assert_eq!(rope.index_to_utf8_offset(rope_index), Utf8CodeUnits(4));
829
830 let rope = Rope::new("abc\ndef");
831 assert_eq!(
832 rope.normalize_index(RopeIndex::new(0, 100)),
833 RopeIndex::new(0, 3),
834 "Normalizing index past end of line should just clamp to line length."
835 );
836 assert_eq!(
837 rope.normalize_index(RopeIndex::new(1, 100)),
838 RopeIndex::new(1, 3),
839 "Normalizing index past end of line should just clamp to line length."
840 );
841}