Skip to main content

ochrepad_core/
selection.rs

1//! Selections are always a set (PLAN 3.3). A plain cursor is a set of one empty
2//! selection.
3
4use std::ops::Range;
5
6use serde::{Deserialize, Serialize};
7
8use crate::position::{ByteOffset, VisualCol};
9use crate::text::Document;
10
11#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
12pub struct Selection {
13    pub anchor: ByteOffset,
14    pub head: ByteOffset,
15    /// Remembered visual column for consecutive vertical movement.
16    pub goal: Option<VisualCol>,
17}
18
19impl Selection {
20    pub fn caret(pos: ByteOffset) -> Self {
21        Self::new(pos, pos)
22    }
23
24    pub fn new(anchor: ByteOffset, head: ByteOffset) -> Self {
25        Self {
26            anchor,
27            head,
28            goal: None,
29        }
30    }
31
32    pub fn with_goal(mut self, goal: Option<VisualCol>) -> Self {
33        self.goal = goal;
34        self
35    }
36
37    pub fn start(&self) -> ByteOffset {
38        self.anchor.min(self.head)
39    }
40
41    pub fn end(&self) -> ByteOffset {
42        self.anchor.max(self.head)
43    }
44
45    pub fn range(&self) -> Range<ByteOffset> {
46        self.start()..self.end()
47    }
48
49    pub fn is_empty(&self) -> bool {
50        self.anchor == self.head
51    }
52
53    fn is_backward(&self) -> bool {
54        self.head < self.anchor
55    }
56}
57
58/// The rectangle a column selection was made from, kept alongside the set so
59/// rectangular copy/paste and column-keyed sort can use it. `start_*` is the
60/// anchor corner and `end_*` the head corner (it may be above or left of the
61/// anchor).
62#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
63pub struct Rectangle {
64    pub start_line: usize,
65    pub end_line: usize,
66    pub start_col: VisualCol,
67    pub end_col: VisualCol,
68}
69
70impl Rectangle {
71    pub fn top(&self) -> usize {
72        self.start_line.min(self.end_line)
73    }
74
75    pub fn bottom(&self) -> usize {
76        self.start_line.max(self.end_line)
77    }
78
79    pub fn left(&self) -> VisualCol {
80        self.start_col.min(self.end_col)
81    }
82
83    pub fn right(&self) -> VisualCol {
84        self.start_col.max(self.end_col)
85    }
86}
87
88/// Non-empty, sorted, non-overlapping selections plus the primary's index.
89#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
90pub struct SelectionSet {
91    ranges: Vec<Selection>,
92    primary: usize,
93    rect: Option<Rectangle>,
94}
95
96impl SelectionSet {
97    pub fn single(sel: Selection) -> Self {
98        Self {
99            ranges: vec![sel],
100            primary: 0,
101            rect: None,
102        }
103    }
104
105    pub fn caret(pos: ByteOffset) -> Self {
106        Self::single(Selection::caret(pos))
107    }
108
109    /// Builds a normalized set. `primary` indexes into `ranges` as given.
110    /// An empty `ranges` yields a caret at 0.
111    pub fn new(ranges: Vec<Selection>, primary: usize) -> Self {
112        if ranges.is_empty() {
113            return Self::caret(ByteOffset(0));
114        }
115        let primary = primary.min(ranges.len() - 1);
116        let mut set = Self {
117            ranges,
118            primary,
119            rect: None,
120        };
121        set.normalize();
122        set
123    }
124
125    pub fn ranges(&self) -> &[Selection] {
126        &self.ranges
127    }
128
129    pub fn primary(&self) -> &Selection {
130        &self.ranges[self.primary]
131    }
132
133    pub fn primary_index(&self) -> usize {
134        self.primary
135    }
136
137    pub fn len(&self) -> usize {
138        self.ranges.len()
139    }
140
141    pub fn is_empty(&self) -> bool {
142        false
143    }
144
145    pub fn rect(&self) -> Option<&Rectangle> {
146        self.rect.as_ref()
147    }
148
149    pub fn with_rect(mut self, rect: Option<Rectangle>) -> Self {
150        self.rect = rect;
151        self
152    }
153
154    /// Applies `f` to every selection, renormalizes and drops the rectangle.
155    pub fn map(&self, mut f: impl FnMut(&Selection) -> Selection) -> Self {
156        Self::new(self.ranges.iter().map(&mut f).collect(), self.primary)
157    }
158
159    /// Sorts, then merges overlapping or touching selections. The merged
160    /// selection keeps the primary's direction if it absorbed the primary,
161    /// otherwise the first one's.
162    fn normalize(&mut self) {
163        let primary = self.primary;
164        let mut indexed: Vec<(usize, Selection)> = std::mem::take(&mut self.ranges)
165            .into_iter()
166            .enumerate()
167            .collect();
168        indexed.sort_by_key(|(_, s)| (s.start(), s.end()));
169
170        let mut out: Vec<Selection> = Vec::with_capacity(indexed.len());
171        let mut new_primary = 0;
172        // Which original selection decides the direction of `out.last()`.
173        let mut leader: Option<(usize, Selection)> = None;
174        for (i, sel) in indexed {
175            match out.last_mut() {
176                Some(last) if sel.start() <= last.end() => {
177                    let (start, end) = (last.start(), last.end().max(sel.end()));
178                    let (lead_idx, lead) = match leader.take() {
179                        Some(l) if l.0 == primary || i != primary => l,
180                        _ => (i, sel),
181                    };
182                    *last = if lead.is_backward() {
183                        Selection {
184                            anchor: end,
185                            head: start,
186                            goal: lead.goal,
187                        }
188                    } else {
189                        Selection {
190                            anchor: start,
191                            head: end,
192                            goal: lead.goal,
193                        }
194                    };
195                    if i == primary {
196                        new_primary = out.len() - 1;
197                    }
198                    leader = Some((lead_idx, lead));
199                }
200                _ => {
201                    if i == primary {
202                        new_primary = out.len();
203                    }
204                    leader = Some((i, sel.clone()));
205                    out.push(sel);
206                }
207            }
208        }
209        self.ranges = out;
210        self.primary = new_primary;
211    }
212
213    /// Checks the invariants; used by tests and debug assertions.
214    pub fn validate(&self, doc: &Document) -> Result<(), String> {
215        if self.ranges.is_empty() || self.primary >= self.ranges.len() {
216            return Err(format!("bad primary/len: {self:?}"));
217        }
218        for s in &self.ranges {
219            for p in [s.anchor, s.head] {
220                if p > doc.len() || !doc.is_char_boundary(p) {
221                    return Err(format!("{p:?} out of range or not a char boundary"));
222                }
223            }
224        }
225        for w in self.ranges.windows(2) {
226            if w[0].end() >= w[1].start() {
227                return Err(format!(
228                    "unsorted, overlapping or touching: {:?}",
229                    self.ranges
230                ));
231            }
232        }
233        Ok(())
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240
241    fn sel(a: usize, h: usize) -> Selection {
242        Selection::new(ByteOffset(a), ByteOffset(h))
243    }
244
245    #[test]
246    fn normalize_sorts_and_merges() {
247        let set = SelectionSet::new(vec![sel(10, 12), sel(0, 2), sel(5, 5), sel(5, 5)], 0);
248        assert_eq!(set.ranges(), &[sel(0, 2), sel(5, 5), sel(10, 12)]);
249        assert_eq!(set.primary(), &sel(10, 12));
250    }
251
252    #[test]
253    fn touching_selections_merge_keeping_primary_direction() {
254        let set = SelectionSet::new(vec![sel(0, 3), sel(6, 3)], 1);
255        assert_eq!(set.ranges(), &[sel(6, 0)]);
256        assert_eq!(set.primary_index(), 0);
257
258        let set = SelectionSet::new(vec![sel(0, 3), sel(6, 3), sel(9, 9)], 2);
259        assert_eq!(set.ranges(), &[sel(0, 6), sel(9, 9)]);
260        assert_eq!(set.primary_index(), 1);
261    }
262
263    #[test]
264    fn caret_inside_selection_merges() {
265        let set = SelectionSet::new(vec![sel(2, 8), sel(4, 4)], 1);
266        assert_eq!(set.ranges(), &[sel(2, 8)]);
267        assert_eq!(set.primary_index(), 0);
268    }
269}