Skip to main content

gpui_base/input/base/
cursor.rs

1use std::ops::{Range, RangeBounds};
2
3/// A selection in the text, represented by start and end byte indices.
4#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
5pub struct Selection {
6    pub start: usize,
7    pub end: usize,
8}
9
10impl Selection {
11    pub fn new(start: usize, end: usize) -> Self {
12        Self { start, end }
13    }
14
15    pub fn len(&self) -> usize {
16        self.end.saturating_sub(self.start)
17    }
18
19    pub fn is_empty(&self) -> bool {
20        self.start == self.end
21    }
22
23    /// Clears the selection, setting start and end to 0.
24    pub fn clear(&mut self) {
25        self.start = 0;
26        self.end = 0;
27    }
28
29    /// Checks if the given offset is within the selection range.
30    pub fn contains(&self, offset: usize) -> bool {
31        offset >= self.start && offset < self.end
32    }
33}
34
35impl From<Range<usize>> for Selection {
36    fn from(value: Range<usize>) -> Self {
37        Self::new(value.start, value.end)
38    }
39}
40impl From<Selection> for Range<usize> {
41    fn from(value: Selection) -> Self {
42        value.start..value.end
43    }
44}
45impl RangeBounds<usize> for Selection {
46    fn start_bound(&self) -> std::ops::Bound<&usize> {
47        std::ops::Bound::Included(&self.start)
48    }
49
50    fn end_bound(&self) -> std::ops::Bound<&usize> {
51        std::ops::Bound::Excluded(&self.end)
52    }
53}
54
55use gpui::Pixels;
56
57use super::selection::CursorId;
58
59#[derive(Debug, Copy, Clone, PartialEq)]
60pub(super) struct CursorSelection {
61    pub(super) id: CursorId,
62    pub(super) start: usize,
63    pub(super) end: usize,
64    pub(super) reversed: bool,
65    pub(super) column_anchor: Option<(Pixels, usize)>,
66}
67
68impl CursorSelection {
69    pub(super) fn new(id: CursorId, start: usize, end: usize) -> Self {
70        Self {
71            id,
72            start,
73            end,
74            reversed: false,
75            column_anchor: None,
76        }
77    }
78
79    pub(super) fn len(&self) -> usize {
80        self.end.saturating_sub(self.start)
81    }
82
83    pub(super) fn is_empty(&self) -> bool {
84        self.start == self.end
85    }
86
87    pub(super) fn clear(&mut self) {
88        self.start = 0;
89        self.end = 0;
90    }
91
92    pub(super) fn contains(&self, offset: usize) -> bool {
93        offset >= self.start && offset < self.end
94    }
95
96    pub(super) fn cursor_offset(&self) -> usize {
97        if self.reversed { self.start } else { self.end }
98    }
99
100    pub(super) fn place_at(&mut self, offset: usize, column_anchor: Option<(Pixels, usize)>) {
101        self.start = offset;
102        self.end = offset;
103        self.reversed = false;
104        self.column_anchor = column_anchor;
105    }
106
107    pub(super) fn is_collapsed(&self) -> bool {
108        self.is_empty()
109    }
110}
111
112impl From<Range<usize>> for CursorSelection {
113    fn from(value: Range<usize>) -> Self {
114        Self::new(CursorId::default(), value.start, value.end)
115    }
116}
117
118impl From<CursorSelection> for Range<usize> {
119    fn from(value: CursorSelection) -> Self {
120        value.start..value.end
121    }
122}
123
124impl RangeBounds<usize> for CursorSelection {
125    fn start_bound(&self) -> std::ops::Bound<&usize> {
126        std::ops::Bound::Included(&self.start)
127    }
128
129    fn end_bound(&self) -> std::ops::Bound<&usize> {
130        std::ops::Bound::Excluded(&self.end)
131    }
132}
133
134pub(super) struct Selections {
135    selections: Vec<CursorSelection>,
136    next_id: usize,
137}
138
139impl Selections {
140    pub(super) fn new() -> Self {
141        Self {
142            selections: vec![CursorSelection::new(CursorId::new(0), 0, 0)],
143            next_id: 1,
144        }
145    }
146
147    /// Returns the active selection.
148    pub(super) fn active(&self) -> &CursorSelection {
149        self.selections
150            .first()
151            .expect("Selections always has at least one selection")
152    }
153
154    /// Returns a mutable reference to the active selection.
155    pub(super) fn active_mut(&mut self) -> &mut CursorSelection {
156        self.selections
157            .first_mut()
158            .expect("Selections always has at least one selection")
159    }
160
161    pub(super) fn iter(&self) -> impl Iterator<Item = &CursorSelection> {
162        self.selections.iter()
163    }
164
165    /// Returns the number of selections (always `>= 1`).
166    pub(super) fn len(&self) -> usize {
167        self.selections.len()
168    }
169
170    /// Returns true when there is exactly one selection.
171    pub(super) fn is_single(&self) -> bool {
172        self.selections.len() == 1
173    }
174
175    /// Generates a new unique cursor id.
176    pub(super) fn generate_id(&mut self) -> CursorId {
177        let id = CursorId::new(self.next_id);
178        self.next_id += 1;
179        id
180    }
181
182    /// Adds an additional selection.
183    pub(super) fn add(&mut self, selection: CursorSelection) {
184        self.selections.push(selection);
185    }
186
187    /// Replaces all selections. Ignores an empty vec to keep the
188    /// "always at least one selection" invariant.
189    pub(super) fn replace_all(&mut self, selections: Vec<CursorSelection>) {
190        if !selections.is_empty() {
191            self.selections = selections;
192        }
193    }
194
195    /// Removes every selection except the active one (index 0).
196    pub(super) fn remove_all_but_active(&mut self) {
197        self.selections.truncate(1);
198    }
199
200    /// Merges overlapping selections.
201    ///
202    /// Selections are sorted by start and folded together when they overlap.
203    /// The active selection is preserved, propagated onto
204    /// the merged result if it was absorbed, and re-fronted afterwards.
205    pub(super) fn merge_overlapping(&mut self) {
206        if self.selections.len() <= 1 {
207            return;
208        }
209
210        let active_id = self.active().id;
211
212        self.selections.sort_by_key(|s| s.start);
213
214        let mut merged: Vec<CursorSelection> = Vec::with_capacity(self.selections.len());
215        for selection in &self.selections {
216            if let Some(last) = merged.last_mut() {
217                if selection.start <= last.end {
218                    // Overlapping or adjacent, extend the last one.
219                    let did_merge = selection.start != last.start || selection.end != last.end;
220                    last.end = last.end.max(selection.end);
221                    if selection.id == active_id {
222                        last.id = active_id;
223                        last.reversed = selection.reversed;
224                    }
225                    // Reset the column anchor on a real merge.
226                    if did_merge {
227                        last.column_anchor = None;
228                    }
229                    continue;
230                }
231            }
232            merged.push(*selection);
233        }
234
235        // Re-front the active selection so it stays at index 0.
236        if let Some(pos) = merged.iter().position(|s| s.id == active_id) {
237            merged.swap(0, pos);
238        }
239
240        self.selections = merged;
241    }
242}
243
244impl Default for Selections {
245    fn default() -> Self {
246        Self::new()
247    }
248}
249
250#[cfg(test)]
251mod tests {
252    use super::*;
253    use crate::input::Position;
254    use gpui::px;
255
256    #[test]
257    fn selection_keeps_its_public_range_api() {
258        fn assert_eq<T: Eq>() {}
259
260        assert_eq::<Selection>();
261        let selection = Selection::new(2, 5);
262        assert_eq!(selection, Selection { start: 2, end: 5 });
263        assert_eq!(Range::<usize>::from(selection), 2..5);
264    }
265
266    #[test]
267    fn test_line_column_from_to() {
268        assert_eq!(
269            Position::new(1, 2),
270            Position {
271                line: 1,
272                character: 2
273            }
274        );
275    }
276
277    #[test]
278    fn test_cursor_offset_reversed() {
279        let mut sel = CursorSelection::new(CursorId::new(0), 5, 10);
280        assert_eq!(sel.cursor_offset(), 10);
281        sel.reversed = true;
282        assert_eq!(sel.cursor_offset(), 5);
283    }
284
285    #[test]
286    fn test_place_at() {
287        let mut sel = CursorSelection::new(CursorId::new(0), 5, 10);
288        sel.reversed = true;
289        sel.place_at(7, Some((px(12.), 3)));
290        assert_eq!(sel.start, 7);
291        assert_eq!(sel.end, 7);
292        assert!(sel.is_collapsed());
293        assert!(!sel.reversed);
294        assert_eq!(sel.column_anchor, Some((px(12.), 3)));
295    }
296
297    #[test]
298    fn test_selections_never_empty() {
299        let selections = Selections::new();
300        assert_eq!(selections.len(), 1);
301        assert_eq!(selections.active().id, CursorId::new(0));
302
303        let default = Selections::default();
304        assert_eq!(default.len(), 1);
305    }
306
307    #[test]
308    fn test_selections_active_mut() {
309        let mut selections = Selections::new();
310        selections.active_mut().place_at(4, None);
311        assert_eq!(selections.active().cursor_offset(), 4);
312    }
313
314    #[test]
315    fn test_selections_merge_overlapping() {
316        let mut selections = Selections::new();
317
318        let id1 = selections.generate_id();
319        let id2 = selections.generate_id();
320        let id3 = selections.generate_id();
321
322        // id1 is the active selection (index 0).
323        selections.replace_all(vec![
324            CursorSelection::new(id1, 0, 10),
325            CursorSelection::new(id2, 5, 15), // Overlaps with the first.
326            CursorSelection::new(id3, 20, 30), // Non-overlapping.
327        ]);
328
329        selections.merge_overlapping();
330
331        // After merge: (0, 15) and (20, 30).
332        assert_eq!(selections.len(), 2);
333        // The active selection stays at index 0 and carries its id.
334        assert_eq!(selections.active().id, id1);
335        assert_eq!(
336            (selections.active().start, selections.active().end),
337            (0, 15)
338        );
339
340        let ranges: Vec<_> = selections.iter().map(|s| (s.start, s.end)).collect();
341        assert!(ranges.contains(&(0, 15)));
342        assert!(ranges.contains(&(20, 30)));
343    }
344
345    #[test]
346    fn merging_preserves_the_active_selection_direction() {
347        let mut selections = Selections::new();
348        let active_id = selections.generate_id();
349        let other_id = selections.generate_id();
350        let mut active = CursorSelection::new(active_id, 5, 15);
351        active.reversed = true;
352        let other = CursorSelection::new(other_id, 0, 10);
353        selections.replace_all(vec![active, other]);
354
355        selections.merge_overlapping();
356
357        assert_eq!(selections.active().id, active_id);
358        assert!(selections.active().reversed);
359    }
360}