Skip to repository content1602 lines · 56.3 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T01:34:18.073Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
selections_collection.rs
1use std::{
2 cmp, fmt, iter, mem,
3 ops::{AddAssign, Deref, DerefMut, Range, Sub},
4 sync::Arc,
5};
6
7use gpui::Pixels;
8use itertools::{Either, Itertools as _};
9use language::{Bias, Point, Selection, SelectionGoal};
10use multi_buffer::{MultiBufferDimension, MultiBufferOffset, ToPoint};
11use util::post_inc;
12
13use crate::{
14 Anchor, DisplayPoint, DisplayRow, MultiBufferSnapshot, SelectMode, ToOffset,
15 display_map::{DisplaySnapshot, ToDisplayPoint},
16 movement::TextLayoutDetails,
17};
18
19#[derive(Debug, Clone)]
20pub struct PendingSelection {
21 selection: Selection<Anchor>,
22 mode: SelectMode,
23}
24
25#[derive(Debug, Clone)]
26pub struct SelectionsCollection {
27 next_selection_id: usize,
28 line_mode: bool,
29 /// The non-pending, non-overlapping selections.
30 /// The [SelectionsCollection::pending] selection could possibly overlap these
31 disjoint: Arc<[Selection<Anchor>]>,
32 /// A pending selection, such as when the mouse is being dragged
33 pending: Option<PendingSelection>,
34 select_mode: SelectMode,
35 is_extending: bool,
36}
37
38impl SelectionsCollection {
39 pub fn new() -> Self {
40 Self {
41 next_selection_id: 1,
42 line_mode: false,
43 disjoint: Arc::default(),
44 pending: Some(PendingSelection {
45 selection: Selection {
46 id: 0,
47 start: Anchor::Min,
48 end: Anchor::Min,
49 reversed: false,
50 goal: SelectionGoal::None,
51 },
52 mode: SelectMode::Character,
53 }),
54 select_mode: SelectMode::Character,
55 is_extending: false,
56 }
57 }
58
59 pub fn clone_state(&mut self, other: &SelectionsCollection) {
60 self.next_selection_id = other.next_selection_id;
61 self.line_mode = other.line_mode;
62 self.disjoint = other.disjoint.clone();
63 self.pending.clone_from(&other.pending);
64 }
65
66 pub fn count(&self) -> usize {
67 let mut count = self.disjoint.len();
68 if self.pending.is_some() {
69 count += 1;
70 }
71 count
72 }
73
74 /// The non-pending, non-overlapping selections. There could be a pending selection that
75 /// overlaps these if the mouse is being dragged, etc. This could also be empty if there is a
76 /// pending selection. Returned as selections over Anchors.
77 pub fn disjoint_anchors_arc(&self) -> Arc<[Selection<Anchor>]> {
78 self.disjoint.clone()
79 }
80
81 /// The non-pending, non-overlapping selections. There could be a pending selection that
82 /// overlaps these if the mouse is being dragged, etc. This could also be empty if there is a
83 /// pending selection. Returned as selections over Anchors.
84 pub fn disjoint_anchors(&self) -> &[Selection<Anchor>] {
85 &self.disjoint
86 }
87
88 pub fn disjoint_anchor_ranges(&self) -> impl Iterator<Item = Range<Anchor>> {
89 // Mapping the Arc slice would borrow it, whereas indexing captures it.
90 let disjoint = self.disjoint_anchors_arc();
91 (0..disjoint.len()).map(move |ix| disjoint[ix].range())
92 }
93
94 /// Non-overlapping selections using anchors, including the pending selection.
95 pub fn all_anchors(&self, snapshot: &DisplaySnapshot) -> Arc<[Selection<Anchor>]> {
96 if self.pending.is_none() {
97 self.disjoint_anchors_arc()
98 } else {
99 let all_offset_selections = self.all::<MultiBufferOffset>(snapshot);
100 all_offset_selections
101 .into_iter()
102 .map(|selection| selection_to_anchor_selection(selection, snapshot))
103 .collect()
104 }
105 }
106
107 pub fn pending_anchor(&self) -> Option<&Selection<Anchor>> {
108 self.pending.as_ref().map(|pending| &pending.selection)
109 }
110
111 pub fn pending<D>(&self, snapshot: &DisplaySnapshot) -> Option<Selection<D>>
112 where
113 D: MultiBufferDimension + Sub + AddAssign<<D as Sub>::Output> + Ord,
114 {
115 resolve_selections_wrapping_blocks(self.pending_anchor(), &snapshot).next()
116 }
117
118 pub(crate) fn pending_mode(&self) -> Option<SelectMode> {
119 self.pending.as_ref().map(|pending| pending.mode.clone())
120 }
121
122 pub fn all<D>(&self, snapshot: &DisplaySnapshot) -> Vec<Selection<D>>
123 where
124 D: MultiBufferDimension + Sub + AddAssign<<D as Sub>::Output> + Ord,
125 {
126 self.all_iter(snapshot).collect()
127 }
128
129 fn all_iter<'a, D>(
130 &'a self,
131 snapshot: &'a DisplaySnapshot,
132 ) -> impl 'a + Iterator<Item = Selection<D>>
133 where
134 D: 'a + MultiBufferDimension + Sub + AddAssign<<D as Sub>::Output> + Ord,
135 {
136 let mut disjoint =
137 resolve_selections_wrapping_blocks::<D, _>(self.disjoint.iter(), snapshot).peekable();
138 let mut pending_opt = self.pending::<D>(snapshot);
139 iter::from_fn(move || {
140 if let Some(pending) = pending_opt.as_mut() {
141 while let Some(next_selection) = disjoint.peek() {
142 if should_merge(
143 pending.start,
144 pending.end,
145 next_selection.start,
146 next_selection.end,
147 false,
148 ) {
149 let next_selection = disjoint.next().unwrap();
150 if next_selection.start < pending.start {
151 pending.start = next_selection.start;
152 }
153 if next_selection.end > pending.end {
154 pending.end = next_selection.end;
155 }
156 } else if next_selection.end < pending.start {
157 return disjoint.next();
158 } else {
159 break;
160 }
161 }
162
163 pending_opt.take()
164 } else {
165 disjoint.next()
166 }
167 })
168 }
169
170 /// Returns all of the selections, adjusted to take into account the selection line_mode
171 pub fn all_adjusted(&self, snapshot: &DisplaySnapshot) -> Vec<Selection<Point>> {
172 let mut selections = self.all::<Point>(&snapshot);
173 if self.line_mode {
174 for selection in &mut selections {
175 let new_range = snapshot.expand_to_line(selection.range());
176 selection.start = new_range.start;
177 selection.end = new_range.end;
178 }
179 }
180 selections
181 }
182
183 /// Returns the newest selection, adjusted to take into account the selection line_mode
184 pub fn newest_adjusted(&self, snapshot: &DisplaySnapshot) -> Selection<Point> {
185 let mut selection = self.newest::<Point>(&snapshot);
186 if self.line_mode {
187 let new_range = snapshot.expand_to_line(selection.range());
188 selection.start = new_range.start;
189 selection.end = new_range.end;
190 }
191 selection
192 }
193
194 pub fn all_adjusted_display(
195 &self,
196 display_map: &DisplaySnapshot,
197 ) -> Vec<Selection<DisplayPoint>> {
198 if self.line_mode {
199 let selections = self.all::<Point>(&display_map);
200 let result = selections
201 .into_iter()
202 .map(|mut selection| {
203 let new_range = display_map.expand_to_line(selection.range());
204 selection.start = new_range.start;
205 selection.end = new_range.end;
206 selection.map(|point| point.to_display_point(&display_map))
207 })
208 .collect();
209 result
210 } else {
211 self.all_display(display_map)
212 }
213 }
214
215 pub fn disjoint_in_row_range<D>(
216 &self,
217 range: Range<Anchor>,
218 snapshot: &DisplaySnapshot,
219 ) -> Vec<Selection<D>>
220 where
221 D: MultiBufferDimension + Sub + AddAssign<<D as Sub>::Output> + Ord + std::fmt::Debug,
222 {
223 let buffer = snapshot.buffer_snapshot();
224 let start_row = range.start.to_point(buffer).row;
225 let end_row = range.end.to_point(buffer).row;
226 let start_ix = self
227 .disjoint
228 .partition_point(|probe| probe.end.to_point(buffer).row < start_row);
229 let end_ix = self
230 .disjoint
231 .partition_point(|probe| probe.start.to_point(buffer).row <= end_row);
232 resolve_selections_wrapping_blocks(&self.disjoint[start_ix..end_ix], snapshot).collect()
233 }
234
235 pub fn disjoint_in_range<D>(
236 &self,
237 range: Range<Anchor>,
238 snapshot: &DisplaySnapshot,
239 ) -> Vec<Selection<D>>
240 where
241 D: MultiBufferDimension + Sub + AddAssign<<D as Sub>::Output> + Ord + std::fmt::Debug,
242 {
243 let buffer = snapshot.buffer_snapshot();
244 let start_ix = self
245 .disjoint
246 .partition_point(|probe| probe.end.cmp(&range.start, buffer).is_lt());
247 let end_ix = self
248 .disjoint
249 .partition_point(|probe| probe.start.cmp(&range.end, buffer).is_le());
250 resolve_selections_wrapping_blocks(&self.disjoint[start_ix..end_ix], snapshot).collect()
251 }
252
253 pub fn all_display(&self, snapshot: &DisplaySnapshot) -> Vec<Selection<DisplayPoint>> {
254 let disjoint_anchors = &self.disjoint;
255 let mut disjoint =
256 resolve_selections_display(disjoint_anchors.iter(), &snapshot).peekable();
257 let mut pending_opt = resolve_selections_display(self.pending_anchor(), &snapshot).next();
258 iter::from_fn(move || {
259 if let Some(pending) = pending_opt.as_mut() {
260 while let Some(next_selection) = disjoint.peek() {
261 if should_merge(
262 pending.start,
263 pending.end,
264 next_selection.start,
265 next_selection.end,
266 false,
267 ) {
268 let next_selection = disjoint.next().unwrap();
269 if next_selection.start < pending.start {
270 pending.start = next_selection.start;
271 }
272 if next_selection.end > pending.end {
273 pending.end = next_selection.end;
274 }
275 } else if next_selection.end < pending.start {
276 return disjoint.next();
277 } else {
278 break;
279 }
280 }
281
282 pending_opt.take()
283 } else {
284 disjoint.next()
285 }
286 })
287 .collect()
288 }
289
290 pub fn newest_anchor(&self) -> &Selection<Anchor> {
291 self.pending
292 .as_ref()
293 .map(|s| &s.selection)
294 .or_else(|| self.disjoint.iter().max_by_key(|s| s.id))
295 .unwrap()
296 }
297
298 pub fn newest<D>(&self, snapshot: &DisplaySnapshot) -> Selection<D>
299 where
300 D: MultiBufferDimension + Sub + AddAssign<<D as Sub>::Output> + Ord,
301 {
302 resolve_selections_wrapping_blocks([self.newest_anchor()], &snapshot)
303 .next()
304 .unwrap()
305 }
306
307 pub fn newest_display(&self, snapshot: &DisplaySnapshot) -> Selection<DisplayPoint> {
308 resolve_selections_display([self.newest_anchor()], &snapshot)
309 .next()
310 .unwrap()
311 }
312
313 pub fn oldest_anchor(&self) -> &Selection<Anchor> {
314 self.disjoint
315 .iter()
316 .min_by_key(|s| s.id)
317 .or_else(|| self.pending.as_ref().map(|p| &p.selection))
318 .unwrap()
319 }
320
321 pub fn oldest<D>(&self, snapshot: &DisplaySnapshot) -> Selection<D>
322 where
323 D: MultiBufferDimension + Sub + AddAssign<<D as Sub>::Output> + Ord,
324 {
325 resolve_selections_wrapping_blocks([self.oldest_anchor()], &snapshot)
326 .next()
327 .unwrap()
328 }
329
330 pub fn first_anchor(&self) -> Selection<Anchor> {
331 self.pending
332 .as_ref()
333 .map(|pending| pending.selection.clone())
334 .unwrap_or_else(|| self.disjoint.first().cloned().unwrap())
335 }
336
337 pub fn first<D>(&self, snapshot: &DisplaySnapshot) -> Selection<D>
338 where
339 D: MultiBufferDimension + Sub + AddAssign<<D as Sub>::Output> + Ord,
340 {
341 // Without a pending selection, the first disjoint selection is also the first resolved
342 // selection, so avoid resolving every selection.
343 if self.pending.is_none()
344 && let Some(first) = self.disjoint.first()
345 {
346 return resolve_selections_wrapping_blocks([first], snapshot)
347 .next()
348 .unwrap();
349 }
350 self.all_iter(snapshot).next().unwrap()
351 }
352
353 pub fn last<D>(&self, snapshot: &DisplaySnapshot) -> Selection<D>
354 where
355 D: MultiBufferDimension + Sub + AddAssign<<D as Sub>::Output> + Ord,
356 {
357 if self.pending.is_none()
358 && let Some(last) = self.disjoint.last()
359 {
360 return resolve_selections_wrapping_blocks([last], snapshot)
361 .next()
362 .unwrap();
363 }
364 self.all_iter(snapshot).last().unwrap()
365 }
366
367 /// Returns a list of (potentially backwards!) ranges representing the selections.
368 /// Useful for test assertions, but prefer `.all()` instead.
369 #[cfg(any(test, feature = "test-support"))]
370 pub fn ranges<D>(&self, snapshot: &DisplaySnapshot) -> Vec<Range<D>>
371 where
372 D: MultiBufferDimension + Sub + AddAssign<<D as Sub>::Output> + Ord,
373 {
374 self.all::<D>(snapshot)
375 .iter()
376 .map(|s| {
377 if s.reversed {
378 s.end..s.start
379 } else {
380 s.start..s.end
381 }
382 })
383 .collect()
384 }
385
386 #[cfg(any(test, feature = "test-support"))]
387 pub fn display_ranges(&self, display_snapshot: &DisplaySnapshot) -> Vec<Range<DisplayPoint>> {
388 self.disjoint_anchors_arc()
389 .iter()
390 .chain(self.pending_anchor())
391 .map(|s| {
392 if s.reversed {
393 s.end.to_display_point(display_snapshot)
394 ..s.start.to_display_point(display_snapshot)
395 } else {
396 s.start.to_display_point(display_snapshot)
397 ..s.end.to_display_point(display_snapshot)
398 }
399 })
400 .collect()
401 }
402
403 /// Attempts to build a selection in the provided `DisplayRow` within the
404 /// same range as the provided range of `Pixels`.
405 /// Returns `None` if the range is not empty but it starts past the line's
406 /// length, meaning that the line isn't long enough to be contained within
407 /// part of the provided range.
408 pub fn build_columnar_selection(
409 &mut self,
410 display_map: &DisplaySnapshot,
411 row: DisplayRow,
412 positions: &Range<Pixels>,
413 reversed: bool,
414 text_layout_details: &TextLayoutDetails,
415 ) -> Option<Selection<Point>> {
416 let is_empty = positions.start == positions.end;
417 let line_len = display_map.line_len(row);
418 let line = display_map.layout_row(row, text_layout_details);
419 let start_col = line.closest_index_for_x(positions.start) as u32;
420
421 let (start, end) = if is_empty {
422 let point = DisplayPoint::new(row, std::cmp::min(start_col, line_len));
423 (point, point)
424 } else {
425 if start_col >= line_len {
426 return None;
427 }
428 let start = DisplayPoint::new(row, start_col);
429 let end_col = line.closest_index_for_x(positions.end) as u32;
430 let end = DisplayPoint::new(row, end_col);
431 (start, end)
432 };
433
434 Some(Selection {
435 id: post_inc(&mut self.next_selection_id),
436 start: start.to_point(display_map),
437 end: end.to_point(display_map),
438 reversed,
439 goal: SelectionGoal::HorizontalRange {
440 start: positions.start.into(),
441 end: positions.end.into(),
442 },
443 })
444 }
445
446 /// Attempts to build a selection in the provided buffer row using the
447 /// same UTF-16 column range as specified.
448 /// Returns `None` if the range is not empty but it starts past the line's
449 /// length, meaning that the line isn't long enough to be contained within
450 /// part of the provided range.
451 fn build_columnar_selection_from_tab_expanded_columns(
452 &mut self,
453 display_map: &DisplaySnapshot,
454 buffer_row: u32,
455 positions: &Range<u32>,
456 reversed: bool,
457 text_layout_details: &TextLayoutDetails,
458 ) -> Option<Selection<Point>> {
459 let is_empty = positions.start == positions.end;
460 let line_len = display_map.tab_expanded_line_len(buffer_row);
461
462 let (start, end) = if is_empty {
463 let point = display_map.point_for_tab_expanded_column(buffer_row, positions.start);
464 (point, point)
465 } else {
466 if positions.start >= line_len {
467 return None;
468 }
469
470 let start = display_map.point_for_tab_expanded_column(buffer_row, positions.start);
471 let end = display_map.point_for_tab_expanded_column(buffer_row, positions.end);
472 (start, end)
473 };
474
475 let start_display_point = start.to_display_point(display_map);
476 let end_display_point = end.to_display_point(display_map);
477 let start_x = display_map.x_for_display_point(start_display_point, text_layout_details);
478 let end_x = display_map.x_for_display_point(end_display_point, text_layout_details);
479
480 Some(Selection {
481 id: post_inc(&mut self.next_selection_id),
482 start,
483 end,
484 reversed,
485 goal: SelectionGoal::HorizontalRange {
486 start: start_x.min(end_x).into(),
487 end: start_x.max(end_x).into(),
488 },
489 })
490 }
491
492 /// Finds the next columnar selection by walking display rows one at a time
493 /// so that soft-wrapped lines are considered and not skipped.
494 pub fn find_next_columnar_selection_by_display_row(
495 &mut self,
496 display_map: &DisplaySnapshot,
497 start_row: DisplayRow,
498 end_row: DisplayRow,
499 above: bool,
500 positions: &Range<Pixels>,
501 reversed: bool,
502 text_layout_details: &TextLayoutDetails,
503 ) -> Option<Selection<Point>> {
504 let mut row = start_row;
505 while row != end_row {
506 if above {
507 row.0 -= 1;
508 } else {
509 row.0 += 1;
510 }
511
512 if let Some(selection) = self.build_columnar_selection(
513 display_map,
514 row,
515 positions,
516 reversed,
517 text_layout_details,
518 ) {
519 return Some(selection);
520 }
521 }
522 None
523 }
524
525 /// Finds the next columnar selection by skipping to the next buffer row,
526 /// ignoring soft-wrapped lines.
527 pub fn find_next_columnar_selection_by_buffer_row(
528 &mut self,
529 display_map: &DisplaySnapshot,
530 start_row: DisplayRow,
531 end_row: DisplayRow,
532 above: bool,
533 goal_columns: &Range<u32>,
534 reversed: bool,
535 text_layout_details: &TextLayoutDetails,
536 ) -> Option<Selection<Point>> {
537 let mut row = start_row;
538 let direction = if above { -1 } else { 1 };
539 while row != end_row {
540 let new_row =
541 display_map.start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction);
542 row = new_row.row();
543 let buffer_row = new_row.to_point(display_map).row;
544
545 if let Some(selection) = self.build_columnar_selection_from_tab_expanded_columns(
546 display_map,
547 buffer_row,
548 goal_columns,
549 reversed,
550 text_layout_details,
551 ) {
552 return Some(selection);
553 }
554 }
555 None
556 }
557
558 pub fn change_with<R>(
559 &mut self,
560 snapshot: &DisplaySnapshot,
561 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
562 ) -> (bool, R) {
563 let mut mutable_collection = MutableSelectionsCollection {
564 snapshot,
565 collection: self,
566 selections_changed: false,
567 };
568
569 let result = change(&mut mutable_collection);
570 assert!(
571 !mutable_collection.disjoint.is_empty() || mutable_collection.pending.is_some(),
572 "There must be at least one selection"
573 );
574 if cfg!(debug_assertions) {
575 mutable_collection.disjoint.iter().for_each(|selection| {
576 assert!(
577 selection.start.cmp(&selection.end, &snapshot).is_le(),
578 "disjoint selection has start > end: {:?}",
579 mutable_collection.disjoint
580 );
581 assert!(
582 snapshot.can_resolve(&selection.start),
583 "disjoint selection start is not resolvable for the given snapshot:\n{selection:?}",
584 );
585 assert!(
586 snapshot.can_resolve(&selection.end),
587 "disjoint selection start is not resolvable for the given snapshot:\n{selection:?}",
588 );
589 });
590 assert!(
591 mutable_collection
592 .disjoint
593 .is_sorted_by(|first, second| first.end.cmp(&second.start, &snapshot).is_le()),
594 "disjoint selections are not sorted: {:?}",
595 mutable_collection.disjoint
596 );
597 if let Some(pending) = &mutable_collection.pending {
598 let selection = &pending.selection;
599 assert!(
600 selection.start.cmp(&selection.end, &snapshot).is_le(),
601 "pending selection has start > end: {:?}",
602 selection
603 );
604 assert!(
605 snapshot.can_resolve(&selection.start),
606 "pending selection start is not resolvable for the given snapshot: {pending:?}",
607 );
608 assert!(
609 snapshot.can_resolve(&selection.end),
610 "pending selection end is not resolvable for the given snapshot: {pending:?}",
611 );
612 }
613 }
614 (mutable_collection.selections_changed, result)
615 }
616
617 pub fn next_selection_id(&self) -> usize {
618 self.next_selection_id
619 }
620
621 pub fn line_mode(&self) -> bool {
622 self.line_mode
623 }
624
625 pub fn set_line_mode(&mut self, line_mode: bool) {
626 self.line_mode = line_mode;
627 }
628
629 pub fn select_mode(&self) -> &SelectMode {
630 &self.select_mode
631 }
632
633 pub fn set_select_mode(&mut self, select_mode: SelectMode) {
634 self.select_mode = select_mode;
635 }
636
637 pub fn is_extending(&self) -> bool {
638 self.is_extending
639 }
640
641 pub fn set_is_extending(&mut self, is_extending: bool) {
642 self.is_extending = is_extending;
643 }
644}
645
646pub struct MutableSelectionsCollection<'snap, 'a> {
647 collection: &'a mut SelectionsCollection,
648 snapshot: &'snap DisplaySnapshot,
649 selections_changed: bool,
650}
651
652impl<'snap, 'a> fmt::Debug for MutableSelectionsCollection<'snap, 'a> {
653 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
654 f.debug_struct("MutableSelectionsCollection")
655 .field("collection", &self.collection)
656 .field("selections_changed", &self.selections_changed)
657 .finish()
658 }
659}
660
661impl<'snap, 'a> MutableSelectionsCollection<'snap, 'a> {
662 pub fn display_snapshot(&self) -> DisplaySnapshot {
663 self.snapshot.clone()
664 }
665
666 pub fn clear_disjoint(&mut self) {
667 self.collection.disjoint = Arc::default();
668 }
669
670 pub fn delete(&mut self, selection_id: usize) {
671 let mut changed = false;
672 self.collection.disjoint = self
673 .disjoint
674 .iter()
675 .filter(|selection| {
676 let found = selection.id == selection_id;
677 changed |= found;
678 !found
679 })
680 .cloned()
681 .collect();
682
683 self.selections_changed |= changed;
684 }
685
686 pub fn remove_selections_from_buffer(&mut self, buffer_id: language::BufferId) {
687 let mut changed = false;
688
689 let filtered_selections: Arc<[Selection<Anchor>]> = {
690 self.disjoint
691 .iter()
692 .filter(|selection| {
693 if let Some((selection_buffer_anchor, _)) =
694 self.snapshot.anchor_to_buffer_anchor(selection.start)
695 {
696 let should_remove = selection_buffer_anchor.buffer_id == buffer_id;
697 changed |= should_remove;
698 !should_remove
699 } else {
700 true
701 }
702 })
703 .cloned()
704 .collect()
705 };
706
707 if filtered_selections.is_empty() {
708 let buffer_snapshot = self.snapshot.buffer_snapshot();
709 let anchor = buffer_snapshot
710 .excerpts()
711 .find(|excerpt| excerpt.context.start.buffer_id == buffer_id)
712 .and_then(|excerpt| buffer_snapshot.anchor_in_excerpt(excerpt.context.start))
713 .unwrap_or_else(|| self.snapshot.anchor_before(MultiBufferOffset(0)));
714 self.collection.disjoint = Arc::from([Selection {
715 id: post_inc(&mut self.collection.next_selection_id),
716 start: anchor,
717 end: anchor,
718 reversed: false,
719 goal: SelectionGoal::None,
720 }]);
721 } else {
722 self.collection.disjoint = filtered_selections;
723 }
724
725 self.selections_changed |= changed;
726 }
727
728 pub fn clear_pending(&mut self) {
729 if self.collection.pending.is_some() {
730 self.collection.pending = None;
731 self.selections_changed = true;
732 }
733 }
734
735 pub(crate) fn set_pending_anchor_range(&mut self, range: Range<Anchor>, mode: SelectMode) {
736 self.collection.pending = Some(PendingSelection {
737 selection: {
738 let mut start = range.start;
739 let mut end = range.end;
740 let reversed = if start.cmp(&end, self.snapshot).is_gt() {
741 mem::swap(&mut start, &mut end);
742 true
743 } else {
744 false
745 };
746 Selection {
747 id: post_inc(&mut self.collection.next_selection_id),
748 start,
749 end,
750 reversed,
751 goal: SelectionGoal::None,
752 }
753 },
754 mode,
755 });
756 self.selections_changed = true;
757 }
758
759 pub(crate) fn set_pending(&mut self, selection: Selection<Anchor>, mode: SelectMode) {
760 self.collection.pending = Some(PendingSelection { selection, mode });
761 self.selections_changed = true;
762 }
763
764 pub fn try_cancel(&mut self) -> bool {
765 if let Some(pending) = self.collection.pending.take() {
766 if self.disjoint.is_empty() {
767 self.collection.disjoint = Arc::from([pending.selection]);
768 }
769 self.selections_changed = true;
770 return true;
771 }
772
773 let mut oldest = self.oldest_anchor().clone();
774 if self.count() > 1 {
775 self.collection.disjoint = Arc::from([oldest]);
776 self.selections_changed = true;
777 return true;
778 }
779
780 if !oldest.start.cmp(&oldest.end, self.snapshot).is_eq() {
781 let head = oldest.head();
782 oldest.start = head;
783 oldest.end = head;
784 self.collection.disjoint = Arc::from([oldest]);
785 self.selections_changed = true;
786 return true;
787 }
788
789 false
790 }
791
792 pub fn insert_range<T>(&mut self, range: Range<T>)
793 where
794 T: ToOffset,
795 {
796 let display_map = self.display_snapshot();
797 let mut selections = self.collection.all(&display_map);
798 let mut start = range.start.to_offset(self.snapshot);
799 let mut end = range.end.to_offset(self.snapshot);
800 let reversed = if start > end {
801 mem::swap(&mut start, &mut end);
802 true
803 } else {
804 false
805 };
806 selections.push(Selection {
807 id: post_inc(&mut self.collection.next_selection_id),
808 start,
809 end,
810 reversed,
811 goal: SelectionGoal::None,
812 });
813 self.select(selections);
814 }
815
816 pub fn select<T>(&mut self, selections: Vec<Selection<T>>)
817 where
818 T: ToOffset + std::marker::Copy + std::fmt::Debug,
819 {
820 let mut selections = selections
821 .into_iter()
822 .map(|selection| selection.map(|it| it.to_offset(self.snapshot)))
823 .map(|mut selection| {
824 if selection.start > selection.end {
825 mem::swap(&mut selection.start, &mut selection.end);
826 selection.reversed = true
827 }
828 selection
829 })
830 .collect::<Vec<_>>();
831 selections.sort_unstable_by_key(|s| s.start);
832
833 let mut i = 1;
834 while i < selections.len() {
835 let prev = &selections[i - 1];
836 let current = &selections[i];
837
838 if should_merge(prev.start, prev.end, current.start, current.end, true) {
839 let removed = selections.remove(i);
840 if removed.start < selections[i - 1].start {
841 selections[i - 1].start = removed.start;
842 }
843 if selections[i - 1].end < removed.end {
844 selections[i - 1].end = removed.end;
845 }
846 } else {
847 i += 1;
848 }
849 }
850
851 self.collection.disjoint = Arc::from_iter(
852 selections
853 .into_iter()
854 .map(|selection| selection_to_anchor_selection(selection, self.snapshot)),
855 );
856 self.collection.pending = None;
857 self.collection.select_mode = SelectMode::Character;
858 self.selections_changed = true;
859 }
860
861 pub fn select_anchors(&mut self, selections: Vec<Selection<Anchor>>) {
862 let map = self.display_snapshot();
863 let resolved_selections =
864 resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(&selections, &map)
865 .collect::<Vec<_>>();
866 self.select(resolved_selections);
867 }
868
869 pub fn select_ranges<I, T>(&mut self, ranges: I)
870 where
871 I: IntoIterator<Item = Range<T>>,
872 T: ToOffset,
873 {
874 let ranges = ranges
875 .into_iter()
876 .map(|range| range.start.to_offset(self.snapshot)..range.end.to_offset(self.snapshot));
877 self.select_offset_ranges(ranges);
878 }
879
880 fn select_offset_ranges<I>(&mut self, ranges: I)
881 where
882 I: IntoIterator<Item = Range<MultiBufferOffset>>,
883 {
884 let selections = ranges
885 .into_iter()
886 .map(|range| {
887 let mut start = range.start;
888 let mut end = range.end;
889 let reversed = if start > end {
890 mem::swap(&mut start, &mut end);
891 true
892 } else {
893 false
894 };
895 Selection {
896 id: post_inc(&mut self.collection.next_selection_id),
897 start,
898 end,
899 reversed,
900 goal: SelectionGoal::None,
901 }
902 })
903 .collect::<Vec<_>>();
904
905 self.select(selections)
906 }
907
908 pub fn select_anchor_ranges<I>(&mut self, ranges: I)
909 where
910 I: IntoIterator<Item = Range<Anchor>>,
911 {
912 let selections = ranges
913 .into_iter()
914 .map(|range| {
915 let mut start = range.start;
916 let mut end = range.end;
917 let reversed = if start.cmp(&end, self.snapshot).is_gt() {
918 mem::swap(&mut start, &mut end);
919 true
920 } else {
921 false
922 };
923 Selection {
924 id: post_inc(&mut self.collection.next_selection_id),
925 start,
926 end,
927 reversed,
928 goal: SelectionGoal::None,
929 }
930 })
931 .collect::<Vec<_>>();
932 self.select_anchors(selections)
933 }
934
935 pub fn new_selection_id(&mut self) -> usize {
936 post_inc(&mut self.next_selection_id)
937 }
938
939 pub fn select_display_ranges<T>(&mut self, ranges: T)
940 where
941 T: IntoIterator<Item = Range<DisplayPoint>>,
942 {
943 let selections = ranges
944 .into_iter()
945 .map(|range| {
946 let mut start = range.start;
947 let mut end = range.end;
948 let reversed = if start > end {
949 mem::swap(&mut start, &mut end);
950 true
951 } else {
952 false
953 };
954 Selection {
955 id: post_inc(&mut self.collection.next_selection_id),
956 start: start.to_point(self.snapshot),
957 end: end.to_point(self.snapshot),
958 reversed,
959 goal: SelectionGoal::None,
960 }
961 })
962 .collect();
963 self.select(selections);
964 }
965
966 pub fn reverse_selections(&mut self) {
967 let mut new_selections: Vec<Selection<Point>> = Vec::new();
968 let disjoint = self.disjoint.clone();
969 for selection in disjoint
970 .iter()
971 .sorted_by(|first, second| Ord::cmp(&second.id, &first.id))
972 {
973 new_selections.push(Selection {
974 id: self.new_selection_id(),
975 start: selection
976 .start
977 .to_display_point(self.snapshot)
978 .to_point(self.snapshot),
979 end: selection
980 .end
981 .to_display_point(self.snapshot)
982 .to_point(self.snapshot),
983 reversed: selection.reversed,
984 goal: selection.goal,
985 });
986 }
987 self.select(new_selections);
988 }
989
990 pub fn move_with(
991 &mut self,
992 move_selection: &mut dyn FnMut(&DisplaySnapshot, &mut Selection<DisplayPoint>),
993 ) {
994 let mut changed = false;
995 let display_map = self.display_snapshot();
996 let selections = self.collection.all_display(&display_map);
997 let selections = selections
998 .into_iter()
999 .map(|selection| {
1000 let mut moved_selection = selection.clone();
1001 move_selection(&display_map, &mut moved_selection);
1002 if selection != moved_selection {
1003 changed = true;
1004 }
1005 moved_selection.map(|display_point| display_point.to_point(&display_map))
1006 })
1007 .collect();
1008
1009 if changed {
1010 self.select(selections)
1011 }
1012 }
1013
1014 pub fn move_offsets_with(
1015 &mut self,
1016 move_selection: &mut dyn FnMut(&MultiBufferSnapshot, &mut Selection<MultiBufferOffset>),
1017 ) {
1018 let mut changed = false;
1019 let display_map = self.display_snapshot();
1020 let selections = self
1021 .collection
1022 .all::<MultiBufferOffset>(&display_map)
1023 .into_iter()
1024 .map(|selection| {
1025 let mut moved_selection = selection.clone();
1026 move_selection(self.snapshot, &mut moved_selection);
1027 if selection != moved_selection {
1028 changed = true;
1029 }
1030 moved_selection
1031 })
1032 .collect();
1033
1034 if changed {
1035 self.select(selections)
1036 }
1037 }
1038
1039 pub fn move_heads_with(
1040 &mut self,
1041 update_head: &mut dyn FnMut(
1042 &DisplaySnapshot,
1043 DisplayPoint,
1044 SelectionGoal,
1045 ) -> (DisplayPoint, SelectionGoal),
1046 ) {
1047 self.move_with(&mut |map, selection| {
1048 let (new_head, new_goal) = update_head(map, selection.head(), selection.goal);
1049 selection.set_head(new_head, new_goal);
1050 });
1051 }
1052
1053 pub fn move_cursors_with(
1054 &mut self,
1055 update_cursor_position: &mut dyn FnMut(
1056 &DisplaySnapshot,
1057 DisplayPoint,
1058 SelectionGoal,
1059 ) -> (DisplayPoint, SelectionGoal),
1060 ) {
1061 self.move_with(&mut |map, selection| {
1062 let (cursor, new_goal) = update_cursor_position(map, selection.head(), selection.goal);
1063 selection.collapse_to(cursor, new_goal)
1064 });
1065 }
1066
1067 pub fn maybe_move_cursors_with(
1068 &mut self,
1069 update_cursor_position: &mut dyn FnMut(
1070 &DisplaySnapshot,
1071 DisplayPoint,
1072 SelectionGoal,
1073 ) -> Option<(DisplayPoint, SelectionGoal)>,
1074 ) {
1075 self.move_cursors_with(&mut |map, point, goal| {
1076 update_cursor_position(map, point, goal).unwrap_or((point, goal))
1077 })
1078 }
1079
1080 pub fn replace_cursors_with(
1081 &mut self,
1082 find_replacement_cursors: impl FnOnce(&DisplaySnapshot) -> Vec<DisplayPoint>,
1083 ) {
1084 let new_selections = find_replacement_cursors(self.snapshot)
1085 .into_iter()
1086 .map(|cursor| {
1087 let cursor_point = cursor.to_point(self.snapshot);
1088 Selection {
1089 id: post_inc(&mut self.collection.next_selection_id),
1090 start: cursor_point,
1091 end: cursor_point,
1092 reversed: false,
1093 goal: SelectionGoal::None,
1094 }
1095 })
1096 .collect();
1097 self.select(new_selections);
1098 }
1099
1100 pub fn pending_anchor_mut(&mut self) -> Option<&mut Selection<Anchor>> {
1101 self.selections_changed = true;
1102 self.pending.as_mut().map(|pending| &mut pending.selection)
1103 }
1104}
1105
1106impl Deref for MutableSelectionsCollection<'_, '_> {
1107 type Target = SelectionsCollection;
1108 fn deref(&self) -> &Self::Target {
1109 self.collection
1110 }
1111}
1112
1113impl DerefMut for MutableSelectionsCollection<'_, '_> {
1114 fn deref_mut(&mut self) -> &mut Self::Target {
1115 self.collection
1116 }
1117}
1118
1119fn selection_to_anchor_selection(
1120 selection: Selection<MultiBufferOffset>,
1121 buffer: &MultiBufferSnapshot,
1122) -> Selection<Anchor> {
1123 let end_bias = if selection.start == selection.end {
1124 Bias::Right
1125 } else {
1126 Bias::Left
1127 };
1128 Selection {
1129 id: selection.id,
1130 start: buffer.anchor_after(selection.start),
1131 end: buffer.anchor_at(selection.end, end_bias),
1132 reversed: selection.reversed,
1133 goal: selection.goal,
1134 }
1135}
1136
1137fn resolve_selections_point<'a>(
1138 selections: impl 'a + IntoIterator<Item = &'a Selection<Anchor>>,
1139 map: &'a DisplaySnapshot,
1140) -> impl 'a + Iterator<Item = Selection<Point>> {
1141 let (to_summarize, selections) = selections.into_iter().tee();
1142 let mut summaries = map
1143 .buffer_snapshot()
1144 .summaries_for_anchors::<Point, _>(to_summarize.flat_map(|s| [&s.start, &s.end]))
1145 .into_iter();
1146 selections.map(move |s| {
1147 let start = summaries.next().unwrap();
1148 let end = summaries.next().unwrap();
1149 assert!(
1150 start <= end,
1151 "anchors: start: {:?}, end: {:?}; resolved to: start: {:?}, end: {:?}",
1152 s.start,
1153 s.end,
1154 start,
1155 end
1156 );
1157 Selection {
1158 id: s.id,
1159 start,
1160 end,
1161 reversed: s.reversed,
1162 goal: s.goal,
1163 }
1164 })
1165}
1166
1167/// Panics if passed selections are not in order
1168/// Resolves the anchors to display positions
1169fn resolve_selections_display<'a>(
1170 selections: impl 'a + IntoIterator<Item = &'a Selection<Anchor>>,
1171 map: &'a DisplaySnapshot,
1172) -> impl 'a + Iterator<Item = Selection<DisplayPoint>> {
1173 let selections = resolve_selections_point(selections, map).map(move |s| {
1174 let display_start = map.point_to_display_point(s.start, Bias::Left);
1175 let display_end = map.point_to_display_point(
1176 s.end,
1177 if s.start == s.end {
1178 Bias::Right
1179 } else {
1180 Bias::Left
1181 },
1182 );
1183 assert!(
1184 display_start <= display_end,
1185 "display_start: {:?}, display_end: {:?}",
1186 display_start,
1187 display_end
1188 );
1189 Selection {
1190 id: s.id,
1191 start: display_start,
1192 end: display_end,
1193 reversed: s.reversed,
1194 goal: s.goal,
1195 }
1196 });
1197 coalesce_selections(selections)
1198}
1199
1200/// Resolves the passed in anchors to [`MultiBufferDimension`]s `D`
1201/// wrapping around blocks inbetween.
1202///
1203/// # Panics
1204///
1205/// Panics if passed selections are not in order
1206pub(crate) fn resolve_selections_wrapping_blocks<'a, D, I>(
1207 selections: I,
1208 map: &'a DisplaySnapshot,
1209) -> impl 'a + Iterator<Item = Selection<D>>
1210where
1211 D: MultiBufferDimension + Sub + AddAssign<<D as Sub>::Output> + Ord,
1212 I: 'a + IntoIterator<Item = &'a Selection<Anchor>>,
1213{
1214 // Without collapsed content, coalescing in buffer point space is equivalent to coalescing in
1215 // display point space, so skip the per-selection display-coordinate round trip.
1216 if !map.has_collapsed_content() {
1217 Either::Left(resolve_selections_without_display_round_trip(
1218 selections, map,
1219 ))
1220 } else {
1221 Either::Right(resolve_selections_via_display_round_trip(selections, map))
1222 }
1223}
1224
1225fn resolve_selections_without_display_round_trip<'a, D, I>(
1226 selections: I,
1227 map: &'a DisplaySnapshot,
1228) -> impl 'a + Iterator<Item = Selection<D>>
1229where
1230 D: MultiBufferDimension + Sub + AddAssign<<D as Sub>::Output> + Ord,
1231 I: 'a + IntoIterator<Item = &'a Selection<Anchor>>,
1232{
1233 let (to_convert, selections) =
1234 coalesce_selections(resolve_selections_point(selections, map)).tee();
1235 let mut converted_endpoints = map
1236 .buffer_snapshot()
1237 .dimensions_from_points::<D>(to_convert.flat_map(|s| [s.start, s.end]));
1238 selections.map(move |s| {
1239 let start = converted_endpoints.next().unwrap();
1240 let end = converted_endpoints.next().unwrap();
1241 Selection {
1242 id: s.id,
1243 start,
1244 end,
1245 reversed: s.reversed,
1246 goal: s.goal,
1247 }
1248 })
1249}
1250
1251fn resolve_selections_via_display_round_trip<'a, D, I>(
1252 selections: I,
1253 map: &'a DisplaySnapshot,
1254) -> impl 'a + Iterator<Item = Selection<D>>
1255where
1256 D: MultiBufferDimension + Sub + AddAssign<<D as Sub>::Output> + Ord,
1257 I: 'a + IntoIterator<Item = &'a Selection<Anchor>>,
1258{
1259 // Transforms `Anchor -> DisplayPoint -> Point -> DisplayPoint -> D`
1260 // todo(lw): We should be able to short circuit the `Anchor -> DisplayPoint -> Point` to `Anchor -> Point`
1261 let (to_convert, selections) = resolve_selections_display(selections, map).tee();
1262 let mut converted_endpoints =
1263 map.buffer_snapshot()
1264 .dimensions_from_points::<D>(to_convert.flat_map(|s| {
1265 let start = map.display_point_to_point(s.start, Bias::Left);
1266 let end = map.display_point_to_point(s.end, Bias::Right);
1267 assert!(start <= end, "start: {:?}, end: {:?}", start, end);
1268 [start, end]
1269 }));
1270 selections.map(move |s| {
1271 let start = converted_endpoints.next().unwrap();
1272 let end = converted_endpoints.next().unwrap();
1273 assert!(start <= end, "start: {:?}, end: {:?}", start, end);
1274 Selection {
1275 id: s.id,
1276 start,
1277 end,
1278 reversed: s.reversed,
1279 goal: s.goal,
1280 }
1281 })
1282}
1283
1284fn coalesce_selections<D: Ord + fmt::Debug + Copy>(
1285 selections: impl Iterator<Item = Selection<D>>,
1286) -> impl Iterator<Item = Selection<D>> {
1287 let mut selections = selections.peekable();
1288 iter::from_fn(move || {
1289 let mut selection = selections.next()?;
1290 while let Some(next_selection) = selections.peek() {
1291 if should_merge(
1292 selection.start,
1293 selection.end,
1294 next_selection.start,
1295 next_selection.end,
1296 true,
1297 ) {
1298 if selection.reversed == next_selection.reversed {
1299 selection.end = cmp::max(selection.end, next_selection.end);
1300 selections.next();
1301 } else {
1302 selection.end = cmp::max(selection.start, next_selection.start);
1303 break;
1304 }
1305 } else {
1306 break;
1307 }
1308 }
1309 assert!(
1310 selection.start <= selection.end,
1311 "selection.start: {:?}, selection.end: {:?}, selection.reversed: {:?}",
1312 selection.start,
1313 selection.end,
1314 selection.reversed
1315 );
1316 Some(selection)
1317 })
1318}
1319
1320/// Determines whether two selections should be merged into one.
1321///
1322/// Two selections should be merged when:
1323/// 1. They overlap: the selections share at least one position
1324/// 2. They have the same start position: one contains or equals the other
1325/// 3. A cursor touches a selection boundary: a zero-width selection (cursor) at the
1326/// start or end of another selection should be absorbed into it
1327///
1328/// Note: two selections that merely touch (one ends exactly where the other begins)
1329/// but don't share any positions remain separate, see: https://github.com/zed-industries/zed/issues/24748
1330fn should_merge<T: Ord + Copy>(a_start: T, a_end: T, b_start: T, b_end: T, sorted: bool) -> bool {
1331 let is_overlapping = if sorted {
1332 // When sorted, `a` starts before or at `b`, so overlap means `b` starts before `a` ends
1333 b_start < a_end
1334 } else {
1335 a_start < b_end && b_start < a_end
1336 };
1337
1338 // Selections starting at the same position should always merge (one contains the other)
1339 let same_start = a_start == b_start;
1340
1341 // A cursor (zero-width selection) touching another selection's boundary should merge.
1342 // This handles cases like a cursor at position X merging with a selection that
1343 // starts or ends at X.
1344 let is_cursor_a = a_start == a_end;
1345 let is_cursor_b = b_start == b_end;
1346 let cursor_at_boundary = (is_cursor_a && (a_start == b_start || a_end == b_end))
1347 || (is_cursor_b && (b_start == a_start || b_end == a_end));
1348
1349 is_overlapping || same_start || cursor_at_boundary
1350}
1351
1352#[cfg(test)]
1353mod tests {
1354 use super::*;
1355 use crate::{
1356 MultiBuffer,
1357 display_map::{BlockPlacement, BlockProperties, BlockStyle, DisplayMap, FoldPlaceholder},
1358 test::test_font,
1359 };
1360 use gpui::{AppContext as _, IntoElement as _, div, px};
1361 use project::project_settings::DiagnosticSeverity;
1362 use rand::{Rng as _, rngs::StdRng};
1363 use settings::SettingsStore;
1364 use std::sync::Arc;
1365
1366 fn row_range_snapshot(cx: &mut gpui::TestAppContext, text: &str) -> DisplaySnapshot {
1367 cx.update(|cx| {
1368 let settings = SettingsStore::test(cx);
1369 cx.set_global(settings);
1370 crate::init(cx);
1371 });
1372 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
1373 let display_map = cx.new(|cx| {
1374 DisplayMap::new(
1375 buffer,
1376 test_font(),
1377 px(14.),
1378 None,
1379 1,
1380 1,
1381 FoldPlaceholder::test(),
1382 DiagnosticSeverity::Warning,
1383 cx,
1384 )
1385 });
1386 display_map.update(cx, |map, cx| map.snapshot(cx))
1387 }
1388
1389 fn row_range_collection(
1390 offset_ranges: impl IntoIterator<Item = Range<usize>>,
1391 buffer_snapshot: &MultiBufferSnapshot,
1392 ) -> SelectionsCollection {
1393 let selections = offset_ranges
1394 .into_iter()
1395 .enumerate()
1396 .map(|(id, range)| {
1397 selection_to_anchor_selection(
1398 Selection {
1399 id,
1400 start: MultiBufferOffset(range.start),
1401 end: MultiBufferOffset(range.end),
1402 reversed: false,
1403 goal: SelectionGoal::None,
1404 },
1405 buffer_snapshot,
1406 )
1407 })
1408 .collect::<Vec<_>>();
1409 let mut collection = SelectionsCollection::new();
1410 collection.disjoint = Arc::from(selections);
1411 collection.pending = None;
1412 collection
1413 }
1414
1415 /// `disjoint_in_row_range` selects by whole rows, so a selection sharing a queried row must be
1416 /// returned even when its columns don't overlap the queried range. `disjoint_in_range` compares
1417 /// exact offsets and would miss this case.
1418 #[gpui::test]
1419 fn disjoint_in_row_range_matches_whole_row(cx: &mut gpui::TestAppContext) {
1420 let snapshot = row_range_snapshot(cx, "aaaa\nbbbbbbbb\ncccc");
1421 let buffer_snapshot = snapshot.buffer_snapshot();
1422
1423 // A selection on row 1 spanning columns 3..5 (buffer offsets 8..10).
1424 let collection = row_range_collection([8..10], buffer_snapshot);
1425
1426 // Query row 1 at columns 0..1, entirely before the selection's columns.
1427 let range = buffer_snapshot.anchor_before(MultiBufferOffset(5))
1428 ..buffer_snapshot.anchor_before(MultiBufferOffset(6));
1429 let result = collection.disjoint_in_row_range::<Point>(range, &snapshot);
1430
1431 assert_eq!(
1432 result.len(),
1433 1,
1434 "row range should include the selection sharing the queried row"
1435 );
1436 assert_eq!(result[0].start, Point::new(1, 3));
1437 assert_eq!(result[0].end, Point::new(1, 5));
1438 }
1439
1440 /// A selection on a row outside the queried row range must not be returned. This guards the
1441 /// row-overlap boundary against becoming over-inclusive.
1442 #[gpui::test]
1443 fn disjoint_in_row_range_excludes_other_rows(cx: &mut gpui::TestAppContext) {
1444 let snapshot = row_range_snapshot(cx, "aaaa\nbbbbbbbb\ncccc");
1445 let buffer_snapshot = snapshot.buffer_snapshot();
1446
1447 // A selection on row 2 (buffer offsets 14..16).
1448 let collection = row_range_collection([14..16], buffer_snapshot);
1449
1450 // Query only row 0, two rows away from the selection.
1451 let range = buffer_snapshot.anchor_before(MultiBufferOffset(0))
1452 ..buffer_snapshot.anchor_before(MultiBufferOffset(1));
1453 let result = collection.disjoint_in_row_range::<Point>(range, &snapshot);
1454
1455 assert!(
1456 result.is_empty(),
1457 "row range should exclude a selection on a non-queried row"
1458 );
1459 }
1460
1461 /// A selection spanning multiple rows must be returned when the query touches any of those rows,
1462 /// not just when the query shares the selection's start or end row.
1463 #[gpui::test]
1464 fn disjoint_in_row_range_matches_interior_row(cx: &mut gpui::TestAppContext) {
1465 let snapshot = row_range_snapshot(cx, "aaaa\nbbbb\ncccc\ndddd");
1466 let buffer_snapshot = snapshot.buffer_snapshot();
1467
1468 // A selection spanning row 1 column 1 through row 3 column 2 (buffer offsets 6..17).
1469 let collection = row_range_collection([6..17], buffer_snapshot);
1470
1471 // Query only row 2, an interior row of the selection.
1472 let range = buffer_snapshot.anchor_before(MultiBufferOffset(11))
1473 ..buffer_snapshot.anchor_before(MultiBufferOffset(12));
1474 let result = collection.disjoint_in_row_range::<Point>(range, &snapshot);
1475
1476 assert_eq!(
1477 result.len(),
1478 1,
1479 "row range should include a selection whose interior row is queried"
1480 );
1481 assert_eq!(result[0].start, Point::new(1, 1));
1482 assert_eq!(result[0].end, Point::new(3, 2));
1483 }
1484
1485 #[gpui::test(iterations = 20)]
1486 fn fast_and_slow_selection_resolution_match_without_collapsed_content(
1487 cx: &mut gpui::TestAppContext,
1488 mut rng: StdRng,
1489 ) {
1490 cx.update(|cx| {
1491 let settings = SettingsStore::test(cx);
1492 cx.set_global(settings);
1493 crate::init(cx);
1494 });
1495
1496 let text = random_text(&mut rng);
1497 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
1498 let display_map = cx.new(|cx| {
1499 DisplayMap::new(
1500 buffer.clone(),
1501 test_font(),
1502 px(14.),
1503 Some(px(rng.random_range(80.0..=180.0))),
1504 1,
1505 1,
1506 FoldPlaceholder::test(),
1507 DiagnosticSeverity::Warning,
1508 cx,
1509 )
1510 });
1511
1512 let snapshot = display_map.update(cx, |map, cx| {
1513 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1514 map.insert_blocks(
1515 [BlockProperties {
1516 placement: BlockPlacement::Above(
1517 buffer_snapshot.anchor_after(MultiBufferOffset(text.len() / 2)),
1518 ),
1519 height: Some(2),
1520 style: BlockStyle::Fixed,
1521 render: Arc::new(|_| div().into_any_element()),
1522 priority: 0,
1523 }],
1524 cx,
1525 );
1526 map.snapshot(cx)
1527 });
1528 assert!(!snapshot.has_collapsed_content());
1529
1530 let selections = random_anchor_selections(&snapshot, text.len(), &mut rng);
1531
1532 let fast_points =
1533 resolve_selections_without_display_round_trip::<Point, _>(selections.iter(), &snapshot)
1534 .collect::<Vec<_>>();
1535 let slow_points =
1536 resolve_selections_via_display_round_trip::<Point, _>(selections.iter(), &snapshot)
1537 .collect::<Vec<_>>();
1538 assert_eq!(fast_points, slow_points);
1539
1540 let fast_offsets = resolve_selections_without_display_round_trip::<MultiBufferOffset, _>(
1541 selections.iter(),
1542 &snapshot,
1543 )
1544 .collect::<Vec<_>>();
1545 let slow_offsets = resolve_selections_via_display_round_trip::<MultiBufferOffset, _>(
1546 selections.iter(),
1547 &snapshot,
1548 )
1549 .collect::<Vec<_>>();
1550 assert_eq!(fast_offsets, slow_offsets);
1551 }
1552
1553 fn random_text(rng: &mut StdRng) -> String {
1554 let mut text = String::new();
1555 for line in 0..rng.random_range(8..32) {
1556 if line > 0 {
1557 text.push('\n');
1558 }
1559 for _ in 0..rng.random_range(8..48) {
1560 let ch = match rng.random_range(0..12) {
1561 0 => '\t',
1562 1 => ' ',
1563 _ => rng.random_range(b'a'..=b'z') as char,
1564 };
1565 text.push(ch);
1566 }
1567 }
1568 text
1569 }
1570
1571 fn random_anchor_selections(
1572 snapshot: &DisplaySnapshot,
1573 text_len: usize,
1574 rng: &mut StdRng,
1575 ) -> Vec<Selection<Anchor>> {
1576 let buffer = snapshot.buffer_snapshot();
1577 let mut selections = Vec::new();
1578 let mut offset = 0;
1579 for id in 0..rng.random_range(1..32) {
1580 if offset > text_len {
1581 break;
1582 }
1583
1584 let start = rng.random_range(offset..=text_len);
1585 let end = rng.random_range(start..=text_len);
1586 selections.push(Selection {
1587 id,
1588 start: MultiBufferOffset(start),
1589 end: MultiBufferOffset(end),
1590 reversed: rng.random(),
1591 goal: SelectionGoal::None,
1592 });
1593 offset = end.saturating_add(rng.random_range(0..=4));
1594 }
1595
1596 selections
1597 .into_iter()
1598 .map(|selection| selection_to_anchor_selection(selection, buffer))
1599 .collect()
1600 }
1601}
1602