1use crate::PointUtf16;
2
3use super::Point;
4use arrayvec::ArrayString;
5use smallvec::SmallVec;
6use std::{cmp, ops::Range, str};
7use sum_tree::{Bias, Dimension, SumTree};
8
9#[cfg(test)]
10const CHUNK_BASE: usize = 6;
11
12#[cfg(not(test))]
13const CHUNK_BASE: usize = 16;
14
15#[derive(Clone, Default, Debug)]
16pub struct Rope {
17 chunks: SumTree<Chunk>,
18}
19
20impl Rope {
21 pub fn new() -> Self {
22 Self::default()
23 }
24
25 pub fn append(&mut self, rope: Rope) {
26 let mut chunks = rope.chunks.cursor::<()>();
27 chunks.next(&());
28 if let Some(chunk) = chunks.item() {
29 if self.chunks.last().map_or(false, |c| c.0.len() < CHUNK_BASE)
30 || chunk.0.len() < CHUNK_BASE
31 {
32 self.push(&chunk.0);
33 chunks.next(&());
34 }
35 }
36
37 self.chunks.push_tree(chunks.suffix(&()), &());
38 self.check_invariants();
39 }
40
41 pub fn push(&mut self, text: &str) {
42 let mut new_chunks = SmallVec::<[_; 16]>::new();
43 let mut new_chunk = ArrayString::new();
44 for ch in text.chars() {
45 if new_chunk.len() + ch.len_utf8() > 2 * CHUNK_BASE {
46 new_chunks.push(Chunk(new_chunk));
47 new_chunk = ArrayString::new();
48 }
49 new_chunk.push(ch);
50 }
51 if !new_chunk.is_empty() {
52 new_chunks.push(Chunk(new_chunk));
53 }
54
55 let mut new_chunks = new_chunks.into_iter();
56 let mut first_new_chunk = new_chunks.next();
57 self.chunks.update_last(
58 |last_chunk| {
59 if let Some(first_new_chunk_ref) = first_new_chunk.as_mut() {
60 if last_chunk.0.len() + first_new_chunk_ref.0.len() <= 2 * CHUNK_BASE {
61 last_chunk.0.push_str(&first_new_chunk.take().unwrap().0);
62 } else {
63 let mut text = ArrayString::<{ 4 * CHUNK_BASE }>::new();
64 text.push_str(&last_chunk.0);
65 text.push_str(&first_new_chunk_ref.0);
66 let (left, right) = text.split_at(find_split_ix(&text));
67 last_chunk.0.clear();
68 last_chunk.0.push_str(left);
69 first_new_chunk_ref.0.clear();
70 first_new_chunk_ref.0.push_str(right);
71 }
72 }
73 },
74 &(),
75 );
76
77 self.chunks
78 .extend(first_new_chunk.into_iter().chain(new_chunks), &());
79 self.check_invariants();
80 }
81
82 fn check_invariants(&self) {
83 #[cfg(test)]
84 {
85 // Ensure all chunks except maybe the last one are not underflowing.
86 // Allow some wiggle room for multibyte characters at chunk boundaries.
87 let mut chunks = self.chunks.cursor::<()>().peekable();
88 while let Some(chunk) = chunks.next() {
89 if chunks.peek().is_some() {
90 assert!(chunk.0.len() + 3 >= CHUNK_BASE);
91 }
92 }
93 }
94 }
95
96 pub fn summary(&self) -> TextSummary {
97 self.chunks.summary()
98 }
99
100 pub fn len(&self) -> usize {
101 self.chunks.extent(&())
102 }
103
104 pub fn max_point(&self) -> Point {
105 self.chunks.extent(&())
106 }
107
108 pub fn cursor(&self, offset: usize) -> Cursor {
109 Cursor::new(self, offset)
110 }
111
112 pub fn chars(&self) -> impl Iterator<Item = char> + '_ {
113 self.chars_at(0)
114 }
115
116 pub fn chars_at(&self, start: usize) -> impl Iterator<Item = char> + '_ {
117 self.chunks_in_range(start..self.len()).flat_map(str::chars)
118 }
119
120 pub fn reversed_chars_at(&self, start: usize) -> impl Iterator<Item = char> + '_ {
121 self.reversed_chunks_in_range(0..start)
122 .flat_map(|chunk| chunk.chars().rev())
123 }
124
125 pub fn bytes_at(&self, start: usize) -> impl Iterator<Item = u8> + '_ {
126 self.chunks_in_range(start..self.len()).flat_map(str::bytes)
127 }
128
129 pub fn chunks<'a>(&'a self) -> Chunks<'a> {
130 self.chunks_in_range(0..self.len())
131 }
132
133 pub fn chunks_in_range(&self, range: Range<usize>) -> Chunks {
134 Chunks::new(self, range, false)
135 }
136
137 pub fn reversed_chunks_in_range(&self, range: Range<usize>) -> Chunks {
138 Chunks::new(self, range, true)
139 }
140
141 pub fn offset_to_point(&self, offset: usize) -> Point {
142 assert!(offset <= self.summary().bytes);
143 let mut cursor = self.chunks.cursor::<(usize, Point)>();
144 cursor.seek(&offset, Bias::Left, &());
145 let overshoot = offset - cursor.start().0;
146 cursor.start().1
147 + cursor
148 .item()
149 .map_or(Point::zero(), |chunk| chunk.offset_to_point(overshoot))
150 }
151
152 pub fn offset_to_point_utf16(&self, offset: usize) -> PointUtf16 {
153 assert!(offset <= self.summary().bytes);
154 let mut cursor = self.chunks.cursor::<(usize, PointUtf16)>();
155 cursor.seek(&offset, Bias::Left, &());
156 let overshoot = offset - cursor.start().0;
157 cursor.start().1
158 + cursor.item().map_or(PointUtf16::zero(), |chunk| {
159 chunk.offset_to_point_utf16(overshoot)
160 })
161 }
162
163 pub fn point_to_offset(&self, point: Point) -> usize {
164 assert!(point <= self.summary().lines);
165 let mut cursor = self.chunks.cursor::<(Point, usize)>();
166 cursor.seek(&point, Bias::Left, &());
167 let overshoot = point - cursor.start().0;
168 cursor.start().1
169 + cursor
170 .item()
171 .map_or(0, |chunk| chunk.point_to_offset(overshoot))
172 }
173
174 pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize {
175 assert!(point <= self.summary().lines_utf16);
176 let mut cursor = self.chunks.cursor::<(PointUtf16, usize)>();
177 cursor.seek(&point, Bias::Left, &());
178 let overshoot = point - cursor.start().0;
179 cursor.start().1
180 + cursor
181 .item()
182 .map_or(0, |chunk| chunk.point_utf16_to_offset(overshoot))
183 }
184
185 pub fn clip_offset(&self, mut offset: usize, bias: Bias) -> usize {
186 let mut cursor = self.chunks.cursor::<usize>();
187 cursor.seek(&offset, Bias::Left, &());
188 if let Some(chunk) = cursor.item() {
189 let mut ix = offset - cursor.start();
190 while !chunk.0.is_char_boundary(ix) {
191 match bias {
192 Bias::Left => {
193 ix -= 1;
194 offset -= 1;
195 }
196 Bias::Right => {
197 ix += 1;
198 offset += 1;
199 }
200 }
201 }
202 offset
203 } else {
204 self.summary().bytes
205 }
206 }
207
208 pub fn clip_point(&self, point: Point, bias: Bias) -> Point {
209 let mut cursor = self.chunks.cursor::<Point>();
210 cursor.seek(&point, Bias::Right, &());
211 if let Some(chunk) = cursor.item() {
212 let overshoot = point - cursor.start();
213 *cursor.start() + chunk.clip_point(overshoot, bias)
214 } else {
215 self.summary().lines
216 }
217 }
218
219 pub fn clip_point_utf16(&self, point: PointUtf16, bias: Bias) -> PointUtf16 {
220 let mut cursor = self.chunks.cursor::<PointUtf16>();
221 cursor.seek(&point, Bias::Right, &());
222 if let Some(chunk) = cursor.item() {
223 let overshoot = point - cursor.start();
224 *cursor.start() + chunk.clip_point_utf16(overshoot, bias)
225 } else {
226 self.summary().lines_utf16
227 }
228 }
229}
230
231impl<'a> From<&'a str> for Rope {
232 fn from(text: &'a str) -> Self {
233 let mut rope = Self::new();
234 rope.push(text);
235 rope
236 }
237}
238
239impl Into<String> for Rope {
240 fn into(self) -> String {
241 self.chunks().collect()
242 }
243}
244
245pub struct Cursor<'a> {
246 rope: &'a Rope,
247 chunks: sum_tree::Cursor<'a, Chunk, usize>,
248 offset: usize,
249}
250
251impl<'a> Cursor<'a> {
252 pub fn new(rope: &'a Rope, offset: usize) -> Self {
253 let mut chunks = rope.chunks.cursor();
254 chunks.seek(&offset, Bias::Right, &());
255 Self {
256 rope,
257 chunks,
258 offset,
259 }
260 }
261
262 pub fn seek_forward(&mut self, end_offset: usize) {
263 debug_assert!(end_offset >= self.offset);
264
265 self.chunks.seek_forward(&end_offset, Bias::Right, &());
266 self.offset = end_offset;
267 }
268
269 pub fn slice(&mut self, end_offset: usize) -> Rope {
270 debug_assert!(
271 end_offset >= self.offset,
272 "cannot slice backwards from {} to {}",
273 self.offset,
274 end_offset
275 );
276
277 let mut slice = Rope::new();
278 if let Some(start_chunk) = self.chunks.item() {
279 let start_ix = self.offset - self.chunks.start();
280 let end_ix = cmp::min(end_offset, self.chunks.end(&())) - self.chunks.start();
281 slice.push(&start_chunk.0[start_ix..end_ix]);
282 }
283
284 if end_offset > self.chunks.end(&()) {
285 self.chunks.next(&());
286 slice.append(Rope {
287 chunks: self.chunks.slice(&end_offset, Bias::Right, &()),
288 });
289 if let Some(end_chunk) = self.chunks.item() {
290 let end_ix = end_offset - self.chunks.start();
291 slice.push(&end_chunk.0[..end_ix]);
292 }
293 }
294
295 self.offset = end_offset;
296 slice
297 }
298
299 pub fn summary<D: TextDimension<'a>>(&mut self, end_offset: usize) -> D {
300 debug_assert!(end_offset >= self.offset);
301
302 let mut summary = D::default();
303 if let Some(start_chunk) = self.chunks.item() {
304 let start_ix = self.offset - self.chunks.start();
305 let end_ix = cmp::min(end_offset, self.chunks.end(&())) - self.chunks.start();
306 summary.add_assign(&D::from_text_summary(&TextSummary::from(
307 &start_chunk.0[start_ix..end_ix],
308 )));
309 }
310
311 if end_offset > self.chunks.end(&()) {
312 self.chunks.next(&());
313 summary.add_assign(&self.chunks.summary(&end_offset, Bias::Right, &()));
314 if let Some(end_chunk) = self.chunks.item() {
315 let end_ix = end_offset - self.chunks.start();
316 summary.add_assign(&D::from_text_summary(&TextSummary::from(
317 &end_chunk.0[..end_ix],
318 )));
319 }
320 }
321
322 self.offset = end_offset;
323 summary
324 }
325
326 pub fn suffix(mut self) -> Rope {
327 self.slice(self.rope.chunks.extent(&()))
328 }
329
330 pub fn offset(&self) -> usize {
331 self.offset
332 }
333}
334
335pub struct Chunks<'a> {
336 chunks: sum_tree::Cursor<'a, Chunk, usize>,
337 range: Range<usize>,
338 reversed: bool,
339}
340
341impl<'a> Chunks<'a> {
342 pub fn new(rope: &'a Rope, range: Range<usize>, reversed: bool) -> Self {
343 let mut chunks = rope.chunks.cursor();
344 if reversed {
345 chunks.seek(&range.end, Bias::Left, &());
346 } else {
347 chunks.seek(&range.start, Bias::Right, &());
348 }
349 Self {
350 chunks,
351 range,
352 reversed,
353 }
354 }
355
356 pub fn offset(&self) -> usize {
357 if self.reversed {
358 self.range.end.min(self.chunks.end(&()))
359 } else {
360 self.range.start.max(*self.chunks.start())
361 }
362 }
363
364 pub fn seek(&mut self, offset: usize) {
365 let bias = if self.reversed {
366 Bias::Left
367 } else {
368 Bias::Right
369 };
370
371 if offset >= self.chunks.end(&()) {
372 self.chunks.seek_forward(&offset, bias, &());
373 } else {
374 self.chunks.seek(&offset, bias, &());
375 }
376
377 if self.reversed {
378 self.range.end = offset;
379 } else {
380 self.range.start = offset;
381 }
382 }
383
384 pub fn peek(&self) -> Option<&'a str> {
385 let chunk = self.chunks.item()?;
386 if self.reversed && self.range.start >= self.chunks.end(&()) {
387 return None;
388 }
389 let chunk_start = *self.chunks.start();
390 if self.range.end <= chunk_start {
391 return None;
392 }
393
394 let start = self.range.start.saturating_sub(chunk_start);
395 let end = self.range.end - chunk_start;
396 Some(&chunk.0[start..chunk.0.len().min(end)])
397 }
398}
399
400impl<'a> Iterator for Chunks<'a> {
401 type Item = &'a str;
402
403 fn next(&mut self) -> Option<Self::Item> {
404 let result = self.peek();
405 if result.is_some() {
406 if self.reversed {
407 self.chunks.prev(&());
408 } else {
409 self.chunks.next(&());
410 }
411 }
412 result
413 }
414}
415
416#[derive(Clone, Debug, Default)]
417struct Chunk(ArrayString<{ 2 * CHUNK_BASE }>);
418
419impl Chunk {
420 fn offset_to_point(&self, target: usize) -> Point {
421 let mut offset = 0;
422 let mut point = Point::new(0, 0);
423 for ch in self.0.chars() {
424 if offset >= target {
425 break;
426 }
427
428 if ch == '\n' {
429 point.row += 1;
430 point.column = 0;
431 } else {
432 point.column += ch.len_utf8() as u32;
433 }
434 offset += ch.len_utf8();
435 }
436 point
437 }
438
439 fn offset_to_point_utf16(&self, target: usize) -> PointUtf16 {
440 let mut offset = 0;
441 let mut point = PointUtf16::new(0, 0);
442 for ch in self.0.chars() {
443 if offset >= target {
444 break;
445 }
446
447 if ch == '\n' {
448 point.row += 1;
449 point.column = 0;
450 } else {
451 point.column += ch.len_utf16() as u32;
452 }
453 offset += ch.len_utf8();
454 }
455 point
456 }
457
458 fn point_to_offset(&self, target: Point) -> usize {
459 let mut offset = 0;
460 let mut point = Point::new(0, 0);
461 for ch in self.0.chars() {
462 if point >= target {
463 if point > target {
464 panic!("point {:?} is inside of character {:?}", target, ch);
465 }
466 break;
467 }
468
469 if ch == '\n' {
470 point.row += 1;
471 point.column = 0;
472 } else {
473 point.column += ch.len_utf8() as u32;
474 }
475 offset += ch.len_utf8();
476 }
477 offset
478 }
479
480 fn point_utf16_to_offset(&self, target: PointUtf16) -> usize {
481 let mut offset = 0;
482 let mut point = PointUtf16::new(0, 0);
483 for ch in self.0.chars() {
484 if point >= target {
485 if point > target {
486 panic!("point {:?} is inside of character {:?}", target, ch);
487 }
488 break;
489 }
490
491 if ch == '\n' {
492 point.row += 1;
493 point.column = 0;
494 } else {
495 point.column += ch.len_utf16() as u32;
496 }
497 offset += ch.len_utf8();
498 }
499 offset
500 }
501
502 fn clip_point(&self, target: Point, bias: Bias) -> Point {
503 for (row, line) in self.0.split('\n').enumerate() {
504 if row == target.row as usize {
505 let mut column = target.column.min(line.len() as u32);
506 while !line.is_char_boundary(column as usize) {
507 match bias {
508 Bias::Left => column -= 1,
509 Bias::Right => column += 1,
510 }
511 }
512 return Point::new(row as u32, column);
513 }
514 }
515 unreachable!()
516 }
517
518 fn clip_point_utf16(&self, target: PointUtf16, bias: Bias) -> PointUtf16 {
519 for (row, line) in self.0.split('\n').enumerate() {
520 if row == target.row as usize {
521 let mut code_units = line.encode_utf16();
522 let mut column = code_units.by_ref().take(target.column as usize).count();
523 if char::decode_utf16(code_units).next().transpose().is_err() {
524 match bias {
525 Bias::Left => column -= 1,
526 Bias::Right => column += 1,
527 }
528 }
529 return PointUtf16::new(row as u32, column as u32);
530 }
531 }
532 unreachable!()
533 }
534}
535
536impl sum_tree::Item for Chunk {
537 type Summary = TextSummary;
538
539 fn summary(&self) -> Self::Summary {
540 TextSummary::from(self.0.as_str())
541 }
542}
543
544#[derive(Clone, Debug, Default, Eq, PartialEq)]
545pub struct TextSummary {
546 pub bytes: usize,
547 pub lines: Point,
548 pub lines_utf16: PointUtf16,
549 pub first_line_chars: u32,
550 pub last_line_chars: u32,
551 pub longest_row: u32,
552 pub longest_row_chars: u32,
553}
554
555impl<'a> From<&'a str> for TextSummary {
556 fn from(text: &'a str) -> Self {
557 let mut lines = Point::new(0, 0);
558 let mut lines_utf16 = PointUtf16::new(0, 0);
559 let mut first_line_chars = 0;
560 let mut last_line_chars = 0;
561 let mut longest_row = 0;
562 let mut longest_row_chars = 0;
563 for c in text.chars() {
564 if c == '\n' {
565 lines += Point::new(1, 0);
566 lines_utf16 += PointUtf16::new(1, 0);
567 last_line_chars = 0;
568 } else {
569 lines.column += c.len_utf8() as u32;
570 lines_utf16.column += c.len_utf16() as u32;
571 last_line_chars += 1;
572 }
573
574 if lines.row == 0 {
575 first_line_chars = last_line_chars;
576 }
577
578 if last_line_chars > longest_row_chars {
579 longest_row = lines.row;
580 longest_row_chars = last_line_chars;
581 }
582 }
583
584 TextSummary {
585 bytes: text.len(),
586 lines,
587 lines_utf16,
588 first_line_chars,
589 last_line_chars,
590 longest_row,
591 longest_row_chars,
592 }
593 }
594}
595
596impl sum_tree::Summary for TextSummary {
597 type Context = ();
598
599 fn add_summary(&mut self, summary: &Self, _: &Self::Context) {
600 *self += summary;
601 }
602}
603
604impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
605 fn add_assign(&mut self, other: &'a Self) {
606 let joined_chars = self.last_line_chars + other.first_line_chars;
607 if joined_chars > self.longest_row_chars {
608 self.longest_row = self.lines.row;
609 self.longest_row_chars = joined_chars;
610 }
611 if other.longest_row_chars > self.longest_row_chars {
612 self.longest_row = self.lines.row + other.longest_row;
613 self.longest_row_chars = other.longest_row_chars;
614 }
615
616 if self.lines.row == 0 {
617 self.first_line_chars += other.first_line_chars;
618 }
619
620 if other.lines.row == 0 {
621 self.last_line_chars += other.first_line_chars;
622 } else {
623 self.last_line_chars = other.last_line_chars;
624 }
625
626 self.bytes += other.bytes;
627 self.lines += other.lines;
628 self.lines_utf16 += other.lines_utf16;
629 }
630}
631
632impl std::ops::AddAssign<Self> for TextSummary {
633 fn add_assign(&mut self, other: Self) {
634 *self += &other;
635 }
636}
637
638pub trait TextDimension<'a>: Dimension<'a, TextSummary> {
639 fn from_text_summary(summary: &TextSummary) -> Self;
640 fn add_assign(&mut self, other: &Self);
641}
642
643impl<'a, D1: TextDimension<'a>, D2: TextDimension<'a>> TextDimension<'a> for (D1, D2) {
644 fn from_text_summary(summary: &TextSummary) -> Self {
645 (
646 D1::from_text_summary(summary),
647 D2::from_text_summary(summary),
648 )
649 }
650
651 fn add_assign(&mut self, other: &Self) {
652 self.0.add_assign(&other.0);
653 self.1.add_assign(&other.1);
654 }
655}
656
657impl<'a> TextDimension<'a> for TextSummary {
658 fn from_text_summary(summary: &TextSummary) -> Self {
659 summary.clone()
660 }
661
662 fn add_assign(&mut self, other: &Self) {
663 *self += other;
664 }
665}
666
667impl<'a> sum_tree::Dimension<'a, TextSummary> for usize {
668 fn add_summary(&mut self, summary: &'a TextSummary, _: &()) {
669 *self += summary.bytes;
670 }
671}
672
673impl<'a> TextDimension<'a> for usize {
674 fn from_text_summary(summary: &TextSummary) -> Self {
675 summary.bytes
676 }
677
678 fn add_assign(&mut self, other: &Self) {
679 *self += other;
680 }
681}
682
683impl<'a> sum_tree::Dimension<'a, TextSummary> for Point {
684 fn add_summary(&mut self, summary: &'a TextSummary, _: &()) {
685 *self += summary.lines;
686 }
687}
688
689impl<'a> TextDimension<'a> for Point {
690 fn from_text_summary(summary: &TextSummary) -> Self {
691 summary.lines
692 }
693
694 fn add_assign(&mut self, other: &Self) {
695 *self += other;
696 }
697}
698
699impl<'a> sum_tree::Dimension<'a, TextSummary> for PointUtf16 {
700 fn add_summary(&mut self, summary: &'a TextSummary, _: &()) {
701 *self += summary.lines_utf16;
702 }
703}
704
705impl<'a> TextDimension<'a> for PointUtf16 {
706 fn from_text_summary(summary: &TextSummary) -> Self {
707 summary.lines_utf16
708 }
709
710 fn add_assign(&mut self, other: &Self) {
711 *self += other;
712 }
713}
714
715fn find_split_ix(text: &str) -> usize {
716 let mut ix = text.len() / 2;
717 while !text.is_char_boundary(ix) {
718 if ix < 2 * CHUNK_BASE {
719 ix += 1;
720 } else {
721 ix = (text.len() / 2) - 1;
722 break;
723 }
724 }
725 while !text.is_char_boundary(ix) {
726 ix -= 1;
727 }
728
729 debug_assert!(ix <= 2 * CHUNK_BASE);
730 debug_assert!(text.len() - ix <= 2 * CHUNK_BASE);
731 ix
732}
733
734#[cfg(test)]
735mod tests {
736 use super::*;
737 use crate::random_char_iter::RandomCharIter;
738 use rand::prelude::*;
739 use std::env;
740 use Bias::{Left, Right};
741
742 #[test]
743 fn test_all_4_byte_chars() {
744 let mut rope = Rope::new();
745 let text = "🏀".repeat(256);
746 rope.push(&text);
747 assert_eq!(rope.text(), text);
748 }
749
750 #[test]
751 fn test_clip() {
752 let rope = Rope::from("🧘");
753
754 assert_eq!(rope.clip_offset(1, Bias::Left), 0);
755 assert_eq!(rope.clip_offset(1, Bias::Right), 4);
756 assert_eq!(rope.clip_offset(5, Bias::Right), 4);
757
758 assert_eq!(
759 rope.clip_point(Point::new(0, 1), Bias::Left),
760 Point::new(0, 0)
761 );
762 assert_eq!(
763 rope.clip_point(Point::new(0, 1), Bias::Right),
764 Point::new(0, 4)
765 );
766 assert_eq!(
767 rope.clip_point(Point::new(0, 5), Bias::Right),
768 Point::new(0, 4)
769 );
770
771 assert_eq!(
772 rope.clip_point_utf16(PointUtf16::new(0, 1), Bias::Left),
773 PointUtf16::new(0, 0)
774 );
775 assert_eq!(
776 rope.clip_point_utf16(PointUtf16::new(0, 1), Bias::Right),
777 PointUtf16::new(0, 2)
778 );
779 assert_eq!(
780 rope.clip_point_utf16(PointUtf16::new(0, 3), Bias::Right),
781 PointUtf16::new(0, 2)
782 );
783 }
784
785 #[gpui::test(iterations = 100)]
786 fn test_random(mut rng: StdRng) {
787 let operations = env::var("OPERATIONS")
788 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
789 .unwrap_or(10);
790
791 let mut expected = String::new();
792 let mut actual = Rope::new();
793 for _ in 0..operations {
794 let end_ix = clip_offset(&expected, rng.gen_range(0..=expected.len()), Right);
795 let start_ix = clip_offset(&expected, rng.gen_range(0..=end_ix), Left);
796 let len = rng.gen_range(0..=64);
797 let new_text: String = RandomCharIter::new(&mut rng).take(len).collect();
798
799 let mut new_actual = Rope::new();
800 let mut cursor = actual.cursor(0);
801 new_actual.append(cursor.slice(start_ix));
802 new_actual.push(&new_text);
803 cursor.seek_forward(end_ix);
804 new_actual.append(cursor.suffix());
805 actual = new_actual;
806
807 expected.replace_range(start_ix..end_ix, &new_text);
808
809 assert_eq!(actual.text(), expected);
810 log::info!("text: {:?}", expected);
811
812 for _ in 0..5 {
813 let end_ix = clip_offset(&expected, rng.gen_range(0..=expected.len()), Right);
814 let start_ix = clip_offset(&expected, rng.gen_range(0..=end_ix), Left);
815 assert_eq!(
816 actual.chunks_in_range(start_ix..end_ix).collect::<String>(),
817 &expected[start_ix..end_ix]
818 );
819
820 assert_eq!(
821 actual
822 .reversed_chunks_in_range(start_ix..end_ix)
823 .collect::<Vec<&str>>()
824 .into_iter()
825 .rev()
826 .collect::<String>(),
827 &expected[start_ix..end_ix]
828 );
829 }
830
831 let mut point = Point::new(0, 0);
832 let mut point_utf16 = PointUtf16::new(0, 0);
833 for (ix, ch) in expected.char_indices().chain(Some((expected.len(), '\0'))) {
834 assert_eq!(actual.offset_to_point(ix), point, "offset_to_point({})", ix);
835 assert_eq!(
836 actual.offset_to_point_utf16(ix),
837 point_utf16,
838 "offset_to_point_utf16({})",
839 ix
840 );
841 assert_eq!(
842 actual.point_to_offset(point),
843 ix,
844 "point_to_offset({:?})",
845 point
846 );
847 assert_eq!(
848 actual.point_utf16_to_offset(point_utf16),
849 ix,
850 "point_utf16_to_offset({:?})",
851 point_utf16
852 );
853 if ch == '\n' {
854 point += Point::new(1, 0);
855 point_utf16 += PointUtf16::new(1, 0);
856 } else {
857 point.column += ch.len_utf8() as u32;
858 point_utf16.column += ch.len_utf16() as u32;
859 }
860 }
861
862 for _ in 0..5 {
863 let end_ix = clip_offset(&expected, rng.gen_range(0..=expected.len()), Right);
864 let start_ix = clip_offset(&expected, rng.gen_range(0..=end_ix), Left);
865 assert_eq!(
866 actual.cursor(start_ix).summary::<TextSummary>(end_ix),
867 TextSummary::from(&expected[start_ix..end_ix])
868 );
869 }
870 }
871 }
872
873 fn clip_offset(text: &str, mut offset: usize, bias: Bias) -> usize {
874 while !text.is_char_boundary(offset) {
875 match bias {
876 Bias::Left => offset -= 1,
877 Bias::Right => offset += 1,
878 }
879 }
880 offset
881 }
882
883 impl Rope {
884 fn text(&self) -> String {
885 let mut text = String::new();
886 for chunk in self.chunks.cursor::<()>() {
887 text.push_str(&chunk.0);
888 }
889 text
890 }
891 }
892}