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_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_summary(&TextSummary::from(&end_chunk.0[..end_ix])));
317 }
318 }
319
320 self.offset = end_offset;
321 summary
322 }
323
324 pub fn suffix(mut self) -> Rope {
325 self.slice(self.rope.chunks.extent(&()))
326 }
327
328 pub fn offset(&self) -> usize {
329 self.offset
330 }
331}
332
333pub struct Chunks<'a> {
334 chunks: sum_tree::Cursor<'a, Chunk, usize>,
335 range: Range<usize>,
336 reversed: bool,
337}
338
339impl<'a> Chunks<'a> {
340 pub fn new(rope: &'a Rope, range: Range<usize>, reversed: bool) -> Self {
341 let mut chunks = rope.chunks.cursor();
342 if reversed {
343 chunks.seek(&range.end, Bias::Left, &());
344 } else {
345 chunks.seek(&range.start, Bias::Right, &());
346 }
347 Self {
348 chunks,
349 range,
350 reversed,
351 }
352 }
353
354 pub fn offset(&self) -> usize {
355 if self.reversed {
356 self.range.end.min(self.chunks.end(&()))
357 } else {
358 self.range.start.max(*self.chunks.start())
359 }
360 }
361
362 pub fn seek(&mut self, offset: usize) {
363 let bias = if self.reversed {
364 Bias::Left
365 } else {
366 Bias::Right
367 };
368
369 if offset >= self.chunks.end(&()) {
370 self.chunks.seek_forward(&offset, bias, &());
371 } else {
372 self.chunks.seek(&offset, bias, &());
373 }
374
375 if self.reversed {
376 self.range.end = offset;
377 } else {
378 self.range.start = offset;
379 }
380 }
381
382 pub fn peek(&self) -> Option<&'a str> {
383 let chunk = self.chunks.item()?;
384 if self.reversed && self.range.start >= self.chunks.end(&()) {
385 return None;
386 }
387 let chunk_start = *self.chunks.start();
388 if self.range.end <= chunk_start {
389 return None;
390 }
391
392 let start = self.range.start.saturating_sub(chunk_start);
393 let end = self.range.end - chunk_start;
394 Some(&chunk.0[start..chunk.0.len().min(end)])
395 }
396}
397
398impl<'a> Iterator for Chunks<'a> {
399 type Item = &'a str;
400
401 fn next(&mut self) -> Option<Self::Item> {
402 let result = self.peek();
403 if result.is_some() {
404 if self.reversed {
405 self.chunks.prev(&());
406 } else {
407 self.chunks.next(&());
408 }
409 }
410 result
411 }
412}
413
414#[derive(Clone, Debug, Default)]
415struct Chunk(ArrayString<{ 2 * CHUNK_BASE }>);
416
417impl Chunk {
418 fn offset_to_point(&self, target: usize) -> Point {
419 let mut offset = 0;
420 let mut point = Point::new(0, 0);
421 for ch in self.0.chars() {
422 if offset >= target {
423 break;
424 }
425
426 if ch == '\n' {
427 point.row += 1;
428 point.column = 0;
429 } else {
430 point.column += ch.len_utf8() as u32;
431 }
432 offset += ch.len_utf8();
433 }
434 point
435 }
436
437 fn offset_to_point_utf16(&self, target: usize) -> PointUtf16 {
438 let mut offset = 0;
439 let mut point = PointUtf16::new(0, 0);
440 for ch in self.0.chars() {
441 if offset >= target {
442 break;
443 }
444
445 if ch == '\n' {
446 point.row += 1;
447 point.column = 0;
448 } else {
449 point.column += ch.len_utf16() as u32;
450 }
451 offset += ch.len_utf8();
452 }
453 point
454 }
455
456 fn point_to_offset(&self, target: Point) -> usize {
457 let mut offset = 0;
458 let mut point = Point::new(0, 0);
459 for ch in self.0.chars() {
460 if point >= target {
461 if point > target {
462 panic!("point {:?} is inside of character {:?}", target, ch);
463 }
464 break;
465 }
466
467 if ch == '\n' {
468 point.row += 1;
469 point.column = 0;
470 } else {
471 point.column += ch.len_utf8() as u32;
472 }
473 offset += ch.len_utf8();
474 }
475 offset
476 }
477
478 fn point_utf16_to_offset(&self, target: PointUtf16) -> usize {
479 let mut offset = 0;
480 let mut point = PointUtf16::new(0, 0);
481 for ch in self.0.chars() {
482 if point >= target {
483 if point > target {
484 panic!("point {:?} is inside of character {:?}", target, ch);
485 }
486 break;
487 }
488
489 if ch == '\n' {
490 point.row += 1;
491 point.column = 0;
492 } else {
493 point.column += ch.len_utf16() as u32;
494 }
495 offset += ch.len_utf8();
496 }
497 offset
498 }
499
500 fn clip_point(&self, target: Point, bias: Bias) -> Point {
501 for (row, line) in self.0.split('\n').enumerate() {
502 if row == target.row as usize {
503 let mut column = target.column.min(line.len() as u32);
504 while !line.is_char_boundary(column as usize) {
505 match bias {
506 Bias::Left => column -= 1,
507 Bias::Right => column += 1,
508 }
509 }
510 return Point::new(row as u32, column);
511 }
512 }
513 unreachable!()
514 }
515
516 fn clip_point_utf16(&self, target: PointUtf16, bias: Bias) -> PointUtf16 {
517 for (row, line) in self.0.split('\n').enumerate() {
518 if row == target.row as usize {
519 let mut code_units = line.encode_utf16();
520 let mut column = code_units.by_ref().take(target.column as usize).count();
521 if char::decode_utf16(code_units).next().transpose().is_err() {
522 match bias {
523 Bias::Left => column -= 1,
524 Bias::Right => column += 1,
525 }
526 }
527 return PointUtf16::new(row as u32, column as u32);
528 }
529 }
530 unreachable!()
531 }
532}
533
534impl sum_tree::Item for Chunk {
535 type Summary = TextSummary;
536
537 fn summary(&self) -> Self::Summary {
538 TextSummary::from(self.0.as_str())
539 }
540}
541
542#[derive(Clone, Debug, Default, Eq, PartialEq)]
543pub struct TextSummary {
544 pub bytes: usize,
545 pub lines: Point,
546 pub lines_utf16: PointUtf16,
547 pub first_line_chars: u32,
548 pub last_line_chars: u32,
549 pub longest_row: u32,
550 pub longest_row_chars: u32,
551}
552
553impl<'a> From<&'a str> for TextSummary {
554 fn from(text: &'a str) -> Self {
555 let mut lines = Point::new(0, 0);
556 let mut lines_utf16 = PointUtf16::new(0, 0);
557 let mut first_line_chars = 0;
558 let mut last_line_chars = 0;
559 let mut longest_row = 0;
560 let mut longest_row_chars = 0;
561 for c in text.chars() {
562 if c == '\n' {
563 lines += Point::new(1, 0);
564 lines_utf16 += PointUtf16::new(1, 0);
565 last_line_chars = 0;
566 } else {
567 lines.column += c.len_utf8() as u32;
568 lines_utf16.column += c.len_utf16() as u32;
569 last_line_chars += 1;
570 }
571
572 if lines.row == 0 {
573 first_line_chars = last_line_chars;
574 }
575
576 if last_line_chars > longest_row_chars {
577 longest_row = lines.row;
578 longest_row_chars = last_line_chars;
579 }
580 }
581
582 TextSummary {
583 bytes: text.len(),
584 lines,
585 lines_utf16,
586 first_line_chars,
587 last_line_chars,
588 longest_row,
589 longest_row_chars,
590 }
591 }
592}
593
594impl sum_tree::Summary for TextSummary {
595 type Context = ();
596
597 fn add_summary(&mut self, summary: &Self, _: &Self::Context) {
598 *self += summary;
599 }
600}
601
602impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
603 fn add_assign(&mut self, other: &'a Self) {
604 let joined_chars = self.last_line_chars + other.first_line_chars;
605 if joined_chars > self.longest_row_chars {
606 self.longest_row = self.lines.row;
607 self.longest_row_chars = joined_chars;
608 }
609 if other.longest_row_chars > self.longest_row_chars {
610 self.longest_row = self.lines.row + other.longest_row;
611 self.longest_row_chars = other.longest_row_chars;
612 }
613
614 if self.lines.row == 0 {
615 self.first_line_chars += other.first_line_chars;
616 }
617
618 if other.lines.row == 0 {
619 self.last_line_chars += other.first_line_chars;
620 } else {
621 self.last_line_chars = other.last_line_chars;
622 }
623
624 self.bytes += other.bytes;
625 self.lines += other.lines;
626 self.lines_utf16 += other.lines_utf16;
627 }
628}
629
630impl std::ops::AddAssign<Self> for TextSummary {
631 fn add_assign(&mut self, other: Self) {
632 *self += &other;
633 }
634}
635
636pub trait TextDimension<'a>: Dimension<'a, TextSummary> {
637 fn from_summary(summary: &TextSummary) -> Self;
638 fn add_assign(&mut self, other: &Self);
639}
640
641impl<'a, D1: TextDimension<'a>, D2: TextDimension<'a>> TextDimension<'a> for (D1, D2) {
642 fn from_summary(summary: &TextSummary) -> Self {
643 (D1::from_summary(summary), D2::from_summary(summary))
644 }
645
646 fn add_assign(&mut self, other: &Self) {
647 self.0.add_assign(&other.0);
648 self.1.add_assign(&other.1);
649 }
650}
651
652impl<'a> TextDimension<'a> for TextSummary {
653 fn from_summary(summary: &TextSummary) -> Self {
654 summary.clone()
655 }
656
657 fn add_assign(&mut self, other: &Self) {
658 *self += other;
659 }
660}
661
662impl<'a> sum_tree::Dimension<'a, TextSummary> for usize {
663 fn add_summary(&mut self, summary: &'a TextSummary, _: &()) {
664 *self += summary.bytes;
665 }
666}
667
668impl<'a> TextDimension<'a> for usize {
669 fn from_summary(summary: &TextSummary) -> Self {
670 summary.bytes
671 }
672
673 fn add_assign(&mut self, other: &Self) {
674 *self += other;
675 }
676}
677
678impl<'a> sum_tree::Dimension<'a, TextSummary> for Point {
679 fn add_summary(&mut self, summary: &'a TextSummary, _: &()) {
680 *self += summary.lines;
681 }
682}
683
684impl<'a> TextDimension<'a> for Point {
685 fn from_summary(summary: &TextSummary) -> Self {
686 summary.lines
687 }
688
689 fn add_assign(&mut self, other: &Self) {
690 *self += other;
691 }
692}
693
694impl<'a> sum_tree::Dimension<'a, TextSummary> for PointUtf16 {
695 fn add_summary(&mut self, summary: &'a TextSummary, _: &()) {
696 *self += summary.lines_utf16;
697 }
698}
699
700impl<'a> TextDimension<'a> for PointUtf16 {
701 fn from_summary(summary: &TextSummary) -> Self {
702 summary.lines_utf16
703 }
704
705 fn add_assign(&mut self, other: &Self) {
706 *self += other;
707 }
708}
709
710fn find_split_ix(text: &str) -> usize {
711 let mut ix = text.len() / 2;
712 while !text.is_char_boundary(ix) {
713 if ix < 2 * CHUNK_BASE {
714 ix += 1;
715 } else {
716 ix = (text.len() / 2) - 1;
717 break;
718 }
719 }
720 while !text.is_char_boundary(ix) {
721 ix -= 1;
722 }
723
724 debug_assert!(ix <= 2 * CHUNK_BASE);
725 debug_assert!(text.len() - ix <= 2 * CHUNK_BASE);
726 ix
727}
728
729#[cfg(test)]
730mod tests {
731 use super::*;
732 use crate::random_char_iter::RandomCharIter;
733 use rand::prelude::*;
734 use std::env;
735 use Bias::{Left, Right};
736
737 #[test]
738 fn test_all_4_byte_chars() {
739 let mut rope = Rope::new();
740 let text = "🏀".repeat(256);
741 rope.push(&text);
742 assert_eq!(rope.text(), text);
743 }
744
745 #[test]
746 fn test_clip() {
747 let rope = Rope::from("🧘");
748
749 assert_eq!(rope.clip_offset(1, Bias::Left), 0);
750 assert_eq!(rope.clip_offset(1, Bias::Right), 4);
751 assert_eq!(rope.clip_offset(5, Bias::Right), 4);
752
753 assert_eq!(
754 rope.clip_point(Point::new(0, 1), Bias::Left),
755 Point::new(0, 0)
756 );
757 assert_eq!(
758 rope.clip_point(Point::new(0, 1), Bias::Right),
759 Point::new(0, 4)
760 );
761 assert_eq!(
762 rope.clip_point(Point::new(0, 5), Bias::Right),
763 Point::new(0, 4)
764 );
765
766 assert_eq!(
767 rope.clip_point_utf16(PointUtf16::new(0, 1), Bias::Left),
768 PointUtf16::new(0, 0)
769 );
770 assert_eq!(
771 rope.clip_point_utf16(PointUtf16::new(0, 1), Bias::Right),
772 PointUtf16::new(0, 2)
773 );
774 assert_eq!(
775 rope.clip_point_utf16(PointUtf16::new(0, 3), Bias::Right),
776 PointUtf16::new(0, 2)
777 );
778 }
779
780 #[gpui::test(iterations = 100)]
781 fn test_random(mut rng: StdRng) {
782 let operations = env::var("OPERATIONS")
783 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
784 .unwrap_or(10);
785
786 let mut expected = String::new();
787 let mut actual = Rope::new();
788 for _ in 0..operations {
789 let end_ix = clip_offset(&expected, rng.gen_range(0..=expected.len()), Right);
790 let start_ix = clip_offset(&expected, rng.gen_range(0..=end_ix), Left);
791 let len = rng.gen_range(0..=64);
792 let new_text: String = RandomCharIter::new(&mut rng).take(len).collect();
793
794 let mut new_actual = Rope::new();
795 let mut cursor = actual.cursor(0);
796 new_actual.append(cursor.slice(start_ix));
797 new_actual.push(&new_text);
798 cursor.seek_forward(end_ix);
799 new_actual.append(cursor.suffix());
800 actual = new_actual;
801
802 expected.replace_range(start_ix..end_ix, &new_text);
803
804 assert_eq!(actual.text(), expected);
805 log::info!("text: {:?}", expected);
806
807 for _ in 0..5 {
808 let end_ix = clip_offset(&expected, rng.gen_range(0..=expected.len()), Right);
809 let start_ix = clip_offset(&expected, rng.gen_range(0..=end_ix), Left);
810 assert_eq!(
811 actual.chunks_in_range(start_ix..end_ix).collect::<String>(),
812 &expected[start_ix..end_ix]
813 );
814
815 assert_eq!(
816 actual
817 .reversed_chunks_in_range(start_ix..end_ix)
818 .collect::<Vec<&str>>()
819 .into_iter()
820 .rev()
821 .collect::<String>(),
822 &expected[start_ix..end_ix]
823 );
824 }
825
826 let mut point = Point::new(0, 0);
827 let mut point_utf16 = PointUtf16::new(0, 0);
828 for (ix, ch) in expected.char_indices().chain(Some((expected.len(), '\0'))) {
829 assert_eq!(actual.offset_to_point(ix), point, "offset_to_point({})", ix);
830 assert_eq!(
831 actual.offset_to_point_utf16(ix),
832 point_utf16,
833 "offset_to_point_utf16({})",
834 ix
835 );
836 assert_eq!(
837 actual.point_to_offset(point),
838 ix,
839 "point_to_offset({:?})",
840 point
841 );
842 assert_eq!(
843 actual.point_utf16_to_offset(point_utf16),
844 ix,
845 "point_utf16_to_offset({:?})",
846 point_utf16
847 );
848 if ch == '\n' {
849 point += Point::new(1, 0);
850 point_utf16 += PointUtf16::new(1, 0);
851 } else {
852 point.column += ch.len_utf8() as u32;
853 point_utf16.column += ch.len_utf16() as u32;
854 }
855 }
856
857 for _ in 0..5 {
858 let end_ix = clip_offset(&expected, rng.gen_range(0..=expected.len()), Right);
859 let start_ix = clip_offset(&expected, rng.gen_range(0..=end_ix), Left);
860 assert_eq!(
861 actual.cursor(start_ix).summary::<TextSummary>(end_ix),
862 TextSummary::from(&expected[start_ix..end_ix])
863 );
864 }
865 }
866 }
867
868 fn clip_offset(text: &str, mut offset: usize, bias: Bias) -> usize {
869 while !text.is_char_boundary(offset) {
870 match bias {
871 Bias::Left => offset -= 1,
872 Bias::Right => offset += 1,
873 }
874 }
875 offset
876 }
877
878 impl Rope {
879 fn text(&self) -> String {
880 let mut text = String::new();
881 for chunk in self.chunks.cursor::<()>() {
882 text.push_str(&chunk.0);
883 }
884 text
885 }
886 }
887}