starsector/
ropeext.rs

1use std::borrow::Cow;
2use std::ops::{Bound, RangeBounds};
3
4use ropey::{Rope, RopeSlice};
5
6macro_rules! common_rope_ext_trait {
7    () => {
8        fn to_contiguous<'a>(&'a self) -> Cow<'a, str>;
9        fn discontangle<'a, 'b>(&'a self, contiguous: &'b str, s: &'b str) -> RopeSlice<'a>;
10        fn is_empty(&self) -> bool;
11        fn slice_bytes<R>(&self, byte_range: R) -> RopeSlice
12        where
13            R: RangeBounds<usize>;
14
15        /// This searches *bytes*, not *chars*, and returns a byte index. Be very
16        /// careful with Unicode.
17        fn memchr(&self, needle: u8, offset: usize) -> usize;
18
19        /// Returns the byte index of the first character of this rope that
20        /// matches the pattern, or None if not found.
21        fn find(&self, needle: &str) -> Option<usize>;
22
23        /// Returns the byte index of the last character of this rope that
24        /// matches the pattern, or None if not found.
25        fn rfind(&self, needle: &str) -> Option<usize>;
26    };
27}
28
29pub trait RopeExt {
30    fn push(&mut self, c: char);
31    fn push_str(&mut self, s: &str);
32    fn push_string(&mut self, s: String);
33    common_rope_ext_trait!();
34}
35
36pub trait RopeSliceExt {
37    common_rope_ext_trait!();
38}
39
40macro_rules! common_rope_ext_impl {
41    () => {
42        fn to_contiguous<'a>(&'a self) -> Cow<'a, str> {
43            let mut it = self.chunks();
44            match it.next() {
45                None => Cow::default(),
46                Some(chunk) if it.next().is_none() => chunk.into(),
47                _ => self.to_string().into(),
48            }
49        }
50
51        // FIXME: Wrap this in a Contiguous type to make it safer.
52        fn discontangle<'a, 'b>(&'a self, contiguous: &'b str, s: &'b str) -> RopeSlice<'a> {
53            if s.is_empty() {
54                return self.slice(0..0);
55            }
56
57            let offset = s.as_ptr() as usize - contiguous.as_ptr() as usize;
58            let start = self.byte_to_char(offset);
59            let end = self.byte_to_char(offset + s.len());
60            self.slice(start..end)
61        }
62
63        fn is_empty(&self) -> bool {
64            return self.len_bytes() == 0;
65        }
66
67        fn slice_bytes<R>(&self, byte_range: R) -> RopeSlice
68        where
69            R: RangeBounds<usize>,
70        {
71            match (byte_range.start_bound(), byte_range.end_bound()) {
72                (Bound::Included(start), Bound::Excluded(end)) => {
73                    self.slice(self.byte_to_char(*start)..self.byte_to_char(*end))
74                }
75                (Bound::Included(start), Bound::Included(end)) => {
76                    self.slice(self.byte_to_char(*start)..=self.byte_to_char(*end))
77                }
78                (Bound::Excluded(start), Bound::Included(end)) => {
79                    self.slice(self.byte_to_char(*start + 1)..=self.byte_to_char(*end))
80                }
81                (Bound::Excluded(start), Bound::Excluded(end)) => {
82                    self.slice(self.byte_to_char(*start + 1)..self.byte_to_char(*end))
83                }
84                (Bound::Unbounded, Bound::Unbounded) => self.slice(..),
85                (Bound::Unbounded, Bound::Included(end)) => self.slice(..=self.byte_to_char(*end)),
86                (Bound::Unbounded, Bound::Excluded(end)) => self.slice(..self.byte_to_char(*end)),
87                (Bound::Included(start), Bound::Unbounded) => {
88                    self.slice(self.byte_to_char(*start)..)
89                }
90                (Bound::Excluded(start), Bound::Unbounded) => {
91                    self.slice(self.byte_to_char(*start + 1)..)
92                }
93            }
94        }
95
96        fn memchr(&self, needle: u8, offset: usize) -> usize {
97            let mut bygones = offset;
98            let (chunks, chunk_start, _, _) = self.chunks_at_byte(offset);
99            let mut skip = offset - chunk_start;
100
101            for mut chunk in chunks {
102                if skip > 0 {
103                    chunk = &chunk[skip..];
104                    skip = 0;
105                }
106
107                match memchr::memchr(needle, &chunk.as_bytes()) {
108                    Some(index) => return index + bygones,
109                    None => {
110                        bygones += chunk.len();
111                    }
112                }
113            }
114
115            bygones
116        }
117
118        fn find(&self, needle: &str) -> Option<usize> {
119            // FIXME: Implement more efficient behavior for multi-chunk ropes.
120            // We can copy chunks into a buffer and empty it as searched, but
121            // many edge cases depending on relative length of needle and
122            // chunks.
123            match self.slice(..).as_str() {
124                Some(s) => s.find(needle),
125                None => self.to_string().find(needle),
126            }
127        }
128
129        fn rfind(&self, needle: &str) -> Option<usize> {
130            // FIXME: Implement more efficient behavior for multi-chunk ropes.
131            // We can copy chunks into a buffer and empty it as searched, but
132            // many edge cases depending on relative length of needle and
133            // chunks.
134            match self.slice(..).as_str() {
135                Some(s) => s.rfind(needle),
136                None => self.to_string().rfind(needle),
137            }
138        }
139    };
140}
141
142impl RopeExt for Rope {
143    fn push(&mut self, c: char) {
144        let pos = self.len_chars();
145        self.insert_char(pos, c);
146    }
147
148    fn push_str(&mut self, s: &str) {
149        let pos = self.len_chars();
150        self.insert(pos, s);
151    }
152
153    fn push_string(&mut self, s: String) {
154        self.append(Rope::from(s));
155    }
156
157    common_rope_ext_impl!();
158}
159
160impl RopeSliceExt for RopeSlice<'_> {
161    common_rope_ext_impl!();
162}