ad_editor/dot/
range.rs

1use crate::{buffer::Buffer, dot::Cur};
2
3/// A [Range] is a closed interval [start, end] that includes _both_ the starting cursor and the
4/// ending cursor.
5///
6/// This is equivalent to a stdlib range of the form `from..=to` rather than `from..to` as you
7/// might expect. This is also different from how GapBuffer slicing works (which uses traditional
8/// half-open ranges) so care needs to be taken when using a [Range] to select sub-regions of a
9/// [Buffer].
10///
11/// A Range where `start == end` is considered a "null range" and may be collapsed to a single
12/// cursor via the collapse_null_range method on `Dot`.
13///
14/// # Why do this?
15///
16/// The primary purpose of a [Range] is to represent the currently selected text within a given
17/// [Buffer] and we need to be able to manipulate both ends of that selection as isolated [Cur]
18/// instances within the text. As such, we _really_ need both the start and end cursor to have the
19/// same semantics and behaviour and we also need to be able to pop them off, manipulate them and
20/// recombine them to produce new Ranges. While using a half-open interval would make using Ranges
21/// for slicing simpler, the trade off is that it makes a lot of the more complicated operations we
22/// need to perform (expanding selections, mapping mouse selections etc) significantly harder to
23/// reason about as you end up needing to map back and forth between the end of a Range and the Cur
24/// that represents the final character in that Range.
25#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
26pub struct Range {
27    pub start: Cur,
28    pub end: Cur,
29    pub start_active: bool,
30}
31
32impl Range {
33    /// Beginning of file
34    pub const BOF: Self = Range {
35        start: Cur { idx: 0 },
36        end: Cur { idx: 0 },
37        start_active: false,
38    };
39
40    pub(crate) fn from_cursors(c1: Cur, c2: Cur, c1_was_active: bool) -> Self {
41        let (start, end, start_active) = if c1 <= c2 {
42            (c1, c2, c1_was_active)
43        } else if c1_was_active {
44            (c2, c1, false)
45        } else {
46            (c2, c1, true)
47        };
48
49        Self {
50            start,
51            end,
52            start_active,
53        }
54    }
55
56    pub fn as_string_addr(&self, b: &Buffer) -> String {
57        format!(
58            "{},{}",
59            self.start.as_string_addr(b),
60            self.end.as_string_addr(b)
61        )
62    }
63
64    pub(crate) fn contains(&self, cur: &Cur) -> bool {
65        cur.idx >= self.start.idx && cur.idx <= self.end.idx
66    }
67
68    pub(crate) fn intersects_range(&self, other: &Range) -> bool {
69        self.start.idx <= other.end.idx && other.start.idx <= self.end.idx
70    }
71
72    /// Extends the STARTING cursor to its line start
73    #[must_use]
74    pub(super) fn extend_to_line_start(mut self, b: &Buffer) -> Self {
75        self.start = self.start.move_to_line_start(b);
76        self
77    }
78
79    /// Extends the ENDING cursor to its line start
80    #[must_use]
81    pub(super) fn extend_to_line_end(mut self, b: &Buffer) -> Self {
82        self.end = self.end.move_to_line_end(b);
83        self
84    }
85
86    pub fn flip(&mut self) {
87        self.start_active = !self.start_active;
88    }
89
90    pub fn active_cursor(&self) -> Cur {
91        if self.start_active {
92            self.start
93        } else {
94            self.end
95        }
96    }
97
98    pub fn set_active_cursor(&mut self, c: Cur) {
99        if c == self.start && c == self.end {
100            return;
101        }
102
103        if self.start_active {
104            if c >= self.end {
105                self.start = self.end;
106                self.end = c;
107                self.start_active = false;
108            } else {
109                self.start = c;
110            }
111        } else if c <= self.start {
112            self.end = self.start;
113            self.start = c;
114            self.start_active = true;
115        } else {
116            self.end = c;
117        }
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124    use simple_test_case::test_case;
125
126    fn c(idx: usize) -> Cur {
127        Cur { idx }
128    }
129
130    fn r(start: usize, end: usize, start_active: bool) -> Range {
131        Range {
132            start: c(start),
133            end: c(end),
134            start_active,
135        }
136    }
137
138    #[test_case(r(2, 2, true), c(0), r(0, 2, true); "null range start active new before")]
139    #[test_case(r(2, 2, true), c(2), r(2, 2, true); "null range start active new equal")]
140    #[test_case(r(2, 2, true), c(5), r(2, 5, false); "null range start active new after")]
141    #[test_case(r(2, 2, false), c(0), r(0, 2, true); "null range end active new before")]
142    #[test_case(r(2, 2, false), c(2), r(2, 2, false); "null range end active new equal")]
143    #[test_case(r(2, 2, false), c(5), r(2, 5, false); "null range end active new after")]
144    #[test_case(r(3, 5, true), c(1), r(1, 5, true); "start active new before")]
145    #[test_case(r(3, 5, true), c(3), r(3, 5, true); "start active new at start")]
146    #[test_case(r(3, 5, true), c(4), r(4, 5, true); "start active new in between")]
147    #[test_case(r(3, 5, true), c(5), r(5, 5, false); "start active new at end")]
148    #[test_case(r(3, 5, true), c(7), r(5, 7, false); "start active new after")]
149    #[test_case(r(3, 5, false), c(1), r(1, 3, true); "end active new before")]
150    #[test_case(r(3, 5, false), c(3), r(3, 3, true); "end active new at start")]
151    #[test_case(r(3, 5, false), c(4), r(3, 4, false); "end active new in between")]
152    #[test_case(r(3, 5, false), c(5), r(3, 5, false); "end active new at end")]
153    #[test_case(r(3, 5, false), c(7), r(3, 7, false); "end active new after")]
154    #[test]
155    fn set_active_cursor_works(mut rng: Range, cur: Cur, expected: Range) {
156        rng.set_active_cursor(cur);
157        assert_eq!(rng, expected);
158    }
159}