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, end) = normalize_range(range, self.grapheme_count());
128 if start < end {
129 let char_start = self.grapheme_to_char_idx(start);
130 let char_end = self.grapheme_to_char_idx(end);
131 self.rope.remove(char_start..char_end);
132 }
133 }
134
135 pub fn replace(&mut self, text: &str) {
137 if text.len() >= 10_000 {
138 tracing::debug!(len = text.len(), "rope replace large text");
139 }
140 self.rope = InnerRope::from(text);
141 }
142
143 pub fn append(&mut self, text: &str) {
145 let len = self.len_chars();
146 self.insert(len, text);
147 }
148
149 pub fn clear(&mut self) {
151 self.rope = InnerRope::new();
152 }
153
154 #[inline]
156 #[must_use]
157 pub fn char_to_byte(&self, char_idx: usize) -> usize {
158 self.rope.char_to_byte(char_idx.min(self.len_chars()))
159 }
160
161 #[inline]
163 #[must_use]
164 pub fn byte_to_char(&self, byte_idx: usize) -> usize {
165 self.rope.byte_to_char(byte_idx.min(self.len_bytes()))
166 }
167
168 #[inline]
170 #[must_use]
171 pub fn char_to_line(&self, char_idx: usize) -> usize {
172 self.rope.char_to_line(char_idx.min(self.len_chars()))
173 }
174
175 #[inline]
177 #[must_use]
178 pub fn line_to_char(&self, line_idx: usize) -> usize {
179 if line_idx >= self.len_lines() {
180 self.len_chars()
181 } else {
182 self.rope.line_to_char(line_idx)
183 }
184 }
185
186 #[inline]
188 #[must_use]
189 pub fn byte_to_line_col(&self, byte_idx: usize) -> (usize, usize) {
190 let char_idx = self.byte_to_char(byte_idx);
191 let line = self.char_to_line(char_idx);
192 let line_start = self.line_to_char(line);
193 (line, char_idx.saturating_sub(line_start))
194 }
195
196 #[inline]
198 #[must_use]
199 pub fn line_col_to_byte(&self, line_idx: usize, col: usize) -> usize {
200 let line_start = self.line_to_char(line_idx);
201 let char_idx = line_start.saturating_add(col).min(self.len_chars());
202 self.char_to_byte(char_idx)
203 }
204
205 pub fn chars(&self) -> impl Iterator<Item = char> + '_ {
207 self.rope.chars()
208 }
209
210 #[must_use]
212 pub fn graphemes(&self) -> Vec<String> {
213 self.to_string()
214 .graphemes(true)
215 .map(str::to_string)
216 .collect()
217 }
218
219 #[must_use]
221 pub fn grapheme_count(&self) -> usize {
222 self.to_string().graphemes(true).count()
223 }
224
225 fn grapheme_to_char_idx(&self, grapheme_idx: usize) -> usize {
226 let mut g_count = 0;
227 let mut char_count = 0;
228
229 for line in self.lines() {
230 let line_g_count = line.graphemes(true).count();
231 if g_count + line_g_count > grapheme_idx {
232 let offset = grapheme_idx - g_count;
233 for (current_g, g) in line.graphemes(true).enumerate() {
234 if current_g == offset {
235 return char_count;
236 }
237 char_count += g.chars().count();
238 }
239 }
240 g_count += line_g_count;
241 char_count += line.chars().count();
242 }
243 self.len_chars()
244 }
245}
246
247impl fmt::Display for Rope {
248 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249 for chunk in self.rope.chunks() {
250 f.write_str(chunk)?;
251 }
252 Ok(())
253 }
254}
255
256impl FromStr for Rope {
257 type Err = std::convert::Infallible;
258
259 fn from_str(s: &str) -> Result<Self, Self::Err> {
260 Ok(Self::from_text(s))
261 }
262}
263
264impl From<&str> for Rope {
265 fn from(s: &str) -> Self {
266 Self::from_text(s)
267 }
268}
269
270impl From<String> for Rope {
271 fn from(s: String) -> Self {
272 Self::from_text(&s)
273 }
274}
275
276fn cow_from_slice(slice: RopeSlice<'_>) -> Cow<'_, str> {
277 match slice.as_str() {
278 Some(s) => Cow::Borrowed(s),
279 None => Cow::Owned(slice.to_string()),
280 }
281}
282
283fn normalize_range<R>(range: R, max: usize) -> (usize, usize)
284where
285 R: RangeBounds<usize>,
286{
287 let start = match range.start_bound() {
288 Bound::Included(&s) => s,
289 Bound::Excluded(&s) => s.saturating_add(1),
290 Bound::Unbounded => 0,
291 };
292 let end = match range.end_bound() {
293 Bound::Included(&e) => e.saturating_add(1),
294 Bound::Excluded(&e) => e,
295 Bound::Unbounded => max,
296 };
297
298 let start = start.min(max);
299 let end = end.min(max);
300 if end < start {
301 (start, start)
302 } else {
303 (start, end)
304 }
305}
306
307#[cfg(test)]
308mod tests {
309 use super::*;
310 use proptest::prelude::*;
311
312 #[test]
313 fn rope_basic_counts() {
314 let rope = Rope::from("Hello, world!");
315 assert_eq!(rope.len_chars(), 13);
316 assert_eq!(rope.len_lines(), 1);
317 }
318
319 #[test]
320 fn rope_multiline_lines() {
321 let rope = Rope::from("Line 1\nLine 2\nLine 3");
322 assert_eq!(rope.len_lines(), 3);
323 assert_eq!(rope.line(0).unwrap(), "Line 1\n");
324 assert_eq!(rope.line(2).unwrap(), "Line 3");
325 }
326
327 #[test]
328 fn rope_insert_remove_replace() {
329 let mut rope = Rope::from("Hello!");
330 rope.insert(5, ", world");
331 assert_eq!(rope.to_string(), "Hello, world!");
332
333 rope.remove(5..12);
334 assert_eq!(rope.to_string(), "Hello!");
335
336 rope.replace("Replaced");
337 assert_eq!(rope.to_string(), "Replaced");
338 }
339
340 #[test]
341 fn rope_append_clear() {
342 let mut rope = Rope::from("Hi");
343 rope.append(" there");
344 assert_eq!(rope.to_string(), "Hi there");
345 rope.clear();
346 assert!(rope.is_empty());
347 assert_eq!(rope.len_lines(), 1);
348 }
349
350 #[test]
351 fn rope_char_byte_conversions() {
352 let s = "a\u{1F600}b";
353 let rope = Rope::from(s);
354 assert_eq!(rope.len_chars(), 3);
355 assert_eq!(rope.char_to_byte(0), 0);
356 assert_eq!(rope.char_to_byte(1), "a".len());
357 assert_eq!(rope.byte_to_char(rope.len_bytes()), 3);
358 }
359
360 #[test]
361 fn rope_line_col_conversions() {
362 let rope = Rope::from("ab\ncde\n");
363 let (line, col) = rope.byte_to_line_col(4);
364 assert_eq!(line, 1);
365 assert_eq!(col, 1);
366
367 let byte = rope.line_col_to_byte(1, 2);
368 assert_eq!(byte, 5);
369 }
370
371 #[test]
372 fn rope_grapheme_ops() {
373 let mut rope = Rope::from("e\u{301}");
374 assert_eq!(rope.grapheme_count(), 1);
375 rope.insert_grapheme(1, "!");
376 assert_eq!(rope.to_string(), "e\u{301}!");
377
378 let mut rope = Rope::from("a\u{1F600}b");
379 rope.remove_grapheme_range(1..2);
380 assert_eq!(rope.to_string(), "ab");
381 }
382
383 proptest! {
384 #[test]
385 fn insert_remove_roundtrip(s in any::<String>(), insert in any::<String>(), idx in 0usize..200) {
386 let mut rope = Rope::from(s.as_str());
387 let insert_len = insert.chars().count();
388 let pos = idx.min(rope.len_chars());
389 rope.insert(pos, &insert);
390 rope.remove(pos..pos.saturating_add(insert_len));
391 prop_assert_eq!(rope.to_string(), s);
392 }
393
394 #[test]
395 fn line_count_matches_newlines(s in "[^\r\u{000B}\u{000C}\u{0085}\u{2028}\u{2029}]*") {
396 let rope = Rope::from(s.as_str());
399 let newlines = s.as_bytes().iter().filter(|&&b| b == b'\n').count();
400 prop_assert_eq!(rope.len_lines(), newlines + 1);
401 }
402 }
403
404 #[test]
407 fn empty_rope_properties() {
408 let rope = Rope::new();
409 assert!(rope.is_empty());
410 assert_eq!(rope.len_bytes(), 0);
411 assert_eq!(rope.len_chars(), 0);
412 assert_eq!(rope.len_lines(), 1); assert_eq!(rope.grapheme_count(), 0);
414 assert_eq!(rope.to_string(), "");
415 }
416
417 #[test]
418 fn empty_rope_line_access() {
419 let rope = Rope::new();
420 assert!(rope.line(0).is_some()); assert!(rope.line(1).is_none());
422 }
423
424 #[test]
425 fn empty_rope_slice() {
426 let rope = Rope::new();
427 assert_eq!(rope.slice(0..0), "");
428 assert_eq!(rope.slice(..), "");
429 }
430
431 #[test]
432 fn empty_rope_conversions() {
433 let rope = Rope::new();
434 assert_eq!(rope.char_to_byte(0), 0);
435 assert_eq!(rope.byte_to_char(0), 0);
436 assert_eq!(rope.char_to_line(0), 0);
437 assert_eq!(rope.line_to_char(0), 0);
438 }
439
440 #[test]
443 fn from_str_impl() {
444 let rope: Rope = "hello".into();
445 assert_eq!(rope.to_string(), "hello");
446 }
447
448 #[test]
449 fn from_string_impl() {
450 let rope: Rope = String::from("hello").into();
451 assert_eq!(rope.to_string(), "hello");
452 }
453
454 #[test]
455 fn from_str_parse() {
456 let rope: Rope = "hello".parse().unwrap();
457 assert_eq!(rope.to_string(), "hello");
458 }
459
460 #[test]
461 fn display_impl() {
462 let rope = Rope::from("hello world");
463 assert_eq!(format!("{rope}"), "hello world");
464 }
465
466 #[test]
469 fn line_out_of_bounds() {
470 let rope = Rope::from("a\nb");
471 assert!(rope.line(0).is_some());
472 assert!(rope.line(1).is_some());
473 assert!(rope.line(2).is_none());
474 assert!(rope.line(100).is_none());
475 }
476
477 #[test]
478 fn trailing_newline_creates_empty_last_line() {
479 let rope = Rope::from("a\n");
480 assert_eq!(rope.len_lines(), 2);
481 assert_eq!(rope.line(0).unwrap(), "a\n");
482 assert_eq!(rope.line(1).unwrap(), "");
483 }
484
485 #[test]
486 fn multiple_newlines() {
487 let rope = Rope::from("\n\n\n");
488 assert_eq!(rope.len_lines(), 4);
489 }
490
491 #[test]
492 fn lines_iterator() {
493 let rope = Rope::from("a\nb\nc");
494 let lines: Vec<String> = rope.lines().map(|c| c.to_string()).collect();
495 assert_eq!(lines.len(), 3);
496 assert_eq!(lines[0], "a\n");
497 assert_eq!(lines[1], "b\n");
498 assert_eq!(lines[2], "c");
499 }
500
501 #[test]
504 fn slice_basic() {
505 let rope = Rope::from("hello world");
506 assert_eq!(rope.slice(0..5), "hello");
507 assert_eq!(rope.slice(6..11), "world");
508 assert_eq!(rope.slice(6..), "world");
509 assert_eq!(rope.slice(..5), "hello");
510 }
511
512 #[test]
513 fn slice_out_of_bounds_returns_empty() {
514 let rope = Rope::from("hi");
515 assert_eq!(rope.slice(100..200), "");
516 }
517
518 #[test]
521 fn insert_at_beginning() {
522 let mut rope = Rope::from("world");
523 rope.insert(0, "hello ");
524 assert_eq!(rope.to_string(), "hello world");
525 }
526
527 #[test]
528 fn insert_at_end() {
529 let mut rope = Rope::from("hello");
530 rope.insert(5, " world");
531 assert_eq!(rope.to_string(), "hello world");
532 }
533
534 #[test]
535 fn insert_beyond_length_clamps() {
536 let mut rope = Rope::from("hi");
537 rope.insert(100, "!");
538 assert_eq!(rope.to_string(), "hi!");
539 }
540
541 #[test]
542 fn insert_empty_string() {
543 let mut rope = Rope::from("hello");
544 rope.insert(2, "");
545 assert_eq!(rope.to_string(), "hello");
546 }
547
548 #[test]
551 fn remove_empty_range() {
552 let mut rope = Rope::from("hello");
553 rope.remove(2..2);
554 assert_eq!(rope.to_string(), "hello");
555 }
556
557 #[test]
558 fn remove_entire_content() {
559 let mut rope = Rope::from("hello");
560 rope.remove(..);
561 assert!(rope.is_empty());
562 }
563
564 #[test]
565 #[allow(clippy::reversed_empty_ranges)]
566 fn remove_inverted_range_is_noop() {
567 let mut rope = Rope::from("hello");
568 rope.remove(3..1); assert_eq!(rope.to_string(), "hello");
570 }
571
572 #[test]
575 fn grapheme_insert_at_beginning() {
576 let mut rope = Rope::from("bc");
577 rope.insert_grapheme(0, "a");
578 assert_eq!(rope.to_string(), "abc");
579 }
580
581 #[test]
582 fn grapheme_insert_with_combining() {
583 let mut rope = Rope::from("e\u{301}x"); assert_eq!(rope.grapheme_count(), 2);
585 rope.insert_grapheme(1, "y");
586 assert_eq!(rope.to_string(), "e\u{301}yx");
587 }
588
589 #[test]
590 fn grapheme_remove_range() {
591 let mut rope = Rope::from("abcd");
592 rope.remove_grapheme_range(1..3);
593 assert_eq!(rope.to_string(), "ad");
594 }
595
596 #[test]
597 fn grapheme_remove_empty_range() {
598 let mut rope = Rope::from("abc");
599 rope.remove_grapheme_range(1..1);
600 assert_eq!(rope.to_string(), "abc");
601 }
602
603 #[test]
604 fn graphemes_returns_correct_list() {
605 let rope = Rope::from("ae\u{301}b"); let gs = rope.graphemes();
607 assert_eq!(gs.len(), 3);
608 assert_eq!(gs[0], "a");
609 assert_eq!(gs[1], "e\u{301}");
610 assert_eq!(gs[2], "b");
611 }
612
613 #[test]
616 fn char_to_byte_with_multibyte() {
617 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); }
622
623 #[test]
624 fn byte_to_char_clamps() {
625 let rope = Rope::from("hi");
626 assert_eq!(rope.byte_to_char(100), 2);
627 }
628
629 #[test]
630 fn char_to_byte_clamps() {
631 let rope = Rope::from("hi");
632 assert_eq!(rope.char_to_byte(100), 2);
633 }
634
635 #[test]
636 fn line_to_char_out_of_bounds() {
637 let rope = Rope::from("a\nb");
638 assert_eq!(rope.line_to_char(0), 0);
639 assert_eq!(rope.line_to_char(1), 2);
640 assert_eq!(rope.line_to_char(100), 3); }
642
643 #[test]
644 fn byte_to_line_col_basic() {
645 let rope = Rope::from("abc\ndef");
646 let (line, col) = rope.byte_to_line_col(5); assert_eq!(line, 1);
648 assert_eq!(col, 1);
649 }
650
651 #[test]
652 fn line_col_to_byte_basic() {
653 let rope = Rope::from("abc\ndef");
654 let byte = rope.line_col_to_byte(1, 1);
655 assert_eq!(byte, 5); }
657
658 #[test]
661 fn chars_iterator() {
662 let rope = Rope::from("ab");
663 let chars: Vec<char> = rope.chars().collect();
664 assert_eq!(chars, vec!['a', 'b']);
665 }
666
667 #[test]
670 fn normalize_range_basic() {
671 assert_eq!(normalize_range(2..5, 10), (2, 5));
672 assert_eq!(normalize_range(0..10, 10), (0, 10));
673 assert_eq!(normalize_range(.., 10), (0, 10));
674 }
675
676 #[test]
677 fn normalize_range_clamps_to_max() {
678 assert_eq!(normalize_range(0..100, 5), (0, 5));
679 assert_eq!(normalize_range(50..100, 5), (5, 5));
680 }
681
682 #[test]
683 #[allow(clippy::reversed_empty_ranges)]
684 fn normalize_range_inverted_becomes_empty() {
685 assert_eq!(normalize_range(5..2, 10), (5, 5));
686 }
687
688 #[test]
689 fn normalize_range_inclusive() {
690 assert_eq!(normalize_range(1..=3, 10), (1, 4));
691 }
692
693 proptest! {
696 #[test]
697 fn append_then_len_grows(s in "\\PC{0,50}", suffix in "\\PC{0,50}") {
698 let mut rope = Rope::from(s.as_str());
699 let before = rope.len_chars();
700 let suffix_len = suffix.chars().count();
701 rope.append(&suffix);
702 prop_assert_eq!(rope.len_chars(), before + suffix_len);
703 }
704
705 #[test]
706 fn replace_yields_new_content(s in "\\PC{0,50}", replacement in "\\PC{0,50}") {
707 let mut rope = Rope::from(s.as_str());
708 rope.replace(&replacement);
709 prop_assert_eq!(rope.to_string(), replacement);
710 }
711
712 #[test]
713 fn clear_always_empty(s in "\\PC{0,100}") {
714 let mut rope = Rope::from(s.as_str());
715 rope.clear();
716 prop_assert!(rope.is_empty());
717 prop_assert_eq!(rope.len_bytes(), 0);
718 prop_assert_eq!(rope.len_chars(), 0);
719 }
720
721 #[test]
722 fn display_matches_to_string(s in "\\PC{0,100}") {
723 let rope = Rope::from(s.as_str());
724 prop_assert_eq!(format!("{rope}"), rope.to_string());
725 }
726
727 #[test]
728 fn char_byte_roundtrip(s in "\\PC{1,50}", idx in 0usize..50) {
729 let rope = Rope::from(s.as_str());
730 let char_idx = idx.min(rope.len_chars());
731 let byte_idx = rope.char_to_byte(char_idx);
732 let back = rope.byte_to_char(byte_idx);
733 prop_assert_eq!(back, char_idx);
734 }
735
736 #[test]
737 fn grapheme_count_leq_char_count(s in "\\PC{0,100}") {
738 let rope = Rope::from(s.as_str());
739 prop_assert!(rope.grapheme_count() <= rope.len_chars());
740 }
741 }
742}