duat_core/
ranges.rs

1//! The [`Ranges`] struct
2//!
3//! This struct uses the same principle as the `ShiftList` in the
4//! [`crate::text`] module. That is, it has an internal `shift` value
5//! to displace all [`Range`]s ahead of the `from` index within. This
6//! enables efficient localized insertion, like we have with
7//! [`GapBuffer`]s, while maintaining the ability to binary search
8//! over the list, getting both efficient insertion as well as
9//! traversal.
10use std::ops::Range;
11
12use gapbuf::{GapBuffer, gap_buffer};
13
14use crate::utils::merging_range_by_guess_and_lazy_shift;
15
16/// A list of non intersecting exclusive [`Range<usize>`]s
17///
18/// The primary purpose of this struct is to serve [`Parser`]s by
19/// telling Duat which ranges need to be updated. This lets Duat
20/// minimize as much as possible the amount of work done to update
21/// the [`Text`] when it changes in a [`Buffer`].
22///
23/// [`Text`]: crate::text::Text
24/// [`Buffer`]: crate::buffer::Buffer
25/// [`Parser`]: crate::buffer::Parser
26#[derive(Clone, Default, Debug)]
27pub struct Ranges {
28    list: GapBuffer<Range<i32>>,
29    from: usize,
30    shift: i32,
31    min_len: usize,
32}
33
34impl Ranges {
35    /// Return a new instance of [`Ranges`] with an initial
36    /// [`Range`]
37    pub fn new(range: Range<usize>) -> Self {
38        Self {
39            list: gap_buffer![range.start as i32..range.end as i32],
40            ..Self::empty()
41        }
42    }
43
44    /// Returns a new empty [`Ranges`]
45    pub fn empty() -> Self {
46        Self {
47            list: GapBuffer::new(),
48            from: 0,
49            shift: 0,
50            min_len: 1,
51        }
52    }
53
54    /// Sets a minimum length to keep [`Range`]s
55    pub fn set_min_len(&mut self, min: usize) {
56        self.min_len = min.max(1);
57
58        let mut i = 0;
59        self.list.retain(|range| {
60            i += 1;
61
62            if range.len() < self.min_len {
63                if i - 1 < self.from {
64                    self.from -= 1;
65                }
66                false
67            } else {
68                true
69            }
70        });
71    }
72
73    /// Adds a range to the list of [`Range<usize>`]s
74    ///
75    /// This range will be merged in with the others on the list,
76    /// so it may bridge gaps between ranges or for longer
77    /// ranges within, without allowing for the existance
78    /// of intersecting ranges.
79    #[track_caller]
80    pub fn add(&mut self, new: Range<usize>) {
81        assert_range(&new);
82        if new.len() < self.min_len {
83            return;
84        }
85
86        let new = new.start as i32..new.end as i32;
87
88        let m_range = merging_range_by_guess_and_lazy_shift(
89            (&self.list, self.list.len()),
90            (self.from, [new.start, new.end]),
91            (self.from, self.shift, 0, std::ops::Add::add),
92            (|r| r.start, |r| r.end),
93        );
94
95        // The ranges in this region have not been callibrated yet.
96        if self.from <= m_range.end {
97            for range in self.list.range_mut(self.from..m_range.end).iter_mut() {
98                range.start += self.shift;
99                range.end += self.shift;
100            }
101        }
102
103        let added_range = {
104            let slice = self.list.range(m_range.clone());
105            let mut iter = slice.iter();
106
107            let first = iter.next().cloned().unwrap_or(new.clone());
108            let last = iter.next_back().cloned().unwrap_or(first.clone());
109
110            first.start.min(new.start)..last.end.max(new.end)
111        };
112
113        let new_from = self.from.saturating_sub(m_range.len()).max(m_range.start) + 1;
114        if new_from < self.list.len() + 1 - m_range.len() {
115            self.from = new_from;
116        } else {
117            self.from = 0;
118            self.shift = 0;
119        }
120
121        self.list.splice(m_range.clone(), [added_range]);
122    }
123
124    /// Removes a [`Range`] from the list, returning an
125    /// [`Iterator`] over the [`Range`]s that were taken
126    #[track_caller]
127    pub fn remove(
128        &mut self,
129        within: Range<usize>,
130    ) -> impl ExactSizeIterator<Item = Range<usize>> + '_ {
131        assert_range(&within);
132        let within = within.start as i32..within.end as i32;
133
134        let m_range = merging_range_by_guess_and_lazy_shift(
135            (&self.list, self.list.len()),
136            (self.from, [within.start, within.end]),
137            (self.from, self.shift, 0, std::ops::Add::add),
138            (|r| r.start, |r| r.end),
139        );
140
141        // The ranges in this region have not been callibrated yet.
142        if self.from <= m_range.end {
143            for range in self.list.range_mut(self.from..m_range.end).iter_mut() {
144                range.start += self.shift;
145                range.end += self.shift;
146            }
147        }
148
149        let split_off = {
150            let slice = self.list.range(m_range.clone());
151
152            let first = slice.iter().next().cloned();
153            let last = slice.iter().next_back().cloned().or(first.clone());
154
155            let split_start = first.filter(|f| f.start < within.start).map(|first| {
156                self.list[m_range.start].start = within.start;
157                first.start..within.start
158            });
159            let split_end = last.filter(|l| l.end > within.end).map(|last| {
160                self.list[m_range.end - 1].end = within.end;
161                within.end..last.end
162            });
163
164            let min_len = |range: &Range<i32>| range.len() >= self.min_len;
165
166            [split_start.filter(min_len), split_end.filter(min_len)]
167        };
168
169        let added = split_off.iter().flatten().count();
170
171        let new_from = self.from.saturating_sub(m_range.len()).max(m_range.start) + added;
172        if new_from < self.list.len() + added - m_range.len() {
173            self.from = new_from;
174        } else {
175            self.from = 0;
176            self.shift = 0;
177        }
178
179        self.list
180            .splice(m_range, split_off.into_iter().flatten())
181            .map(|r| r.start as usize..r.end as usize)
182    }
183
184    /// Applies the [`add`] function to another [`Ranges`]s
185    ///
186    /// [`add`]: Self::add
187    #[track_caller]
188    pub fn merge(&mut self, other: Self) {
189        for range in other.list {
190            self.add(range.start as usize..range.end as usize)
191        }
192    }
193
194    /// Shifts the [`Range`]s within by a [`Change`]
195    ///
196    /// If the `diff` is negative (i.e. a part of the ranges were
197    /// removed), then ranges will be removed ahead of `from`
198    /// accordingly.
199    ///
200    /// [`Change`]: crate::text::Change
201    pub fn shift_by(&mut self, from: usize, shift: i32) {
202        let from = from as i32;
203
204        // The range of changes that will be drained
205        let m_range = merging_range_by_guess_and_lazy_shift(
206            (&self.list, self.list.len()),
207            (self.from, [from, from - shift.min(0)]),
208            (self.from, self.shift, 0, std::ops::Add::add),
209            (|r| r.start, |r| r.end),
210        );
211
212        // If self.from < m_range.end, I need to shift the changes between
213        // the two, so that they match the shifting of the changes
214        // before self.from.
215        if self.from < m_range.end && self.shift != 0 {
216            for range in self.list.range_mut(self.from..m_range.end).iter_mut() {
217                range.start += self.shift;
218                range.end += self.shift;
219            }
220        // If self.from > m_range.end, then I shift the ranges
221        // inbetween by -self.shift, in order to keep the ranges
222        // after m_range.start equally unshifted by self.shift +
223        // shift.
224        } else if self.from > m_range.end && self.shift != 0 {
225            for range in &mut self.list.range_mut(m_range.end..self.from) {
226                range.start -= self.shift;
227                range.end -= self.shift;
228            }
229        }
230
231        let range_left = {
232            let slice = self.list.range(m_range.clone());
233            let mut iter = slice.iter();
234
235            iter.next().and_then(|first| {
236                let last = iter.next_back().unwrap_or(first);
237                let range = first.start.min(from)..(last.end + shift).max(from);
238
239                (range.len() >= self.min_len).then_some(range)
240            })
241        };
242
243        let new_from = m_range.start + range_left.is_some() as usize;
244
245        self.list.splice(m_range.clone(), range_left);
246
247        if new_from < self.list.len() {
248            self.from = new_from;
249            self.shift += shift;
250        } else {
251            self.from = 0;
252            self.shift = 0;
253        }
254    }
255
256    /// Returns the number of [`Range<usize>`]s
257    pub fn len(&self) -> usize {
258        self.list.len()
259    }
260
261    /// Returns `true` if there are no [`Range<usize>`]s
262    pub fn is_empty(&self) -> bool {
263        self.list.is_empty()
264    }
265
266    /// An [`Iterator`] over the [`Range`]s in this list
267    pub fn iter(&self) -> impl Iterator<Item = Range<usize>> {
268        self.list.iter().enumerate().map(move |(i, range)| {
269            if i >= self.from {
270                (range.start + self.shift) as usize..(range.end + self.shift) as usize
271            } else {
272                range.start as usize..range.end as usize
273            }
274        })
275    }
276
277    /// The same as [`Ranges::remove`], but without removing, just
278    /// iterating over the relevant ranges
279    #[track_caller]
280    pub fn iter_over(&self, within: Range<usize>) -> impl Iterator<Item = Range<usize>> {
281        assert_range(&within);
282        let within = within.start as i32..within.end as i32;
283
284        let m_range = merging_range_by_guess_and_lazy_shift(
285            (&self.list, self.list.len()),
286            (self.from, [within.start, within.end]),
287            (self.from, self.shift, 0, std::ops::Add::add),
288            (|r| r.start, |r| r.end),
289        );
290
291        let (s0, s1) = self.list.range(m_range.clone()).as_slices();
292        s0.iter().chain(s1).enumerate().map(move |(i, range)| {
293            let mut range = if i + m_range.start > self.from {
294                range.start + self.shift..range.end + self.shift
295            } else {
296                range.clone()
297            };
298
299            if i == 0 {
300                range.start = range.start.max(within.start);
301            }
302            if i == m_range.len() - 1 {
303                range.end = range.end.min(within.end);
304            }
305
306            range.start as usize..range.end as usize
307        })
308    }
309}
310
311impl IntoIterator for Ranges {
312    type IntoIter = IntoIter;
313    type Item = Range<usize>;
314
315    fn into_iter(self) -> Self::IntoIter {
316        IntoIter {
317            iter: self.list.into_iter().enumerate(),
318            from: self.from,
319            shift: self.shift,
320        }
321    }
322}
323
324pub struct IntoIter {
325    iter: std::iter::Enumerate<gapbuf::IntoIter<Range<i32>>>,
326    from: usize,
327    shift: i32,
328}
329
330impl Iterator for IntoIter {
331    type Item = Range<usize>;
332
333    fn next(&mut self) -> Option<Self::Item> {
334        self.iter.next().map(|(i, range)| {
335            if i >= self.from {
336                (range.start + self.shift) as usize..(range.end + self.shift) as usize
337            } else {
338                range.start as usize..range.end as usize
339            }
340        })
341    }
342}
343
344impl ExactSizeIterator for IntoIter {}
345
346#[track_caller]
347fn assert_range(range: &Range<usize>) {
348    assert!(
349        range.start <= range.end,
350        "range starts at {} but ends at {}",
351        range.start,
352        range.end
353    );
354}