1use rope::Rope;
2use std::{cmp::Ordering, ops::Range};
3
4pub(crate) fn text_in_range_omitting_ranges(
5 rope: &Rope,
6 range: Range<usize>,
7 omit_ranges: &[Range<usize>],
8) -> String {
9 let mut content = String::with_capacity(range.len());
10 let mut omit_ranges = omit_ranges
11 .iter()
12 .skip_while(|omit_range| omit_range.end <= range.start)
13 .peekable();
14 let mut offset = range.start;
15 let mut chunks = rope.chunks_in_range(range.clone());
16 while let Some(chunk) = chunks.next() {
17 if let Some(omit_range) = omit_ranges.peek() {
18 match offset.cmp(&omit_range.start) {
19 Ordering::Less => {
20 let max_len = omit_range.start - offset;
21 if chunk.len() < max_len {
22 content.push_str(chunk);
23 offset += chunk.len();
24 } else {
25 content.push_str(&chunk[..max_len]);
26 chunks.seek(omit_range.end.min(range.end));
27 offset = omit_range.end;
28 omit_ranges.next();
29 }
30 }
31 Ordering::Equal | Ordering::Greater => {
32 chunks.seek(omit_range.end.min(range.end));
33 offset = omit_range.end;
34 omit_ranges.next();
35 }
36 }
37 } else {
38 content.push_str(chunk);
39 offset += chunk.len();
40 }
41 }
42
43 content
44}
45
46#[cfg(test)]
47mod tests {
48 use super::*;
49 use rand::{rngs::StdRng, Rng as _};
50 use util::RandomCharIter;
51
52 #[gpui::test(iterations = 100)]
53 fn test_text_in_range_omitting_ranges(mut rng: StdRng) {
54 let text = RandomCharIter::new(&mut rng).take(1024).collect::<String>();
55 let rope = Rope::from(text.as_str());
56
57 let mut start = rng.gen_range(0..=text.len() / 2);
58 let mut end = rng.gen_range(text.len() / 2..=text.len());
59 while !text.is_char_boundary(start) {
60 start -= 1;
61 }
62 while !text.is_char_boundary(end) {
63 end += 1;
64 }
65 let range = start..end;
66
67 let mut ix = 0;
68 let mut omit_ranges = Vec::new();
69 for _ in 0..rng.gen_range(0..10) {
70 let mut start = rng.gen_range(ix..=text.len());
71 while !text.is_char_boundary(start) {
72 start += 1;
73 }
74 let mut end = rng.gen_range(start..=text.len());
75 while !text.is_char_boundary(end) {
76 end += 1;
77 }
78 omit_ranges.push(start..end);
79 ix = end;
80 if ix == text.len() {
81 break;
82 }
83 }
84
85 let mut expected_text = text[range.clone()].to_string();
86 for omit_range in omit_ranges.iter().rev() {
87 let start = omit_range
88 .start
89 .saturating_sub(range.start)
90 .min(range.len());
91 let end = omit_range.end.saturating_sub(range.start).min(range.len());
92 expected_text.replace_range(start..end, "");
93 }
94
95 assert_eq!(
96 text_in_range_omitting_ranges(&rope, range.clone(), &omit_ranges),
97 expected_text,
98 "text: {text:?}\nrange: {range:?}\nomit_ranges: {omit_ranges:?}"
99 );
100 }
101}