1#![forbid(unsafe_code)]
2
3use std::borrow::Cow;
9use std::fmt;
10use std::ops::{Bound, RangeBounds};
11use std::str::FromStr;
12
13use ropey::{Rope as InnerRope, RopeSlice};
14use unicode_segmentation::UnicodeSegmentation;
15
16#[derive(Clone, Debug, Default)]
18pub struct Rope {
19 rope: InnerRope,
20}
21
22impl Rope {
23 #[must_use]
25 pub fn new() -> Self {
26 Self {
27 rope: InnerRope::new(),
28 }
29 }
30
31 #[must_use]
35 pub fn from_text(s: &str) -> Self {
36 Self {
37 rope: InnerRope::from_str(s),
38 }
39 }
40
41 #[inline]
43 #[must_use]
44 pub fn len_bytes(&self) -> usize {
45 self.rope.len_bytes()
46 }
47
48 #[inline]
50 #[must_use]
51 pub fn len_chars(&self) -> usize {
52 self.rope.len_chars()
53 }
54
55 #[inline]
57 #[must_use]
58 pub fn len_lines(&self) -> usize {
59 self.rope.len_lines()
60 }
61
62 #[inline]
64 #[must_use]
65 pub fn is_empty(&self) -> bool {
66 self.rope.len_bytes() == 0
67 }
68
69 #[must_use]
71 pub fn line(&self, idx: usize) -> Option<Cow<'_, str>> {
72 if idx < self.len_lines() {
73 Some(cow_from_slice(self.rope.line(idx)))
74 } else {
75 None
76 }
77 }
78
79 pub fn lines(&self) -> impl Iterator<Item = Cow<'_, str>> + '_ {
81 self.rope.lines().map(cow_from_slice)
82 }
83
84 #[must_use]
86 pub fn slice<R>(&self, range: R) -> Cow<'_, str>
87 where
88 R: RangeBounds<usize>,
89 {
90 self.rope
91 .get_slice(range)
92 .map(cow_from_slice)
93 .unwrap_or_else(|| Cow::Borrowed(""))
94 }
95
96 pub fn insert(&mut self, char_idx: usize, text: &str) {
98 if text.len() >= 10_000 {
99 tracing::debug!(len = text.len(), "rope insert large text");
100 }
101 let idx = char_idx.min(self.len_chars());
102 self.rope.insert(idx, text);
103 }
104
105 pub fn insert_grapheme(&mut self, grapheme_idx: usize, text: &str) {
107 let char_idx = self.grapheme_to_char_idx(grapheme_idx);
108 self.insert(char_idx, text);
109 }
110
111 pub fn remove<R>(&mut self, range: R)
113 where
114 R: RangeBounds<usize>,
115 {
116 let (start, end) = normalize_range(range, self.len_chars());
117 if start < end {
118 self.rope.remove(start..end);
119 }
120 }
121
122 pub fn remove_grapheme_range<R>(&mut self, range: R)
124 where
125 R: RangeBounds<usize>,
126 {
127 let start = match range.start_bound() {
128 Bound::Included(&s) => s,
129 Bound::Excluded(&s) => s.saturating_add(1),
130 Bound::Unbounded => 0,
131 };
132 let end = match range.end_bound() {
133 Bound::Included(&e) => e.saturating_add(1),
134 Bound::Excluded(&e) => e,
135 Bound::Unbounded => usize::MAX,
136 };
137
138 let start = start.min(end);
139 if start < end {
140 let char_start = self.grapheme_to_char_idx(start);
141 let char_end = self.grapheme_to_char_idx(end);
142 if char_start < char_end {
143 self.rope.remove(char_start..char_end);
144 }
145 }
146 }
147
148 pub fn replace(&mut self, text: &str) {
150 if text.len() >= 10_000 {
151 tracing::debug!(len = text.len(), "rope replace large text");
152 }
153 self.rope = InnerRope::from(text);
154 }
155
156 pub fn append(&mut self, text: &str) {
158 let len = self.len_chars();
159 self.insert(len, text);
160 }
161
162 pub fn clear(&mut self) {
164 self.rope = InnerRope::new();
165 }
166
167 #[inline]
169 #[must_use]
170 pub fn char_to_byte(&self, char_idx: usize) -> usize {
171 self.rope.char_to_byte(char_idx.min(self.len_chars()))
172 }
173
174 #[inline]
176 #[must_use]
177 pub fn byte_to_char(&self, byte_idx: usize) -> usize {
178 self.rope.byte_to_char(byte_idx.min(self.len_bytes()))
179 }
180
181 #[inline]
183 #[must_use]
184 pub fn char_to_line(&self, char_idx: usize) -> usize {
185 self.rope.char_to_line(char_idx.min(self.len_chars()))
186 }
187
188 #[inline]
190 #[must_use]
191 pub fn line_to_char(&self, line_idx: usize) -> usize {
192 if line_idx >= self.len_lines() {
193 self.len_chars()
194 } else {
195 self.rope.line_to_char(line_idx)
196 }
197 }
198
199 #[inline]
201 #[must_use]
202 pub fn byte_to_line_col(&self, byte_idx: usize) -> (usize, usize) {
203 let char_idx = self.byte_to_char(byte_idx);
204 let line = self.char_to_line(char_idx);
205 let line_start = self.line_to_char(line);
206 (line, char_idx.saturating_sub(line_start))
207 }
208
209 #[inline]
211 #[must_use]
212 pub fn line_col_to_byte(&self, line_idx: usize, col: usize) -> usize {
213 let line_start = self.line_to_char(line_idx);
214 let next_line_start = if line_idx + 1 < self.len_lines() {
215 self.line_to_char(line_idx + 1)
216 } else {
217 self.len_chars()
218 };
219 let line_len = next_line_start.saturating_sub(line_start);
220
221 let char_idx = line_start
222 .saturating_add(col.min(line_len))
223 .min(self.len_chars());
224 self.char_to_byte(char_idx)
225 }
226
227 pub fn chars(&self) -> impl Iterator<Item = char> + '_ {
229 self.rope.chars()
230 }
231
232 #[must_use]
234 pub fn graphemes(&self) -> Vec<String> {
235 let mut result = Vec::with_capacity(self.len_chars());
236 for line in self.lines() {
237 result.extend(line.graphemes(true).map(String::from));
238 }
239 result
240 }
241
242 #[must_use]
244 pub fn grapheme_count(&self) -> usize {
245 self.lines().map(|line| line.graphemes(true).count()).sum()
246 }
247
248 fn grapheme_to_char_idx(&self, grapheme_idx: usize) -> usize {
249 if grapheme_idx == 0 {
250 return 0;
251 }
252 if grapheme_idx >= self.len_chars() {
253 return self.len_chars();
254 }
255
256 let mut g_count = 0;
257 let mut char_count = 0;
258
259 for line_slice in self.rope.lines() {
260 let line = cow_from_slice(line_slice);
261 for g in line.graphemes(true) {
262 if g_count == grapheme_idx {
263 return char_count;
264 }
265 g_count += 1;
266 char_count += g.chars().count();
267 }
268 }
269 self.len_chars()
270 }
271}
272
273impl fmt::Display for Rope {
274 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275 for chunk in self.rope.chunks() {
276 f.write_str(chunk)?;
277 }
278 Ok(())
279 }
280}
281
282impl FromStr for Rope {
283 type Err = std::convert::Infallible;
284
285 fn from_str(s: &str) -> Result<Self, Self::Err> {
286 Ok(Self::from_text(s))
287 }
288}
289
290impl From<&str> for Rope {
291 fn from(s: &str) -> Self {
292 Self::from_text(s)
293 }
294}
295
296impl From<String> for Rope {
297 fn from(s: String) -> Self {
298 Self::from_text(&s)
299 }
300}
301
302fn cow_from_slice(slice: RopeSlice<'_>) -> Cow<'_, str> {
303 match slice.as_str() {
304 Some(s) => Cow::Borrowed(s),
305 None => Cow::Owned(slice.to_string()),
306 }
307}
308
309fn normalize_range<R>(range: R, max: usize) -> (usize, usize)
310where
311 R: RangeBounds<usize>,
312{
313 let start = match range.start_bound() {
314 Bound::Included(&s) => s,
315 Bound::Excluded(&s) => s.saturating_add(1),
316 Bound::Unbounded => 0,
317 };
318 let end = match range.end_bound() {
319 Bound::Included(&e) => e.saturating_add(1),
320 Bound::Excluded(&e) => e,
321 Bound::Unbounded => max,
322 };
323
324 let start = start.min(max);
325 let end = end.min(max);
326 if end < start {
327 (start, start)
328 } else {
329 (start, end)
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336 use proptest::prelude::*;
337
338 #[test]
339 fn rope_basic_counts() {
340 let rope = Rope::from("Hello, world!");
341 assert_eq!(rope.len_chars(), 13);
342 assert_eq!(rope.len_lines(), 1);
343 }
344
345 #[test]
346 fn rope_multiline_lines() {
347 let rope = Rope::from("Line 1\nLine 2\nLine 3");
348 assert_eq!(rope.len_lines(), 3);
349 assert_eq!(rope.line(0).unwrap(), "Line 1\n");
350 assert_eq!(rope.line(2).unwrap(), "Line 3");
351 }
352
353 #[test]
354 fn rope_insert_remove_replace() {
355 let mut rope = Rope::from("Hello!");
356 rope.insert(5, ", world");
357 assert_eq!(rope.to_string(), "Hello, world!");
358
359 rope.remove(5..12);
360 assert_eq!(rope.to_string(), "Hello!");
361
362 rope.replace("Replaced");
363 assert_eq!(rope.to_string(), "Replaced");
364 }
365
366 #[test]
367 fn rope_append_clear() {
368 let mut rope = Rope::from("Hi");
369 rope.append(" there");
370 assert_eq!(rope.to_string(), "Hi there");
371 rope.clear();
372 assert!(rope.is_empty());
373 assert_eq!(rope.len_lines(), 1);
374 }
375
376 #[test]
377 fn rope_char_byte_conversions() {
378 let s = "a\u{1F600}b";
379 let rope = Rope::from(s);
380 assert_eq!(rope.len_chars(), 3);
381 assert_eq!(rope.char_to_byte(0), 0);
382 assert_eq!(rope.char_to_byte(1), "a".len());
383 assert_eq!(rope.byte_to_char(rope.len_bytes()), 3);
384 }
385
386 #[test]
387 fn rope_line_col_conversions() {
388 let rope = Rope::from("ab\ncde\n");
389 let (line, col) = rope.byte_to_line_col(4);
390 assert_eq!(line, 1);
391 assert_eq!(col, 1);
392
393 let byte = rope.line_col_to_byte(1, 2);
394 assert_eq!(byte, 5);
395 }
396
397 #[test]
398 fn rope_grapheme_ops() {
399 let mut rope = Rope::from("e\u{301}");
400 assert_eq!(rope.grapheme_count(), 1);
401 rope.insert_grapheme(1, "!");
402 assert_eq!(rope.to_string(), "e\u{301}!");
403
404 let mut rope = Rope::from("a\u{1F600}b");
405 rope.remove_grapheme_range(1..2);
406 assert_eq!(rope.to_string(), "ab");
407 }
408
409 proptest! {
410 #[test]
411 fn insert_remove_roundtrip(s in any::<String>(), insert in any::<String>(), idx in 0usize..200) {
412 let mut rope = Rope::from(s.as_str());
413 let insert_len = insert.chars().count();
414 let pos = idx.min(rope.len_chars());
415 rope.insert(pos, &insert);
416 rope.remove(pos..pos.saturating_add(insert_len));
417 prop_assert_eq!(rope.to_string(), s);
418 }
419
420 #[test]
421 fn line_count_matches_newlines(s in "[^\r\u{000B}\u{000C}\u{0085}\u{2028}\u{2029}]*") {
422 let rope = Rope::from(s.as_str());
425 let newlines = s.as_bytes().iter().filter(|&&b| b == b'\n').count();
426 prop_assert_eq!(rope.len_lines(), newlines + 1);
427 }
428 }
429
430 #[test]
433 fn empty_rope_properties() {
434 let rope = Rope::new();
435 assert!(rope.is_empty());
436 assert_eq!(rope.len_bytes(), 0);
437 assert_eq!(rope.len_chars(), 0);
438 assert_eq!(rope.len_lines(), 1); assert_eq!(rope.grapheme_count(), 0);
440 assert_eq!(rope.to_string(), "");
441 }
442
443 #[test]
444 fn empty_rope_line_access() {
445 let rope = Rope::new();
446 assert!(rope.line(0).is_some()); assert!(rope.line(1).is_none());
448 }
449
450 #[test]
451 fn empty_rope_slice() {
452 let rope = Rope::new();
453 assert_eq!(rope.slice(0..0), "");
454 assert_eq!(rope.slice(..), "");
455 }
456
457 #[test]
458 fn empty_rope_conversions() {
459 let rope = Rope::new();
460 assert_eq!(rope.char_to_byte(0), 0);
461 assert_eq!(rope.byte_to_char(0), 0);
462 assert_eq!(rope.char_to_line(0), 0);
463 assert_eq!(rope.line_to_char(0), 0);
464 }
465
466 #[test]
469 fn from_str_impl() {
470 let rope: Rope = "hello".into();
471 assert_eq!(rope.to_string(), "hello");
472 }
473
474 #[test]
475 fn from_string_impl() {
476 let rope: Rope = String::from("hello").into();
477 assert_eq!(rope.to_string(), "hello");
478 }
479
480 #[test]
481 fn from_str_parse() {
482 let rope: Rope = "hello".parse().unwrap();
483 assert_eq!(rope.to_string(), "hello");
484 }
485
486 #[test]
487 fn display_impl() {
488 let rope = Rope::from("hello world");
489 assert_eq!(format!("{rope}"), "hello world");
490 }
491
492 #[test]
495 fn line_out_of_bounds() {
496 let rope = Rope::from("a\nb");
497 assert!(rope.line(0).is_some());
498 assert!(rope.line(1).is_some());
499 assert!(rope.line(2).is_none());
500 assert!(rope.line(100).is_none());
501 }
502
503 #[test]
504 fn trailing_newline_creates_empty_last_line() {
505 let rope = Rope::from("a\n");
506 assert_eq!(rope.len_lines(), 2);
507 assert_eq!(rope.line(0).unwrap(), "a\n");
508 assert_eq!(rope.line(1).unwrap(), "");
509 }
510
511 #[test]
512 fn multiple_newlines() {
513 let rope = Rope::from("\n\n\n");
514 assert_eq!(rope.len_lines(), 4);
515 }
516
517 #[test]
518 fn lines_iterator() {
519 let rope = Rope::from("a\nb\nc");
520 let lines: Vec<String> = rope.lines().map(|c| c.to_string()).collect();
521 assert_eq!(lines.len(), 3);
522 assert_eq!(lines[0], "a\n");
523 assert_eq!(lines[1], "b\n");
524 assert_eq!(lines[2], "c");
525 }
526
527 #[test]
530 fn slice_basic() {
531 let rope = Rope::from("hello world");
532 assert_eq!(rope.slice(0..5), "hello");
533 assert_eq!(rope.slice(6..11), "world");
534 assert_eq!(rope.slice(6..), "world");
535 assert_eq!(rope.slice(..5), "hello");
536 }
537
538 #[test]
539 fn slice_out_of_bounds_returns_empty() {
540 let rope = Rope::from("hi");
541 assert_eq!(rope.slice(100..200), "");
542 }
543
544 #[test]
547 fn insert_at_beginning() {
548 let mut rope = Rope::from("world");
549 rope.insert(0, "hello ");
550 assert_eq!(rope.to_string(), "hello world");
551 }
552
553 #[test]
554 fn insert_at_end() {
555 let mut rope = Rope::from("hello");
556 rope.insert(5, " world");
557 assert_eq!(rope.to_string(), "hello world");
558 }
559
560 #[test]
561 fn insert_beyond_length_clamps() {
562 let mut rope = Rope::from("hi");
563 rope.insert(100, "!");
564 assert_eq!(rope.to_string(), "hi!");
565 }
566
567 #[test]
568 fn insert_empty_string() {
569 let mut rope = Rope::from("hello");
570 rope.insert(2, "");
571 assert_eq!(rope.to_string(), "hello");
572 }
573
574 #[test]
577 fn remove_empty_range() {
578 let mut rope = Rope::from("hello");
579 rope.remove(2..2);
580 assert_eq!(rope.to_string(), "hello");
581 }
582
583 #[test]
584 fn remove_entire_content() {
585 let mut rope = Rope::from("hello");
586 rope.remove(..);
587 assert!(rope.is_empty());
588 }
589
590 #[test]
591 #[allow(clippy::reversed_empty_ranges)]
592 fn remove_inverted_range_is_noop() {
593 let mut rope = Rope::from("hello");
594 rope.remove(3..1); assert_eq!(rope.to_string(), "hello");
596 }
597
598 #[test]
601 fn grapheme_insert_at_beginning() {
602 let mut rope = Rope::from("bc");
603 rope.insert_grapheme(0, "a");
604 assert_eq!(rope.to_string(), "abc");
605 }
606
607 #[test]
608 fn grapheme_insert_with_combining() {
609 let mut rope = Rope::from("e\u{301}x"); assert_eq!(rope.grapheme_count(), 2);
611 rope.insert_grapheme(1, "y");
612 assert_eq!(rope.to_string(), "e\u{301}yx");
613 }
614
615 #[test]
616 fn grapheme_remove_range() {
617 let mut rope = Rope::from("abcd");
618 rope.remove_grapheme_range(1..3);
619 assert_eq!(rope.to_string(), "ad");
620 }
621
622 #[test]
623 fn grapheme_remove_empty_range() {
624 let mut rope = Rope::from("abc");
625 rope.remove_grapheme_range(1..1);
626 assert_eq!(rope.to_string(), "abc");
627 }
628
629 #[test]
630 fn graphemes_returns_correct_list() {
631 let rope = Rope::from("ae\u{301}b"); let gs = rope.graphemes();
633 assert_eq!(gs.len(), 3);
634 assert_eq!(gs[0], "a");
635 assert_eq!(gs[1], "e\u{301}");
636 assert_eq!(gs[2], "b");
637 }
638
639 #[test]
642 fn char_to_byte_with_multibyte() {
643 let rope = Rope::from("a\u{1F600}b"); assert_eq!(rope.char_to_byte(0), 0); assert_eq!(rope.char_to_byte(1), 1); assert_eq!(rope.char_to_byte(2), 5); }
648
649 #[test]
650 fn byte_to_char_clamps() {
651 let rope = Rope::from("hi");
652 assert_eq!(rope.byte_to_char(100), 2);
653 }
654
655 #[test]
656 fn char_to_byte_clamps() {
657 let rope = Rope::from("hi");
658 assert_eq!(rope.char_to_byte(100), 2);
659 }
660
661 #[test]
662 fn line_to_char_out_of_bounds() {
663 let rope = Rope::from("a\nb");
664 assert_eq!(rope.line_to_char(0), 0);
665 assert_eq!(rope.line_to_char(1), 2);
666 assert_eq!(rope.line_to_char(100), 3); }
668
669 #[test]
670 fn byte_to_line_col_basic() {
671 let rope = Rope::from("abc\ndef");
672 let (line, col) = rope.byte_to_line_col(5); assert_eq!(line, 1);
674 assert_eq!(col, 1);
675 }
676
677 #[test]
678 fn line_col_to_byte_basic() {
679 let rope = Rope::from("abc\ndef");
680 let byte = rope.line_col_to_byte(1, 1);
681 assert_eq!(byte, 5); }
683
684 #[test]
687 fn chars_iterator() {
688 let rope = Rope::from("ab");
689 let chars: Vec<char> = rope.chars().collect();
690 assert_eq!(chars, vec!['a', 'b']);
691 }
692
693 #[test]
696 fn normalize_range_basic() {
697 assert_eq!(normalize_range(2..5, 10), (2, 5));
698 assert_eq!(normalize_range(0..10, 10), (0, 10));
699 assert_eq!(normalize_range(.., 10), (0, 10));
700 }
701
702 #[test]
703 fn normalize_range_clamps_to_max() {
704 assert_eq!(normalize_range(0..100, 5), (0, 5));
705 assert_eq!(normalize_range(50..100, 5), (5, 5));
706 }
707
708 #[test]
709 #[allow(clippy::reversed_empty_ranges)]
710 fn normalize_range_inverted_becomes_empty() {
711 assert_eq!(normalize_range(5..2, 10), (5, 5));
712 }
713
714 #[test]
715 fn normalize_range_inclusive() {
716 assert_eq!(normalize_range(1..=3, 10), (1, 4));
717 }
718
719 proptest! {
722 #[test]
723 fn append_then_len_grows(s in "\\PC{0,50}", suffix in "\\PC{0,50}") {
724 let mut rope = Rope::from(s.as_str());
725 let before = rope.len_chars();
726 let suffix_len = suffix.chars().count();
727 rope.append(&suffix);
728 prop_assert_eq!(rope.len_chars(), before + suffix_len);
729 }
730
731 #[test]
732 fn replace_yields_new_content(s in "\\PC{0,50}", replacement in "\\PC{0,50}") {
733 let mut rope = Rope::from(s.as_str());
734 rope.replace(&replacement);
735 prop_assert_eq!(rope.to_string(), replacement);
736 }
737
738 #[test]
739 fn clear_always_empty(s in "\\PC{0,100}") {
740 let mut rope = Rope::from(s.as_str());
741 rope.clear();
742 prop_assert!(rope.is_empty());
743 prop_assert_eq!(rope.len_bytes(), 0);
744 prop_assert_eq!(rope.len_chars(), 0);
745 }
746
747 #[test]
748 fn display_matches_to_string(s in "\\PC{0,100}") {
749 let rope = Rope::from(s.as_str());
750 prop_assert_eq!(format!("{rope}"), rope.to_string());
751 }
752
753 #[test]
754 fn char_byte_roundtrip(s in "\\PC{1,50}", idx in 0usize..50) {
755 let rope = Rope::from(s.as_str());
756 let char_idx = idx.min(rope.len_chars());
757 let byte_idx = rope.char_to_byte(char_idx);
758 let back = rope.byte_to_char(byte_idx);
759 prop_assert_eq!(back, char_idx);
760 }
761
762 #[test]
763 fn grapheme_count_leq_char_count(s in "\\PC{0,100}") {
764 let rope = Rope::from(s.as_str());
765 prop_assert!(rope.grapheme_count() <= rope.len_chars());
766 }
767 }
768}