use std::{ cmp, fmt, iter, mem, ops::{AddAssign, Deref, DerefMut, Range, Sub}, sync::Arc, }; use gpui::Pixels; use itertools::{Either, Itertools as _}; use language::{Bias, Point, Selection, SelectionGoal}; use multi_buffer::{MultiBufferDimension, MultiBufferOffset, ToPoint}; use util::post_inc; use crate::{ Anchor, DisplayPoint, DisplayRow, MultiBufferSnapshot, SelectMode, ToOffset, display_map::{DisplaySnapshot, ToDisplayPoint}, movement::TextLayoutDetails, }; #[derive(Debug, Clone)] pub struct PendingSelection { selection: Selection, mode: SelectMode, } #[derive(Debug, Clone)] pub struct SelectionsCollection { next_selection_id: usize, line_mode: bool, /// The non-pending, non-overlapping selections. /// The [SelectionsCollection::pending] selection could possibly overlap these disjoint: Arc<[Selection]>, /// A pending selection, such as when the mouse is being dragged pending: Option, select_mode: SelectMode, is_extending: bool, } impl SelectionsCollection { pub fn new() -> Self { Self { next_selection_id: 1, line_mode: false, disjoint: Arc::default(), pending: Some(PendingSelection { selection: Selection { id: 0, start: Anchor::Min, end: Anchor::Min, reversed: false, goal: SelectionGoal::None, }, mode: SelectMode::Character, }), select_mode: SelectMode::Character, is_extending: false, } } pub fn clone_state(&mut self, other: &SelectionsCollection) { self.next_selection_id = other.next_selection_id; self.line_mode = other.line_mode; self.disjoint = other.disjoint.clone(); self.pending.clone_from(&other.pending); } pub fn count(&self) -> usize { let mut count = self.disjoint.len(); if self.pending.is_some() { count += 1; } count } /// The non-pending, non-overlapping selections. There could be a pending selection that /// overlaps these if the mouse is being dragged, etc. This could also be empty if there is a /// pending selection. Returned as selections over Anchors. pub fn disjoint_anchors_arc(&self) -> Arc<[Selection]> { self.disjoint.clone() } /// The non-pending, non-overlapping selections. There could be a pending selection that /// overlaps these if the mouse is being dragged, etc. This could also be empty if there is a /// pending selection. Returned as selections over Anchors. pub fn disjoint_anchors(&self) -> &[Selection] { &self.disjoint } pub fn disjoint_anchor_ranges(&self) -> impl Iterator> { // Mapping the Arc slice would borrow it, whereas indexing captures it. let disjoint = self.disjoint_anchors_arc(); (0..disjoint.len()).map(move |ix| disjoint[ix].range()) } /// Non-overlapping selections using anchors, including the pending selection. pub fn all_anchors(&self, snapshot: &DisplaySnapshot) -> Arc<[Selection]> { if self.pending.is_none() { self.disjoint_anchors_arc() } else { let all_offset_selections = self.all::(snapshot); all_offset_selections .into_iter() .map(|selection| selection_to_anchor_selection(selection, snapshot)) .collect() } } pub fn pending_anchor(&self) -> Option<&Selection> { self.pending.as_ref().map(|pending| &pending.selection) } pub fn pending(&self, snapshot: &DisplaySnapshot) -> Option> where D: MultiBufferDimension + Sub + AddAssign<::Output> + Ord, { resolve_selections_wrapping_blocks(self.pending_anchor(), &snapshot).next() } pub(crate) fn pending_mode(&self) -> Option { self.pending.as_ref().map(|pending| pending.mode.clone()) } pub fn all(&self, snapshot: &DisplaySnapshot) -> Vec> where D: MultiBufferDimension + Sub + AddAssign<::Output> + Ord, { self.all_iter(snapshot).collect() } fn all_iter<'a, D>( &'a self, snapshot: &'a DisplaySnapshot, ) -> impl 'a + Iterator> where D: 'a + MultiBufferDimension + Sub + AddAssign<::Output> + Ord, { let mut disjoint = resolve_selections_wrapping_blocks::(self.disjoint.iter(), snapshot).peekable(); let mut pending_opt = self.pending::(snapshot); iter::from_fn(move || { if let Some(pending) = pending_opt.as_mut() { while let Some(next_selection) = disjoint.peek() { if should_merge( pending.start, pending.end, next_selection.start, next_selection.end, false, ) { let next_selection = disjoint.next().unwrap(); if next_selection.start < pending.start { pending.start = next_selection.start; } if next_selection.end > pending.end { pending.end = next_selection.end; } } else if next_selection.end < pending.start { return disjoint.next(); } else { break; } } pending_opt.take() } else { disjoint.next() } }) } /// Returns all of the selections, adjusted to take into account the selection line_mode pub fn all_adjusted(&self, snapshot: &DisplaySnapshot) -> Vec> { let mut selections = self.all::(&snapshot); if self.line_mode { for selection in &mut selections { let new_range = snapshot.expand_to_line(selection.range()); selection.start = new_range.start; selection.end = new_range.end; } } selections } /// Returns the newest selection, adjusted to take into account the selection line_mode pub fn newest_adjusted(&self, snapshot: &DisplaySnapshot) -> Selection { let mut selection = self.newest::(&snapshot); if self.line_mode { let new_range = snapshot.expand_to_line(selection.range()); selection.start = new_range.start; selection.end = new_range.end; } selection } pub fn all_adjusted_display( &self, display_map: &DisplaySnapshot, ) -> Vec> { if self.line_mode { let selections = self.all::(&display_map); let result = selections .into_iter() .map(|mut selection| { let new_range = display_map.expand_to_line(selection.range()); selection.start = new_range.start; selection.end = new_range.end; selection.map(|point| point.to_display_point(&display_map)) }) .collect(); result } else { self.all_display(display_map) } } pub fn disjoint_in_row_range( &self, range: Range, snapshot: &DisplaySnapshot, ) -> Vec> where D: MultiBufferDimension + Sub + AddAssign<::Output> + Ord + std::fmt::Debug, { let buffer = snapshot.buffer_snapshot(); let start_row = range.start.to_point(buffer).row; let end_row = range.end.to_point(buffer).row; let start_ix = self .disjoint .partition_point(|probe| probe.end.to_point(buffer).row < start_row); let end_ix = self .disjoint .partition_point(|probe| probe.start.to_point(buffer).row <= end_row); resolve_selections_wrapping_blocks(&self.disjoint[start_ix..end_ix], snapshot).collect() } pub fn disjoint_in_range( &self, range: Range, snapshot: &DisplaySnapshot, ) -> Vec> where D: MultiBufferDimension + Sub + AddAssign<::Output> + Ord + std::fmt::Debug, { let buffer = snapshot.buffer_snapshot(); let start_ix = self .disjoint .partition_point(|probe| probe.end.cmp(&range.start, buffer).is_lt()); let end_ix = self .disjoint .partition_point(|probe| probe.start.cmp(&range.end, buffer).is_le()); resolve_selections_wrapping_blocks(&self.disjoint[start_ix..end_ix], snapshot).collect() } pub fn all_display(&self, snapshot: &DisplaySnapshot) -> Vec> { let disjoint_anchors = &self.disjoint; let mut disjoint = resolve_selections_display(disjoint_anchors.iter(), &snapshot).peekable(); let mut pending_opt = resolve_selections_display(self.pending_anchor(), &snapshot).next(); iter::from_fn(move || { if let Some(pending) = pending_opt.as_mut() { while let Some(next_selection) = disjoint.peek() { if should_merge( pending.start, pending.end, next_selection.start, next_selection.end, false, ) { let next_selection = disjoint.next().unwrap(); if next_selection.start < pending.start { pending.start = next_selection.start; } if next_selection.end > pending.end { pending.end = next_selection.end; } } else if next_selection.end < pending.start { return disjoint.next(); } else { break; } } pending_opt.take() } else { disjoint.next() } }) .collect() } pub fn newest_anchor(&self) -> &Selection { self.pending .as_ref() .map(|s| &s.selection) .or_else(|| self.disjoint.iter().max_by_key(|s| s.id)) .unwrap() } pub fn newest(&self, snapshot: &DisplaySnapshot) -> Selection where D: MultiBufferDimension + Sub + AddAssign<::Output> + Ord, { resolve_selections_wrapping_blocks([self.newest_anchor()], &snapshot) .next() .unwrap() } pub fn newest_display(&self, snapshot: &DisplaySnapshot) -> Selection { resolve_selections_display([self.newest_anchor()], &snapshot) .next() .unwrap() } pub fn oldest_anchor(&self) -> &Selection { self.disjoint .iter() .min_by_key(|s| s.id) .or_else(|| self.pending.as_ref().map(|p| &p.selection)) .unwrap() } pub fn oldest(&self, snapshot: &DisplaySnapshot) -> Selection where D: MultiBufferDimension + Sub + AddAssign<::Output> + Ord, { resolve_selections_wrapping_blocks([self.oldest_anchor()], &snapshot) .next() .unwrap() } pub fn first_anchor(&self) -> Selection { self.pending .as_ref() .map(|pending| pending.selection.clone()) .unwrap_or_else(|| self.disjoint.first().cloned().unwrap()) } pub fn first(&self, snapshot: &DisplaySnapshot) -> Selection where D: MultiBufferDimension + Sub + AddAssign<::Output> + Ord, { // Without a pending selection, the first disjoint selection is also the first resolved // selection, so avoid resolving every selection. if self.pending.is_none() && let Some(first) = self.disjoint.first() { return resolve_selections_wrapping_blocks([first], snapshot) .next() .unwrap(); } self.all_iter(snapshot).next().unwrap() } pub fn last(&self, snapshot: &DisplaySnapshot) -> Selection where D: MultiBufferDimension + Sub + AddAssign<::Output> + Ord, { if self.pending.is_none() && let Some(last) = self.disjoint.last() { return resolve_selections_wrapping_blocks([last], snapshot) .next() .unwrap(); } self.all_iter(snapshot).last().unwrap() } /// Returns a list of (potentially backwards!) ranges representing the selections. /// Useful for test assertions, but prefer `.all()` instead. #[cfg(any(test, feature = "test-support"))] pub fn ranges(&self, snapshot: &DisplaySnapshot) -> Vec> where D: MultiBufferDimension + Sub + AddAssign<::Output> + Ord, { self.all::(snapshot) .iter() .map(|s| { if s.reversed { s.end..s.start } else { s.start..s.end } }) .collect() } #[cfg(any(test, feature = "test-support"))] pub fn display_ranges(&self, display_snapshot: &DisplaySnapshot) -> Vec> { self.disjoint_anchors_arc() .iter() .chain(self.pending_anchor()) .map(|s| { if s.reversed { s.end.to_display_point(display_snapshot) ..s.start.to_display_point(display_snapshot) } else { s.start.to_display_point(display_snapshot) ..s.end.to_display_point(display_snapshot) } }) .collect() } /// Attempts to build a selection in the provided `DisplayRow` within the /// same range as the provided range of `Pixels`. /// Returns `None` if the range is not empty but it starts past the line's /// length, meaning that the line isn't long enough to be contained within /// part of the provided range. pub fn build_columnar_selection( &mut self, display_map: &DisplaySnapshot, row: DisplayRow, positions: &Range, reversed: bool, text_layout_details: &TextLayoutDetails, ) -> Option> { let is_empty = positions.start == positions.end; let line_len = display_map.line_len(row); let line = display_map.layout_row(row, text_layout_details); let start_col = line.closest_index_for_x(positions.start) as u32; let (start, end) = if is_empty { let point = DisplayPoint::new(row, std::cmp::min(start_col, line_len)); (point, point) } else { if start_col >= line_len { return None; } let start = DisplayPoint::new(row, start_col); let end_col = line.closest_index_for_x(positions.end) as u32; let end = DisplayPoint::new(row, end_col); (start, end) }; Some(Selection { id: post_inc(&mut self.next_selection_id), start: start.to_point(display_map), end: end.to_point(display_map), reversed, goal: SelectionGoal::HorizontalRange { start: positions.start.into(), end: positions.end.into(), }, }) } /// Attempts to build a selection in the provided buffer row using the /// same UTF-16 column range as specified. /// Returns `None` if the range is not empty but it starts past the line's /// length, meaning that the line isn't long enough to be contained within /// part of the provided range. fn build_columnar_selection_from_tab_expanded_columns( &mut self, display_map: &DisplaySnapshot, buffer_row: u32, positions: &Range, reversed: bool, text_layout_details: &TextLayoutDetails, ) -> Option> { let is_empty = positions.start == positions.end; let line_len = display_map.tab_expanded_line_len(buffer_row); let (start, end) = if is_empty { let point = display_map.point_for_tab_expanded_column(buffer_row, positions.start); (point, point) } else { if positions.start >= line_len { return None; } let start = display_map.point_for_tab_expanded_column(buffer_row, positions.start); let end = display_map.point_for_tab_expanded_column(buffer_row, positions.end); (start, end) }; let start_display_point = start.to_display_point(display_map); let end_display_point = end.to_display_point(display_map); let start_x = display_map.x_for_display_point(start_display_point, text_layout_details); let end_x = display_map.x_for_display_point(end_display_point, text_layout_details); Some(Selection { id: post_inc(&mut self.next_selection_id), start, end, reversed, goal: SelectionGoal::HorizontalRange { start: start_x.min(end_x).into(), end: start_x.max(end_x).into(), }, }) } /// Finds the next columnar selection by walking display rows one at a time /// so that soft-wrapped lines are considered and not skipped. pub fn find_next_columnar_selection_by_display_row( &mut self, display_map: &DisplaySnapshot, start_row: DisplayRow, end_row: DisplayRow, above: bool, positions: &Range, reversed: bool, text_layout_details: &TextLayoutDetails, ) -> Option> { let mut row = start_row; while row != end_row { if above { row.0 -= 1; } else { row.0 += 1; } if let Some(selection) = self.build_columnar_selection( display_map, row, positions, reversed, text_layout_details, ) { return Some(selection); } } None } /// Finds the next columnar selection by skipping to the next buffer row, /// ignoring soft-wrapped lines. pub fn find_next_columnar_selection_by_buffer_row( &mut self, display_map: &DisplaySnapshot, start_row: DisplayRow, end_row: DisplayRow, above: bool, goal_columns: &Range, reversed: bool, text_layout_details: &TextLayoutDetails, ) -> Option> { let mut row = start_row; let direction = if above { -1 } else { 1 }; while row != end_row { let new_row = display_map.start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction); row = new_row.row(); let buffer_row = new_row.to_point(display_map).row; if let Some(selection) = self.build_columnar_selection_from_tab_expanded_columns( display_map, buffer_row, goal_columns, reversed, text_layout_details, ) { return Some(selection); } } None } pub fn change_with( &mut self, snapshot: &DisplaySnapshot, change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R, ) -> (bool, R) { let mut mutable_collection = MutableSelectionsCollection { snapshot, collection: self, selections_changed: false, }; let result = change(&mut mutable_collection); assert!( !mutable_collection.disjoint.is_empty() || mutable_collection.pending.is_some(), "There must be at least one selection" ); if cfg!(debug_assertions) { mutable_collection.disjoint.iter().for_each(|selection| { assert!( selection.start.cmp(&selection.end, &snapshot).is_le(), "disjoint selection has start > end: {:?}", mutable_collection.disjoint ); assert!( snapshot.can_resolve(&selection.start), "disjoint selection start is not resolvable for the given snapshot:\n{selection:?}", ); assert!( snapshot.can_resolve(&selection.end), "disjoint selection start is not resolvable for the given snapshot:\n{selection:?}", ); }); assert!( mutable_collection .disjoint .is_sorted_by(|first, second| first.end.cmp(&second.start, &snapshot).is_le()), "disjoint selections are not sorted: {:?}", mutable_collection.disjoint ); if let Some(pending) = &mutable_collection.pending { let selection = &pending.selection; assert!( selection.start.cmp(&selection.end, &snapshot).is_le(), "pending selection has start > end: {:?}", selection ); assert!( snapshot.can_resolve(&selection.start), "pending selection start is not resolvable for the given snapshot: {pending:?}", ); assert!( snapshot.can_resolve(&selection.end), "pending selection end is not resolvable for the given snapshot: {pending:?}", ); } } (mutable_collection.selections_changed, result) } pub fn next_selection_id(&self) -> usize { self.next_selection_id } pub fn line_mode(&self) -> bool { self.line_mode } pub fn set_line_mode(&mut self, line_mode: bool) { self.line_mode = line_mode; } pub fn select_mode(&self) -> &SelectMode { &self.select_mode } pub fn set_select_mode(&mut self, select_mode: SelectMode) { self.select_mode = select_mode; } pub fn is_extending(&self) -> bool { self.is_extending } pub fn set_is_extending(&mut self, is_extending: bool) { self.is_extending = is_extending; } } pub struct MutableSelectionsCollection<'snap, 'a> { collection: &'a mut SelectionsCollection, snapshot: &'snap DisplaySnapshot, selections_changed: bool, } impl<'snap, 'a> fmt::Debug for MutableSelectionsCollection<'snap, 'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("MutableSelectionsCollection") .field("collection", &self.collection) .field("selections_changed", &self.selections_changed) .finish() } } impl<'snap, 'a> MutableSelectionsCollection<'snap, 'a> { pub fn display_snapshot(&self) -> DisplaySnapshot { self.snapshot.clone() } pub fn clear_disjoint(&mut self) { self.collection.disjoint = Arc::default(); } pub fn delete(&mut self, selection_id: usize) { let mut changed = false; self.collection.disjoint = self .disjoint .iter() .filter(|selection| { let found = selection.id == selection_id; changed |= found; !found }) .cloned() .collect(); self.selections_changed |= changed; } pub fn remove_selections_from_buffer(&mut self, buffer_id: language::BufferId) { let mut changed = false; let filtered_selections: Arc<[Selection]> = { self.disjoint .iter() .filter(|selection| { if let Some((selection_buffer_anchor, _)) = self.snapshot.anchor_to_buffer_anchor(selection.start) { let should_remove = selection_buffer_anchor.buffer_id == buffer_id; changed |= should_remove; !should_remove } else { true } }) .cloned() .collect() }; if filtered_selections.is_empty() { let buffer_snapshot = self.snapshot.buffer_snapshot(); let anchor = buffer_snapshot .excerpts() .find(|excerpt| excerpt.context.start.buffer_id == buffer_id) .and_then(|excerpt| buffer_snapshot.anchor_in_excerpt(excerpt.context.start)) .unwrap_or_else(|| self.snapshot.anchor_before(MultiBufferOffset(0))); self.collection.disjoint = Arc::from([Selection { id: post_inc(&mut self.collection.next_selection_id), start: anchor, end: anchor, reversed: false, goal: SelectionGoal::None, }]); } else { self.collection.disjoint = filtered_selections; } self.selections_changed |= changed; } pub fn clear_pending(&mut self) { if self.collection.pending.is_some() { self.collection.pending = None; self.selections_changed = true; } } pub(crate) fn set_pending_anchor_range(&mut self, range: Range, mode: SelectMode) { self.collection.pending = Some(PendingSelection { selection: { let mut start = range.start; let mut end = range.end; let reversed = if start.cmp(&end, self.snapshot).is_gt() { mem::swap(&mut start, &mut end); true } else { false }; Selection { id: post_inc(&mut self.collection.next_selection_id), start, end, reversed, goal: SelectionGoal::None, } }, mode, }); self.selections_changed = true; } pub(crate) fn set_pending(&mut self, selection: Selection, mode: SelectMode) { self.collection.pending = Some(PendingSelection { selection, mode }); self.selections_changed = true; } pub fn try_cancel(&mut self) -> bool { if let Some(pending) = self.collection.pending.take() { if self.disjoint.is_empty() { self.collection.disjoint = Arc::from([pending.selection]); } self.selections_changed = true; return true; } let mut oldest = self.oldest_anchor().clone(); if self.count() > 1 { self.collection.disjoint = Arc::from([oldest]); self.selections_changed = true; return true; } if !oldest.start.cmp(&oldest.end, self.snapshot).is_eq() { let head = oldest.head(); oldest.start = head; oldest.end = head; self.collection.disjoint = Arc::from([oldest]); self.selections_changed = true; return true; } false } pub fn insert_range(&mut self, range: Range) where T: ToOffset, { let display_map = self.display_snapshot(); let mut selections = self.collection.all(&display_map); let mut start = range.start.to_offset(self.snapshot); let mut end = range.end.to_offset(self.snapshot); let reversed = if start > end { mem::swap(&mut start, &mut end); true } else { false }; selections.push(Selection { id: post_inc(&mut self.collection.next_selection_id), start, end, reversed, goal: SelectionGoal::None, }); self.select(selections); } pub fn select(&mut self, selections: Vec>) where T: ToOffset + std::marker::Copy + std::fmt::Debug, { let mut selections = selections .into_iter() .map(|selection| selection.map(|it| it.to_offset(self.snapshot))) .map(|mut selection| { if selection.start > selection.end { mem::swap(&mut selection.start, &mut selection.end); selection.reversed = true } selection }) .collect::>(); selections.sort_unstable_by_key(|s| s.start); let mut i = 1; while i < selections.len() { let prev = &selections[i - 1]; let current = &selections[i]; if should_merge(prev.start, prev.end, current.start, current.end, true) { let removed = selections.remove(i); if removed.start < selections[i - 1].start { selections[i - 1].start = removed.start; } if selections[i - 1].end < removed.end { selections[i - 1].end = removed.end; } } else { i += 1; } } self.collection.disjoint = Arc::from_iter( selections .into_iter() .map(|selection| selection_to_anchor_selection(selection, self.snapshot)), ); self.collection.pending = None; self.collection.select_mode = SelectMode::Character; self.selections_changed = true; } pub fn select_anchors(&mut self, selections: Vec>) { let map = self.display_snapshot(); let resolved_selections = resolve_selections_wrapping_blocks::(&selections, &map) .collect::>(); self.select(resolved_selections); } pub fn select_ranges(&mut self, ranges: I) where I: IntoIterator>, T: ToOffset, { let ranges = ranges .into_iter() .map(|range| range.start.to_offset(self.snapshot)..range.end.to_offset(self.snapshot)); self.select_offset_ranges(ranges); } fn select_offset_ranges(&mut self, ranges: I) where I: IntoIterator>, { let selections = ranges .into_iter() .map(|range| { let mut start = range.start; let mut end = range.end; let reversed = if start > end { mem::swap(&mut start, &mut end); true } else { false }; Selection { id: post_inc(&mut self.collection.next_selection_id), start, end, reversed, goal: SelectionGoal::None, } }) .collect::>(); self.select(selections) } pub fn select_anchor_ranges(&mut self, ranges: I) where I: IntoIterator>, { let selections = ranges .into_iter() .map(|range| { let mut start = range.start; let mut end = range.end; let reversed = if start.cmp(&end, self.snapshot).is_gt() { mem::swap(&mut start, &mut end); true } else { false }; Selection { id: post_inc(&mut self.collection.next_selection_id), start, end, reversed, goal: SelectionGoal::None, } }) .collect::>(); self.select_anchors(selections) } pub fn new_selection_id(&mut self) -> usize { post_inc(&mut self.next_selection_id) } pub fn select_display_ranges(&mut self, ranges: T) where T: IntoIterator>, { let selections = ranges .into_iter() .map(|range| { let mut start = range.start; let mut end = range.end; let reversed = if start > end { mem::swap(&mut start, &mut end); true } else { false }; Selection { id: post_inc(&mut self.collection.next_selection_id), start: start.to_point(self.snapshot), end: end.to_point(self.snapshot), reversed, goal: SelectionGoal::None, } }) .collect(); self.select(selections); } pub fn reverse_selections(&mut self) { let mut new_selections: Vec> = Vec::new(); let disjoint = self.disjoint.clone(); for selection in disjoint .iter() .sorted_by(|first, second| Ord::cmp(&second.id, &first.id)) { new_selections.push(Selection { id: self.new_selection_id(), start: selection .start .to_display_point(self.snapshot) .to_point(self.snapshot), end: selection .end .to_display_point(self.snapshot) .to_point(self.snapshot), reversed: selection.reversed, goal: selection.goal, }); } self.select(new_selections); } pub fn move_with( &mut self, move_selection: &mut dyn FnMut(&DisplaySnapshot, &mut Selection), ) { let mut changed = false; let display_map = self.display_snapshot(); let selections = self.collection.all_display(&display_map); let selections = selections .into_iter() .map(|selection| { let mut moved_selection = selection.clone(); move_selection(&display_map, &mut moved_selection); if selection != moved_selection { changed = true; } moved_selection.map(|display_point| display_point.to_point(&display_map)) }) .collect(); if changed { self.select(selections) } } pub fn move_offsets_with( &mut self, move_selection: &mut dyn FnMut(&MultiBufferSnapshot, &mut Selection), ) { let mut changed = false; let display_map = self.display_snapshot(); let selections = self .collection .all::(&display_map) .into_iter() .map(|selection| { let mut moved_selection = selection.clone(); move_selection(self.snapshot, &mut moved_selection); if selection != moved_selection { changed = true; } moved_selection }) .collect(); if changed { self.select(selections) } } pub fn move_heads_with( &mut self, update_head: &mut dyn FnMut( &DisplaySnapshot, DisplayPoint, SelectionGoal, ) -> (DisplayPoint, SelectionGoal), ) { self.move_with(&mut |map, selection| { let (new_head, new_goal) = update_head(map, selection.head(), selection.goal); selection.set_head(new_head, new_goal); }); } pub fn move_cursors_with( &mut self, update_cursor_position: &mut dyn FnMut( &DisplaySnapshot, DisplayPoint, SelectionGoal, ) -> (DisplayPoint, SelectionGoal), ) { self.move_with(&mut |map, selection| { let (cursor, new_goal) = update_cursor_position(map, selection.head(), selection.goal); selection.collapse_to(cursor, new_goal) }); } pub fn maybe_move_cursors_with( &mut self, update_cursor_position: &mut dyn FnMut( &DisplaySnapshot, DisplayPoint, SelectionGoal, ) -> Option<(DisplayPoint, SelectionGoal)>, ) { self.move_cursors_with(&mut |map, point, goal| { update_cursor_position(map, point, goal).unwrap_or((point, goal)) }) } pub fn replace_cursors_with( &mut self, find_replacement_cursors: impl FnOnce(&DisplaySnapshot) -> Vec, ) { let new_selections = find_replacement_cursors(self.snapshot) .into_iter() .map(|cursor| { let cursor_point = cursor.to_point(self.snapshot); Selection { id: post_inc(&mut self.collection.next_selection_id), start: cursor_point, end: cursor_point, reversed: false, goal: SelectionGoal::None, } }) .collect(); self.select(new_selections); } pub fn pending_anchor_mut(&mut self) -> Option<&mut Selection> { self.selections_changed = true; self.pending.as_mut().map(|pending| &mut pending.selection) } } impl Deref for MutableSelectionsCollection<'_, '_> { type Target = SelectionsCollection; fn deref(&self) -> &Self::Target { self.collection } } impl DerefMut for MutableSelectionsCollection<'_, '_> { fn deref_mut(&mut self) -> &mut Self::Target { self.collection } } fn selection_to_anchor_selection( selection: Selection, buffer: &MultiBufferSnapshot, ) -> Selection { let end_bias = if selection.start == selection.end { Bias::Right } else { Bias::Left }; Selection { id: selection.id, start: buffer.anchor_after(selection.start), end: buffer.anchor_at(selection.end, end_bias), reversed: selection.reversed, goal: selection.goal, } } fn resolve_selections_point<'a>( selections: impl 'a + IntoIterator>, map: &'a DisplaySnapshot, ) -> impl 'a + Iterator> { let (to_summarize, selections) = selections.into_iter().tee(); let mut summaries = map .buffer_snapshot() .summaries_for_anchors::(to_summarize.flat_map(|s| [&s.start, &s.end])) .into_iter(); selections.map(move |s| { let start = summaries.next().unwrap(); let end = summaries.next().unwrap(); assert!( start <= end, "anchors: start: {:?}, end: {:?}; resolved to: start: {:?}, end: {:?}", s.start, s.end, start, end ); Selection { id: s.id, start, end, reversed: s.reversed, goal: s.goal, } }) } /// Panics if passed selections are not in order /// Resolves the anchors to display positions fn resolve_selections_display<'a>( selections: impl 'a + IntoIterator>, map: &'a DisplaySnapshot, ) -> impl 'a + Iterator> { let selections = resolve_selections_point(selections, map).map(move |s| { let display_start = map.point_to_display_point(s.start, Bias::Left); let display_end = map.point_to_display_point( s.end, if s.start == s.end { Bias::Right } else { Bias::Left }, ); assert!( display_start <= display_end, "display_start: {:?}, display_end: {:?}", display_start, display_end ); Selection { id: s.id, start: display_start, end: display_end, reversed: s.reversed, goal: s.goal, } }); coalesce_selections(selections) } /// Resolves the passed in anchors to [`MultiBufferDimension`]s `D` /// wrapping around blocks inbetween. /// /// # Panics /// /// Panics if passed selections are not in order pub(crate) fn resolve_selections_wrapping_blocks<'a, D, I>( selections: I, map: &'a DisplaySnapshot, ) -> impl 'a + Iterator> where D: MultiBufferDimension + Sub + AddAssign<::Output> + Ord, I: 'a + IntoIterator>, { // Without collapsed content, coalescing in buffer point space is equivalent to coalescing in // display point space, so skip the per-selection display-coordinate round trip. if !map.has_collapsed_content() { Either::Left(resolve_selections_without_display_round_trip( selections, map, )) } else { Either::Right(resolve_selections_via_display_round_trip(selections, map)) } } fn resolve_selections_without_display_round_trip<'a, D, I>( selections: I, map: &'a DisplaySnapshot, ) -> impl 'a + Iterator> where D: MultiBufferDimension + Sub + AddAssign<::Output> + Ord, I: 'a + IntoIterator>, { let (to_convert, selections) = coalesce_selections(resolve_selections_point(selections, map)).tee(); let mut converted_endpoints = map .buffer_snapshot() .dimensions_from_points::(to_convert.flat_map(|s| [s.start, s.end])); selections.map(move |s| { let start = converted_endpoints.next().unwrap(); let end = converted_endpoints.next().unwrap(); Selection { id: s.id, start, end, reversed: s.reversed, goal: s.goal, } }) } fn resolve_selections_via_display_round_trip<'a, D, I>( selections: I, map: &'a DisplaySnapshot, ) -> impl 'a + Iterator> where D: MultiBufferDimension + Sub + AddAssign<::Output> + Ord, I: 'a + IntoIterator>, { // Transforms `Anchor -> DisplayPoint -> Point -> DisplayPoint -> D` // todo(lw): We should be able to short circuit the `Anchor -> DisplayPoint -> Point` to `Anchor -> Point` let (to_convert, selections) = resolve_selections_display(selections, map).tee(); let mut converted_endpoints = map.buffer_snapshot() .dimensions_from_points::(to_convert.flat_map(|s| { let start = map.display_point_to_point(s.start, Bias::Left); let end = map.display_point_to_point(s.end, Bias::Right); assert!(start <= end, "start: {:?}, end: {:?}", start, end); [start, end] })); selections.map(move |s| { let start = converted_endpoints.next().unwrap(); let end = converted_endpoints.next().unwrap(); assert!(start <= end, "start: {:?}, end: {:?}", start, end); Selection { id: s.id, start, end, reversed: s.reversed, goal: s.goal, } }) } fn coalesce_selections( selections: impl Iterator>, ) -> impl Iterator> { let mut selections = selections.peekable(); iter::from_fn(move || { let mut selection = selections.next()?; while let Some(next_selection) = selections.peek() { if should_merge( selection.start, selection.end, next_selection.start, next_selection.end, true, ) { if selection.reversed == next_selection.reversed { selection.end = cmp::max(selection.end, next_selection.end); selections.next(); } else { selection.end = cmp::max(selection.start, next_selection.start); break; } } else { break; } } assert!( selection.start <= selection.end, "selection.start: {:?}, selection.end: {:?}, selection.reversed: {:?}", selection.start, selection.end, selection.reversed ); Some(selection) }) } /// Determines whether two selections should be merged into one. /// /// Two selections should be merged when: /// 1. They overlap: the selections share at least one position /// 2. They have the same start position: one contains or equals the other /// 3. A cursor touches a selection boundary: a zero-width selection (cursor) at the /// start or end of another selection should be absorbed into it /// /// Note: two selections that merely touch (one ends exactly where the other begins) /// but don't share any positions remain separate, see: https://github.com/zed-industries/zed/issues/24748 fn should_merge(a_start: T, a_end: T, b_start: T, b_end: T, sorted: bool) -> bool { let is_overlapping = if sorted { // When sorted, `a` starts before or at `b`, so overlap means `b` starts before `a` ends b_start < a_end } else { a_start < b_end && b_start < a_end }; // Selections starting at the same position should always merge (one contains the other) let same_start = a_start == b_start; // A cursor (zero-width selection) touching another selection's boundary should merge. // This handles cases like a cursor at position X merging with a selection that // starts or ends at X. let is_cursor_a = a_start == a_end; let is_cursor_b = b_start == b_end; let cursor_at_boundary = (is_cursor_a && (a_start == b_start || a_end == b_end)) || (is_cursor_b && (b_start == a_start || b_end == a_end)); is_overlapping || same_start || cursor_at_boundary } #[cfg(test)] mod tests { use super::*; use crate::{ MultiBuffer, display_map::{BlockPlacement, BlockProperties, BlockStyle, DisplayMap, FoldPlaceholder}, test::test_font, }; use gpui::{AppContext as _, IntoElement as _, div, px}; use project::project_settings::DiagnosticSeverity; use rand::{Rng as _, rngs::StdRng}; use settings::SettingsStore; use std::sync::Arc; fn row_range_snapshot(cx: &mut gpui::TestAppContext, text: &str) -> DisplaySnapshot { cx.update(|cx| { let settings = SettingsStore::test(cx); cx.set_global(settings); crate::init(cx); }); let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx)); let display_map = cx.new(|cx| { DisplayMap::new( buffer, test_font(), px(14.), None, 1, 1, FoldPlaceholder::test(), DiagnosticSeverity::Warning, cx, ) }); display_map.update(cx, |map, cx| map.snapshot(cx)) } fn row_range_collection( offset_ranges: impl IntoIterator>, buffer_snapshot: &MultiBufferSnapshot, ) -> SelectionsCollection { let selections = offset_ranges .into_iter() .enumerate() .map(|(id, range)| { selection_to_anchor_selection( Selection { id, start: MultiBufferOffset(range.start), end: MultiBufferOffset(range.end), reversed: false, goal: SelectionGoal::None, }, buffer_snapshot, ) }) .collect::>(); let mut collection = SelectionsCollection::new(); collection.disjoint = Arc::from(selections); collection.pending = None; collection } /// `disjoint_in_row_range` selects by whole rows, so a selection sharing a queried row must be /// returned even when its columns don't overlap the queried range. `disjoint_in_range` compares /// exact offsets and would miss this case. #[gpui::test] fn disjoint_in_row_range_matches_whole_row(cx: &mut gpui::TestAppContext) { let snapshot = row_range_snapshot(cx, "aaaa\nbbbbbbbb\ncccc"); let buffer_snapshot = snapshot.buffer_snapshot(); // A selection on row 1 spanning columns 3..5 (buffer offsets 8..10). let collection = row_range_collection([8..10], buffer_snapshot); // Query row 1 at columns 0..1, entirely before the selection's columns. let range = buffer_snapshot.anchor_before(MultiBufferOffset(5)) ..buffer_snapshot.anchor_before(MultiBufferOffset(6)); let result = collection.disjoint_in_row_range::(range, &snapshot); assert_eq!( result.len(), 1, "row range should include the selection sharing the queried row" ); assert_eq!(result[0].start, Point::new(1, 3)); assert_eq!(result[0].end, Point::new(1, 5)); } /// A selection on a row outside the queried row range must not be returned. This guards the /// row-overlap boundary against becoming over-inclusive. #[gpui::test] fn disjoint_in_row_range_excludes_other_rows(cx: &mut gpui::TestAppContext) { let snapshot = row_range_snapshot(cx, "aaaa\nbbbbbbbb\ncccc"); let buffer_snapshot = snapshot.buffer_snapshot(); // A selection on row 2 (buffer offsets 14..16). let collection = row_range_collection([14..16], buffer_snapshot); // Query only row 0, two rows away from the selection. let range = buffer_snapshot.anchor_before(MultiBufferOffset(0)) ..buffer_snapshot.anchor_before(MultiBufferOffset(1)); let result = collection.disjoint_in_row_range::(range, &snapshot); assert!( result.is_empty(), "row range should exclude a selection on a non-queried row" ); } /// A selection spanning multiple rows must be returned when the query touches any of those rows, /// not just when the query shares the selection's start or end row. #[gpui::test] fn disjoint_in_row_range_matches_interior_row(cx: &mut gpui::TestAppContext) { let snapshot = row_range_snapshot(cx, "aaaa\nbbbb\ncccc\ndddd"); let buffer_snapshot = snapshot.buffer_snapshot(); // A selection spanning row 1 column 1 through row 3 column 2 (buffer offsets 6..17). let collection = row_range_collection([6..17], buffer_snapshot); // Query only row 2, an interior row of the selection. let range = buffer_snapshot.anchor_before(MultiBufferOffset(11)) ..buffer_snapshot.anchor_before(MultiBufferOffset(12)); let result = collection.disjoint_in_row_range::(range, &snapshot); assert_eq!( result.len(), 1, "row range should include a selection whose interior row is queried" ); assert_eq!(result[0].start, Point::new(1, 1)); assert_eq!(result[0].end, Point::new(3, 2)); } #[gpui::test(iterations = 20)] fn fast_and_slow_selection_resolution_match_without_collapsed_content( cx: &mut gpui::TestAppContext, mut rng: StdRng, ) { cx.update(|cx| { let settings = SettingsStore::test(cx); cx.set_global(settings); crate::init(cx); }); let text = random_text(&mut rng); let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx)); let display_map = cx.new(|cx| { DisplayMap::new( buffer.clone(), test_font(), px(14.), Some(px(rng.random_range(80.0..=180.0))), 1, 1, FoldPlaceholder::test(), DiagnosticSeverity::Warning, cx, ) }); let snapshot = display_map.update(cx, |map, cx| { let buffer_snapshot = buffer.read(cx).snapshot(cx); map.insert_blocks( [BlockProperties { placement: BlockPlacement::Above( buffer_snapshot.anchor_after(MultiBufferOffset(text.len() / 2)), ), height: Some(2), style: BlockStyle::Fixed, render: Arc::new(|_| div().into_any_element()), priority: 0, }], cx, ); map.snapshot(cx) }); assert!(!snapshot.has_collapsed_content()); let selections = random_anchor_selections(&snapshot, text.len(), &mut rng); let fast_points = resolve_selections_without_display_round_trip::(selections.iter(), &snapshot) .collect::>(); let slow_points = resolve_selections_via_display_round_trip::(selections.iter(), &snapshot) .collect::>(); assert_eq!(fast_points, slow_points); let fast_offsets = resolve_selections_without_display_round_trip::( selections.iter(), &snapshot, ) .collect::>(); let slow_offsets = resolve_selections_via_display_round_trip::( selections.iter(), &snapshot, ) .collect::>(); assert_eq!(fast_offsets, slow_offsets); } fn random_text(rng: &mut StdRng) -> String { let mut text = String::new(); for line in 0..rng.random_range(8..32) { if line > 0 { text.push('\n'); } for _ in 0..rng.random_range(8..48) { let ch = match rng.random_range(0..12) { 0 => '\t', 1 => ' ', _ => rng.random_range(b'a'..=b'z') as char, }; text.push(ch); } } text } fn random_anchor_selections( snapshot: &DisplaySnapshot, text_len: usize, rng: &mut StdRng, ) -> Vec> { let buffer = snapshot.buffer_snapshot(); let mut selections = Vec::new(); let mut offset = 0; for id in 0..rng.random_range(1..32) { if offset > text_len { break; } let start = rng.random_range(offset..=text_len); let end = rng.random_range(start..=text_len); selections.push(Selection { id, start: MultiBufferOffset(start), end: MultiBufferOffset(end), reversed: rng.random(), goal: SelectionGoal::None, }); offset = end.saturating_add(rng.random_range(0..=4)); } selections .into_iter() .map(|selection| selection_to_anchor_selection(selection, buffer)) .collect() } }