1use std::ops::{Range, RangeBounds};
2
3#[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 pub fn clear(&mut self) {
25 self.start = 0;
26 self.end = 0;
27 }
28
29 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 pub(super) fn active(&self) -> &CursorSelection {
149 self.selections
150 .first()
151 .expect("Selections always has at least one selection")
152 }
153
154 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 pub(super) fn len(&self) -> usize {
167 self.selections.len()
168 }
169
170 pub(super) fn is_single(&self) -> bool {
172 self.selections.len() == 1
173 }
174
175 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 pub(super) fn add(&mut self, selection: CursorSelection) {
184 self.selections.push(selection);
185 }
186
187 pub(super) fn replace_all(&mut self, selections: Vec<CursorSelection>) {
190 if !selections.is_empty() {
191 self.selections = selections;
192 }
193 }
194
195 pub(super) fn remove_all_but_active(&mut self) {
197 self.selections.truncate(1);
198 }
199
200 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 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 if did_merge {
227 last.column_anchor = None;
228 }
229 continue;
230 }
231 }
232 merged.push(*selection);
233 }
234
235 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 selections.replace_all(vec![
324 CursorSelection::new(id1, 0, 10),
325 CursorSelection::new(id2, 5, 15), CursorSelection::new(id3, 20, 30), ]);
328
329 selections.merge_overlapping();
330
331 assert_eq!(selections.len(), 2);
333 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}