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