duat-core 0.10.0

The core of Duat, a highly customizable text editor.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
//! The [`Ranges`] struct
//!
//! This struct uses the same principle as the `ShiftList` in the
//! [`crate::text`] module. That is, it has an internal `shift` value
//! to displace all [`Range`]s ahead of the `from` index within. This
//! enables efficient localized insertion, like we have with
//! [`GapBuffer`]s, while maintaining the ability to binary search
//! over the list, getting both efficient insertion as well as
//! traversal.
use std::ops::Range;

use gap_buf::{GapBuffer, gap_buffer};

use crate::utils::merging_range_by_guess_and_lazy_shift;

/// A list of non intersecting exclusive [`Range<usize>`]s.
///
/// This is useful for tracking ranges within a [`Text`], wether it is
/// ranges that need updating, or that have some property, this struct
/// is useful for efficintly keeping track of stuff.
///
/// [`Text`]: crate::text::Text
/// [`Buffer`]: crate::buffer::Buffer
#[derive(Clone, Default, Debug)]
pub struct Ranges {
    list: GapBuffer<Range<i32>>,
    from: usize,
    shift: i32,
    min_len: usize,
}

impl Ranges {
    /// Return a new instance of [`Ranges`] with an initial
    /// [`Range`]
    pub fn new(range: Range<usize>) -> Self {
        Self {
            list: gap_buffer![range.start as i32..range.end as i32],
            ..Self::empty()
        }
    }

    /// Returns a new empty [`Ranges`]
    pub const fn empty() -> Self {
        Self {
            list: GapBuffer::new(),
            from: 0,
            shift: 0,
            min_len: 1,
        }
    }

    /// Sets a minimum length to keep [`Range`]s
    pub fn set_min_len(&mut self, min: usize) {
        self.min_len = min.max(1);

        let mut i = 0;
        self.list.retain(|range| {
            i += 1;

            if range.len() < self.min_len {
                if i - 1 < self.from {
                    self.from -= 1;
                }
                false
            } else {
                true
            }
        });
    }

    /// Adds a range to the list of [`Range<usize>`]s
    ///
    /// This range will be merged in with the others on the list,
    /// so it may bridge gaps between ranges or for longer
    /// ranges within, without allowing for the existance
    /// of intersecting ranges.
    ///
    /// This function will return `true` if the `Ranges` were changed
    /// by this addition.
    #[track_caller]
    pub fn add(&mut self, new: Range<usize>) -> bool {
        assert_range(&new);
        if new.len() < self.min_len {
            return false;
        }

        let new = new.start as i32..new.end as i32;

        let m_range = merging_range_by_guess_and_lazy_shift(
            (&self.list, self.list.len()),
            (self.from, [new.start, new.end]),
            (self.from, self.shift, 0, std::ops::Add::add),
            (|r| r.start, |r| r.end),
        );

        // The ranges in this region have not been callibrated yet.
        if self.from <= m_range.end {
            for range in self.list.range_mut(self.from..m_range.end).iter_mut() {
                range.start += self.shift;
                range.end += self.shift;
            }
        }

        let changed_ranges = if m_range.len() == 1 {
            let range = self.list[m_range.start].clone();
            !(range.start <= new.start && range.end >= new.end)
        } else {
            true
        };

        let added_range = {
            let slice = self.list.range(m_range.clone());
            let mut iter = slice.iter();

            let first = iter.next().cloned().unwrap_or(new.clone());
            let last = iter.next_back().cloned().unwrap_or(first.clone());

            first.start.min(new.start)..last.end.max(new.end)
        };

        let new_from = self.from.saturating_sub(m_range.len()).max(m_range.start) + 1;
        if new_from < self.list.len() + 1 - m_range.len() {
            self.from = new_from;
        } else {
            self.from = 0;
            self.shift = 0;
        }

        self.list.splice(m_range.clone(), [added_range]);

        changed_ranges
    }

    /// Adds many [`Range`]s to be merged with the existing ones
    ///
    /// This function will return `true` if the `Ranges` were changed
    /// by this addition.
    pub fn extend(&mut self, ranges: impl IntoIterator<Item = Range<usize>>) -> bool {
        let mut has_changed = false;
        for range in ranges {
            has_changed |= self.add(range);
        }
        has_changed
    }

    /// Removes a [`Range`] from the list, returning an
    /// [`Iterator`] over the [`Range`]s that were taken
    #[track_caller]
    pub fn remove_on(&mut self, within: Range<usize>) -> impl Iterator<Item = Range<usize>> {
        assert_range(&within);
        let within = within.start as i32..within.end as i32;

        let m_range = merging_range_by_guess_and_lazy_shift(
            (&self.list, self.list.len()),
            (self.from, [within.start, within.end]),
            (self.from, self.shift, 0, std::ops::Add::add),
            (|r| r.start, |r| r.end),
        );

        // The ranges in this region have not been callibrated yet.
        if self.from <= m_range.end {
            for range in self.list.range_mut(self.from..m_range.end).iter_mut() {
                range.start += self.shift;
                range.end += self.shift;
            }
        }

        let split_off = {
            let slice = self.list.range(m_range.clone());

            let first = slice.iter().next().cloned();
            let last = slice.iter().next_back().cloned().or(first.clone());

            let split_start = first.filter(|f| f.start < within.start).map(|first| {
                self.list[m_range.start].start = within.start;
                first.start..within.start
            });
            let split_end = last.filter(|l| l.end > within.end).map(|last| {
                self.list[m_range.end - 1].end = within.end;
                within.end..last.end
            });

            let min_len = |range: &Range<i32>| range.len() >= self.min_len;

            [split_start.filter(min_len), split_end.filter(min_len)]
        };

        let added = split_off.iter().flatten().count();

        let new_from = self.from.saturating_sub(m_range.len()).max(m_range.start) + added;
        if new_from < self.list.len() + added - m_range.len() {
            self.from = new_from;
        } else {
            self.from = 0;
            self.shift = 0;
        }

        self.list
            .splice(m_range, split_off.into_iter().flatten())
            .map(|r| r.start as usize..r.end as usize)
    }

    /// Removes all [`Range`]s that intersect with the given one,
    /// returning an [`Iterator`] over them
    #[track_caller]
    pub fn remove_intersecting(
        &mut self,
        within: Range<usize>,
    ) -> impl Iterator<Item = Range<usize>> {
        assert_range(&within);
        let within = within.start as i32..within.end as i32;

        let m_range = merging_range_by_guess_and_lazy_shift(
            (&self.list, self.list.len()),
            (self.from, [within.start, within.end]),
            (self.from, self.shift, 0, std::ops::Add::add),
            (|r| r.start, |r| r.end),
        );

        // The ranges in this region have not been callibrated yet.
        if self.from <= m_range.end {
            for range in self.list.range_mut(self.from..m_range.end).iter_mut() {
                range.start += self.shift;
                range.end += self.shift;
            }
        }

        let new_from = self.from.saturating_sub(m_range.len()).max(m_range.start);
        if new_from < self.list.len() - m_range.len() {
            self.from = new_from;
        } else {
            self.from = 0;
            self.shift = 0;
        }

        self.list
            .extract_if(m_range, move |range| {
                range.start != within.end && range.end != within.start
            })
            .map(|range| range.start as usize..range.end as usize)
    }

    /// Applies the [`add`] function to another [`Ranges`]s
    ///
    /// [`add`]: Self::add
    #[track_caller]
    pub fn merge(&mut self, other: Self) -> bool {
        let mut has_changed = false;
        for range in other.list {
            has_changed |= self.add(range.start as usize..range.end as usize);
        }
        has_changed
    }

    /// Shifts the [`Range`]s within by a [`Change`]
    ///
    /// If the `diff` is negative (i.e. a part of the ranges were
    /// removed), then ranges will be removed ahead of `from`
    /// accordingly.
    ///
    /// [`Change`]: crate::buffer::Change
    pub fn shift_by(&mut self, from: usize, shift: i32) {
        let from = from as i32;

        // The range of changes that will be drained
        let m_range = merging_range_by_guess_and_lazy_shift(
            (&self.list, self.list.len()),
            (self.from, [from, from - shift.min(0)]),
            (self.from, self.shift, 0, std::ops::Add::add),
            (|r| r.start, |r| r.end),
        );

        // If self.from < m_range.end, I need to shift the changes between
        // the two, so that they match the shifting of the changes
        // before self.from.
        if self.from < m_range.end && self.shift != 0 {
            for range in self.list.range_mut(self.from..m_range.end).iter_mut() {
                range.start += self.shift;
                range.end += self.shift;
            }
        // If self.from > m_range.end, then I shift the ranges
        // inbetween by -self.shift, in order to keep the ranges
        // after m_range.start equally unshifted by self.shift +
        // shift.
        } else if self.from > m_range.end && self.shift != 0 {
            for range in &mut self.list.range_mut(m_range.end..self.from) {
                range.start -= self.shift;
                range.end -= self.shift;
            }
        }

        let range_left = {
            let slice = self.list.range(m_range.clone());
            let mut iter = slice.iter();

            iter.next().and_then(|first| {
                let last = iter.next_back().unwrap_or(first);
                let range = first.start.min(from)..(last.end + shift).max(from);

                (range.len() >= self.min_len).then_some(range)
            })
        };

        let new_from = m_range.start + range_left.is_some() as usize;

        self.list.splice(m_range.clone(), range_left);

        if new_from < self.list.len() {
            self.from = new_from;
            self.shift += shift;
        } else {
            self.from = 0;
            self.shift = 0;
        }
    }

    /// Returns the number of [`Range<usize>`]s
    pub fn len(&self) -> usize {
        self.list.len()
    }

    /// Returns `true` if there are no [`Range<usize>`]s
    pub fn is_empty(&self) -> bool {
        self.list.is_empty()
    }

    /// An [`Iterator`] over the [`Range`]s in this list
    pub fn iter(&self) -> impl Iterator<Item = Range<usize>> {
        self.list.iter().enumerate().map(move |(i, range)| {
            if i >= self.from {
                (range.start + self.shift) as usize..(range.end + self.shift) as usize
            } else {
                range.start as usize..range.end as usize
            }
        })
    }

    /// The same as [`Ranges::remove_on`], but without removing, just
    /// iterating over the relevant ranges
    ///
    /// This method will trim the iterated [`Range`]s to the bounds of
    /// `within`. If you want a non trimmed version of this method,
    /// check out [`iter_intersecting`].
    ///
    /// [`iter_intersecting`]: Ranges::iter_intersecting
    #[track_caller]
    pub fn iter_over(&self, within: Range<usize>) -> impl Iterator<Item = Range<usize>> {
        self.iter_intersecting(within.clone())
            .map(move |range| range.start.max(within.start)..range.end.min(within.end))
            .filter(|range| !range.is_empty())
    }

    /// Iterates over all [`Range`]s that intersect with `within`
    ///
    /// If you want to automatically trim those ranges to the bounds
    /// of `within`, check out [`iter_over`]. If you want to remove
    /// the ranges that intersect with the given one, see,
    /// [`Ranges::remove_intersecting`]
    ///
    /// [`iter_over`]: Self::iter_over
    #[track_caller]
    pub fn iter_intersecting(&self, within: Range<usize>) -> impl Iterator<Item = Range<usize>> {
        assert_range(&within);
        let m_range = merging_range_by_guess_and_lazy_shift(
            (&self.list, self.list.len()),
            (self.from, [within.start as i32, within.end as i32]),
            (self.from, self.shift, 0, std::ops::Add::add),
            (|r| r.start, |r| r.end),
        );

        let (s0, s1) = self.list.range(m_range.clone()).as_slices();
        s0.iter().chain(s1).enumerate().filter_map(move |(i, r)| {
            let range = if i + m_range.start >= self.from {
                (r.start + self.shift) as usize..(r.end + self.shift) as usize
            } else {
                r.start as usize..r.end as usize
            };

            (range.start != within.end && range.end != within.start).then_some(range)
        })
    }

    /// Wether any [`Range`] in this `Ranges` intersects with the
    /// given `range`
    #[track_caller]
    pub fn intersects_with(&self, range: Range<usize>) -> bool {
        assert_range(&range);

        let m_range = merging_range_by_guess_and_lazy_shift(
            (&self.list, self.list.len()),
            (self.from, [range.start as i32, range.end as i32]),
            (self.from, self.shift, 0, std::ops::Add::add),
            (|r| r.start, |r| r.end),
        );

        !m_range.is_empty()
    }
}

impl IntoIterator for Ranges {
    type IntoIter = IntoIter;
    type Item = Range<usize>;

    fn into_iter(self) -> Self::IntoIter {
        IntoIter {
            iter: self.list.into_iter().enumerate(),
            from: self.from,
            shift: self.shift,
        }
    }
}

pub struct IntoIter {
    iter: std::iter::Enumerate<gap_buf::IntoIter<Range<i32>>>,
    from: usize,
    shift: i32,
}

impl Iterator for IntoIter {
    type Item = Range<usize>;

    fn next(&mut self) -> Option<Self::Item> {
        self.iter.next().map(|(i, range)| {
            if i >= self.from {
                (range.start + self.shift) as usize..(range.end + self.shift) as usize
            } else {
                range.start as usize..range.end as usize
            }
        })
    }
}

impl ExactSizeIterator for IntoIter {}

impl PartialEq for Ranges {
    fn eq(&self, other: &Self) -> bool {
        self.len() == other.len() && self.iter().eq(other.iter())
    }
}

impl Eq for Ranges {}

impl PartialOrd for Ranges {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Ranges {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        let cmp = |r: Range<usize>| [r.start, r.end];
        self.iter().flat_map(cmp).cmp(other.iter().flat_map(cmp))
    }
}

#[track_caller]
pub fn assert_range(range: &Range<usize>) {
    assert!(
        range.start <= range.end,
        "range starts at {} but ends at {}",
        range.start,
        range.end
    );
}