diff --git a/crates/breadcrumbs/src/breadcrumbs.rs b/crates/breadcrumbs/src/breadcrumbs.rs index b2bba37e38..bbd11deac8 100644 --- a/crates/breadcrumbs/src/breadcrumbs.rs +++ b/crates/breadcrumbs/src/breadcrumbs.rs @@ -37,7 +37,7 @@ impl Breadcrumbs { cx: &AppContext, ) -> Option<(ModelHandle, Vec>)> { let editor = self.editor.as_ref()?.read(cx); - let cursor = editor.newest_anchor_selection().head(); + let cursor = editor.selections.newest_anchor().head(); let multibuffer = &editor.buffer().read(cx); let (buffer_id, symbols) = multibuffer .read(cx) diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 40c3ba582a..b2fce39616 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -3003,7 +3003,7 @@ mod tests { // Type a completion trigger character as the guest. editor_b.update(cx_b, |editor, cx| { - editor.select_ranges([13..13], None, cx); + editor.change_selections(None, cx, |s| s.select_ranges([13..13])); editor.handle_input(&Input(".".into()), cx); cx.focus(&editor_b); }); @@ -4215,7 +4215,9 @@ mod tests { // Move cursor to a location that contains code actions. editor_b.update(cx_b, |editor, cx| { - editor.select_ranges([Point::new(1, 31)..Point::new(1, 31)], None, cx); + editor.change_selections(None, cx, |s| { + s.select_ranges([Point::new(1, 31)..Point::new(1, 31)]) + }); cx.focus(&editor_b); }); @@ -4452,7 +4454,7 @@ mod tests { // Move cursor to a location that can be renamed. let prepare_rename = editor_b.update(cx_b, |editor, cx| { - editor.select_ranges([7..7], None, cx); + editor.change_selections(None, cx, |s| s.select_ranges([7..7])); editor.rename(&Rename, cx).unwrap() }); @@ -5460,8 +5462,12 @@ mod tests { }); // When client B starts following client A, all visible view states are replicated to client B. - editor_a1.update(cx_a, |editor, cx| editor.select_ranges([0..1], None, cx)); - editor_a2.update(cx_a, |editor, cx| editor.select_ranges([2..3], None, cx)); + editor_a1.update(cx_a, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([0..1])) + }); + editor_a2.update(cx_a, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([2..3])) + }); workspace_b .update(cx_b, |workspace, cx| { workspace @@ -5483,11 +5489,11 @@ mod tests { Some((worktree_id, "2.txt").into()) ); assert_eq!( - editor_b2.read_with(cx_b, |editor, cx| editor.selected_ranges(cx)), + editor_b2.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)), vec![2..3] ); assert_eq!( - editor_b1.read_with(cx_b, |editor, cx| editor.selected_ranges(cx)), + editor_b1.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)), vec![0..1] ); @@ -5526,11 +5532,11 @@ mod tests { // Changes to client A's editor are reflected on client B. editor_a1.update(cx_a, |editor, cx| { - editor.select_ranges([1..1, 2..2], None, cx); + editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2])); }); editor_b1 .condition(cx_b, |editor, cx| { - editor.selected_ranges(cx) == vec![1..1, 2..2] + editor.selections.ranges(cx) == vec![1..1, 2..2] }) .await; @@ -5540,11 +5546,13 @@ mod tests { .await; editor_a1.update(cx_a, |editor, cx| { - editor.select_ranges([3..3], None, cx); + editor.change_selections(None, cx, |s| s.select_ranges([3..3])); editor.set_scroll_position(vec2f(0., 100.), cx); }); editor_b1 - .condition(cx_b, |editor, cx| editor.selected_ranges(cx) == vec![3..3]) + .condition(cx_b, |editor, cx| { + editor.selections.ranges(cx) == vec![3..3] + }) .await; // After unfollowing, client B stops receiving updates from client A. diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index eb8fae2ab2..5e94bbe055 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -5,7 +5,7 @@ use collections::{BTreeSet, HashSet}; use editor::{ diagnostic_block_renderer, display_map::{BlockDisposition, BlockId, BlockProperties, RenderBlock}, - highlight_diagnostic_message, Editor, ExcerptId, MultiBuffer, ToOffset, + highlight_diagnostic_message, Autoscroll, Editor, ExcerptId, MultiBuffer, ToOffset, }; use gpui::{ actions, elements::*, fonts::TextStyle, serde_json, AnyViewHandle, AppContext, Entity, @@ -417,8 +417,9 @@ impl ProjectDiagnosticsEditor { }]; } else { groups = self.path_states.get(path_ix)?.diagnostic_groups.as_slice(); - new_excerpt_ids_by_selection_id = editor.refresh_selections(cx); - selections = editor.local_selections::(cx); + new_excerpt_ids_by_selection_id = + editor.change_selections(Some(Autoscroll::Fit), cx, |s| s.refresh()); + selections = editor.selections.all::(cx); } // If any selection has lost its position, move it to start of the next primary diagnostic. @@ -441,7 +442,9 @@ impl ProjectDiagnosticsEditor { } } } - editor.update_selections(selections, None, cx); + editor.change_selections(None, cx, |s| { + s.select(selections); + }); Some(()) }); @@ -894,7 +897,7 @@ mod tests { // Cursor is at the first diagnostic view.editor.update(cx, |editor, cx| { assert_eq!( - editor.selected_display_ranges(cx), + editor.selections.display_ranges(cx), [DisplayPoint::new(12, 6)..DisplayPoint::new(12, 6)] ); }); @@ -995,7 +998,7 @@ mod tests { // Cursor keeps its position. view.editor.update(cx, |editor, cx| { assert_eq!( - editor.selected_display_ranges(cx), + editor.selections.display_ranges(cx), [DisplayPoint::new(19, 6)..DisplayPoint::new(19, 6)] ); }); diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index ef99cbf5a8..752fc26f0f 100644 --- a/crates/diagnostics/src/items.rs +++ b/crates/diagnostics/src/items.rs @@ -58,9 +58,7 @@ impl DiagnosticIndicator { fn update(&mut self, editor: ViewHandle, cx: &mut ViewContext) { let editor = editor.read(cx); let buffer = editor.buffer().read(cx); - let cursor_position = editor - .newest_selection_with_snapshot::(&buffer.read(cx)) - .head(); + let cursor_position = editor.selections.newest::(cx).head(); let new_diagnostic = buffer .read(cx) .diagnostics_in_range::<_, usize>(cursor_position..cursor_position, false) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index a9a95280d8..9b160acf64 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3,6 +3,7 @@ mod element; pub mod items; pub mod movement; mod multi_buffer; +mod selections_collection; #[cfg(test)] mod test; @@ -28,7 +29,6 @@ use gpui::{ ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; -use itertools::Itertools as _; pub use language::{char_kind, CharKind}; use language::{ BracketPair, Buffer, CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticSeverity, @@ -40,6 +40,7 @@ pub use multi_buffer::{ }; use ordered_float::OrderedFloat; use project::{Project, ProjectTransaction}; +use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection}; use serde::{Deserialize, Serialize}; use settings::Settings; use smallvec::SmallVec; @@ -49,14 +50,12 @@ use std::{ any::TypeId, borrow::Cow, cmp::{self, Ordering, Reverse}, - iter::{self, FromIterator}, - mem, - ops::{Deref, DerefMut, Range, RangeInclusive, Sub}, + iter, mem, + ops::{Deref, DerefMut, Range, RangeInclusive}, sync::Arc, time::{Duration, Instant}, }; pub use sum_tree::Bias; -use text::rope::TextDimension; use theme::{DiagnosticStyle, Theme}; use util::{post_inc, ResultExt, TryFutureExt}; use workspace::{ItemNavHistory, Workspace}; @@ -377,9 +376,7 @@ pub struct Editor { handle: WeakViewHandle, buffer: ModelHandle, display_map: ModelHandle, - next_selection_id: usize, - selections: Arc<[Selection]>, - pending_selection: Option, + pub selections: SelectionsCollection, columnar_selection_tail: Option, add_selections_state: Option, select_next_state: Option, @@ -429,13 +426,7 @@ pub struct EditorSnapshot { scroll_top_anchor: Anchor, } -#[derive(Clone)] -pub struct PendingSelection { - selection: Selection, - mode: SelectMode, -} - -#[derive(Clone)] +#[derive(Clone, Debug)] struct SelectionHistoryEntry { selections: Arc<[Selection]>, select_next_state: Option, @@ -527,13 +518,13 @@ impl SelectionHistory { } } -#[derive(Clone)] +#[derive(Clone, Debug)] struct AddSelectionsState { above: bool, stack: Vec, } -#[derive(Clone)] +#[derive(Clone, Debug)] struct SelectNextState { query: AhoCorasick, wordwise: bool, @@ -545,6 +536,7 @@ struct BracketPairState { pair: BracketPair, } +#[derive(Debug)] struct SnippetState { ranges: Vec>>, active_index: usize, @@ -945,23 +937,14 @@ impl Editor { cx.observe(&display_map, Self::on_display_map_changed) .detach(); + let selections = SelectionsCollection::new(display_map.clone(), buffer.clone()); + let mut this = Self { handle: cx.weak_handle(), buffer, display_map, - selections: Arc::from([]), - pending_selection: Some(PendingSelection { - selection: Selection { - id: 0, - start: Anchor::min(), - end: Anchor::min(), - reversed: false, - goal: SelectionGoal::None, - }, - mode: SelectMode::Character, - }), + selections, columnar_selection_tail: None, - next_selection_id: 1, add_selections_state: None, select_next_state: None, selection_history: Default::default(), @@ -1204,12 +1187,11 @@ impl Editor { first_cursor_top = highlighted_rows.start as f32; last_cursor_bottom = first_cursor_top + 1.; } else if autoscroll == Autoscroll::Newest { - let newest_selection = - self.newest_selection_with_snapshot::(&display_map.buffer_snapshot); + let newest_selection = self.selections.newest::(cx); first_cursor_top = newest_selection.head().to_display_point(&display_map).row() as f32; last_cursor_bottom = first_cursor_top + 1.; } else { - let selections = self.local_selections::(cx); + let selections = self.selections.all::(cx); first_cursor_top = selections .first() .unwrap() @@ -1269,7 +1251,7 @@ impl Editor { cx: &mut ViewContext, ) -> bool { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let selections = self.local_selections::(cx); + let selections = self.selections.all::(cx); let mut target_left; let mut target_right; @@ -1318,83 +1300,96 @@ impl Editor { } } - pub fn replace_selections_with( + fn selections_did_change( &mut self, + local: bool, + old_cursor_position: &Anchor, cx: &mut ViewContext, - mut find_replacement: impl FnMut(&DisplaySnapshot) -> DisplayPoint, ) { - let display_map = self.snapshot(cx); - let cursor = find_replacement(&display_map); - let selection = Selection { - id: post_inc(&mut self.next_selection_id), - start: cursor, - end: cursor, - reversed: false, - goal: SelectionGoal::None, + if self.focused && self.leader_replica_id.is_none() { + self.buffer.update(cx, |buffer, cx| { + buffer.set_active_selections(&self.selections.disjoint_anchors(), cx) + }); } - .map(|display_point| display_point.to_point(&display_map)); - self.update_selections(vec![selection], None, cx); + + let display_map = self + .display_map + .update(cx, |display_map, cx| display_map.snapshot(cx)); + let buffer = &display_map.buffer_snapshot; + self.add_selections_state = None; + self.select_next_state = None; + self.select_larger_syntax_node_stack.clear(); + self.autoclose_stack + .invalidate(&self.selections.disjoint_anchors(), buffer); + self.snippet_stack + .invalidate(&self.selections.disjoint_anchors(), buffer); + self.take_rename(false, cx); + + let new_cursor_position = self.selections.newest_anchor().head(); + + self.push_to_nav_history( + old_cursor_position.clone(), + Some(new_cursor_position.to_point(buffer)), + cx, + ); + + if local { + let new_cursor_position = self.selections.newest_anchor().head(); + let completion_menu = match self.context_menu.as_mut() { + Some(ContextMenu::Completions(menu)) => Some(menu), + _ => { + self.context_menu.take(); + None + } + }; + + if let Some(completion_menu) = completion_menu { + let cursor_position = new_cursor_position.to_offset(buffer); + let (word_range, kind) = + buffer.surrounding_word(completion_menu.initial_position.clone()); + if kind == Some(CharKind::Word) + && word_range.to_inclusive().contains(&cursor_position) + { + let query = Self::completion_query(buffer, cursor_position); + cx.background() + .block(completion_menu.filter(query.as_deref(), cx.background().clone())); + self.show_completions(&ShowCompletions, cx); + } else { + self.hide_context_menu(cx); + } + } + + if old_cursor_position.to_display_point(&display_map).row() + != new_cursor_position.to_display_point(&display_map).row() + { + self.available_code_actions.take(); + } + self.refresh_code_actions(cx); + self.refresh_document_highlights(cx); + } + + self.pause_cursor_blinking(cx); + cx.emit(Event::SelectionsChanged { local }); + cx.notify(); } - pub fn display_selections( + pub fn change_selections( &mut self, + autoscroll: Option, cx: &mut ViewContext, - ) -> (DisplaySnapshot, Vec>) { - let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let selections = self - .local_selections::(cx) - .into_iter() - .map(|selection| selection.map(|point| point.to_display_point(&display_map))) - .collect(); - (display_map, selections) - } + change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R, + ) -> R { + let old_cursor_position = self.selections.newest_anchor().head(); + self.push_to_selection_history(); - pub fn move_selections( - &mut self, - cx: &mut ViewContext, - mut move_selection: impl FnMut(&DisplaySnapshot, &mut Selection), - ) { - let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let selections = self - .local_selections::(cx) - .into_iter() - .map(|selection| { - let mut selection = selection.map(|point| point.to_display_point(&display_map)); - move_selection(&display_map, &mut selection); - selection.map(|display_point| display_point.to_point(&display_map)) - }) - .collect(); - self.update_selections(selections, Some(Autoscroll::Fit), cx); - } + let result = self.selections.change_with(cx, change); - pub fn move_selection_heads( - &mut self, - cx: &mut ViewContext, - mut update_head: impl FnMut( - &DisplaySnapshot, - DisplayPoint, - SelectionGoal, - ) -> (DisplayPoint, SelectionGoal), - ) { - self.move_selections(cx, |map, selection| { - let (new_head, new_goal) = update_head(map, selection.head(), selection.goal); - selection.set_head(new_head, new_goal); - }); - } + if let Some(autoscroll) = autoscroll { + self.request_autoscroll(autoscroll, cx); + } + self.selections_did_change(true, &old_cursor_position, cx); - pub fn move_cursors( - &mut self, - cx: &mut ViewContext, - mut update_cursor_position: impl FnMut( - &DisplaySnapshot, - DisplayPoint, - SelectionGoal, - ) -> (DisplayPoint, SelectionGoal), - ) { - self.move_selections(cx, |map, selection| { - let (cursor, new_goal) = update_cursor_position(map, selection.head(), selection.goal); - selection.collapse_to(cursor, new_goal) - }); + result } pub fn edit(&mut self, edits: I, cx: &mut ViewContext) @@ -1449,30 +1444,34 @@ impl Editor { cx: &mut ViewContext, ) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let tail = self - .newest_selection_with_snapshot::(&display_map.buffer_snapshot) - .tail(); + let tail = self.selections.newest::(cx).tail(); self.begin_selection(position, false, click_count, cx); let position = position.to_offset(&display_map, Bias::Left); let tail_anchor = display_map.buffer_snapshot.anchor_before(tail); - let mut pending = self.pending_selection.clone().unwrap(); + let mut pending_selection = self + .selections + .pending_anchor() + .expect("extend_selection not called with pending selection"); if position >= tail { - pending.selection.start = tail_anchor.clone(); + pending_selection.start = tail_anchor.clone(); } else { - pending.selection.end = tail_anchor.clone(); - pending.selection.reversed = true; + pending_selection.end = tail_anchor.clone(); + pending_selection.reversed = true; } - match &mut pending.mode { + let mut pending_mode = self.selections.pending_mode().unwrap(); + match &mut pending_mode { SelectMode::Word(range) | SelectMode::Line(range) => { *range = tail_anchor.clone()..tail_anchor } _ => {} } - self.set_selections(self.selections.clone(), Some(pending), true, cx); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.set_pending(pending_selection, pending_mode) + }); } fn begin_selection( @@ -1489,7 +1488,7 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = &display_map.buffer_snapshot; - let newest_selection = self.newest_anchor_selection().clone(); + let newest_selection = self.selections.newest_anchor().clone(); let position = display_map.clip_point(position, Bias::Left); let start; @@ -1527,38 +1526,17 @@ impl Editor { } } - let selection = Selection { - id: post_inc(&mut self.next_selection_id), - start, - end, - reversed: false, - goal: SelectionGoal::None, - }; - - let mut selections; - if add { - selections = self.selections.clone(); - // Remove the newest selection if it was added due to a previous mouse up - // within this multi-click. - if click_count > 1 { - selections = self - .selections - .iter() - .filter(|selection| selection.id != newest_selection.id) - .cloned() - .collect(); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + if add { + if click_count > 1 { + s.delete(newest_selection.id); + } + } else { + s.clear_disjoint(); } - } else { - selections = Arc::from([]); - } - self.set_selections( - selections, - Some(PendingSelection { selection, mode }), - true, - cx, - ); - cx.notify(); + s.set_pending_range(start..end, mode); + }); } fn begin_columnar_selection( @@ -1573,9 +1551,7 @@ impl Editor { } let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let tail = self - .newest_selection_with_snapshot::(&display_map.buffer_snapshot) - .tail(); + let tail = self.selections.newest::(cx).tail(); self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail)); self.select_columns( @@ -1599,14 +1575,15 @@ impl Editor { if let Some(tail) = self.columnar_selection_tail.as_ref() { let tail = tail.to_display_point(&display_map); self.select_columns(tail, position, overshoot, &display_map, cx); - } else if let Some(mut pending) = self.pending_selection.clone() { + } else if let Some(mut pending) = self.selections.pending_anchor().clone() { let buffer = self.buffer.read(cx).snapshot(cx); let head; let tail; - match &pending.mode { + let mode = self.selections.pending_mode().unwrap(); + match &mode { SelectMode::Character => { head = position.to_point(&display_map); - tail = pending.selection.tail().to_point(&buffer); + tail = pending.tail().to_point(&buffer); } SelectMode::Word(original_range) => { let original_display_range = original_range.start.to_display_point(&display_map) @@ -1662,15 +1639,18 @@ impl Editor { }; if head < tail { - pending.selection.start = buffer.anchor_before(head); - pending.selection.end = buffer.anchor_before(tail); - pending.selection.reversed = true; + pending.start = buffer.anchor_before(head); + pending.end = buffer.anchor_before(tail); + pending.reversed = true; } else { - pending.selection.start = buffer.anchor_before(tail); - pending.selection.end = buffer.anchor_before(head); - pending.selection.reversed = false; + pending.start = buffer.anchor_before(tail); + pending.end = buffer.anchor_before(head); + pending.reversed = false; } - self.set_selections(self.selections.clone(), Some(pending), true, cx); + + self.change_selections(None, cx, |s| { + s.set_pending(pending, mode); + }); } else { log::error!("update_selection dispatched with no pending selection"); return; @@ -1682,9 +1662,12 @@ impl Editor { fn end_selection(&mut self, cx: &mut ViewContext) { self.columnar_selection_tail.take(); - if self.pending_selection.is_some() { - let selections = self.local_selections::(cx); - self.update_selections(selections, None, cx); + if self.selections.pending_anchor().is_some() { + let selections = self.selections.all::(cx); + self.change_selections(None, cx, |s| { + s.select(selections); + s.clear_pending(); + }); } } @@ -1702,7 +1685,7 @@ impl Editor { let end_column = cmp::max(tail.column(), head.column() + overshoot); let reversed = start_column < tail.column(); - let selections = (start_row..=end_row) + let selection_ranges = (start_row..=end_row) .filter_map(|row| { if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) { let start = display_map @@ -1711,25 +1694,25 @@ impl Editor { let end = display_map .clip_point(DisplayPoint::new(row, end_column), Bias::Right) .to_point(&display_map); - Some(Selection { - id: post_inc(&mut self.next_selection_id), - start, - end, - reversed, - goal: SelectionGoal::None, - }) + if reversed { + Some(end..start) + } else { + Some(start..end) + } } else { None } }) .collect::>(); - self.update_selections(selections, None, cx); + self.change_selections(None, cx, |s| { + s.select_ranges(selection_ranges); + }); cx.notify(); } pub fn is_selecting(&self) -> bool { - self.pending_selection.is_some() || self.columnar_selection_tail.is_some() + self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some() } pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext) { @@ -1751,26 +1734,7 @@ impl Editor { return; } - if let Some(pending) = self.pending_selection.clone() { - let mut selections = self.selections.clone(); - if selections.is_empty() { - selections = Arc::from([pending.selection]); - } - self.set_selections(selections, None, true, cx); - self.request_autoscroll(Autoscroll::Fit, cx); - return; - } - - let mut oldest_selection = self.oldest_selection::(&cx); - if self.selection_count() > 1 { - self.update_selections(vec![oldest_selection], Some(Autoscroll::Fit), cx); - return; - } - - if !oldest_selection.is_empty() { - oldest_selection.start = oldest_selection.head().clone(); - oldest_selection.end = oldest_selection.head().clone(); - self.update_selections(vec![oldest_selection], Some(Autoscroll::Fit), cx); + if self.change_selections(Some(Autoscroll::Fit), cx, |s| s.try_cancel()) { return; } } @@ -1778,107 +1742,6 @@ impl Editor { cx.propagate_action(); } - #[cfg(any(test, feature = "test-support"))] - pub fn selected_ranges>( - &self, - cx: &AppContext, - ) -> Vec> { - self.local_selections::(cx) - .iter() - .map(|s| { - if s.reversed { - s.end.clone()..s.start.clone() - } else { - s.start.clone()..s.end.clone() - } - }) - .collect() - } - - #[cfg(any(test, feature = "test-support"))] - pub fn selected_display_ranges(&self, cx: &mut MutableAppContext) -> Vec> { - let display_map = self - .display_map - .update(cx, |display_map, cx| display_map.snapshot(cx)); - self.selections - .iter() - .chain( - self.pending_selection - .as_ref() - .map(|pending| &pending.selection), - ) - .map(|s| { - if s.reversed { - s.end.to_display_point(&display_map)..s.start.to_display_point(&display_map) - } else { - s.start.to_display_point(&display_map)..s.end.to_display_point(&display_map) - } - }) - .collect() - } - - pub fn select_ranges( - &mut self, - ranges: I, - autoscroll: Option, - cx: &mut ViewContext, - ) where - I: IntoIterator>, - T: ToOffset, - { - let buffer = self.buffer.read(cx).snapshot(cx); - let selections = ranges - .into_iter() - .map(|range| { - let mut start = range.start.to_offset(&buffer); - let mut end = range.end.to_offset(&buffer); - let reversed = if start > end { - mem::swap(&mut start, &mut end); - true - } else { - false - }; - Selection { - id: post_inc(&mut self.next_selection_id), - start, - end, - reversed, - goal: SelectionGoal::None, - } - }) - .collect::>(); - self.update_selections(selections, autoscroll, cx); - } - - #[cfg(any(test, feature = "test-support"))] - pub fn select_display_ranges<'a, T>(&mut self, ranges: T, cx: &mut ViewContext) - where - T: IntoIterator>, - { - let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - 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.next_selection_id), - start: start.to_point(&display_map), - end: end.to_point(&display_map), - reversed, - goal: SelectionGoal::None, - } - }) - .collect(); - self.update_selections(selections, None, cx); - } - pub fn handle_input(&mut self, action: &Input, cx: &mut ViewContext) { if !self.input_enabled { cx.propagate_action(); @@ -1900,7 +1763,8 @@ impl Editor { pub fn newline(&mut self, _: &Newline, cx: &mut ViewContext) { self.transact(cx, |this, cx| { let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = { - let selections = this.local_selections::(cx); + let selections = this.selections.all::(cx); + let buffer = this.buffer.read(cx).snapshot(cx); selections .iter() @@ -1947,9 +1811,12 @@ impl Editor { if insert_extra_newline { new_text = new_text.repeat(2); } + + let anchor = buffer.anchor_after(end); + let new_selection = selection.map(|_| anchor.clone()); ( (start..end, new_text), - (insert_extra_newline, buffer.anchor_after(end)), + (insert_extra_newline, new_selection), ) }) .unzip() @@ -1957,35 +1824,28 @@ impl Editor { this.buffer.update(cx, |buffer, cx| { buffer.edit_with_autoindent(edits, cx); - - let buffer = buffer.read(cx); - this.selections = this - .selections - .iter() - .cloned() - .zip(selection_fixup_info) - .map(|(mut new_selection, (extra_newline_inserted, end))| { - let mut cursor = end.to_point(&buffer); - if extra_newline_inserted { - cursor.row -= 1; - cursor.column = buffer.line_len(cursor.row); - } - let anchor = buffer.anchor_after(cursor); - new_selection.start = anchor.clone(); - new_selection.end = anchor; - new_selection - }) - .collect(); }); + let buffer = this.buffer.read(cx).snapshot(cx); + let new_selections = selection_fixup_info + .into_iter() + .map(|(extra_newline_inserted, new_selection)| { + let mut cursor = new_selection.end.to_point(&buffer); + if extra_newline_inserted { + cursor.row -= 1; + cursor.column = buffer.line_len(cursor.row); + } + new_selection.map(|_| cursor.clone()) + }) + .collect(); - this.request_autoscroll(Autoscroll::Fit, cx); + this.change_selections(Some(Autoscroll::Fit), cx, |s| s.select(new_selections)); }); } pub fn insert(&mut self, text: &str, cx: &mut ViewContext) { let text: Arc = text.into(); self.transact(cx, |this, cx| { - let old_selections = this.local_selections::(cx); + let old_selections = this.selections.all::(cx); let selection_anchors = this.buffer.update(cx, |buffer, cx| { let anchors = { let snapshot = buffer.read(cx); @@ -2019,12 +1879,15 @@ impl Editor { }) .collect() }; - this.update_selections(selections, Some(Autoscroll::Fit), cx); + + this.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.select(selections); + }) }); } fn trigger_completion_on_input(&mut self, text: &str, cx: &mut ViewContext) { - let selection = self.newest_anchor_selection(); + let selection = self.selections.newest_anchor(); if self .buffer .read(cx) @@ -2044,64 +1907,58 @@ impl Editor { .cloned() { if self - .local_selections::(cx) + .selections + .all::(cx) .iter() .any(|selection| selection.is_empty()) { - false - } else { - let mut selections = self.selections.to_vec(); - for selection in &mut selections { - selection.end = selection.end.bias_left(&snapshot); - } - drop(snapshot); - - self.buffer.update(cx, |buffer, cx| { - let pair_start: Arc = pair.start.clone().into(); - buffer.edit( - selections - .iter() - .map(|s| (s.start.clone()..s.start.clone(), pair_start.clone())), - cx, - ); - let pair_end: Arc = pair.end.clone().into(); - buffer.edit( - selections - .iter() - .map(|s| (s.end.clone()..s.end.clone(), pair_end.clone())), - cx, - ); - }); - - let snapshot = self.buffer.read(cx).read(cx); - for selection in &mut selections { - selection.end = selection.end.bias_right(&snapshot); - } - drop(snapshot); - - self.set_selections(selections.into(), None, true, cx); - true + return false; } + + let mut selections = self.selections.disjoint_anchors().to_vec(); + for selection in &mut selections { + selection.end = selection.end.bias_left(&snapshot); + } + drop(snapshot); + + self.buffer.update(cx, |buffer, cx| { + let pair_start: Arc = pair.start.clone().into(); + let pair_end: Arc = pair.end.clone().into(); + buffer.edit( + selections + .iter() + .map(|s| (s.start.clone()..s.start.clone(), pair_start.clone())) + .chain( + selections + .iter() + .map(|s| (s.end.clone()..s.end.clone(), pair_end.clone())), + ), + cx, + ); + }); + + let snapshot = self.buffer.read(cx).read(cx); + for selection in &mut selections { + selection.end = selection.end.bias_right(&snapshot); + } + drop(snapshot); + + self.change_selections(None, cx, |s| s.select_anchors(selections)); + true } else { false } } fn autoclose_bracket_pairs(&mut self, cx: &mut ViewContext) { - let selections = self.local_selections::(cx); + let selections = self.selections.all::(cx); let mut bracket_pair_state = None; let mut new_selections = None; self.buffer.update(cx, |buffer, cx| { let mut snapshot = buffer.snapshot(cx); let left_biased_selections = selections .iter() - .map(|selection| Selection { - id: selection.id, - start: snapshot.anchor_before(selection.start), - end: snapshot.anchor_before(selection.end), - reversed: selection.reversed, - goal: selection.goal, - }) + .map(|selection| selection.map(|p| snapshot.anchor_before(p))) .collect::>(); let autoclose_pair = snapshot.language().and_then(|language| { @@ -2155,7 +2012,7 @@ impl Editor { snapshot = buffer.snapshot(cx); new_selections = Some( - self.resolve_selections::(left_biased_selections.iter(), &snapshot) + resolve_multiple::(left_biased_selections.iter(), &snapshot) .collect::>(), ); @@ -2177,7 +2034,9 @@ impl Editor { }); if let Some(new_selections) = new_selections { - self.update_selections(new_selections, None, cx); + self.change_selections(None, cx, |s| { + s.select(new_selections); + }); } if let Some(bracket_pair_state) = bracket_pair_state { self.autoclose_stack.push(bracket_pair_state); @@ -2185,7 +2044,8 @@ impl Editor { } fn skip_autoclose_end(&mut self, text: &str, cx: &mut ViewContext) -> bool { - let old_selections = self.local_selections::(cx); + let buffer = self.buffer.read(cx).snapshot(cx); + let old_selections = self.selections.all::(cx); let autoclose_pair = if let Some(autoclose_pair) = self.autoclose_stack.last() { autoclose_pair } else { @@ -2197,7 +2057,6 @@ impl Editor { debug_assert_eq!(old_selections.len(), autoclose_pair.ranges.len()); - let buffer = self.buffer.read(cx).snapshot(cx); if old_selections .iter() .zip(autoclose_pair.ranges.iter().map(|r| r.to_offset(&buffer))) @@ -2220,7 +2079,9 @@ impl Editor { }) .collect(); self.autoclose_stack.pop(); - self.update_selections(new_selections, Some(Autoscroll::Fit), cx); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.select(new_selections); + }); true } else { false @@ -2252,7 +2113,7 @@ impl Editor { return; }; - let position = self.newest_anchor_selection().head(); + let position = self.selections.newest_anchor().head(); let (buffer, buffer_position) = if let Some(output) = self .buffer .read(cx) @@ -2353,12 +2214,12 @@ impl Editor { snippet = None; text = completion.new_text.clone(); }; + let selections = self.selections.all::(cx); let buffer = buffer_handle.read(cx); let old_range = completion.old_range.to_offset(&buffer); let old_text = buffer.text_for_range(old_range.clone()).collect::(); - let selections = self.local_selections::(cx); - let newest_selection = self.newest_anchor_selection(); + let newest_selection = self.selections.newest_anchor(); if newest_selection.start.buffer_id != Some(buffer_handle.id()) { return None; } @@ -2524,7 +2385,7 @@ impl Editor { editor .buffer() .read(cx) - .excerpt_containing(editor.newest_anchor_selection().head(), cx) + .excerpt_containing(editor.selections.newest_anchor().head(), cx) }); if let Some((excerpted_buffer, excerpt_range)) = excerpt { if excerpted_buffer == *buffer { @@ -2585,7 +2446,7 @@ impl Editor { fn refresh_code_actions(&mut self, cx: &mut ViewContext) -> Option<()> { let project = self.project.as_ref()?; let buffer = self.buffer.read(cx); - let newest_selection = self.newest_anchor_selection().clone(); + let newest_selection = self.selections.newest_anchor().clone(); let (start_buffer, start) = buffer.text_anchor_for_position(newest_selection.start, cx)?; let (end_buffer, end) = buffer.text_anchor_for_position(newest_selection.end, cx)?; if start_buffer != end_buffer { @@ -2620,7 +2481,7 @@ impl Editor { let project = self.project.as_ref()?; let buffer = self.buffer.read(cx); - let newest_selection = self.newest_anchor_selection().clone(); + let newest_selection = self.selections.newest_anchor().clone(); let cursor_position = newest_selection.head(); let (cursor_buffer, cursor_buffer_position) = buffer.text_anchor_for_position(cursor_position.clone(), cx)?; @@ -2808,7 +2669,9 @@ impl Editor { }); if let Some(tabstop) = tabstops.first() { - self.select_ranges(tabstop.iter().cloned(), Some(Autoscroll::Fit), cx); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.select_ranges(tabstop.iter().cloned()); + }); self.snippet_stack.push(SnippetState { active_index: 0, ranges: tabstops, @@ -2827,14 +2690,13 @@ impl Editor { } pub fn move_to_snippet_tabstop(&mut self, bias: Bias, cx: &mut ViewContext) -> bool { - let buffer = self.buffer.read(cx).snapshot(cx); - - if let Some(snippet) = self.snippet_stack.last_mut() { + if let Some(mut snippet) = self.snippet_stack.pop() { match bias { Bias::Left => { if snippet.active_index > 0 { snippet.active_index -= 1; } else { + self.snippet_stack.push(snippet); return false; } } @@ -2842,34 +2704,21 @@ impl Editor { if snippet.active_index + 1 < snippet.ranges.len() { snippet.active_index += 1; } else { + self.snippet_stack.push(snippet); return false; } } } if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) { - let new_selections = current_ranges - .iter() - .map(|new_range| { - let new_range = new_range.to_offset(&buffer); - Selection { - id: post_inc(&mut self.next_selection_id), - start: new_range.start, - end: new_range.end, - reversed: false, - goal: SelectionGoal::None, - } - }) - .collect(); - - // Remove the snippet state when moving to the last tabstop. - if snippet.active_index + 1 == snippet.ranges.len() { - self.snippet_stack.pop(); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.select_anchor_ranges(current_ranges.into_iter().cloned()) + }); + // If snippet state is not at the last tabstop, push it back on the stack + if snippet.active_index + 1 < snippet.ranges.len() { + self.snippet_stack.push(snippet); } - - self.update_selections(new_selections, Some(Autoscroll::Fit), cx); return true; } - self.snippet_stack.pop(); } false @@ -2883,8 +2732,8 @@ impl Editor { } pub fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext) { - let mut selections = self.local_selections::(cx); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let mut selections = self.selections.all::(cx); for selection in &mut selections { if selection.is_empty() { let old_head = selection.head(); @@ -2911,18 +2760,20 @@ impl Editor { } self.transact(cx, |this, cx| { - this.update_selections(selections, Some(Autoscroll::Fit), cx); + this.change_selections(Some(Autoscroll::Fit), cx, |s| s.select(selections)); this.insert("", cx); }); } pub fn delete(&mut self, _: &Delete, cx: &mut ViewContext) { self.transact(cx, |this, cx| { - this.move_selections(cx, |map, selection| { - if selection.is_empty() { - let cursor = movement::right(map, selection.head()); - selection.set_head(cursor, SelectionGoal::None); - } + this.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_with(|map, selection| { + if selection.is_empty() { + let cursor = movement::right(map, selection.head()); + selection.set_head(cursor, SelectionGoal::None); + } + }) }); this.insert(&"", cx); }); @@ -2941,7 +2792,7 @@ impl Editor { return; } - let mut selections = self.local_selections::(cx); + let mut selections = self.selections.all::(cx); if selections.iter().all(|s| s.is_empty()) { self.transact(cx, |this, cx| { this.buffer.update(cx, |buffer, cx| { @@ -2966,7 +2817,9 @@ impl Editor { selection.end = selection.start; } }); - this.update_selections(selections, Some(Autoscroll::Fit), cx); + this.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.select(selections); + }); }); } else { self.indent(&Indent, cx); @@ -2974,7 +2827,7 @@ impl Editor { } pub fn indent(&mut self, _: &Indent, cx: &mut ViewContext) { - let mut selections = self.local_selections::(cx); + let mut selections = self.selections.all::(cx); self.transact(cx, |this, cx| { let mut last_indent = None; this.buffer.update(cx, |buffer, cx| { @@ -3029,13 +2882,15 @@ impl Editor { } }); - this.update_selections(selections, Some(Autoscroll::Fit), cx); + this.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.select(selections); + }); }); } pub fn outdent(&mut self, _: &Outdent, cx: &mut ViewContext) { - let selections = self.local_selections::(cx); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let selections = self.selections.all::(cx); let mut deletion_ranges = Vec::new(); let mut last_outdent = None; { @@ -3078,18 +2933,14 @@ impl Editor { cx, ); }); - this.update_selections( - this.local_selections::(cx), - Some(Autoscroll::Fit), - cx, - ); + let selections = this.selections.all::(cx); + this.change_selections(Some(Autoscroll::Fit), cx, |s| s.select(selections)); }); } pub fn delete_line(&mut self, _: &DeleteLine, cx: &mut ViewContext) { - let selections = self.local_selections::(cx); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let buffer = self.buffer.read(cx).snapshot(cx); + let selections = self.selections.all::(cx); let mut new_cursors = Vec::new(); let mut edit_ranges = Vec::new(); @@ -3109,6 +2960,7 @@ impl Editor { } } + let buffer = &display_map.buffer_snapshot; let mut edit_start = Point::new(rows.start, 0).to_offset(&buffer); let edit_end; let cursor_buffer_row; @@ -3160,14 +3012,17 @@ impl Editor { } }) .collect(); - this.update_selections(new_selections, Some(Autoscroll::Fit), cx); + + this.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.select(new_selections); + }); }); } pub fn duplicate_line(&mut self, _: &DuplicateLine, cx: &mut ViewContext) { - let selections = self.local_selections::(cx); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = &display_map.buffer_snapshot; + let selections = self.selections.all::(cx); let mut edits = Vec::new(); let mut selections_iter = selections.iter().peekable(); @@ -3212,7 +3067,7 @@ impl Editor { let mut unfold_ranges = Vec::new(); let mut refold_ranges = Vec::new(); - let selections = self.local_selections::(cx); + let selections = self.selections.all::(cx); let mut selections = selections.iter().peekable(); let mut contiguous_row_selections = Vec::new(); let mut new_selections = Vec::new(); @@ -3310,7 +3165,9 @@ impl Editor { } }); this.fold_ranges(refold_ranges, cx); - this.update_selections(new_selections, Some(Autoscroll::Fit), cx); + this.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.select(new_selections); + }) }); } @@ -3322,7 +3179,7 @@ impl Editor { let mut unfold_ranges = Vec::new(); let mut refold_ranges = Vec::new(); - let selections = self.local_selections::(cx); + let selections = self.selections.all::(cx); let mut selections = selections.iter().peekable(); let mut contiguous_row_selections = Vec::new(); let mut new_selections = Vec::new(); @@ -3413,62 +3270,66 @@ impl Editor { } }); this.fold_ranges(refold_ranges, cx); - this.update_selections(new_selections, Some(Autoscroll::Fit), cx); + this.change_selections(Some(Autoscroll::Fit), cx, |s| s.select(new_selections)); }); } pub fn transpose(&mut self, _: &Transpose, cx: &mut ViewContext) { self.transact(cx, |this, cx| { - let mut edits: Vec<(Range, String)> = Default::default(); - this.move_selections(cx, |display_map, selection| { - if !selection.is_empty() { - return; - } + let edits = this.change_selections(Some(Autoscroll::Fit), cx, |s| { + let mut edits: Vec<(Range, String)> = Default::default(); + s.move_with(|display_map, selection| { + if !selection.is_empty() { + return; + } - let mut head = selection.head(); - let mut transpose_offset = head.to_offset(display_map, Bias::Right); - if head.column() == display_map.line_len(head.row()) { - transpose_offset = display_map + let mut head = selection.head(); + let mut transpose_offset = head.to_offset(display_map, Bias::Right); + if head.column() == display_map.line_len(head.row()) { + transpose_offset = display_map + .buffer_snapshot + .clip_offset(transpose_offset.saturating_sub(1), Bias::Left); + } + + if transpose_offset == 0 { + return; + } + + *head.column_mut() += 1; + head = display_map.clip_point(head, Bias::Right); + selection.collapse_to(head, SelectionGoal::Column(head.column())); + + let transpose_start = display_map .buffer_snapshot .clip_offset(transpose_offset.saturating_sub(1), Bias::Left); - } - - if transpose_offset == 0 { - return; - } - - *head.column_mut() += 1; - head = display_map.clip_point(head, Bias::Right); - selection.collapse_to(head, SelectionGoal::Column(head.column())); - - let transpose_start = display_map - .buffer_snapshot - .clip_offset(transpose_offset.saturating_sub(1), Bias::Left); - if edits.last().map_or(true, |e| e.0.end <= transpose_start) { - let transpose_end = display_map - .buffer_snapshot - .clip_offset(transpose_offset + 1, Bias::Right); - if let Some(ch) = display_map.buffer_snapshot.chars_at(transpose_start).next() { - edits.push((transpose_start..transpose_offset, String::new())); - edits.push((transpose_end..transpose_end, ch.to_string())); + if edits.last().map_or(true, |e| e.0.end <= transpose_start) { + let transpose_end = display_map + .buffer_snapshot + .clip_offset(transpose_offset + 1, Bias::Right); + if let Some(ch) = + display_map.buffer_snapshot.chars_at(transpose_start).next() + { + edits.push((transpose_start..transpose_offset, String::new())); + edits.push((transpose_end..transpose_end, ch.to_string())); + } } - } + }); + edits }); this.buffer.update(cx, |buffer, cx| buffer.edit(edits, cx)); - this.update_selections( - this.local_selections::(cx), - Some(Autoscroll::Fit), - cx, - ); + let selections = this.selections.all::(cx); + this.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.select(selections); + }); }); } pub fn cut(&mut self, _: &Cut, cx: &mut ViewContext) { let mut text = String::new(); - let mut selections = self.local_selections::(cx); + let buffer = self.buffer.read(cx).snapshot(cx); + let mut selections = self.selections.all::(cx); let mut clipboard_selections = Vec::with_capacity(selections.len()); { - let buffer = self.buffer.read(cx).read(cx); let max_point = buffer.max_point(); for selection in &mut selections { let is_entire_line = selection.is_empty(); @@ -3490,18 +3351,20 @@ impl Editor { } self.transact(cx, |this, cx| { - this.update_selections(selections, Some(Autoscroll::Fit), cx); + this.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.select(selections); + }); this.insert("", cx); cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections)); }); } pub fn copy(&mut self, _: &Copy, cx: &mut ViewContext) { - let selections = self.local_selections::(cx); + let selections = self.selections.all::(cx); + let buffer = self.buffer.read(cx).read(cx); let mut text = String::new(); let mut clipboard_selections = Vec::with_capacity(selections.len()); { - let buffer = self.buffer.read(cx).read(cx); let max_point = buffer.max_point(); for selection in selections.iter() { let mut start = selection.start; @@ -3531,7 +3394,7 @@ impl Editor { if let Some(item) = cx.as_mut().read_from_clipboard() { let mut clipboard_text = Cow::Borrowed(item.text()); if let Some(mut clipboard_selections) = item.metadata::>() { - let old_selections = this.local_selections::(cx); + let old_selections = this.selections.all::(cx); let all_selections_were_entire_line = clipboard_selections.iter().all(|s| s.is_entire_line); if clipboard_selections.len() != old_selections.len() { @@ -3584,8 +3447,8 @@ impl Editor { buffer.edit_with_autoindent(edits, cx); }); - let selections = this.local_selections::(cx); - this.update_selections(selections, Some(Autoscroll::Fit), cx); + let selections = this.selections.all::(cx); + this.change_selections(Some(Autoscroll::Fit), cx, |s| s.select(selections)); } else { this.insert(&clipboard_text, cx); } @@ -3596,7 +3459,9 @@ impl Editor { pub fn undo(&mut self, _: &Undo, cx: &mut ViewContext) { if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) { if let Some((selections, _)) = self.selection_history.transaction(tx_id).cloned() { - self.set_selections(selections, None, true, cx); + self.change_selections(None, cx, |s| { + s.select_anchors(selections.to_vec()); + }); } self.request_autoscroll(Autoscroll::Fit, cx); cx.emit(Event::Edited); @@ -3607,7 +3472,9 @@ impl Editor { if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) { if let Some((_, Some(selections))) = self.selection_history.transaction(tx_id).cloned() { - self.set_selections(selections, None, true, cx); + self.change_selections(None, cx, |s| { + s.select_anchors(selections.to_vec()); + }); } self.request_autoscroll(Autoscroll::Fit, cx); cx.emit(Event::Edited); @@ -3620,37 +3487,41 @@ impl Editor { } pub fn move_left(&mut self, _: &MoveLeft, cx: &mut ViewContext) { - self.move_selections(cx, |map, selection| { - let cursor = if selection.is_empty() { - movement::left(map, selection.start) - } else { - selection.start - }; - selection.collapse_to(cursor, SelectionGoal::None); - }); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_with(|map, selection| { + let cursor = if selection.is_empty() { + movement::left(map, selection.start) + } else { + selection.start + }; + selection.collapse_to(cursor, SelectionGoal::None); + }); + }) } pub fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext) { - self.move_selection_heads(cx, |map, head, _| { - (movement::left(map, head), SelectionGoal::None) - }); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None)); + }) } pub fn move_right(&mut self, _: &MoveRight, cx: &mut ViewContext) { - self.move_selections(cx, |map, selection| { - let cursor = if selection.is_empty() { - movement::right(map, selection.end) - } else { - selection.end - }; - selection.collapse_to(cursor, SelectionGoal::None) - }); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_with(|map, selection| { + let cursor = if selection.is_empty() { + movement::right(map, selection.end) + } else { + selection.end + }; + selection.collapse_to(cursor, SelectionGoal::None) + }); + }) } pub fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext) { - self.move_selection_heads(cx, |map, head, _| { - (movement::right(map, head), SelectionGoal::None) - }); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None)); + }) } pub fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext) { @@ -3669,17 +3540,21 @@ impl Editor { return; } - self.move_selections(cx, |map, selection| { - if !selection.is_empty() { - selection.goal = SelectionGoal::None; - } - let (cursor, goal) = movement::up(&map, selection.start, selection.goal, false); - selection.collapse_to(cursor, goal); - }); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_with(|map, selection| { + if !selection.is_empty() { + selection.goal = SelectionGoal::None; + } + let (cursor, goal) = movement::up(&map, selection.start, selection.goal, false); + selection.collapse_to(cursor, goal); + }); + }) } pub fn select_up(&mut self, _: &SelectUp, cx: &mut ViewContext) { - self.move_selection_heads(cx, |map, head, goal| movement::up(map, head, goal, false)) + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_heads_with(|map, head, goal| movement::up(map, head, goal, false)) + }) } pub fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext) { @@ -3696,17 +3571,21 @@ impl Editor { return; } - self.move_selections(cx, |map, selection| { - if !selection.is_empty() { - selection.goal = SelectionGoal::None; - } - let (cursor, goal) = movement::down(&map, selection.end, selection.goal, false); - selection.collapse_to(cursor, goal); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_with(|map, selection| { + if !selection.is_empty() { + selection.goal = SelectionGoal::None; + } + let (cursor, goal) = movement::down(&map, selection.end, selection.goal, false); + selection.collapse_to(cursor, goal); + }); }); } pub fn select_down(&mut self, _: &SelectDown, cx: &mut ViewContext) { - self.move_selection_heads(cx, |map, head, goal| movement::down(map, head, goal, false)) + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_heads_with(|map, head, goal| movement::down(map, head, goal, false)) + }); } pub fn move_to_previous_word_start( @@ -3714,12 +3593,14 @@ impl Editor { _: &MoveToPreviousWordStart, cx: &mut ViewContext, ) { - self.move_cursors(cx, |map, head, _| { - ( - movement::previous_word_start(map, head), - SelectionGoal::None, - ) - }); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_cursors_with(|map, head, _| { + ( + movement::previous_word_start(map, head), + SelectionGoal::None, + ) + }); + }) } pub fn move_to_previous_subword_start( @@ -3727,12 +3608,14 @@ impl Editor { _: &MoveToPreviousSubwordStart, cx: &mut ViewContext, ) { - self.move_cursors(cx, |map, head, _| { - ( - movement::previous_subword_start(map, head), - SelectionGoal::None, - ) - }); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_cursors_with(|map, head, _| { + ( + movement::previous_subword_start(map, head), + SelectionGoal::None, + ) + }); + }) } pub fn select_to_previous_word_start( @@ -3740,12 +3623,14 @@ impl Editor { _: &SelectToPreviousWordStart, cx: &mut ViewContext, ) { - self.move_selection_heads(cx, |map, head, _| { - ( - movement::previous_word_start(map, head), - SelectionGoal::None, - ) - }); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_heads_with(|map, head, _| { + ( + movement::previous_word_start(map, head), + SelectionGoal::None, + ) + }); + }) } pub fn select_to_previous_subword_start( @@ -3753,12 +3638,14 @@ impl Editor { _: &SelectToPreviousSubwordStart, cx: &mut ViewContext, ) { - self.move_selection_heads(cx, |map, head, _| { - ( - movement::previous_subword_start(map, head), - SelectionGoal::None, - ) - }); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_heads_with(|map, head, _| { + ( + movement::previous_subword_start(map, head), + SelectionGoal::None, + ) + }); + }) } pub fn delete_to_previous_word_start( @@ -3767,11 +3654,13 @@ impl Editor { cx: &mut ViewContext, ) { self.transact(cx, |this, cx| { - this.move_selections(cx, |map, selection| { - if selection.is_empty() { - let cursor = movement::previous_word_start(map, selection.head()); - selection.set_head(cursor, SelectionGoal::None); - } + this.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_with(|map, selection| { + if selection.is_empty() { + let cursor = movement::previous_word_start(map, selection.head()); + selection.set_head(cursor, SelectionGoal::None); + } + }); }); this.insert("", cx); }); @@ -3783,20 +3672,24 @@ impl Editor { cx: &mut ViewContext, ) { self.transact(cx, |this, cx| { - this.move_selections(cx, |map, selection| { - if selection.is_empty() { - let cursor = movement::previous_subword_start(map, selection.head()); - selection.set_head(cursor, SelectionGoal::None); - } + this.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_with(|map, selection| { + if selection.is_empty() { + let cursor = movement::previous_subword_start(map, selection.head()); + selection.set_head(cursor, SelectionGoal::None); + } + }); }); this.insert("", cx); }); } pub fn move_to_next_word_end(&mut self, _: &MoveToNextWordEnd, cx: &mut ViewContext) { - self.move_cursors(cx, |map, head, _| { - (movement::next_word_end(map, head), SelectionGoal::None) - }); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_cursors_with(|map, head, _| { + (movement::next_word_end(map, head), SelectionGoal::None) + }); + }) } pub fn move_to_next_subword_end( @@ -3804,15 +3697,19 @@ impl Editor { _: &MoveToNextSubwordEnd, cx: &mut ViewContext, ) { - self.move_cursors(cx, |map, head, _| { - (movement::next_subword_end(map, head), SelectionGoal::None) - }); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_cursors_with(|map, head, _| { + (movement::next_subword_end(map, head), SelectionGoal::None) + }); + }) } pub fn select_to_next_word_end(&mut self, _: &SelectToNextWordEnd, cx: &mut ViewContext) { - self.move_selection_heads(cx, |map, head, _| { - (movement::next_word_end(map, head), SelectionGoal::None) - }); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_heads_with(|map, head, _| { + (movement::next_word_end(map, head), SelectionGoal::None) + }); + }) } pub fn select_to_next_subword_end( @@ -3820,18 +3717,22 @@ impl Editor { _: &SelectToNextSubwordEnd, cx: &mut ViewContext, ) { - self.move_selection_heads(cx, |map, head, _| { - (movement::next_subword_end(map, head), SelectionGoal::None) - }); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_heads_with(|map, head, _| { + (movement::next_subword_end(map, head), SelectionGoal::None) + }); + }) } pub fn delete_to_next_word_end(&mut self, _: &DeleteToNextWordEnd, cx: &mut ViewContext) { self.transact(cx, |this, cx| { - this.move_selections(cx, |map, selection| { - if selection.is_empty() { - let cursor = movement::next_word_end(map, selection.head()); - selection.set_head(cursor, SelectionGoal::None); - } + this.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_with(|map, selection| { + if selection.is_empty() { + let cursor = movement::next_word_end(map, selection.head()); + selection.set_head(cursor, SelectionGoal::None); + } + }); }); this.insert("", cx); }); @@ -3843,11 +3744,13 @@ impl Editor { cx: &mut ViewContext, ) { self.transact(cx, |this, cx| { - this.move_selections(cx, |map, selection| { - if selection.is_empty() { - let cursor = movement::next_subword_end(map, selection.head()); - selection.set_head(cursor, SelectionGoal::None); - } + this.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_with(|map, selection| { + if selection.is_empty() { + let cursor = movement::next_subword_end(map, selection.head()); + selection.set_head(cursor, SelectionGoal::None); + } + }); }); this.insert("", cx); }); @@ -3858,12 +3761,14 @@ impl Editor { _: &MoveToBeginningOfLine, cx: &mut ViewContext, ) { - self.move_cursors(cx, |map, head, _| { - ( - movement::line_beginning(map, head, true), - SelectionGoal::None, - ) - }); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_cursors_with(|map, head, _| { + ( + movement::line_beginning(map, head, true), + SelectionGoal::None, + ) + }); + }) } pub fn select_to_beginning_of_line( @@ -3871,11 +3776,13 @@ impl Editor { action: &SelectToBeginningOfLine, cx: &mut ViewContext, ) { - self.move_selection_heads(cx, |map, head, _| { - ( - movement::line_beginning(map, head, action.stop_at_soft_wraps), - SelectionGoal::None, - ) + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_heads_with(|map, head, _| { + ( + movement::line_beginning(map, head, action.stop_at_soft_wraps), + SelectionGoal::None, + ) + }); }); } @@ -3885,8 +3792,10 @@ impl Editor { cx: &mut ViewContext, ) { self.transact(cx, |this, cx| { - this.move_selections(cx, |_, selection| { - selection.reversed = true; + this.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_with(|_, selection| { + selection.reversed = true; + }); }); this.select_to_beginning_of_line( @@ -3900,9 +3809,11 @@ impl Editor { } pub fn move_to_end_of_line(&mut self, _: &MoveToEndOfLine, cx: &mut ViewContext) { - self.move_cursors(cx, |map, head, _| { - (movement::line_end(map, head, true), SelectionGoal::None) - }); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_cursors_with(|map, head, _| { + (movement::line_end(map, head, true), SelectionGoal::None) + }); + }) } pub fn select_to_end_of_line( @@ -3910,12 +3821,14 @@ impl Editor { action: &SelectToEndOfLine, cx: &mut ViewContext, ) { - self.move_selection_heads(cx, |map, head, _| { - ( - movement::line_end(map, head, action.stop_at_soft_wraps), - SelectionGoal::None, - ) - }); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_heads_with(|map, head, _| { + ( + movement::line_end(map, head, action.stop_at_soft_wraps), + SelectionGoal::None, + ) + }); + }) } pub fn delete_to_end_of_line(&mut self, _: &DeleteToEndOfLine, cx: &mut ViewContext) { @@ -3948,20 +3861,18 @@ impl Editor { return; } - let selection = Selection { - id: post_inc(&mut self.next_selection_id), - start: 0, - end: 0, - reversed: false, - goal: SelectionGoal::None, - }; - self.update_selections(vec![selection], Some(Autoscroll::Fit), cx); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.select_ranges(vec![0..0]); + }); } pub fn select_to_beginning(&mut self, _: &SelectToBeginning, cx: &mut ViewContext) { - let mut selection = self.local_selections::(cx).last().unwrap().clone(); + let mut selection = self.selections.last::(cx); selection.set_head(Point::zero(), SelectionGoal::None); - self.update_selections(vec![selection], Some(Autoscroll::Fit), cx); + + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.select(vec![selection]); + }); } pub fn move_to_end(&mut self, _: &MoveToEnd, cx: &mut ViewContext) { @@ -3971,14 +3882,9 @@ impl Editor { } let cursor = self.buffer.read(cx).read(cx).len(); - let selection = Selection { - id: post_inc(&mut self.next_selection_id), - start: cursor, - end: cursor, - reversed: false, - goal: SelectionGoal::None, - }; - self.update_selections(vec![selection], Some(Autoscroll::Fit), cx); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.select_ranges(vec![cursor..cursor]) + }); } pub fn set_nav_history(&mut self, nav_history: Option) { @@ -4019,25 +3925,24 @@ impl Editor { } pub fn select_to_end(&mut self, _: &SelectToEnd, cx: &mut ViewContext) { - let mut selection = self.local_selections::(cx).first().unwrap().clone(); - selection.set_head(self.buffer.read(cx).read(cx).len(), SelectionGoal::None); - self.update_selections(vec![selection], Some(Autoscroll::Fit), cx); + let buffer = self.buffer.read(cx).snapshot(cx); + let mut selection = self.selections.first::(cx); + selection.set_head(buffer.len(), SelectionGoal::None); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.select(vec![selection]); + }); } pub fn select_all(&mut self, _: &SelectAll, cx: &mut ViewContext) { - let selection = Selection { - id: post_inc(&mut self.next_selection_id), - start: 0, - end: self.buffer.read(cx).read(cx).len(), - reversed: false, - goal: SelectionGoal::None, - }; - self.update_selections(vec![selection], None, cx); + let end = self.buffer.read(cx).read(cx).len(); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.select_ranges(vec![0..end]); + }); } pub fn select_line(&mut self, _: &SelectLine, cx: &mut ViewContext) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let mut selections = self.local_selections::(cx); + let mut selections = self.selections.all::(cx); let max_point = display_map.buffer_snapshot.max_point(); for selection in &mut selections { let rows = selection.spanned_rows(true, &display_map); @@ -4045,7 +3950,9 @@ impl Editor { selection.end = cmp::min(max_point, Point::new(rows.end, 0)); selection.reversed = false; } - self.update_selections(selections, Some(Autoscroll::Fit), cx); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.select(selections); + }); } pub fn split_selection_into_lines( @@ -4054,33 +3961,23 @@ impl Editor { cx: &mut ViewContext, ) { let mut to_unfold = Vec::new(); - let mut new_selections = Vec::new(); + let mut new_selection_ranges = Vec::new(); { - let selections = self.local_selections::(cx); + let selections = self.selections.all::(cx); let buffer = self.buffer.read(cx).read(cx); for selection in selections { for row in selection.start.row..selection.end.row { let cursor = Point::new(row, buffer.line_len(row)); - new_selections.push(Selection { - id: post_inc(&mut self.next_selection_id), - start: cursor, - end: cursor, - reversed: false, - goal: SelectionGoal::None, - }); + new_selection_ranges.push(cursor..cursor); } - new_selections.push(Selection { - id: selection.id, - start: selection.end, - end: selection.end, - reversed: false, - goal: SelectionGoal::None, - }); + new_selection_ranges.push(selection.end..selection.end); to_unfold.push(selection.start..selection.end); } } self.unfold_ranges(to_unfold, true, cx); - self.update_selections(new_selections, Some(Autoscroll::Fit), cx); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.select_ranges(new_selection_ranges); + }); } pub fn add_selection_above(&mut self, _: &AddSelectionAbove, cx: &mut ViewContext) { @@ -4092,9 +3989,8 @@ impl Editor { } fn add_selection(&mut self, above: bool, cx: &mut ViewContext) { - self.push_to_selection_history(); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let mut selections = self.local_selections::(cx); + let mut selections = self.selections.all::(cx); let mut state = self.add_selections_state.take().unwrap_or_else(|| { let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone(); let range = oldest_selection.display_range(&display_map).sorted(); @@ -4104,7 +4000,7 @@ impl Editor { selections.clear(); let mut stack = Vec::new(); for row in range.start.row()..=range.end.row() { - if let Some(selection) = self.build_columnar_selection( + if let Some(selection) = self.selections.build_columnar_selection( &display_map, row, &columns, @@ -4151,7 +4047,7 @@ impl Editor { row += 1; } - if let Some(new_selection) = self.build_columnar_selection( + if let Some(new_selection) = self.selections.build_columnar_selection( &display_map, row, &columns, @@ -4179,7 +4075,9 @@ impl Editor { state.stack.pop(); } - self.update_selections(new_selections, Some(Autoscroll::Newest), cx); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.select(new_selections); + }); if state.stack.len() > 1 { self.add_selections_state = Some(state); } @@ -4189,7 +4087,7 @@ impl Editor { self.push_to_selection_history(); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = &display_map.buffer_snapshot; - let mut selections = self.local_selections::(cx); + let mut selections = self.selections.all::(cx); if let Some(mut select_next_state) = self.select_next_state.take() { let query = &select_next_state.query; if !select_next_state.done { @@ -4225,22 +4123,13 @@ impl Editor { } if let Some(next_selected_range) = next_selected_range { - if action.replace_newest { - if let Some(newest_id) = - selections.iter().max_by_key(|s| s.id).map(|s| s.id) - { - selections.retain(|s| s.id != newest_id); + self.unfold_ranges([next_selected_range.clone()], false, cx); + self.change_selections(Some(Autoscroll::Newest), cx, |s| { + if action.replace_newest { + s.delete(s.newest_anchor().id); } - } - selections.push(Selection { - id: post_inc(&mut self.next_selection_id), - start: next_selected_range.start, - end: next_selected_range.end, - reversed: false, - goal: SelectionGoal::None, + s.insert_range(next_selected_range); }); - self.unfold_ranges([next_selected_range], false, cx); - self.update_selections(selections, Some(Autoscroll::Newest), cx); } else { select_next_state.done = true; } @@ -4268,7 +4157,9 @@ impl Editor { done: false, }; self.unfold_ranges([selection.start..selection.end], false, cx); - self.update_selections(selections, Some(Autoscroll::Newest), cx); + self.change_selections(Some(Autoscroll::Newest), cx, |s| { + s.select(selections); + }); self.select_next_state = Some(select_state); } else { let query = buffer @@ -4286,7 +4177,7 @@ impl Editor { pub fn toggle_comments(&mut self, _: &ToggleComments, cx: &mut ViewContext) { self.transact(cx, |this, cx| { - let mut selections = this.local_selections::(cx); + let mut selections = this.selections.all::(cx); let mut all_selection_lines_are_comments = true; let mut edit_ranges = Vec::new(); let mut last_toggled_row = None; @@ -4387,11 +4278,8 @@ impl Editor { } }); - this.update_selections( - this.local_selections::(cx), - Some(Autoscroll::Fit), - cx, - ); + let selections = this.selections.all::(cx); + this.change_selections(Some(Autoscroll::Fit), cx, |s| s.select(selections)); }); } @@ -4400,9 +4288,9 @@ impl Editor { _: &SelectLargerSyntaxNode, cx: &mut ViewContext, ) { - let old_selections = self.local_selections::(cx).into_boxed_slice(); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = self.buffer.read(cx).snapshot(cx); + let old_selections = self.selections.all::(cx).into_boxed_slice(); let mut stack = mem::take(&mut self.select_larger_syntax_node_stack); let mut selected_larger_node = false; @@ -4435,7 +4323,9 @@ impl Editor { if selected_larger_node { stack.push(old_selections); - self.update_selections(new_selections, Some(Autoscroll::Fit), cx); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.select(new_selections); + }); } self.select_larger_syntax_node_stack = stack; } @@ -4447,7 +4337,9 @@ impl Editor { ) { let mut stack = mem::take(&mut self.select_larger_syntax_node_stack); if let Some(selections) = stack.pop() { - self.update_selections(selections.to_vec(), Some(Autoscroll::Fit), cx); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.select(selections.to_vec()); + }); } self.select_larger_syntax_node_stack = stack; } @@ -4457,8 +4349,8 @@ impl Editor { _: &MoveToEnclosingBracket, cx: &mut ViewContext, ) { - let mut selections = self.local_selections::(cx); let buffer = self.buffer.read(cx).snapshot(cx); + let mut selections = self.selections.all::(cx); for selection in &mut selections { if let Some((open_range, close_range)) = buffer.enclosing_bracket_ranges(selection.start..selection.end) @@ -4476,14 +4368,16 @@ impl Editor { } } - self.update_selections(selections, Some(Autoscroll::Fit), cx); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.select(selections); + }); } pub fn undo_selection(&mut self, _: &UndoSelection, cx: &mut ViewContext) { self.end_selection(cx); self.selection_history.mode = SelectionHistoryMode::Undoing; if let Some(entry) = self.selection_history.undo_stack.pop_back() { - self.set_selections(entry.selections, None, true, cx); + self.change_selections(None, cx, |s| s.select_anchors(entry.selections.to_vec())); self.select_next_state = entry.select_next_state; self.add_selections_state = entry.add_selections_state; self.request_autoscroll(Autoscroll::Newest, cx); @@ -4495,7 +4389,7 @@ impl Editor { self.end_selection(cx); self.selection_history.mode = SelectionHistoryMode::Redoing; if let Some(entry) = self.selection_history.redo_stack.pop_back() { - self.set_selections(entry.selections, None, true, cx); + self.change_selections(None, cx, |s| s.select_anchors(entry.selections.to_vec())); self.select_next_state = entry.select_next_state; self.add_selections_state = entry.add_selections_state; self.request_autoscroll(Autoscroll::Newest, cx); @@ -4513,7 +4407,7 @@ impl Editor { pub fn go_to_diagnostic(&mut self, direction: Direction, cx: &mut ViewContext) { let buffer = self.buffer.read(cx).snapshot(cx); - let selection = self.newest_selection_with_snapshot::(&buffer); + let selection = self.selections.newest::(cx); let mut active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| { active_diagnostics .primary_range @@ -4550,17 +4444,15 @@ impl Editor { if let Some((primary_range, group_id)) = group { self.activate_diagnostics(group_id, cx); - self.update_selections( - vec![Selection { + self.change_selections(Some(Autoscroll::Center), cx, |s| { + s.select(vec![Selection { id: selection.id, start: primary_range.start, end: primary_range.start, reversed: false, goal: SelectionGoal::None, - }], - Some(Autoscroll::Center), - cx, - ); + }]); + }); break; } else { // Cycle around to the start of the buffer, potentially moving back to the start of @@ -4599,13 +4491,13 @@ impl Editor { }; let editor = editor_handle.read(cx); - let head = editor.newest_selection::(cx).head(); - let (buffer, head) = - if let Some(text_anchor) = editor.buffer.read(cx).text_anchor_for_position(head, cx) { - text_anchor - } else { - return; - }; + let buffer = editor.buffer.read(cx); + let head = editor.selections.newest::(cx).head(); + let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) { + text_anchor + } else { + return; + }; let project = workspace.project().clone(); let definitions = project.update(cx, |project, cx| project.definition(&buffer, head, cx)); @@ -4623,7 +4515,10 @@ impl Editor { if editor_handle != target_editor_handle { nav_history.borrow_mut().disable(); } - target_editor.select_ranges([range], Some(Autoscroll::Center), cx); + target_editor.change_selections(Some(Autoscroll::Center), cx, |s| { + s.select_ranges([range]); + }); + nav_history.borrow_mut().enable(); }); } @@ -4643,8 +4538,9 @@ impl Editor { let editor_handle = active_item.act_as::(cx)?; let editor = editor_handle.read(cx); - let head = editor.newest_selection::(cx).head(); - let (buffer, head) = editor.buffer.read(cx).text_anchor_for_position(head, cx)?; + let buffer = editor.buffer.read(cx); + let head = editor.selections.newest::(cx).head(); + let (buffer, head) = buffer.text_anchor_for_position(head, cx)?; let replica_id = editor.replica_id(cx); let project = workspace.project().clone(); @@ -4712,7 +4608,7 @@ impl Editor { use language::ToOffset as _; let project = self.project.clone()?; - let selection = self.newest_anchor_selection().clone(); + let selection = self.selections.newest_anchor().clone(); let (cursor_buffer, cursor_buffer_position) = self .buffer .read(cx) @@ -4901,8 +4797,8 @@ impl Editor { self.show_local_selections = true; if moving_cursor { - let cursor_in_rename_editor = - rename.editor.read(cx).newest_selection::(cx).head(); + let rename_editor = rename.editor.read(cx); + let cursor_in_rename_editor = rename_editor.selections.newest::(cx).head(); // Update the selection to match the position of the selection inside // the rename editor. @@ -4913,17 +4809,9 @@ impl Editor { .min(rename_range.end); drop(snapshot); - self.update_selections( - vec![Selection { - id: self.newest_anchor_selection().id, - start: cursor_in_editor, - end: cursor_in_editor, - reversed: false, - goal: SelectionGoal::None, - }], - None, - cx, - ); + self.change_selections(None, cx, |s| { + s.select_ranges(vec![cursor_in_editor..cursor_in_editor]) + }); } Some(rename) @@ -5034,457 +4922,21 @@ impl Editor { } } - fn build_columnar_selection( - &mut self, - display_map: &DisplaySnapshot, - row: u32, - columns: &Range, - reversed: bool, - ) -> Option> { - let is_empty = columns.start == columns.end; - let line_len = display_map.line_len(row); - if columns.start < line_len || (is_empty && columns.start == line_len) { - let start = DisplayPoint::new(row, columns.start); - let end = DisplayPoint::new(row, cmp::min(columns.end, line_len)); - 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::ColumnRange { - start: columns.start, - end: columns.end, - }, - }) - } else { - None - } - } - - pub fn local_selections_in_range( - &self, - range: Range, - display_map: &DisplaySnapshot, - ) -> Vec> { - let buffer = &display_map.buffer_snapshot; - - let start_ix = match self - .selections - .binary_search_by(|probe| probe.end.cmp(&range.start, &buffer)) - { - Ok(ix) | Err(ix) => ix, - }; - let end_ix = match self - .selections - .binary_search_by(|probe| probe.start.cmp(&range.end, &buffer)) - { - Ok(ix) => ix + 1, - Err(ix) => ix, - }; - - fn point_selection( - selection: &Selection, - buffer: &MultiBufferSnapshot, - ) -> Selection { - let start = selection.start.to_point(&buffer); - let end = selection.end.to_point(&buffer); - Selection { - id: selection.id, - start, - end, - reversed: selection.reversed, - goal: selection.goal, - } - } - - self.selections[start_ix..end_ix] - .iter() - .chain( - self.pending_selection - .as_ref() - .map(|pending| &pending.selection), - ) - .map(|s| point_selection(s, &buffer)) - .collect() - } - - pub fn local_selections<'a, D>(&self, cx: &'a AppContext) -> Vec> - where - D: 'a + TextDimension + Ord + Sub, - { - let buffer = self.buffer.read(cx).snapshot(cx); - let mut selections = self - .resolve_selections::(self.selections.iter(), &buffer) - .peekable(); - - let mut pending_selection = self.pending_selection::(&buffer); - - iter::from_fn(move || { - if let Some(pending) = pending_selection.as_mut() { - while let Some(next_selection) = selections.peek() { - if pending.start <= next_selection.end && pending.end >= next_selection.start { - let next_selection = selections.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 selections.next(); - } else { - break; - } - } - - pending_selection.take() - } else { - selections.next() - } - }) - .collect() - } - - fn resolve_selections<'a, D, I>( - &self, - selections: I, - snapshot: &MultiBufferSnapshot, - ) -> impl 'a + Iterator> - where - D: TextDimension + Ord + Sub, - I: 'a + IntoIterator>, - { - let (to_summarize, selections) = selections.into_iter().tee(); - let mut summaries = snapshot - .summaries_for_anchors::(to_summarize.flat_map(|s| [&s.start, &s.end])) - .into_iter(); - selections.map(move |s| Selection { - id: s.id, - start: summaries.next().unwrap(), - end: summaries.next().unwrap(), - reversed: s.reversed, - goal: s.goal, - }) - } - - fn pending_selection>( - &self, - snapshot: &MultiBufferSnapshot, - ) -> Option> { - self.pending_selection - .as_ref() - .map(|pending| self.resolve_selection(&pending.selection, &snapshot)) - } - - fn resolve_selection>( - &self, - selection: &Selection, - buffer: &MultiBufferSnapshot, - ) -> Selection { - Selection { - id: selection.id, - start: selection.start.summary::(&buffer), - end: selection.end.summary::(&buffer), - reversed: selection.reversed, - goal: selection.goal, - } - } - - fn selection_count<'a>(&self) -> usize { - let mut count = self.selections.len(); - if self.pending_selection.is_some() { - count += 1; - } - count - } - - pub fn oldest_selection>( - &self, - cx: &AppContext, - ) -> Selection { - let snapshot = self.buffer.read(cx).read(cx); - self.selections - .iter() - .min_by_key(|s| s.id) - .map(|selection| self.resolve_selection(selection, &snapshot)) - .or_else(|| self.pending_selection(&snapshot)) - .unwrap() - } - - pub fn newest_selection>( - &self, - cx: &AppContext, - ) -> Selection { - self.resolve_selection( - self.newest_anchor_selection(), - &self.buffer.read(cx).read(cx), - ) - } - - pub fn newest_selection_with_snapshot>( - &self, - snapshot: &MultiBufferSnapshot, - ) -> Selection { - self.resolve_selection(self.newest_anchor_selection(), snapshot) - } - - pub fn newest_anchor_selection(&self) -> &Selection { - self.pending_selection - .as_ref() - .map(|s| &s.selection) - .or_else(|| self.selections.iter().max_by_key(|s| s.id)) - .unwrap() - } - - pub fn update_selections( - &mut self, - mut selections: Vec>, - autoscroll: Option, - cx: &mut ViewContext, - ) where - T: ToOffset + ToPoint + Ord + std::marker::Copy + std::fmt::Debug, - { - let buffer = self.buffer.read(cx).snapshot(cx); - selections.sort_unstable_by_key(|s| s.start); - - // Merge overlapping selections. - let mut i = 1; - while i < selections.len() { - if selections[i - 1].end >= selections[i].start { - let removed = selections.remove(i); - if removed.start < selections[i - 1].start { - selections[i - 1].start = removed.start; - } - if removed.end > selections[i - 1].end { - selections[i - 1].end = removed.end; - } - } else { - i += 1; - } - } - - if let Some(autoscroll) = autoscroll { - self.request_autoscroll(autoscroll, cx); - } - - self.set_selections( - Arc::from_iter(selections.into_iter().map(|selection| { - let end_bias = if selection.end > selection.start { - Bias::Left - } else { - Bias::Right - }; - Selection { - id: selection.id, - start: buffer.anchor_after(selection.start), - end: buffer.anchor_at(selection.end, end_bias), - reversed: selection.reversed, - goal: selection.goal, - } - })), - None, - true, - cx, - ); - } - pub fn set_selections_from_remote( &mut self, - mut selections: Vec>, + selections: Vec>, cx: &mut ViewContext, ) { - let buffer = self.buffer.read(cx); - let buffer = buffer.read(cx); - selections.sort_by(|a, b| { - a.start - .cmp(&b.start, &*buffer) - .then_with(|| b.end.cmp(&a.end, &*buffer)) + let old_cursor_position = self.selections.newest_anchor().head(); + self.selections.change_with(cx, |s| { + s.select_anchors(selections); }); - - // Merge overlapping selections - let mut i = 1; - while i < selections.len() { - if selections[i - 1] - .end - .cmp(&selections[i].start, &*buffer) - .is_ge() - { - let removed = selections.remove(i); - if removed - .start - .cmp(&selections[i - 1].start, &*buffer) - .is_lt() - { - selections[i - 1].start = removed.start; - } - if removed.end.cmp(&selections[i - 1].end, &*buffer).is_gt() { - selections[i - 1].end = removed.end; - } - } else { - i += 1; - } - } - - drop(buffer); - self.set_selections(selections.into(), None, false, cx); - } - - /// Compute new ranges for any selections that were located in excerpts that have - /// since been removed. - /// - /// Returns a `HashMap` indicating which selections whose former head position - /// was no longer present. The keys of the map are selection ids. The values are - /// the id of the new excerpt where the head of the selection has been moved. - pub fn refresh_selections(&mut self, cx: &mut ViewContext) -> HashMap { - let snapshot = self.buffer.read(cx).read(cx); - let mut selections_with_lost_position = HashMap::default(); - - let mut pending_selection = self.pending_selection.take(); - if let Some(pending) = pending_selection.as_mut() { - let anchors = - snapshot.refresh_anchors([&pending.selection.start, &pending.selection.end]); - let (_, start, kept_start) = anchors[0].clone(); - let (_, end, kept_end) = anchors[1].clone(); - let kept_head = if pending.selection.reversed { - kept_start - } else { - kept_end - }; - if !kept_head { - selections_with_lost_position.insert( - pending.selection.id, - pending.selection.head().excerpt_id.clone(), - ); - } - - pending.selection.start = start; - pending.selection.end = end; - } - - let anchors_with_status = snapshot.refresh_anchors( - self.selections - .iter() - .flat_map(|selection| [&selection.start, &selection.end]), - ); - self.selections = anchors_with_status - .chunks(2) - .map(|selection_anchors| { - let (anchor_ix, start, kept_start) = selection_anchors[0].clone(); - let (_, end, kept_end) = selection_anchors[1].clone(); - let selection = &self.selections[anchor_ix / 2]; - let kept_head = if selection.reversed { - kept_start - } else { - kept_end - }; - if !kept_head { - selections_with_lost_position - .insert(selection.id, selection.head().excerpt_id.clone()); - } - - Selection { - id: selection.id, - start, - end, - reversed: selection.reversed, - goal: selection.goal, - } - }) - .collect(); - drop(snapshot); - - let new_selections = self.local_selections::(cx); - if !new_selections.is_empty() { - self.update_selections(new_selections, Some(Autoscroll::Fit), cx); - } - self.pending_selection = pending_selection; - - selections_with_lost_position - } - - fn set_selections( - &mut self, - selections: Arc<[Selection]>, - pending_selection: Option, - local: bool, - cx: &mut ViewContext, - ) { - assert!( - !selections.is_empty() || pending_selection.is_some(), - "must have at least one selection" - ); - - let old_cursor_position = self.newest_anchor_selection().head(); - - self.push_to_selection_history(); - self.selections = selections; - self.pending_selection = pending_selection; - if self.focused && self.leader_replica_id.is_none() { - self.buffer.update(cx, |buffer, cx| { - buffer.set_active_selections(&self.selections, cx) - }); - } - - let display_map = self - .display_map - .update(cx, |display_map, cx| display_map.snapshot(cx)); - let buffer = &display_map.buffer_snapshot; - self.add_selections_state = None; - self.select_next_state = None; - self.select_larger_syntax_node_stack.clear(); - self.autoclose_stack.invalidate(&self.selections, &buffer); - self.snippet_stack.invalidate(&self.selections, &buffer); - self.take_rename(false, cx); - - let new_cursor_position = self.newest_anchor_selection().head(); - - self.push_to_nav_history( - old_cursor_position.clone(), - Some(new_cursor_position.to_point(&buffer)), - cx, - ); - - if local { - let completion_menu = match self.context_menu.as_mut() { - Some(ContextMenu::Completions(menu)) => Some(menu), - _ => { - self.context_menu.take(); - None - } - }; - - if let Some(completion_menu) = completion_menu { - let cursor_position = new_cursor_position.to_offset(&buffer); - let (word_range, kind) = - buffer.surrounding_word(completion_menu.initial_position.clone()); - if kind == Some(CharKind::Word) - && word_range.to_inclusive().contains(&cursor_position) - { - let query = Self::completion_query(&buffer, cursor_position); - cx.background() - .block(completion_menu.filter(query.as_deref(), cx.background().clone())); - self.show_completions(&ShowCompletions, cx); - } else { - self.hide_context_menu(cx); - } - } - - if old_cursor_position.to_display_point(&display_map).row() - != new_cursor_position.to_display_point(&display_map).row() - { - self.available_code_actions.take(); - } - self.refresh_code_actions(cx); - self.refresh_document_highlights(cx); - } - - self.pause_cursor_blinking(cx); - cx.emit(Event::SelectionsChanged { local }); + self.selections_did_change(false, &old_cursor_position, cx); } fn push_to_selection_history(&mut self) { self.selection_history.push(SelectionHistoryEntry { - selections: self.selections.clone(), + selections: self.selections.disjoint_anchors().clone(), select_next_state: self.select_next_state.clone(), add_selections_state: self.add_selections_state.clone(), }); @@ -5517,7 +4969,7 @@ impl Editor { .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx)) { self.selection_history - .insert_transaction(tx_id, self.selections.clone()); + .insert_transaction(tx_id, self.selections.disjoint_anchors().clone()); } } @@ -5527,7 +4979,7 @@ impl Editor { .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx)) { if let Some((_, end_selections)) = self.selection_history.transaction_mut(tx_id) { - *end_selections = Some(self.selections.clone()); + *end_selections = Some(self.selections.disjoint_anchors().clone()); } else { log::error!("unexpectedly ended a transaction that wasn't started by this editor"); } @@ -5547,8 +4999,8 @@ impl Editor { pub fn fold(&mut self, _: &Fold, cx: &mut ViewContext) { let mut fold_ranges = Vec::new(); - let selections = self.local_selections::(cx); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let selections = self.selections.all::(cx); for selection in selections { let range = selection.display_range(&display_map).sorted(); let buffer_start_row = range.start.to_point(&display_map).row; @@ -5570,9 +5022,9 @@ impl Editor { } pub fn unfold_lines(&mut self, _: &UnfoldLines, cx: &mut ViewContext) { - let selections = self.local_selections::(cx); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = &display_map.buffer_snapshot; + let selections = self.selections.all::(cx); let ranges = selections .iter() .map(|s| { @@ -5630,7 +5082,7 @@ impl Editor { } pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext) { - let selections = self.local_selections::(cx); + let selections = self.selections.all::(cx); let ranges = selections.into_iter().map(|s| s.start..s.end); self.fold_ranges(ranges, cx); } @@ -6007,7 +5459,7 @@ impl Editor { } let mut new_selections_by_buffer = HashMap::default(); - for selection in editor.local_selections::(cx) { + for selection in editor.selections.all::(cx) { for (buffer, mut range) in buffer.range_to_buffer_ranges(selection.start..selection.end, cx) { @@ -6022,7 +5474,7 @@ impl Editor { } editor_handle.update(cx, |editor, cx| { - editor.push_to_nav_history(editor.newest_anchor_selection().head(), None, cx); + editor.push_to_nav_history(editor.selections.newest_anchor().head(), None, cx); }); let nav_history = workspace.active_pane().read(cx).nav_history().clone(); nav_history.borrow_mut().disable(); @@ -6034,7 +5486,9 @@ impl Editor { for (buffer, ranges) in new_selections_by_buffer.into_iter() { let editor = workspace.open_project_item::(buffer, cx); editor.update(cx, |editor, cx| { - editor.select_ranges(ranges, Some(Autoscroll::Newest), cx); + editor.change_selections(Some(Autoscroll::Newest), cx, |s| { + s.select_ranges(ranges); + }); }); } @@ -6134,7 +5588,7 @@ impl View for Editor { self.buffer.update(cx, |buffer, cx| { buffer.finalize_last_transaction(cx); if self.leader_replica_id.is_none() { - buffer.set_active_selections(&self.selections, cx); + buffer.set_active_selections(&self.selections.disjoint_anchors(), cx); } }); } @@ -6571,7 +6025,7 @@ mod tests { use std::{cell::RefCell, rc::Rc, time::Instant}; use text::Point; use unindent::Unindent; - use util::test::{marked_text_by, marked_text_ranges, sample_text}; + use util::test::{marked_text_by, marked_text_ranges, marked_text_ranges_by, sample_text}; use workspace::{FollowableItem, ItemHandle}; #[gpui::test] @@ -6676,7 +6130,8 @@ mod tests { // No event is emitted when the mutation is a no-op. editor2.update(cx, |editor, cx| { - editor.select_ranges([0..0], None, cx); + editor.change_selections(None, cx, |s| s.select_ranges([0..0])); + editor.backspace(&Backspace, cx); }); assert_eq!(mem::take(&mut *events.borrow_mut()), []); @@ -6693,21 +6148,22 @@ mod tests { editor.update(cx, |editor, cx| { editor.start_transaction_at(now, cx); - editor.select_ranges([2..4], None, cx); + editor.change_selections(None, cx, |s| s.select_ranges([2..4])); + editor.insert("cd", cx); editor.end_transaction_at(now, cx); assert_eq!(editor.text(cx), "12cd56"); - assert_eq!(editor.selected_ranges(cx), vec![4..4]); + assert_eq!(editor.selections.ranges(cx), vec![4..4]); editor.start_transaction_at(now, cx); - editor.select_ranges([4..5], None, cx); + editor.change_selections(None, cx, |s| s.select_ranges([4..5])); editor.insert("e", cx); editor.end_transaction_at(now, cx); assert_eq!(editor.text(cx), "12cde6"); - assert_eq!(editor.selected_ranges(cx), vec![5..5]); + assert_eq!(editor.selections.ranges(cx), vec![5..5]); now += group_interval + Duration::from_millis(1); - editor.select_ranges([2..2], None, cx); + editor.change_selections(None, cx, |s| s.select_ranges([2..2])); // Simulate an edit in another editor buffer.update(cx, |buffer, cx| { @@ -6718,30 +6174,30 @@ mod tests { }); assert_eq!(editor.text(cx), "ab2cde6"); - assert_eq!(editor.selected_ranges(cx), vec![3..3]); + assert_eq!(editor.selections.ranges(cx), vec![3..3]); // Last transaction happened past the group interval in a different editor. // Undo it individually and don't restore selections. editor.undo(&Undo, cx); assert_eq!(editor.text(cx), "12cde6"); - assert_eq!(editor.selected_ranges(cx), vec![2..2]); + assert_eq!(editor.selections.ranges(cx), vec![2..2]); // First two transactions happened within the group interval in this editor. // Undo them together and restore selections. editor.undo(&Undo, cx); editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op. assert_eq!(editor.text(cx), "123456"); - assert_eq!(editor.selected_ranges(cx), vec![0..0]); + assert_eq!(editor.selections.ranges(cx), vec![0..0]); // Redo the first two transactions together. editor.redo(&Redo, cx); assert_eq!(editor.text(cx), "12cde6"); - assert_eq!(editor.selected_ranges(cx), vec![5..5]); + assert_eq!(editor.selections.ranges(cx), vec![5..5]); // Redo the last transaction on its own. editor.redo(&Redo, cx); assert_eq!(editor.text(cx), "ab2cde6"); - assert_eq!(editor.selected_ranges(cx), vec![6..6]); + assert_eq!(editor.selections.ranges(cx), vec![6..6]); // Test empty transactions. editor.start_transaction_at(now, cx); @@ -6761,7 +6217,7 @@ mod tests { view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx); }); assert_eq!( - editor.update(cx, |view, cx| view.selected_display_ranges(cx)), + editor.update(cx, |view, cx| view.selections.display_ranges(cx)), [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)] ); @@ -6770,7 +6226,7 @@ mod tests { }); assert_eq!( - editor.update(cx, |view, cx| view.selected_display_ranges(cx)), + editor.update(cx, |view, cx| view.selections.display_ranges(cx)), [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)] ); @@ -6779,7 +6235,7 @@ mod tests { }); assert_eq!( - editor.update(cx, |view, cx| view.selected_display_ranges(cx)), + editor.update(cx, |view, cx| view.selections.display_ranges(cx)), [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)] ); @@ -6789,7 +6245,7 @@ mod tests { }); assert_eq!( - editor.update(cx, |view, cx| view.selected_display_ranges(cx)), + editor.update(cx, |view, cx| view.selections.display_ranges(cx)), [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)] ); @@ -6799,7 +6255,7 @@ mod tests { }); assert_eq!( - editor.update(cx, |view, cx| view.selected_display_ranges(cx)), + editor.update(cx, |view, cx| view.selections.display_ranges(cx)), [ DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1), DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0) @@ -6811,7 +6267,7 @@ mod tests { }); assert_eq!( - editor.update(cx, |view, cx| view.selected_display_ranges(cx)), + editor.update(cx, |view, cx| view.selections.display_ranges(cx)), [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)] ); } @@ -6825,7 +6281,7 @@ mod tests { view.update(cx, |view, cx| { view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)] ); }); @@ -6833,7 +6289,7 @@ mod tests { view.update(cx, |view, cx| { view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)] ); }); @@ -6842,7 +6298,7 @@ mod tests { view.cancel(&Cancel, cx); view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)] ); }); @@ -6861,18 +6317,24 @@ mod tests { // Move the cursor a small distance. // Nothing is added to the navigation history. - editor.select_display_ranges(&[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)], cx); - editor.select_display_ranges(&[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)], cx); + editor.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]) + }); + editor.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)]) + }); assert!(nav_history.borrow_mut().pop_backward().is_none()); // Move the cursor a large distance. // The history can jump back to the previous position. - editor.select_display_ranges(&[DisplayPoint::new(13, 0)..DisplayPoint::new(13, 3)], cx); + editor.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(13, 0)..DisplayPoint::new(13, 3)]) + }); let nav_entry = nav_history.borrow_mut().pop_backward().unwrap(); editor.navigate(nav_entry.data.unwrap(), cx); assert_eq!(nav_entry.item.id(), cx.view_id()); assert_eq!( - editor.selected_display_ranges(cx), + editor.selections.display_ranges(cx), &[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)] ); assert!(nav_history.borrow_mut().pop_backward().is_none()); @@ -6882,7 +6344,7 @@ mod tests { editor.begin_selection(DisplayPoint::new(5, 0), false, 1, cx); editor.end_selection(cx); assert_eq!( - editor.selected_display_ranges(cx), + editor.selections.display_ranges(cx), &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)] ); assert!(nav_history.borrow_mut().pop_backward().is_none()); @@ -6892,14 +6354,14 @@ mod tests { editor.begin_selection(DisplayPoint::new(15, 0), false, 1, cx); editor.end_selection(cx); assert_eq!( - editor.selected_display_ranges(cx), + editor.selections.display_ranges(cx), &[DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)] ); let nav_entry = nav_history.borrow_mut().pop_backward().unwrap(); editor.navigate(nav_entry.data.unwrap(), cx); assert_eq!(nav_entry.item.id(), cx.view_id()); assert_eq!( - editor.selected_display_ranges(cx), + editor.selections.display_ranges(cx), &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)] ); assert!(nav_history.borrow_mut().pop_backward().is_none()); @@ -6935,7 +6397,7 @@ mod tests { cx, ); assert_eq!( - editor.selected_display_ranges(cx), + editor.selections.display_ranges(cx), &[editor.max_point(cx)..editor.max_point(cx)] ); assert_eq!( @@ -6962,7 +6424,7 @@ mod tests { view.update_selection(DisplayPoint::new(0, 3), 0, Vector2F::zero(), cx); view.end_selection(cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), [ DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3), DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1), @@ -6973,7 +6435,7 @@ mod tests { view.update(cx, |view, cx| { view.cancel(&Cancel, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), [DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1)] ); }); @@ -6981,7 +6443,7 @@ mod tests { view.update(cx, |view, cx| { view.cancel(&Cancel, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), [DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1)] ); }); @@ -7014,7 +6476,9 @@ mod tests { let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx)); view.update(cx, |view, cx| { - view.select_display_ranges(&[DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)], cx); + view.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)]); + }); view.fold(&Fold, cx); assert_eq!( view.display_text(cx), @@ -7090,56 +6554,58 @@ mod tests { view.update(cx, |view, cx| { assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)] ); view.move_down(&MoveDown, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)] ); view.move_right(&MoveRight, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4)] ); view.move_left(&MoveLeft, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)] ); view.move_up(&MoveUp, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)] ); view.move_to_end(&MoveToEnd, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[DisplayPoint::new(5, 6)..DisplayPoint::new(5, 6)] ); view.move_to_beginning(&MoveToBeginning, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)] ); - view.select_display_ranges(&[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)], cx); + view.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)]); + }); view.select_to_beginning(&SelectToBeginning, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 0)] ); view.select_to_end(&SelectToEnd, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[DisplayPoint::new(0, 1)..DisplayPoint::new(5, 6)] ); }); @@ -7167,80 +6633,80 @@ mod tests { view.move_right(&MoveRight, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[empty_range(0, "ⓐ".len())] ); view.move_right(&MoveRight, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[empty_range(0, "ⓐⓑ".len())] ); view.move_right(&MoveRight, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[empty_range(0, "ⓐⓑ…".len())] ); view.move_down(&MoveDown, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[empty_range(1, "ab…".len())] ); view.move_left(&MoveLeft, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[empty_range(1, "ab".len())] ); view.move_left(&MoveLeft, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[empty_range(1, "a".len())] ); view.move_down(&MoveDown, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[empty_range(2, "α".len())] ); view.move_right(&MoveRight, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[empty_range(2, "αβ".len())] ); view.move_right(&MoveRight, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[empty_range(2, "αβ…".len())] ); view.move_right(&MoveRight, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[empty_range(2, "αβ…ε".len())] ); view.move_up(&MoveUp, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[empty_range(1, "ab…e".len())] ); view.move_up(&MoveUp, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[empty_range(0, "ⓐⓑ…ⓔ".len())] ); view.move_left(&MoveLeft, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[empty_range(0, "ⓐⓑ…".len())] ); view.move_left(&MoveLeft, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[empty_range(0, "ⓐⓑ".len())] ); view.move_left(&MoveLeft, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[empty_range(0, "ⓐ".len())] ); }); @@ -7252,40 +6718,42 @@ mod tests { let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx); let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx)); view.update(cx, |view, cx| { - view.select_display_ranges(&[empty_range(0, "ⓐⓑⓒⓓⓔ".len())], cx); + view.change_selections(None, cx, |s| { + s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]); + }); view.move_down(&MoveDown, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[empty_range(1, "abcd".len())] ); view.move_down(&MoveDown, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[empty_range(2, "αβγ".len())] ); view.move_down(&MoveDown, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[empty_range(3, "abcd".len())] ); view.move_down(&MoveDown, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())] ); view.move_up(&MoveUp, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[empty_range(3, "abcd".len())] ); view.move_up(&MoveUp, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[empty_range(2, "αβγ".len())] ); }); @@ -7297,19 +6765,18 @@ mod tests { let buffer = MultiBuffer::build_simple("abc\n def", cx); let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx)); view.update(cx, |view, cx| { - view.select_display_ranges( - &[ + view.change_selections(None, cx, |s| { + s.select_display_ranges([ DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1), DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4), - ], - cx, - ); + ]); + }); }); view.update(cx, |view, cx| { view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[ DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0), DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2), @@ -7320,7 +6787,7 @@ mod tests { view.update(cx, |view, cx| { view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[ DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0), DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0), @@ -7331,7 +6798,7 @@ mod tests { view.update(cx, |view, cx| { view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[ DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0), DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2), @@ -7342,7 +6809,7 @@ mod tests { view.update(cx, |view, cx| { view.move_to_end_of_line(&MoveToEndOfLine, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[ DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3), DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5), @@ -7354,7 +6821,7 @@ mod tests { view.update(cx, |view, cx| { view.move_to_end_of_line(&MoveToEndOfLine, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[ DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3), DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5), @@ -7371,7 +6838,7 @@ mod tests { cx, ); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[ DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0), DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2), @@ -7387,7 +6854,7 @@ mod tests { cx, ); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[ DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0), DisplayPoint::new(1, 4)..DisplayPoint::new(1, 0), @@ -7403,7 +6870,7 @@ mod tests { cx, ); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[ DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0), DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2), @@ -7419,7 +6886,7 @@ mod tests { cx, ); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[ DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3), DisplayPoint::new(1, 4)..DisplayPoint::new(1, 5), @@ -7431,7 +6898,7 @@ mod tests { view.delete_to_end_of_line(&DeleteToEndOfLine, cx); assert_eq!(view.display_text(cx), "ab\n de"); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[ DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2), DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4), @@ -7443,7 +6910,7 @@ mod tests { view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx); assert_eq!(view.display_text(cx), "\n"); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[ DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0), DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0), @@ -7458,13 +6925,12 @@ mod tests { let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx); let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx)); view.update(cx, |view, cx| { - view.select_display_ranges( - &[ + view.change_selections(None, cx, |s| { + s.select_display_ranges([ DisplayPoint::new(0, 11)..DisplayPoint::new(0, 11), DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4), - ], - cx, - ); + ]) + }); view.move_to_previous_word_start(&MoveToPreviousWordStart, cx); assert_selection_ranges( @@ -7570,41 +7036,43 @@ mod tests { "use one::{\n two::three::\n four::five\n};" ); - view.select_display_ranges(&[DisplayPoint::new(1, 7)..DisplayPoint::new(1, 7)], cx); + view.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(1, 7)..DisplayPoint::new(1, 7)]); + }); view.move_to_next_word_end(&MoveToNextWordEnd, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[DisplayPoint::new(1, 9)..DisplayPoint::new(1, 9)] ); view.move_to_next_word_end(&MoveToNextWordEnd, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)] ); view.move_to_next_word_end(&MoveToNextWordEnd, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)] ); view.move_to_next_word_end(&MoveToNextWordEnd, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[DisplayPoint::new(2, 8)..DisplayPoint::new(2, 8)] ); view.move_to_previous_word_start(&MoveToPreviousWordStart, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)] ); view.move_to_previous_word_start(&MoveToPreviousWordStart, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)] ); }); @@ -7619,7 +7087,7 @@ mod tests { let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx)); editor.update(cx, |editor, cx| { - editor.select_ranges(ranges, None, cx); + editor.change_selections(None, cx, |s| s.select_ranges(ranges)); editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx); assert_eq!(editor.text(cx), " four"); }); @@ -7632,30 +7100,28 @@ mod tests { let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx)); view.update(cx, |view, cx| { - view.select_display_ranges( - &[ + view.change_selections(None, cx, |s| { + s.select_display_ranges([ // an empty selection - the preceding word fragment is deleted DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2), // characters selected - they are deleted DisplayPoint::new(0, 9)..DisplayPoint::new(0, 12), - ], - cx, - ); + ]) + }); view.delete_to_previous_word_start(&DeleteToPreviousWordStart, cx); }); assert_eq!(buffer.read(cx).read(cx).text(), "e two te four"); view.update(cx, |view, cx| { - view.select_display_ranges( - &[ + view.change_selections(None, cx, |s| { + s.select_display_ranges([ // an empty selection - the following word fragment is deleted DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3), // characters selected - they are deleted DisplayPoint::new(0, 9)..DisplayPoint::new(0, 10), - ], - cx, - ); + ]) + }); view.delete_to_next_word_end(&DeleteToNextWordEnd, cx); }); @@ -7669,14 +7135,13 @@ mod tests { let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx)); view.update(cx, |view, cx| { - view.select_display_ranges( - &[ + view.change_selections(None, cx, |s| { + s.select_display_ranges([ DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2), DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2), DisplayPoint::new(1, 6)..DisplayPoint::new(1, 6), - ], - cx, - ); + ]) + }); view.newline(&Newline, cx); assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n"); @@ -7703,14 +7168,12 @@ mod tests { let (_, editor) = cx.add_window(Default::default(), |cx| { let mut editor = build_editor(buffer.clone(), cx); - editor.select_ranges( - [ + editor.change_selections(None, cx, |s| { + s.select_ranges([ Point::new(2, 4)..Point::new(2, 5), Point::new(5, 4)..Point::new(5, 5), - ], - None, - cx, - ); + ]) + }); editor }); @@ -7736,7 +7199,7 @@ mod tests { editor.update(cx, |editor, cx| { assert_eq!( - editor.selected_ranges(cx), + editor.selections.ranges(cx), &[ Point::new(1, 2)..Point::new(1, 2), Point::new(2, 2)..Point::new(2, 2), @@ -7758,7 +7221,7 @@ mod tests { // The selections are moved after the inserted newlines assert_eq!( - editor.selected_ranges(cx), + editor.selections.ranges(cx), &[ Point::new(2, 0)..Point::new(2, 0), Point::new(4, 0)..Point::new(4, 0), @@ -7773,7 +7236,7 @@ mod tests { let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx); let (_, editor) = cx.add_window(Default::default(), |cx| { let mut editor = build_editor(buffer.clone(), cx); - editor.select_ranges([3..4, 11..12, 19..20], None, cx); + editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20])); editor }); @@ -7784,13 +7247,13 @@ mod tests { }); editor.update(cx, |editor, cx| { - assert_eq!(editor.selected_ranges(cx), &[2..2, 7..7, 12..12],); + assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],); editor.insert("Z", cx); assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)"); // The selections are moved after the inserted characters - assert_eq!(editor.selected_ranges(cx), &[3..3, 9..9, 15..15],); + assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],); }); } @@ -8022,23 +7485,22 @@ mod tests { view.update(cx, |view, cx| { view.set_text("one two three\nfour five six\nseven eight nine\nten\n", cx); - view.select_display_ranges( - &[ + view.change_selections(None, cx, |s| { + s.select_display_ranges([ // an empty selection - the preceding character is deleted DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2), // one character selected - it is deleted DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3), // a line suffix selected - it is deleted DisplayPoint::new(2, 6)..DisplayPoint::new(3, 0), - ], - cx, - ); + ]) + }); view.backspace(&Backspace, cx); assert_eq!(view.text(cx), "oe two three\nfou five six\nseven ten\n"); view.set_text(" one\n two\n three\n four", cx); - view.select_display_ranges( - &[ + view.change_selections(None, cx, |s| { + s.select_display_ranges([ // cursors at the the end of leading indent - last indent is deleted DisplayPoint::new(0, 4)..DisplayPoint::new(0, 4), DisplayPoint::new(1, 8)..DisplayPoint::new(1, 8), @@ -8050,9 +7512,8 @@ mod tests { DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0), // selection inside leading indent - only the selected character is deleted DisplayPoint::new(3, 2)..DisplayPoint::new(3, 3), - ], - cx, - ); + ]) + }); view.backspace(&Backspace, cx); assert_eq!(view.text(cx), "one\n two\n three four"); }); @@ -8066,17 +7527,16 @@ mod tests { let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx)); view.update(cx, |view, cx| { - view.select_display_ranges( - &[ + view.change_selections(None, cx, |s| { + s.select_display_ranges([ // an empty selection - the following character is deleted DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2), // one character selected - it is deleted DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3), // a line suffix selected - it is deleted DisplayPoint::new(2, 6)..DisplayPoint::new(3, 0), - ], - cx, - ); + ]) + }); view.delete(&Delete, cx); }); @@ -8092,18 +7552,17 @@ mod tests { let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx)); view.update(cx, |view, cx| { - view.select_display_ranges( - &[ + view.change_selections(None, cx, |s| { + s.select_display_ranges([ DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1), DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1), DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0), - ], - cx, - ); + ]) + }); view.delete_line(&DeleteLine, cx); assert_eq!(view.display_text(cx), "ghi"); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), vec![ DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0), DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1) @@ -8115,11 +7574,13 @@ mod tests { let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx)); view.update(cx, |view, cx| { - view.select_display_ranges(&[DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)], cx); + view.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)]) + }); view.delete_line(&DeleteLine, cx); assert_eq!(view.display_text(cx), "ghi\n"); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), vec![DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)] ); }); @@ -8131,19 +7592,18 @@ mod tests { let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx)); view.update(cx, |view, cx| { - view.select_display_ranges( - &[ + view.change_selections(None, cx, |s| { + s.select_display_ranges([ DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1), DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2), DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0), DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0), - ], - cx, - ); + ]) + }); view.duplicate_line(&DuplicateLine, cx); assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n"); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), vec![ DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1), DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2), @@ -8156,17 +7616,16 @@ mod tests { let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx)); view.update(cx, |view, cx| { - view.select_display_ranges( - &[ + view.change_selections(None, cx, |s| { + s.select_display_ranges([ DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1), DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1), - ], - cx, - ); + ]) + }); view.duplicate_line(&DuplicateLine, cx); assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n"); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), vec![ DisplayPoint::new(3, 1)..DisplayPoint::new(4, 1), DisplayPoint::new(4, 2)..DisplayPoint::new(5, 1), @@ -8189,15 +7648,14 @@ mod tests { ], cx, ); - view.select_display_ranges( - &[ + view.change_selections(None, cx, |s| { + s.select_display_ranges([ DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1), DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1), DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3), DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2), - ], - cx, - ); + ]) + }); assert_eq!( view.display_text(cx), "aa…bbb\nccc…eeee\nfffff\nggggg\n…i\njjjjj" @@ -8209,7 +7667,7 @@ mod tests { "aa…bbb\nccc…eeee\nggggg\n…i\njjjjj\nfffff" ); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), vec![ DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1), DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1), @@ -8226,7 +7684,7 @@ mod tests { "ccc…eeee\naa…bbb\nfffff\nggggg\n…i\njjjjj" ); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), vec![ DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1), DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1), @@ -8243,7 +7701,7 @@ mod tests { "ccc…eeee\nfffff\naa…bbb\nggggg\n…i\njjjjj" ); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), vec![ DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1), DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1), @@ -8260,7 +7718,7 @@ mod tests { "ccc…eeee\naa…bbb\nggggg\n…i\njjjjj\nfffff" ); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), vec![ DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1), DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1), @@ -8287,7 +7745,9 @@ mod tests { }], cx, ); - editor.select_ranges([Point::new(2, 0)..Point::new(2, 0)], None, cx); + editor.change_selections(None, cx, |s| { + s.select_ranges([Point::new(2, 0)..Point::new(2, 0)]) + }); editor.move_line_down(&MoveLineDown, cx); }); } @@ -8299,18 +7759,18 @@ mod tests { cx.add_window(Default::default(), |cx| { let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx); - editor.select_ranges([1..1], None, cx); + editor.change_selections(None, cx, |s| s.select_ranges([1..1])); editor.transpose(&Default::default(), cx); assert_eq!(editor.text(cx), "bac"); - assert_eq!(editor.selected_ranges(cx), [2..2]); + assert_eq!(editor.selections.ranges(cx), [2..2]); editor.transpose(&Default::default(), cx); assert_eq!(editor.text(cx), "bca"); - assert_eq!(editor.selected_ranges(cx), [3..3]); + assert_eq!(editor.selections.ranges(cx), [3..3]); editor.transpose(&Default::default(), cx); assert_eq!(editor.text(cx), "bac"); - assert_eq!(editor.selected_ranges(cx), [3..3]); + assert_eq!(editor.selections.ranges(cx), [3..3]); editor }) @@ -8319,23 +7779,23 @@ mod tests { cx.add_window(Default::default(), |cx| { let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx); - editor.select_ranges([3..3], None, cx); + editor.change_selections(None, cx, |s| s.select_ranges([3..3])); editor.transpose(&Default::default(), cx); assert_eq!(editor.text(cx), "acb\nde"); - assert_eq!(editor.selected_ranges(cx), [3..3]); + assert_eq!(editor.selections.ranges(cx), [3..3]); - editor.select_ranges([4..4], None, cx); + editor.change_selections(None, cx, |s| s.select_ranges([4..4])); editor.transpose(&Default::default(), cx); assert_eq!(editor.text(cx), "acbd\ne"); - assert_eq!(editor.selected_ranges(cx), [5..5]); + assert_eq!(editor.selections.ranges(cx), [5..5]); editor.transpose(&Default::default(), cx); assert_eq!(editor.text(cx), "acbde\n"); - assert_eq!(editor.selected_ranges(cx), [6..6]); + assert_eq!(editor.selections.ranges(cx), [6..6]); editor.transpose(&Default::default(), cx); assert_eq!(editor.text(cx), "acbd\ne"); - assert_eq!(editor.selected_ranges(cx), [6..6]); + assert_eq!(editor.selections.ranges(cx), [6..6]); editor }) @@ -8344,26 +7804,26 @@ mod tests { cx.add_window(Default::default(), |cx| { let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx); - editor.select_ranges([1..1, 2..2, 4..4], None, cx); + editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4])); editor.transpose(&Default::default(), cx); assert_eq!(editor.text(cx), "bacd\ne"); - assert_eq!(editor.selected_ranges(cx), [2..2, 3..3, 5..5]); + assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]); editor.transpose(&Default::default(), cx); assert_eq!(editor.text(cx), "bcade\n"); - assert_eq!(editor.selected_ranges(cx), [3..3, 4..4, 6..6]); + assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]); editor.transpose(&Default::default(), cx); assert_eq!(editor.text(cx), "bcda\ne"); - assert_eq!(editor.selected_ranges(cx), [4..4, 6..6]); + assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]); editor.transpose(&Default::default(), cx); assert_eq!(editor.text(cx), "bcade\n"); - assert_eq!(editor.selected_ranges(cx), [4..4, 6..6]); + assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]); editor.transpose(&Default::default(), cx); assert_eq!(editor.text(cx), "bcaed\n"); - assert_eq!(editor.selected_ranges(cx), [5..5, 6..6]); + assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]); editor }) @@ -8372,18 +7832,18 @@ mod tests { cx.add_window(Default::default(), |cx| { let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx); - editor.select_ranges([4..4], None, cx); + editor.change_selections(None, cx, |s| s.select_ranges([4..4])); editor.transpose(&Default::default(), cx); assert_eq!(editor.text(cx), "🏀🍐✋"); - assert_eq!(editor.selected_ranges(cx), [8..8]); + assert_eq!(editor.selections.ranges(cx), [8..8]); editor.transpose(&Default::default(), cx); assert_eq!(editor.text(cx), "🏀✋🍐"); - assert_eq!(editor.selected_ranges(cx), [11..11]); + assert_eq!(editor.selections.ranges(cx), [11..11]); editor.transpose(&Default::default(), cx); assert_eq!(editor.text(cx), "🏀🍐✋"); - assert_eq!(editor.selected_ranges(cx), [11..11]); + assert_eq!(editor.selections.ranges(cx), [11..11]); editor }) @@ -8400,18 +7860,18 @@ mod tests { // Cut with three selections. Clipboard text is divided into three slices. view.update(cx, |view, cx| { - view.select_ranges(vec![0..7, 11..17, 22..27], None, cx); + view.change_selections(None, cx, |s| s.select_ranges(vec![0..7, 11..17, 22..27])); view.cut(&Cut, cx); assert_eq!(view.display_text(cx), "two four six "); }); // Paste with three cursors. Each cursor pastes one slice of the clipboard text. view.update(cx, |view, cx| { - view.select_ranges(vec![4..4, 9..9, 13..13], None, cx); + view.change_selections(None, cx, |s| s.select_ranges(vec![4..4, 9..9, 13..13])); view.paste(&Paste, cx); assert_eq!(view.display_text(cx), "two one✅ four three six five "); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[ DisplayPoint::new(0, 11)..DisplayPoint::new(0, 11), DisplayPoint::new(0, 22)..DisplayPoint::new(0, 22), @@ -8424,7 +7884,7 @@ mod tests { // match the number of slices in the clipboard, the entire clipboard text // is pasted at each cursor. view.update(cx, |view, cx| { - view.select_ranges(vec![0..0, 31..31], None, cx); + view.change_selections(None, cx, |s| s.select_ranges(vec![0..0, 31..31])); view.handle_input(&Input("( ".into()), cx); view.paste(&Paste, cx); view.handle_input(&Input(") ".into()), cx); @@ -8435,7 +7895,7 @@ mod tests { }); view.update(cx, |view, cx| { - view.select_ranges(vec![0..0], None, cx); + view.change_selections(None, cx, |s| s.select_ranges(vec![0..0])); view.handle_input(&Input("123\n4567\n89\n".into()), cx); assert_eq!( view.display_text(cx), @@ -8445,14 +7905,13 @@ mod tests { // Cut with three selections, one of which is full-line. view.update(cx, |view, cx| { - view.select_display_ranges( - &[ + view.change_selections(None, cx, |s| s.select_display_ranges( + [ DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2), DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1), DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1), ], - cx, - ); + )); view.cut(&Cut, cx); assert_eq!( view.display_text(cx), @@ -8463,21 +7922,20 @@ mod tests { // Paste with three selections, noticing how the copied selection that was full-line // gets inserted before the second cursor. view.update(cx, |view, cx| { - view.select_display_ranges( - &[ + view.change_selections(None, cx, |s| s.select_display_ranges( + [ DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1), DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1), DisplayPoint::new(2, 2)..DisplayPoint::new(2, 3), ], - cx, - ); + )); view.paste(&Paste, cx); assert_eq!( view.display_text(cx), "123\n4567\n9\n( 8ne✅ \nthree \nfive ) two one✅ four three six five ( one✅ \nthree \nfive ) " ); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[ DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2), DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1), @@ -8488,28 +7946,29 @@ mod tests { // Copy with a single cursor only, which writes the whole line into the clipboard. view.update(cx, |view, cx| { - view.select_display_ranges(&[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)], cx); + view.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)]) + }); view.copy(&Copy, cx); }); // Paste with three selections, noticing how the copied full-line selection is inserted // before the empty selections but replaces the selection that is non-empty. view.update(cx, |view, cx| { - view.select_display_ranges( - &[ + view.change_selections(None, cx, |s| s.select_display_ranges( + [ DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1), DisplayPoint::new(1, 0)..DisplayPoint::new(1, 2), DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1), ], - cx, - ); + )); view.paste(&Paste, cx); assert_eq!( view.display_text(cx), "123\n123\n123\n67\n123\n9\n( 8ne✅ \nthree \nfive ) two one✅ four three six five ( one✅ \nthree \nfive ) " ); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[ DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1), DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0), @@ -8527,7 +7986,7 @@ mod tests { view.update(cx, |view, cx| { view.select_all(&SelectAll, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &[DisplayPoint::new(0, 0)..DisplayPoint::new(2, 3)] ); }); @@ -8539,18 +7998,17 @@ mod tests { let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx); let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx)); view.update(cx, |view, cx| { - view.select_display_ranges( - &[ + view.change_selections(None, cx, |s| { + s.select_display_ranges([ DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1), DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2), DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0), DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2), - ], - cx, - ); + ]) + }); view.select_line(&SelectLine, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), vec![ DisplayPoint::new(0, 0)..DisplayPoint::new(2, 0), DisplayPoint::new(4, 0)..DisplayPoint::new(5, 0), @@ -8561,7 +8019,7 @@ mod tests { view.update(cx, |view, cx| { view.select_line(&SelectLine, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), vec![ DisplayPoint::new(0, 0)..DisplayPoint::new(3, 0), DisplayPoint::new(4, 0)..DisplayPoint::new(5, 5), @@ -8572,7 +8030,7 @@ mod tests { view.update(cx, |view, cx| { view.select_line(&SelectLine, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)] ); }); @@ -8592,15 +8050,14 @@ mod tests { ], cx, ); - view.select_display_ranges( - &[ + view.change_selections(None, cx, |s| { + s.select_display_ranges([ DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1), DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2), DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0), DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4), - ], - cx, - ); + ]) + }); assert_eq!(view.display_text(cx), "aa…bbb\nccc…eeee\nfffff\nggggg\n…i"); }); @@ -8611,7 +8068,7 @@ mod tests { "aaaaa\nbbbbb\nccc…eeee\nfffff\nggggg\n…i" ); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), [ DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1), DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2), @@ -8622,14 +8079,16 @@ mod tests { }); view.update(cx, |view, cx| { - view.select_display_ranges(&[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 1)], cx); + view.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(5, 0)..DisplayPoint::new(0, 1)]) + }); view.split_selection_into_lines(&SplitSelectionIntoLines, cx); assert_eq!( view.display_text(cx), "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii" ); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), [ DisplayPoint::new(0, 5)..DisplayPoint::new(0, 5), DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5), @@ -8651,12 +8110,14 @@ mod tests { let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx)); view.update(cx, |view, cx| { - view.select_display_ranges(&[DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)], cx); + view.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)]) + }); }); view.update(cx, |view, cx| { view.add_selection_above(&AddSelectionAbove, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), vec![ DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3), DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3) @@ -8667,7 +8128,7 @@ mod tests { view.update(cx, |view, cx| { view.add_selection_above(&AddSelectionAbove, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), vec![ DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3), DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3) @@ -8678,13 +8139,13 @@ mod tests { view.update(cx, |view, cx| { view.add_selection_below(&AddSelectionBelow, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)] ); view.undo_selection(&UndoSelection, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), vec![ DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3), DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3) @@ -8693,7 +8154,7 @@ mod tests { view.redo_selection(&RedoSelection, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)] ); }); @@ -8701,7 +8162,7 @@ mod tests { view.update(cx, |view, cx| { view.add_selection_below(&AddSelectionBelow, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), vec![ DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3), DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3) @@ -8712,7 +8173,7 @@ mod tests { view.update(cx, |view, cx| { view.add_selection_below(&AddSelectionBelow, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), vec![ DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3), DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3) @@ -8721,12 +8182,14 @@ mod tests { }); view.update(cx, |view, cx| { - view.select_display_ranges(&[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)], cx); + view.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]) + }); }); view.update(cx, |view, cx| { view.add_selection_below(&AddSelectionBelow, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), vec![ DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3), DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3) @@ -8737,7 +8200,7 @@ mod tests { view.update(cx, |view, cx| { view.add_selection_below(&AddSelectionBelow, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), vec![ DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3), DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3) @@ -8748,7 +8211,7 @@ mod tests { view.update(cx, |view, cx| { view.add_selection_above(&AddSelectionAbove, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)] ); }); @@ -8756,16 +8219,18 @@ mod tests { view.update(cx, |view, cx| { view.add_selection_above(&AddSelectionAbove, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)] ); }); view.update(cx, |view, cx| { - view.select_display_ranges(&[DisplayPoint::new(0, 1)..DisplayPoint::new(1, 4)], cx); + view.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(1, 4)]) + }); view.add_selection_below(&AddSelectionBelow, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), vec![ DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3), DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4), @@ -8777,7 +8242,7 @@ mod tests { view.update(cx, |view, cx| { view.add_selection_below(&AddSelectionBelow, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), vec![ DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3), DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4), @@ -8790,7 +8255,7 @@ mod tests { view.update(cx, |view, cx| { view.add_selection_above(&AddSelectionAbove, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), vec![ DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3), DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4), @@ -8800,12 +8265,14 @@ mod tests { }); view.update(cx, |view, cx| { - view.select_display_ranges(&[DisplayPoint::new(4, 3)..DisplayPoint::new(1, 1)], cx); + view.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(4, 3)..DisplayPoint::new(1, 1)]) + }); }); view.update(cx, |view, cx| { view.add_selection_above(&AddSelectionAbove, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), vec![ DisplayPoint::new(0, 3)..DisplayPoint::new(0, 1), DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1), @@ -8818,7 +8285,7 @@ mod tests { view.update(cx, |view, cx| { view.add_selection_below(&AddSelectionBelow, cx); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), vec![ DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1), DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1), @@ -8837,14 +8304,16 @@ mod tests { let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx)); view.update(cx, |view, cx| { - view.select_ranges([ranges[1].start + 1..ranges[1].start + 1], None, cx); + view.change_selections(None, cx, |s| { + s.select_ranges([ranges[1].start + 1..ranges[1].start + 1]) + }); view.select_next( &SelectNext { replace_newest: false, }, cx, ); - assert_eq!(view.selected_ranges(cx), &ranges[1..2]); + assert_eq!(view.selections.ranges(cx), &ranges[1..2]); view.select_next( &SelectNext { @@ -8852,13 +8321,13 @@ mod tests { }, cx, ); - assert_eq!(view.selected_ranges(cx), &ranges[1..3]); + assert_eq!(view.selections.ranges(cx), &ranges[1..3]); view.undo_selection(&UndoSelection, cx); - assert_eq!(view.selected_ranges(cx), &ranges[1..2]); + assert_eq!(view.selections.ranges(cx), &ranges[1..2]); view.redo_selection(&RedoSelection, cx); - assert_eq!(view.selected_ranges(cx), &ranges[1..3]); + assert_eq!(view.selections.ranges(cx), &ranges[1..3]); view.select_next( &SelectNext { @@ -8866,7 +8335,7 @@ mod tests { }, cx, ); - assert_eq!(view.selected_ranges(cx), &ranges[1..4]); + assert_eq!(view.selections.ranges(cx), &ranges[1..4]); view.select_next( &SelectNext { @@ -8874,7 +8343,7 @@ mod tests { }, cx, ); - assert_eq!(view.selected_ranges(cx), &ranges[0..4]); + assert_eq!(view.selections.ranges(cx), &ranges[0..4]); }); } @@ -8902,18 +8371,17 @@ mod tests { .await; view.update(cx, |view, cx| { - view.select_display_ranges( - &[ + view.change_selections(None, cx, |s| { + s.select_display_ranges([ DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25), DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12), DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18), - ], - cx, - ); + ]); + }); view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx); }); assert_eq!( - view.update(cx, |view, cx| view.selected_display_ranges(cx)), + view.update(cx, |view, cx| { view.selections.display_ranges(cx) }), &[ DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27), DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7), @@ -8925,7 +8393,7 @@ mod tests { view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx); }); assert_eq!( - view.update(cx, |view, cx| view.selected_display_ranges(cx)), + view.update(cx, |view, cx| view.selections.display_ranges(cx)), &[ DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28), DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0), @@ -8936,7 +8404,7 @@ mod tests { view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx); }); assert_eq!( - view.update(cx, |view, cx| view.selected_display_ranges(cx)), + view.update(cx, |view, cx| view.selections.display_ranges(cx)), &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)] ); @@ -8945,7 +8413,7 @@ mod tests { view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx); }); assert_eq!( - view.update(cx, |view, cx| view.selected_display_ranges(cx)), + view.update(cx, |view, cx| view.selections.display_ranges(cx)), &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)] ); @@ -8953,7 +8421,7 @@ mod tests { view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx); }); assert_eq!( - view.update(cx, |view, cx| view.selected_display_ranges(cx)), + view.update(cx, |view, cx| view.selections.display_ranges(cx)), &[ DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28), DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0), @@ -8964,7 +8432,7 @@ mod tests { view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx); }); assert_eq!( - view.update(cx, |view, cx| view.selected_display_ranges(cx)), + view.update(cx, |view, cx| view.selections.display_ranges(cx)), &[ DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27), DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7), @@ -8976,7 +8444,7 @@ mod tests { view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx); }); assert_eq!( - view.update(cx, |view, cx| view.selected_display_ranges(cx)), + view.update(cx, |view, cx| view.selections.display_ranges(cx)), &[ DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25), DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12), @@ -8989,7 +8457,7 @@ mod tests { view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx); }); assert_eq!( - view.update(cx, |view, cx| view.selected_display_ranges(cx)), + view.update(cx, |view, cx| view.selections.display_ranges(cx)), &[ DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25), DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12), @@ -9010,7 +8478,7 @@ mod tests { view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx); }); assert_eq!( - view.update(cx, |view, cx| view.selected_display_ranges(cx)), + view.update(cx, |view, cx| view.selections.display_ranges(cx)), &[ DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28), DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7), @@ -9062,11 +8530,11 @@ mod tests { .await; editor.update(cx, |editor, cx| { - editor.select_ranges([5..5, 8..8, 9..9], None, cx); + editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9])); editor.newline(&Newline, cx); assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n"); assert_eq!( - editor.selected_ranges(cx), + editor.selections.ranges(cx), &[ Point::new(1, 4)..Point::new(1, 4), Point::new(3, 4)..Point::new(3, 4), @@ -9116,13 +8584,12 @@ mod tests { .await; view.update(cx, |view, cx| { - view.select_display_ranges( - &[ + view.change_selections(None, cx, |s| { + s.select_display_ranges([ DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1), DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0), - ], - cx, - ); + ]) + }); view.handle_input(&Input("{".to_string()), cx); view.handle_input(&Input("{".to_string()), cx); @@ -9168,13 +8635,12 @@ mod tests { ); view.undo(&Undo, cx); - view.select_display_ranges( - &[ + view.change_selections(None, cx, |s| { + s.select_display_ranges([ DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1), DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0), - ], - cx, - ); + ]) + }); view.handle_input(&Input("*".to_string()), cx); assert_eq!( view.text(cx), @@ -9190,7 +8656,9 @@ mod tests { // Don't autoclose if the next character isn't whitespace and isn't // listed in the language's "autoclose_before" section. view.finalize_last_transaction(cx); - view.select_display_ranges(&[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)], cx); + view.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]) + }); view.handle_input(&Input("{".to_string()), cx); assert_eq!( view.text(cx), @@ -9204,7 +8672,9 @@ mod tests { ); view.undo(&Undo, cx); - view.select_display_ranges(&[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1)], cx); + view.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1)]) + }); view.handle_input(&Input("{".to_string()), cx); assert_eq!( view.text(cx), @@ -9217,7 +8687,7 @@ mod tests { .unindent() ); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), [DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)] ); }); @@ -9227,105 +8697,96 @@ mod tests { async fn test_snippets(cx: &mut gpui::TestAppContext) { cx.update(|cx| cx.set_global(Settings::test(cx))); - let text = " - a. b - a. b - a. b - " - .unindent(); + let (text, insertion_ranges) = marked_text_ranges(indoc! {" + a.| b + a.| b + a.| b"}); let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx)); let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx)); editor.update(cx, |editor, cx| { - let buffer = &editor.snapshot(cx).buffer_snapshot; let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap(); - let insertion_ranges = [ - Point::new(0, 2).to_offset(buffer)..Point::new(0, 2).to_offset(buffer), - Point::new(1, 2).to_offset(buffer)..Point::new(1, 2).to_offset(buffer), - Point::new(2, 2).to_offset(buffer)..Point::new(2, 2).to_offset(buffer), - ]; editor .insert_snippet(&insertion_ranges, snippet, cx) .unwrap(); - assert_eq!( - editor.text(cx), - " - a.f(one, two, three) b - a.f(one, two, three) b - a.f(one, two, three) b - " - .unindent() - ); - assert_eq!( - editor.selected_ranges::(cx), - &[ - Point::new(0, 4)..Point::new(0, 7), - Point::new(0, 14)..Point::new(0, 19), - Point::new(1, 4)..Point::new(1, 7), - Point::new(1, 14)..Point::new(1, 19), - Point::new(2, 4)..Point::new(2, 7), - Point::new(2, 14)..Point::new(2, 19), - ] + + fn assert(editor: &mut Editor, cx: &mut ViewContext, marked_text_ranges: &str) { + let range_markers = ('<', '>'); + let (expected_text, mut selection_ranges_lookup) = + marked_text_ranges_by(marked_text_ranges, vec![range_markers.clone()]); + let selection_ranges = selection_ranges_lookup.remove(&range_markers).unwrap(); + assert_eq!(editor.text(cx), expected_text); + assert_eq!(editor.selections.ranges::(cx), selection_ranges); + } + assert( + editor, + cx, + indoc! {" + a.f(, two, ) b + a.f(, two, ) b + a.f(, two, ) b"}, ); // Can't move earlier than the first tab stop - editor.move_to_prev_snippet_tabstop(cx); - assert_eq!( - editor.selected_ranges::(cx), - &[ - Point::new(0, 4)..Point::new(0, 7), - Point::new(0, 14)..Point::new(0, 19), - Point::new(1, 4)..Point::new(1, 7), - Point::new(1, 14)..Point::new(1, 19), - Point::new(2, 4)..Point::new(2, 7), - Point::new(2, 14)..Point::new(2, 19), - ] + assert!(!editor.move_to_prev_snippet_tabstop(cx)); + assert( + editor, + cx, + indoc! {" + a.f(, two, ) b + a.f(, two, ) b + a.f(, two, ) b"}, ); assert!(editor.move_to_next_snippet_tabstop(cx)); - assert_eq!( - editor.selected_ranges::(cx), - &[ - Point::new(0, 9)..Point::new(0, 12), - Point::new(1, 9)..Point::new(1, 12), - Point::new(2, 9)..Point::new(2, 12) - ] + assert( + editor, + cx, + indoc! {" + a.f(one, , three) b + a.f(one, , three) b + a.f(one, , three) b"}, ); editor.move_to_prev_snippet_tabstop(cx); - assert_eq!( - editor.selected_ranges::(cx), - &[ - Point::new(0, 4)..Point::new(0, 7), - Point::new(0, 14)..Point::new(0, 19), - Point::new(1, 4)..Point::new(1, 7), - Point::new(1, 14)..Point::new(1, 19), - Point::new(2, 4)..Point::new(2, 7), - Point::new(2, 14)..Point::new(2, 19), - ] + assert( + editor, + cx, + indoc! {" + a.f(, two, ) b + a.f(, two, ) b + a.f(, two, ) b"}, ); assert!(editor.move_to_next_snippet_tabstop(cx)); + assert( + editor, + cx, + indoc! {" + a.f(one, , three) b + a.f(one, , three) b + a.f(one, , three) b"}, + ); assert!(editor.move_to_next_snippet_tabstop(cx)); - assert_eq!( - editor.selected_ranges::(cx), - &[ - Point::new(0, 20)..Point::new(0, 20), - Point::new(1, 20)..Point::new(1, 20), - Point::new(2, 20)..Point::new(2, 20) - ] + assert( + editor, + cx, + indoc! {" + a.f(one, two, three)<> b + a.f(one, two, three)<> b + a.f(one, two, three)<> b"}, ); // As soon as the last tab stop is reached, snippet state is gone editor.move_to_prev_snippet_tabstop(cx); - assert_eq!( - editor.selected_ranges::(cx), - &[ - Point::new(0, 20)..Point::new(0, 20), - Point::new(1, 20)..Point::new(1, 20), - Point::new(2, 20)..Point::new(2, 20) - ] + assert( + editor, + cx, + indoc! {" + a.f(one, two, three)<> b + a.f(one, two, three)<> b + a.f(one, two, three)<> b"}, ); }); } @@ -9489,7 +8950,9 @@ mod tests { editor.update(cx, |editor, cx| { editor.project = Some(project); - editor.select_ranges([Point::new(0, 3)..Point::new(0, 3)], None, cx); + editor.change_selections(None, cx, |s| { + s.select_ranges([Point::new(0, 3)..Point::new(0, 3)]) + }); editor.handle_input(&Input(".".to_string()), cx); }); @@ -9542,14 +9005,12 @@ mod tests { ); editor.update(cx, |editor, cx| { - editor.select_ranges( - [ + editor.change_selections(None, cx, |s| { + s.select_ranges([ Point::new(1, 3)..Point::new(1, 3), Point::new(2, 5)..Point::new(2, 5), - ], - None, - cx, - ); + ]) + }); editor.handle_input(&Input(" ".to_string()), cx); assert!(editor.context_menu.is_none()); @@ -9702,13 +9163,12 @@ mod tests { view.update(cx, |editor, cx| { // If multiple selections intersect a line, the line is only // toggled once. - editor.select_display_ranges( - &[ + editor.change_selections(None, cx, |s| { + s.select_display_ranges([ DisplayPoint::new(1, 3)..DisplayPoint::new(2, 3), DisplayPoint::new(3, 5)..DisplayPoint::new(3, 6), - ], - cx, - ); + ]) + }); editor.toggle_comments(&ToggleComments, cx); assert_eq!( editor.text(cx), @@ -9724,7 +9184,9 @@ mod tests { // The comment prefix is inserted at the same column for every line // in a selection. - editor.select_display_ranges(&[DisplayPoint::new(1, 3)..DisplayPoint::new(3, 6)], cx); + editor.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(1, 3)..DisplayPoint::new(3, 6)]) + }); editor.toggle_comments(&ToggleComments, cx); assert_eq!( editor.text(cx), @@ -9739,7 +9201,9 @@ mod tests { ); // If a selection ends at the beginning of a line, that line is not toggled. - editor.select_display_ranges(&[DisplayPoint::new(2, 0)..DisplayPoint::new(3, 0)], cx); + editor.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(3, 0)]) + }); editor.toggle_comments(&ToggleComments, cx); assert_eq!( editor.text(cx), @@ -9777,19 +9241,17 @@ mod tests { let (_, view) = cx.add_window(Default::default(), |cx| build_editor(multibuffer, cx)); view.update(cx, |view, cx| { assert_eq!(view.text(cx), "aaaa\nbbbb"); - view.select_ranges( - [ + view.change_selections(None, cx, |s| { + s.select_ranges([ Point::new(0, 0)..Point::new(0, 0), Point::new(1, 0)..Point::new(1, 0), - ], - None, - cx, - ); + ]) + }); view.handle_input(&Input("X".to_string()), cx); assert_eq!(view.text(cx), "Xaaaa\nXbbbb"); assert_eq!( - view.selected_ranges(cx), + view.selections.ranges(cx), [ Point::new(0, 1)..Point::new(0, 1), Point::new(1, 1)..Point::new(1, 1), @@ -9820,7 +9282,7 @@ mod tests { b|bb|b cccc"}); assert_eq!(view.text(cx), expected_text); - view.select_ranges(selection_ranges, None, cx); + view.change_selections(None, cx, |s| s.select_ranges(selection_ranges)); view.handle_input(&Input("X".to_string()), cx); @@ -9830,7 +9292,7 @@ mod tests { bX|bbX|b cccc"}); assert_eq!(view.text(cx), expected_text); - assert_eq!(view.selected_ranges(cx), expected_selections); + assert_eq!(view.selections.ranges(cx), expected_selections); view.newline(&Newline, cx); let (expected_text, expected_selections) = marked_text_ranges(indoc! {" @@ -9843,7 +9305,7 @@ mod tests { |b cccc"}); assert_eq!(view.text(cx), expected_text); - assert_eq!(view.selected_ranges(cx), expected_selections); + assert_eq!(view.selections.ranges(cx), expected_selections); }); } @@ -9874,10 +9336,12 @@ mod tests { let (_, editor) = cx.add_window(Default::default(), |cx| { let mut editor = build_editor(multibuffer.clone(), cx); let snapshot = editor.snapshot(cx); - editor.select_ranges([Point::new(1, 3)..Point::new(1, 3)], None, cx); + editor.change_selections(None, cx, |s| { + s.select_ranges([Point::new(1, 3)..Point::new(1, 3)]) + }); editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx); assert_eq!( - editor.selected_ranges(cx), + editor.selections.ranges(cx), [ Point::new(1, 3)..Point::new(1, 3), Point::new(2, 1)..Point::new(2, 1), @@ -9888,9 +9352,11 @@ mod tests { // Refreshing selections is a no-op when excerpts haven't changed. editor.update(cx, |editor, cx| { - editor.refresh_selections(cx); + editor.change_selections(None, cx, |s| { + s.refresh(); + }); assert_eq!( - editor.selected_ranges(cx), + editor.selections.ranges(cx), [ Point::new(1, 3)..Point::new(1, 3), Point::new(2, 1)..Point::new(2, 1), @@ -9904,7 +9370,7 @@ mod tests { editor.update(cx, |editor, cx| { // Removing an excerpt causes the first selection to become degenerate. assert_eq!( - editor.selected_ranges(cx), + editor.selections.ranges(cx), [ Point::new(0, 0)..Point::new(0, 0), Point::new(0, 1)..Point::new(0, 1) @@ -9913,15 +9379,17 @@ mod tests { // Refreshing selections will relocate the first selection to the original buffer // location. - editor.refresh_selections(cx); + editor.change_selections(None, cx, |s| { + s.refresh(); + }); assert_eq!( - editor.selected_ranges(cx), + editor.selections.ranges(cx), [ Point::new(0, 1)..Point::new(0, 1), Point::new(0, 3)..Point::new(0, 3) ] ); - assert!(editor.pending_selection.is_some()); + assert!(editor.selections.pending_anchor().is_some()); }); } @@ -9954,7 +9422,7 @@ mod tests { let snapshot = editor.snapshot(cx); editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx); assert_eq!( - editor.selected_ranges(cx), + editor.selections.ranges(cx), [Point::new(1, 3)..Point::new(1, 3)] ); editor @@ -9965,17 +9433,19 @@ mod tests { }); editor.update(cx, |editor, cx| { assert_eq!( - editor.selected_ranges(cx), + editor.selections.ranges(cx), [Point::new(0, 0)..Point::new(0, 0)] ); // Ensure we don't panic when selections are refreshed and that the pending selection is finalized. - editor.refresh_selections(cx); + editor.change_selections(None, cx, |s| { + s.refresh(); + }); assert_eq!( - editor.selected_ranges(cx), + editor.selections.ranges(cx), [Point::new(0, 3)..Point::new(0, 3)] ); - assert!(editor.pending_selection.is_some()); + assert!(editor.selections.pending_anchor().is_some()); }); } @@ -10018,14 +9488,13 @@ mod tests { .await; view.update(cx, |view, cx| { - view.select_display_ranges( - &[ + view.change_selections(None, cx, |s| { + s.select_display_ranges([ DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3), DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5), DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4), - ], - cx, - ); + ]) + }); view.newline(&Newline, cx); assert_eq!( @@ -10158,14 +9627,14 @@ mod tests { // Update the selections only leader.update(cx, |leader, cx| { - leader.select_ranges([1..1], None, cx); + leader.change_selections(None, cx, |s| s.select_ranges([1..1])); }); follower.update(cx, |follower, cx| { follower .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx) .unwrap(); }); - assert_eq!(follower.read(cx).selected_ranges(cx), vec![1..1]); + assert_eq!(follower.read(cx).selections.ranges(cx), vec![1..1]); // Update the scroll position only leader.update(cx, |leader, cx| { @@ -10183,7 +9652,7 @@ mod tests { // Update the selections and scroll position leader.update(cx, |leader, cx| { - leader.select_ranges([0..0], None, cx); + leader.change_selections(None, cx, |s| s.select_ranges([0..0])); leader.request_autoscroll(Autoscroll::Newest, cx); leader.set_scroll_position(vec2f(1.5, 3.5), cx); }); @@ -10195,11 +9664,11 @@ mod tests { assert_eq!(follower.scroll_position(cx), initial_scroll_position); assert!(follower.autoscroll_request.is_some()); }); - assert_eq!(follower.read(cx).selected_ranges(cx), vec![0..0]); + assert_eq!(follower.read(cx).selections.ranges(cx), vec![0..0]); // Creating a pending selection that precedes another selection leader.update(cx, |leader, cx| { - leader.select_ranges([1..1], None, cx); + leader.change_selections(None, cx, |s| s.select_ranges([1..1])); leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx); }); follower.update(cx, |follower, cx| { @@ -10207,7 +9676,7 @@ mod tests { .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx) .unwrap(); }); - assert_eq!(follower.read(cx).selected_ranges(cx), vec![0..0, 1..1]); + assert_eq!(follower.read(cx).selections.ranges(cx), vec![0..0, 1..1]); // Extend the pending selection so that it surrounds another selection leader.update(cx, |leader, cx| { @@ -10218,7 +9687,7 @@ mod tests { .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx) .unwrap(); }); - assert_eq!(follower.read(cx).selected_ranges(cx), vec![0..2]); + assert_eq!(follower.read(cx).selections.ranges(cx), vec![0..2]); } #[test] @@ -10321,7 +9790,7 @@ mod tests { }) .collect(); assert_eq!( - view.selected_display_ranges(cx), + view.selections.display_ranges(cx), &asserted_ranges[..], "Assert selections are {}", marked_text diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index a04d27a8bb..d3d67060f2 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -957,8 +957,10 @@ impl Element for EditorElement { selections.extend(remote_selections); if view.show_local_selections { - let local_selections = - view.local_selections_in_range(start_anchor..end_anchor, &display_map); + let mut local_selections = view + .selections + .disjoint_in_range(start_anchor..end_anchor, cx); + local_selections.extend(view.selections.pending(cx)); for selection in &local_selections { let is_empty = selection.start == selection.end; let selection_start = snapshot.prev_line_boundary(selection.start).1; @@ -1041,7 +1043,8 @@ impl Element for EditorElement { } let newest_selection_head = view - .newest_selection_with_snapshot::(&snapshot.buffer_snapshot) + .selections + .newest::(cx) .head() .to_display_point(&snapshot); diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 120826321b..4b4df09c3a 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -102,7 +102,7 @@ impl FollowableItem for Editor { } else { self.buffer.update(cx, |buffer, cx| { if self.focused { - buffer.set_active_selections(&self.selections, cx); + buffer.set_active_selections(&self.selections.disjoint_anchors(), cx); } }); } @@ -118,7 +118,12 @@ impl FollowableItem for Editor { )), scroll_x: self.scroll_position.x(), scroll_y: self.scroll_position.y(), - selections: self.selections.iter().map(serialize_selection).collect(), + selections: self + .selections + .disjoint_anchors() + .iter() + .map(serialize_selection) + .collect(), })) } @@ -144,8 +149,9 @@ impl FollowableItem for Editor { Event::SelectionsChanged { .. } => { update.selections = self .selections + .disjoint_anchors() .iter() - .chain(self.pending_selection.as_ref().map(|p| &p.selection)) + .chain(self.selections.pending_anchor().as_ref()) .map(serialize_selection) .collect(); true @@ -246,13 +252,13 @@ fn deserialize_selection( impl Item for Editor { fn navigate(&mut self, data: Box, cx: &mut ViewContext) -> bool { if let Ok(data) = data.downcast::() { + let newest_selection = self.selections.newest::(cx); let buffer = self.buffer.read(cx).read(cx); let offset = if buffer.can_resolve(&data.cursor_anchor) { data.cursor_anchor.to_point(&buffer) } else { buffer.clip_point(data.cursor_position, Bias::Left) }; - let newest_selection = self.newest_selection_with_snapshot::(&buffer); let scroll_top_anchor = if buffer.can_resolve(&data.scroll_top_anchor) { data.scroll_top_anchor @@ -270,7 +276,9 @@ impl Item for Editor { let nav_history = self.nav_history.take(); self.scroll_position = data.scroll_position; self.scroll_top_anchor = scroll_top_anchor; - self.select_ranges([offset..offset], Some(Autoscroll::Fit), cx); + self.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.select_ranges([offset..offset]) + }); self.nav_history = nav_history; true } @@ -307,7 +315,7 @@ impl Item for Editor { } fn deactivated(&mut self, cx: &mut ViewContext) { - let selection = self.newest_anchor_selection(); + let selection = self.selections.newest_anchor(); self.push_to_nav_history(selection.head(), None, cx); } @@ -457,7 +465,7 @@ impl CursorPosition { self.selected_count = 0; let mut last_selection: Option> = None; - for selection in editor.local_selections::(cx) { + for selection in editor.selections.all::(cx) { self.selected_count += selection.end - selection.start; if last_selection .as_ref() diff --git a/crates/editor/src/selections_collection.rs b/crates/editor/src/selections_collection.rs new file mode 100644 index 0000000000..aabfb676ff --- /dev/null +++ b/crates/editor/src/selections_collection.rs @@ -0,0 +1,729 @@ +use std::{ + cell::Ref, + cmp, iter, mem, + ops::{Deref, Range, Sub}, + sync::Arc, +}; + +use collections::HashMap; +use gpui::{AppContext, ModelHandle, MutableAppContext}; +use itertools::Itertools; +use language::{rope::TextDimension, Bias, Point, Selection, SelectionGoal, ToPoint}; +use util::post_inc; + +use crate::{ + display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint}, + Anchor, DisplayPoint, ExcerptId, MultiBuffer, MultiBufferSnapshot, SelectMode, ToOffset, +}; + +#[derive(Clone)] +pub struct PendingSelection { + pub selection: Selection, + pub mode: SelectMode, +} + +pub struct SelectionsCollection { + display_map: ModelHandle, + buffer: ModelHandle, + pub next_selection_id: usize, + disjoint: Arc<[Selection]>, + pending: Option, +} + +impl SelectionsCollection { + pub fn new(display_map: ModelHandle, buffer: ModelHandle) -> Self { + Self { + display_map, + buffer, + next_selection_id: 1, + disjoint: Arc::from([]), + pending: Some(PendingSelection { + selection: Selection { + id: 0, + start: Anchor::min(), + end: Anchor::min(), + reversed: false, + goal: SelectionGoal::None, + }, + mode: SelectMode::Character, + }), + } + } + + fn display_map(&self, cx: &mut MutableAppContext) -> DisplaySnapshot { + self.display_map.update(cx, |map, cx| map.snapshot(cx)) + } + + fn buffer<'a>(&self, cx: &'a AppContext) -> Ref<'a, MultiBufferSnapshot> { + self.buffer.read(cx).read(cx) + } + + pub fn count<'a>(&self) -> usize { + let mut count = self.disjoint.len(); + if self.pending.is_some() { + count += 1; + } + count + } + + pub fn disjoint_anchors(&self) -> Arc<[Selection]> { + self.disjoint.clone() + } + + pub fn pending_anchor(&self) -> Option> { + self.pending + .as_ref() + .map(|pending| pending.selection.clone()) + } + + pub fn pending>( + &self, + cx: &AppContext, + ) -> Option> { + self.pending_anchor() + .as_ref() + .map(|pending| pending.map(|p| p.summary::(&self.buffer(cx)))) + } + + pub fn pending_mode(&self) -> Option { + self.pending.as_ref().map(|pending| pending.mode.clone()) + } + + pub fn all<'a, D>(&self, cx: &AppContext) -> Vec> + where + D: 'a + TextDimension + Ord + Sub + std::fmt::Debug, + { + let disjoint_anchors = &self.disjoint; + let mut disjoint = + resolve_multiple::(disjoint_anchors.iter(), &self.buffer(cx)).peekable(); + + let mut pending_opt = self.pending::(cx); + + iter::from_fn(move || { + if let Some(pending) = pending_opt.as_mut() { + while let Some(next_selection) = disjoint.peek() { + if pending.start <= next_selection.end && pending.end >= next_selection.start { + 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 disjoint_in_range<'a, D>( + &self, + range: Range, + cx: &AppContext, + ) -> Vec> + where + D: 'a + TextDimension + Ord + Sub + std::fmt::Debug, + { + let buffer = self.buffer(cx); + let start_ix = match self + .disjoint + .binary_search_by(|probe| probe.end.cmp(&range.start, &buffer)) + { + Ok(ix) | Err(ix) => ix, + }; + let end_ix = match self + .disjoint + .binary_search_by(|probe| probe.start.cmp(&range.end, &buffer)) + { + Ok(ix) => ix + 1, + Err(ix) => ix, + }; + resolve_multiple(&self.disjoint[start_ix..end_ix], &buffer).collect() + } + + pub fn all_display( + &mut self, + cx: &mut MutableAppContext, + ) -> (DisplaySnapshot, Vec>) { + let display_map = self.display_map(cx); + let selections = self + .all::(cx) + .into_iter() + .map(|selection| selection.map(|point| point.to_display_point(&display_map))) + .collect(); + (display_map, selections) + } + + 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, + cx: &AppContext, + ) -> Selection { + resolve(self.newest_anchor(), &self.buffer(cx)) + } + + 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, + cx: &AppContext, + ) -> Selection { + resolve(self.oldest_anchor(), &self.buffer(cx)) + } + + pub fn first>( + &self, + cx: &AppContext, + ) -> Selection { + self.all(cx).first().unwrap().clone() + } + + pub fn last>( + &self, + cx: &AppContext, + ) -> Selection { + self.all(cx).last().unwrap().clone() + } + + #[cfg(any(test, feature = "test-support"))] + pub fn ranges + std::fmt::Debug>( + &self, + cx: &AppContext, + ) -> Vec> { + self.all::(cx) + .iter() + .map(|s| { + if s.reversed { + s.end.clone()..s.start.clone() + } else { + s.start.clone()..s.end.clone() + } + }) + .collect() + } + + #[cfg(any(test, feature = "test-support"))] + pub fn display_ranges(&self, cx: &mut MutableAppContext) -> Vec> { + let display_map = self.display_map(cx); + self.disjoint_anchors() + .iter() + .chain(self.pending_anchor().as_ref()) + .map(|s| { + if s.reversed { + s.end.to_display_point(&display_map)..s.start.to_display_point(&display_map) + } else { + s.start.to_display_point(&display_map)..s.end.to_display_point(&display_map) + } + }) + .collect() + } + + pub fn build_columnar_selection( + &mut self, + display_map: &DisplaySnapshot, + row: u32, + columns: &Range, + reversed: bool, + ) -> Option> { + let is_empty = columns.start == columns.end; + let line_len = display_map.line_len(row); + if columns.start < line_len || (is_empty && columns.start == line_len) { + let start = DisplayPoint::new(row, columns.start); + let end = DisplayPoint::new(row, cmp::min(columns.end, line_len)); + + 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::ColumnRange { + start: columns.start, + end: columns.end, + }, + }) + } else { + None + } + } + + pub(crate) fn change_with( + &mut self, + cx: &mut MutableAppContext, + change: impl FnOnce(&mut MutableSelectionsCollection) -> R, + ) -> R { + let mut mutable_collection = MutableSelectionsCollection { + collection: self, + cx, + }; + + let result = change(&mut mutable_collection); + assert!( + !mutable_collection.disjoint.is_empty() || mutable_collection.pending.is_some(), + "There must be at least one selection" + ); + result + } +} + +pub struct MutableSelectionsCollection<'a> { + collection: &'a mut SelectionsCollection, + cx: &'a mut MutableAppContext, +} + +impl<'a> MutableSelectionsCollection<'a> { + fn display_map(&mut self) -> DisplaySnapshot { + self.collection.display_map(self.cx) + } + + fn buffer(&self) -> Ref { + self.collection.buffer(self.cx) + } + + pub fn clear_disjoint(&mut self) { + self.collection.disjoint = Arc::from([]); + } + + pub fn delete(&mut self, selection_id: usize) { + self.collection.disjoint = self + .disjoint + .into_iter() + .filter(|selection| selection.id != selection_id) + .cloned() + .collect(); + } + + pub fn clear_pending(&mut self) { + self.collection.pending = None; + } + + pub fn set_pending_range(&mut self, range: Range, mode: SelectMode) { + self.collection.pending = Some(PendingSelection { + selection: Selection { + id: post_inc(&mut self.collection.next_selection_id), + start: range.start, + end: range.end, + reversed: false, + goal: SelectionGoal::None, + }, + mode, + }) + } + + pub fn set_pending(&mut self, selection: Selection, mode: SelectMode) { + self.collection.pending = Some(PendingSelection { selection, mode }); + } + + 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]); + } + return true; + } + + let mut oldest = self.oldest_anchor().clone(); + if self.count() > 1 { + self.collection.disjoint = Arc::from([oldest]); + return true; + } + + if !oldest.start.cmp(&oldest.end, &self.buffer()).is_eq() { + let head = oldest.head(); + oldest.start = head.clone(); + oldest.end = head; + self.collection.disjoint = Arc::from([oldest]); + return true; + } + + return false; + } + + pub fn reset_biases(&mut self) { + let buffer = self.buffer.read(self.cx).snapshot(self.cx); + self.collection.disjoint = self + .collection + .disjoint + .into_iter() + .cloned() + .map(|selection| reset_biases(selection, &buffer)) + .collect(); + + if let Some(pending) = self.collection.pending.as_mut() { + pending.selection = reset_biases(pending.selection.clone(), &buffer); + } + } + + pub fn insert_range(&mut self, range: Range) + where + T: 'a + ToOffset + ToPoint + TextDimension + Ord + Sub + std::marker::Copy, + { + let mut selections = self.all(self.cx); + let mut start = range.start.to_offset(&self.buffer()); + let mut end = range.end.to_offset(&self.buffer()); + 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, mut selections: Vec>) + where + T: ToOffset + ToPoint + Ord + std::marker::Copy + std::fmt::Debug, + { + let buffer = self.buffer.read(self.cx).snapshot(self.cx); + selections.sort_unstable_by_key(|s| s.start); + // Merge overlapping selections. + let mut i = 1; + while i < selections.len() { + if selections[i - 1].end >= selections[i].start { + let removed = selections.remove(i); + if removed.start < selections[i - 1].start { + selections[i - 1].start = removed.start; + } + if removed.end > selections[i - 1].end { + selections[i - 1].end = removed.end; + } + } else { + i += 1; + } + } + + self.collection.disjoint = Arc::from_iter(selections.into_iter().map(|selection| { + let end_bias = if selection.end > selection.start { + Bias::Left + } else { + Bias::Right + }; + Selection { + id: selection.id, + start: buffer.anchor_after(selection.start), + end: buffer.anchor_at(selection.end, end_bias), + reversed: selection.reversed, + goal: selection.goal, + } + })); + + self.collection.pending = None; + } + + pub fn select_anchors(&mut self, selections: Vec>) { + let buffer = self.buffer.read(self.cx).snapshot(self.cx); + let resolved_selections = + resolve_multiple::(&selections, &buffer).collect::>(); + self.select(resolved_selections); + } + + pub fn select_ranges(&mut self, ranges: I) + where + I: IntoIterator>, + T: ToOffset, + { + let buffer = self.buffer.read(self.cx).snapshot(self.cx); + let selections = ranges + .into_iter() + .map(|range| { + let mut start = range.start.to_offset(&buffer); + let mut end = range.end.to_offset(&buffer); + 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) { + let buffer = self.buffer.read(self.cx).snapshot(self.cx); + let selections = ranges + .into_iter() + .map(|range| { + let mut start = range.start; + let mut end = range.end; + let reversed = if start.cmp(&end, &buffer).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) + } + + #[cfg(any(test, feature = "test-support"))] + pub fn select_display_ranges(&mut self, ranges: T) + where + T: IntoIterator>, + { + let display_map = self.display_map(); + 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(&display_map), + end: end.to_point(&display_map), + reversed, + goal: SelectionGoal::None, + } + }) + .collect(); + self.select(selections); + } + + pub fn move_with( + &mut self, + mut move_selection: impl FnMut(&DisplaySnapshot, &mut Selection), + ) { + let display_map = self.display_map(); + let selections = self + .all::(self.cx) + .into_iter() + .map(|selection| { + let mut selection = selection.map(|point| point.to_display_point(&display_map)); + move_selection(&display_map, &mut selection); + selection.map(|display_point| display_point.to_point(&display_map)) + }) + .collect(); + + self.select(selections) + } + + pub fn move_heads_with( + &mut self, + mut update_head: impl FnMut( + &DisplaySnapshot, + DisplayPoint, + SelectionGoal, + ) -> (DisplayPoint, SelectionGoal), + ) { + self.move_with(|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, + mut update_cursor_position: impl FnMut( + &DisplaySnapshot, + DisplayPoint, + SelectionGoal, + ) -> (DisplayPoint, SelectionGoal), + ) { + self.move_with(|map, selection| { + let (cursor, new_goal) = update_cursor_position(map, selection.head(), selection.goal); + selection.collapse_to(cursor, new_goal) + }); + } + + pub fn replace_cursors_with( + &mut self, + mut find_replacement_cursors: impl FnMut(&DisplaySnapshot) -> Vec, + ) { + let display_map = self.display_map(); + let new_selections = find_replacement_cursors(&display_map) + .into_iter() + .map(|cursor| { + let cursor_point = cursor.to_point(&display_map); + 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); + } + + /// Compute new ranges for any selections that were located in excerpts that have + /// since been removed. + /// + /// Returns a `HashMap` indicating which selections whose former head position + /// was no longer present. The keys of the map are selection ids. The values are + /// the id of the new excerpt where the head of the selection has been moved. + pub fn refresh(&mut self) -> HashMap { + let mut pending = self.collection.pending.take(); + let mut selections_with_lost_position = HashMap::default(); + + let anchors_with_status = { + let buffer = self.buffer(); + let disjoint_anchors = self + .disjoint + .iter() + .flat_map(|selection| [&selection.start, &selection.end]); + buffer.refresh_anchors(disjoint_anchors) + }; + let adjusted_disjoint: Vec<_> = anchors_with_status + .chunks(2) + .map(|selection_anchors| { + let (anchor_ix, start, kept_start) = selection_anchors[0].clone(); + let (_, end, kept_end) = selection_anchors[1].clone(); + let selection = &self.disjoint[anchor_ix / 2]; + let kept_head = if selection.reversed { + kept_start + } else { + kept_end + }; + if !kept_head { + selections_with_lost_position + .insert(selection.id, selection.head().excerpt_id.clone()); + } + + Selection { + id: selection.id, + start, + end, + reversed: selection.reversed, + goal: selection.goal, + } + }) + .collect(); + + if !adjusted_disjoint.is_empty() { + let resolved_selections = + resolve_multiple(adjusted_disjoint.iter(), &self.buffer()).collect(); + self.select::(resolved_selections); + } + + if let Some(pending) = pending.as_mut() { + let buffer = self.buffer(); + let anchors = + buffer.refresh_anchors([&pending.selection.start, &pending.selection.end]); + let (_, start, kept_start) = anchors[0].clone(); + let (_, end, kept_end) = anchors[1].clone(); + let kept_head = if pending.selection.reversed { + kept_start + } else { + kept_end + }; + if !kept_head { + selections_with_lost_position.insert( + pending.selection.id, + pending.selection.head().excerpt_id.clone(), + ); + } + + pending.selection.start = start; + pending.selection.end = end; + } + self.collection.pending = pending; + + selections_with_lost_position + } +} + +impl<'a> Deref for MutableSelectionsCollection<'a> { + type Target = SelectionsCollection; + fn deref(&self) -> &Self::Target { + self.collection + } +} + +// Panics if passed selections are not in order +pub fn resolve_multiple<'a, D, I>( + selections: I, + snapshot: &MultiBufferSnapshot, +) -> impl 'a + Iterator> +where + D: TextDimension + Ord + Sub + std::fmt::Debug, + I: 'a + IntoIterator>, +{ + let (to_summarize, selections) = selections.into_iter().tee(); + let mut summaries = snapshot + .summaries_for_anchors::( + to_summarize + .flat_map(|s| [&s.start, &s.end]) + .collect::>(), + ) + .into_iter(); + selections.map(move |s| Selection { + id: s.id, + start: summaries.next().unwrap(), + end: summaries.next().unwrap(), + reversed: s.reversed, + goal: s.goal, + }) +} + +fn resolve>( + selection: &Selection, + buffer: &MultiBufferSnapshot, +) -> Selection { + selection.map(|p| p.summary::(&buffer)) +} + +fn reset_biases( + mut selection: Selection, + buffer: &MultiBufferSnapshot, +) -> Selection { + let end_bias = if selection.end.to_offset(buffer) > selection.start.to_offset(buffer) { + Bias::Left + } else { + Bias::Right + }; + selection.start = buffer.anchor_after(selection.start); + selection.end = buffer.anchor_at(selection.end, end_bias); + selection +} diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index eb23d7e15f..cb064be545 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -43,7 +43,7 @@ pub fn marked_display_snapshot( pub fn select_ranges(editor: &mut Editor, marked_text: &str, cx: &mut ViewContext) { let (umarked_text, text_ranges) = marked_text_ranges(marked_text); assert_eq!(editor.text(cx), umarked_text); - editor.select_ranges(text_ranges, None, cx); + editor.change_selections(None, cx, |s| s.select_ranges(text_ranges)); } pub fn assert_text_with_selections( @@ -54,5 +54,5 @@ pub fn assert_text_with_selections( let (unmarked_text, text_ranges) = marked_text_ranges(marked_text); assert_eq!(editor.text(cx), unmarked_text); - assert_eq!(editor.selected_ranges(cx), text_ranges); + assert_eq!(editor.selections.ranges(cx), text_ranges); } diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index 9e2c79c5dc..a2753483b3 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/crates/go_to_line/src/go_to_line.rs @@ -43,7 +43,7 @@ impl GoToLine { let buffer = editor.buffer().read(cx).read(cx); ( Some(scroll_position), - editor.newest_selection_with_snapshot(&buffer).head(), + editor.selections.newest(cx).head(), buffer.max_point(), ) }); @@ -80,7 +80,9 @@ impl GoToLine { if let Some(rows) = active_editor.highlighted_rows() { let snapshot = active_editor.snapshot(cx).display_snapshot; let position = DisplayPoint::new(rows.start, 0).to_point(&snapshot); - active_editor.select_ranges([position..position], Some(Autoscroll::Center), cx); + active_editor.change_selections(Some(Autoscroll::Center), cx, |s| { + s.select_ranges([position..position]) + }); } }); cx.emit(Event::Dismissed); diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index 19c044e65f..dd105fd4e3 100644 --- a/crates/journal/src/journal.rs +++ b/crates/journal/src/journal.rs @@ -57,7 +57,9 @@ pub fn new_journal_entry(app_state: Arc, cx: &mut MutableAppContext) { if let Some(editor) = item.downcast::() { editor.update(&mut cx, |editor, cx| { let len = editor.buffer().read(cx).read(cx).len(); - editor.select_ranges([len..len], Some(Autoscroll::Center), cx); + editor.change_selections(Some(Autoscroll::Center), cx, |s| { + s.select_ranges([len..len]) + }); if len > 0 { editor.insert("\n\n", cx); } diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index f5057ba39d..47b022304b 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -172,9 +172,7 @@ impl PickerDelegate for OutlineView { let editor = self.active_editor.read(cx); let buffer = editor.buffer().read(cx).read(cx); - let cursor_offset = editor - .newest_selection_with_snapshot::(&buffer) - .head(); + let cursor_offset = editor.selections.newest::(cx).head(); selected_index = self .outline .items @@ -217,7 +215,9 @@ impl PickerDelegate for OutlineView { if let Some(rows) = active_editor.highlighted_rows() { let snapshot = active_editor.snapshot(cx).display_snapshot; let position = DisplayPoint::new(rows.start, 0).to_point(&snapshot); - active_editor.select_ranges([position..position], Some(Autoscroll::Center), cx); + active_editor.change_selections(Some(Autoscroll::Center), cx, |s| { + s.select_ranges([position..position]) + }); } }); cx.emit(Event::Dismissed); diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 5322a8924a..f30370a0c0 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -145,11 +145,9 @@ impl ProjectSymbolsView { let editor = workspace.open_project_item::(buffer, cx); editor.update(cx, |editor, cx| { - editor.select_ranges( - [position..position], - Some(Autoscroll::Center), - cx, - ); + editor.change_selections(Some(Autoscroll::Center), cx, |s| { + s.select_ranges([position..position]) + }); }); }); Ok::<_, anyhow::Error>(()) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 549edf89e7..ec09fd45b5 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -225,9 +225,7 @@ impl BufferSearchBar { let display_map = editor .update(cx, |editor, cx| editor.snapshot(cx)) .display_snapshot; - let selection = editor - .read(cx) - .newest_selection_with_snapshot::(&display_map.buffer_snapshot); + let selection = editor.read(cx).selections.newest::(cx); let mut text: String; if selection.start == selection.end { @@ -387,14 +385,16 @@ impl BufferSearchBar { if let Some(ranges) = self.editors_with_matches.get(&cx.weak_handle()) { let new_index = match_index_for_direction( ranges, - &editor.newest_anchor_selection().head(), + &editor.selections.newest_anchor().head(), index, direction, &editor.buffer().read(cx).read(cx), ); let range_to_select = ranges[new_index].clone(); editor.unfold_ranges([range_to_select.clone()], false, cx); - editor.select_ranges([range_to_select], Some(Autoscroll::Fit), cx); + editor.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.select_ranges([range_to_select]) + }); } }); } @@ -535,10 +535,10 @@ impl BufferSearchBar { editor.update(cx, |editor, cx| { if select_closest_match { if let Some(match_ix) = this.active_match_index { - editor.select_ranges( - [ranges[match_ix].clone()], + editor.change_selections( Some(Autoscroll::Fit), cx, + |s| s.select_ranges([ranges[match_ix].clone()]), ); } } @@ -564,7 +564,7 @@ impl BufferSearchBar { let editor = editor.read(cx); active_match_index( &ranges, - &editor.newest_anchor_selection().head(), + &editor.selections.newest_anchor().head(), &editor.buffer().read(cx).read(cx), ) }); @@ -721,13 +721,15 @@ mod tests { }); editor.update(cx, |editor, cx| { - editor.select_display_ranges(&[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)], cx); + editor.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]) + }); }); search_bar.update(cx, |search_bar, cx| { assert_eq!(search_bar.active_match_index, Some(0)); search_bar.select_next_match(&SelectNextMatch, cx); assert_eq!( - editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)), + editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)), [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)] ); }); @@ -738,7 +740,7 @@ mod tests { search_bar.update(cx, |search_bar, cx| { search_bar.select_next_match(&SelectNextMatch, cx); assert_eq!( - editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)), + editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)), [DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13)] ); }); @@ -749,7 +751,7 @@ mod tests { search_bar.update(cx, |search_bar, cx| { search_bar.select_next_match(&SelectNextMatch, cx); assert_eq!( - editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)), + editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)), [DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58)] ); }); @@ -760,7 +762,7 @@ mod tests { search_bar.update(cx, |search_bar, cx| { search_bar.select_next_match(&SelectNextMatch, cx); assert_eq!( - editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)), + editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)), [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)] ); }); @@ -771,7 +773,7 @@ mod tests { search_bar.update(cx, |search_bar, cx| { search_bar.select_prev_match(&SelectPrevMatch, cx); assert_eq!( - editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)), + editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)), [DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58)] ); }); @@ -782,7 +784,7 @@ mod tests { search_bar.update(cx, |search_bar, cx| { search_bar.select_prev_match(&SelectPrevMatch, cx); assert_eq!( - editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)), + editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)), [DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13)] ); }); @@ -793,7 +795,7 @@ mod tests { search_bar.update(cx, |search_bar, cx| { search_bar.select_prev_match(&SelectPrevMatch, cx); assert_eq!( - editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)), + editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)), [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)] ); }); @@ -804,13 +806,15 @@ mod tests { // Park the cursor in between matches and ensure that going to the previous match selects // the closest match to the left. editor.update(cx, |editor, cx| { - editor.select_display_ranges(&[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)], cx); + editor.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]) + }); }); search_bar.update(cx, |search_bar, cx| { assert_eq!(search_bar.active_match_index, Some(1)); search_bar.select_prev_match(&SelectPrevMatch, cx); assert_eq!( - editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)), + editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)), [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)] ); }); @@ -821,13 +825,15 @@ mod tests { // Park the cursor in between matches and ensure that going to the next match selects the // closest match to the right. editor.update(cx, |editor, cx| { - editor.select_display_ranges(&[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)], cx); + editor.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]) + }); }); search_bar.update(cx, |search_bar, cx| { assert_eq!(search_bar.active_match_index, Some(1)); search_bar.select_next_match(&SelectNextMatch, cx); assert_eq!( - editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)), + editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)), [DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13)] ); }); @@ -838,13 +844,15 @@ mod tests { // Park the cursor after the last match and ensure that going to the previous match selects // the last match. editor.update(cx, |editor, cx| { - editor.select_display_ranges(&[DisplayPoint::new(3, 60)..DisplayPoint::new(3, 60)], cx); + editor.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(3, 60)..DisplayPoint::new(3, 60)]) + }); }); search_bar.update(cx, |search_bar, cx| { assert_eq!(search_bar.active_match_index, Some(2)); search_bar.select_prev_match(&SelectPrevMatch, cx); assert_eq!( - editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)), + editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)), [DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58)] ); }); @@ -855,13 +863,15 @@ mod tests { // Park the cursor after the last match and ensure that going to the next match selects the // first match. editor.update(cx, |editor, cx| { - editor.select_display_ranges(&[DisplayPoint::new(3, 60)..DisplayPoint::new(3, 60)], cx); + editor.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(3, 60)..DisplayPoint::new(3, 60)]) + }); }); search_bar.update(cx, |search_bar, cx| { assert_eq!(search_bar.active_match_index, Some(2)); search_bar.select_next_match(&SelectNextMatch, cx); assert_eq!( - editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)), + editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)), [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)] ); }); @@ -872,13 +882,15 @@ mod tests { // Park the cursor before the first match and ensure that going to the previous match // selects the last match. editor.update(cx, |editor, cx| { - editor.select_display_ranges(&[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)], cx); + editor.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]) + }); }); search_bar.update(cx, |search_bar, cx| { assert_eq!(search_bar.active_match_index, Some(0)); search_bar.select_prev_match(&SelectPrevMatch, cx); assert_eq!( - editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)), + editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)), [DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58)] ); }); diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index cbd373c468..4915d50ec9 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -454,7 +454,7 @@ impl ProjectSearchView { let results_editor = self.results_editor.read(cx); let new_index = match_index_for_direction( &model.match_ranges, - &results_editor.newest_anchor_selection().head(), + &results_editor.selections.newest_anchor().head(), index, direction, &results_editor.buffer().read(cx).read(cx), @@ -462,7 +462,9 @@ impl ProjectSearchView { let range_to_select = model.match_ranges[new_index].clone(); self.results_editor.update(cx, |editor, cx| { editor.unfold_ranges([range_to_select.clone()], false, cx); - editor.select_ranges([range_to_select], Some(Autoscroll::Fit), cx); + editor.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.select_ranges([range_to_select]) + }); }); } } @@ -476,8 +478,8 @@ impl ProjectSearchView { fn focus_results_editor(&self, cx: &mut ViewContext) { self.query_editor.update(cx, |query_editor, cx| { - let cursor = query_editor.newest_anchor_selection().head(); - query_editor.select_ranges([cursor.clone()..cursor], None, cx); + let cursor = query_editor.selections.newest_anchor().head(); + query_editor.change_selections(None, cx, |s| s.select_ranges([cursor.clone()..cursor])); }); cx.focus(&self.results_editor); } @@ -489,7 +491,9 @@ impl ProjectSearchView { } else { self.results_editor.update(cx, |editor, cx| { if reset_selections { - editor.select_ranges(match_ranges.first().cloned(), Some(Autoscroll::Fit), cx); + editor.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.select_ranges(match_ranges.first().cloned()) + }); } editor.highlight_background::( match_ranges, @@ -510,7 +514,7 @@ impl ProjectSearchView { let results_editor = self.results_editor.read(cx); let new_index = active_match_index( &self.model.read(cx).match_ranges, - &results_editor.newest_anchor_selection().head(), + &results_editor.selections.newest_anchor().head(), &results_editor.buffer().read(cx).read(cx), ); if self.active_match_index != new_index { @@ -887,7 +891,7 @@ mod tests { assert_eq!( search_view .results_editor - .update(cx, |editor, cx| editor.selected_display_ranges(cx)), + .update(cx, |editor, cx| editor.selections.display_ranges(cx)), [DisplayPoint::new(2, 32)..DisplayPoint::new(2, 35)] ); @@ -899,7 +903,7 @@ mod tests { assert_eq!( search_view .results_editor - .update(cx, |editor, cx| editor.selected_display_ranges(cx)), + .update(cx, |editor, cx| editor.selections.display_ranges(cx)), [DisplayPoint::new(2, 37)..DisplayPoint::new(2, 40)] ); search_view.select_match(Direction::Next, cx); @@ -910,7 +914,7 @@ mod tests { assert_eq!( search_view .results_editor - .update(cx, |editor, cx| editor.selected_display_ranges(cx)), + .update(cx, |editor, cx| editor.selections.display_ranges(cx)), [DisplayPoint::new(5, 6)..DisplayPoint::new(5, 9)] ); search_view.select_match(Direction::Next, cx); @@ -921,7 +925,7 @@ mod tests { assert_eq!( search_view .results_editor - .update(cx, |editor, cx| editor.selected_display_ranges(cx)), + .update(cx, |editor, cx| editor.selections.display_ranges(cx)), [DisplayPoint::new(2, 32)..DisplayPoint::new(2, 35)] ); search_view.select_match(Direction::Prev, cx); @@ -932,7 +936,7 @@ mod tests { assert_eq!( search_view .results_editor - .update(cx, |editor, cx| editor.selected_display_ranges(cx)), + .update(cx, |editor, cx| editor.selections.display_ranges(cx)), [DisplayPoint::new(5, 6)..DisplayPoint::new(5, 9)] ); search_view.select_match(Direction::Prev, cx); @@ -943,7 +947,7 @@ mod tests { assert_eq!( search_view .results_editor - .update(cx, |editor, cx| editor.selected_display_ranges(cx)), + .update(cx, |editor, cx| editor.selections.display_ranges(cx)), [DisplayPoint::new(2, 37)..DisplayPoint::new(2, 40)] ); }); diff --git a/crates/vim/src/insert.rs b/crates/vim/src/insert.rs index afaeda17b0..1e19b7d918 100644 --- a/crates/vim/src/insert.rs +++ b/crates/vim/src/insert.rs @@ -1,5 +1,5 @@ use crate::{state::Mode, Vim}; -use editor::Bias; +use editor::{Autoscroll, Bias}; use gpui::{actions, MutableAppContext, ViewContext}; use language::SelectionGoal; use workspace::Workspace; @@ -13,9 +13,11 @@ pub fn init(cx: &mut MutableAppContext) { fn normal_before(_: &mut Workspace, _: &NormalBefore, cx: &mut ViewContext) { Vim::update(cx, |state, cx| { state.update_active_editor(cx, |editor, cx| { - editor.move_cursors(cx, |map, mut cursor, _| { - *cursor.column_mut() = cursor.column().saturating_sub(1); - (map.clip_point(cursor, Bias::Left), SelectionGoal::None) + editor.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_cursors_with(|map, mut cursor, _| { + *cursor.column_mut() = cursor.column().saturating_sub(1); + (map.clip_point(cursor, Bias::Left), SelectionGoal::None) + }); }); }); state.switch_mode(Mode::Normal, cx); diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 63d5ab4ccb..2a391676fa 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -8,7 +8,7 @@ use crate::{ }; use change::init as change_init; use collections::HashSet; -use editor::{Bias, DisplayPoint}; +use editor::{Autoscroll, Bias, DisplayPoint}; use gpui::{actions, MutableAppContext, ViewContext}; use language::SelectionGoal; use workspace::Workspace; @@ -76,7 +76,9 @@ pub fn normal_motion(motion: Motion, cx: &mut MutableAppContext) { fn move_cursor(vim: &mut Vim, motion: Motion, cx: &mut MutableAppContext) { vim.update_active_editor(cx, |editor, cx| { - editor.move_cursors(cx, |map, cursor, goal| motion.move_point(map, cursor, goal)) + editor.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_cursors_with(|map, cursor, goal| motion.move_point(map, cursor, goal)) + }) }); } @@ -84,8 +86,10 @@ fn insert_after(_: &mut Workspace, _: &InsertAfter, cx: &mut ViewContext = old_selections .into_iter() .map(|selection| selection.start.row()) @@ -137,10 +145,12 @@ fn insert_line_above(_: &mut Workspace, _: &InsertLineAbove, cx: &mut ViewContex (start_of_line..start_of_line, new_text) }); editor.edit_with_autoindent(edits, cx); - editor.move_cursors(cx, |map, mut cursor, _| { - *cursor.row_mut() -= 1; - *cursor.column_mut() = map.line_len(cursor.row()); - (map.clip_point(cursor, Bias::Left), SelectionGoal::None) + editor.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_cursors_with(|map, mut cursor, _| { + *cursor.row_mut() -= 1; + *cursor.column_mut() = map.line_len(cursor.row()); + (map.clip_point(cursor, Bias::Left), SelectionGoal::None) + }); }); }); }); @@ -152,7 +162,7 @@ fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContex vim.switch_mode(Mode::Insert, cx); vim.update_active_editor(cx, |editor, cx| { editor.transact(cx, |editor, cx| { - let (map, old_selections) = editor.display_selections(cx); + let (map, old_selections) = editor.selections.all_display(cx); let selection_end_rows: HashSet = old_selections .into_iter() .map(|selection| selection.end.row()) @@ -166,8 +176,10 @@ fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContex new_text.push_str(&" ".repeat(indent as usize)); (end_of_line..end_of_line, new_text) }); - editor.move_cursors(cx, |map, cursor, goal| { - Motion::EndOfLine.move_point(map, cursor, goal) + editor.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_cursors_with(|map, cursor, goal| { + Motion::EndOfLine.move_point(map, cursor, goal) + }); }); editor.edit_with_autoindent(edits, cx); }); diff --git a/crates/vim/src/normal/change.rs b/crates/vim/src/normal/change.rs index daca2174e5..8124f8a200 100644 --- a/crates/vim/src/normal/change.rs +++ b/crates/vim/src/normal/change.rs @@ -1,5 +1,5 @@ use crate::{motion::Motion, state::Mode, Vim}; -use editor::{char_kind, movement}; +use editor::{char_kind, movement, Autoscroll}; use gpui::{impl_actions, MutableAppContext, ViewContext}; use serde::Deserialize; use workspace::Workspace; @@ -22,8 +22,10 @@ pub fn change_over(vim: &mut Vim, motion: Motion, cx: &mut MutableAppContext) { editor.transact(cx, |editor, cx| { // We are swapping to insert mode anyway. Just set the line end clipping behavior now editor.set_clip_at_line_ends(false, cx); - editor.move_selections(cx, |map, selection| { - motion.expand_selection(map, selection, false); + editor.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_with(|map, selection| { + motion.expand_selection(map, selection, false); + }); }); editor.insert(&"", cx); }); @@ -46,16 +48,21 @@ fn change_word( editor.transact(cx, |editor, cx| { // We are swapping to insert mode anyway. Just set the line end clipping behavior now editor.set_clip_at_line_ends(false, cx); - editor.move_selections(cx, |map, selection| { - if selection.end.column() == map.line_len(selection.end.row()) { - return; - } + editor.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_with(|map, selection| { + if selection.end.column() == map.line_len(selection.end.row()) { + return; + } - selection.end = movement::find_boundary(map, selection.end, |left, right| { - let left_kind = char_kind(left).coerce_punctuation(ignore_punctuation); - let right_kind = char_kind(right).coerce_punctuation(ignore_punctuation); + selection.end = + movement::find_boundary(map, selection.end, |left, right| { + let left_kind = + char_kind(left).coerce_punctuation(ignore_punctuation); + let right_kind = + char_kind(right).coerce_punctuation(ignore_punctuation); - left_kind != right_kind || left == '\n' || right == '\n' + left_kind != right_kind || left == '\n' || right == '\n' + }); }); }); editor.insert(&"", cx); diff --git a/crates/vim/src/normal/delete.rs b/crates/vim/src/normal/delete.rs index c90c1dbafd..b44f0a1f34 100644 --- a/crates/vim/src/normal/delete.rs +++ b/crates/vim/src/normal/delete.rs @@ -1,6 +1,6 @@ use crate::{motion::Motion, Vim}; use collections::HashMap; -use editor::Bias; +use editor::{Autoscroll, Bias}; use gpui::MutableAppContext; pub fn delete_over(vim: &mut Vim, motion: Motion, cx: &mut MutableAppContext) { @@ -8,24 +8,28 @@ pub fn delete_over(vim: &mut Vim, motion: Motion, cx: &mut MutableAppContext) { editor.transact(cx, |editor, cx| { editor.set_clip_at_line_ends(false, cx); let mut original_columns: HashMap<_, _> = Default::default(); - editor.move_selections(cx, |map, selection| { - let original_head = selection.head(); - motion.expand_selection(map, selection, true); - original_columns.insert(selection.id, original_head.column()); + editor.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_with(|map, selection| { + let original_head = selection.head(); + motion.expand_selection(map, selection, true); + original_columns.insert(selection.id, original_head.column()); + }); }); editor.insert(&"", cx); // Fixup cursor position after the deletion editor.set_clip_at_line_ends(true, cx); - editor.move_selections(cx, |map, selection| { - let mut cursor = selection.head(); - if motion.linewise() { - if let Some(column) = original_columns.get(&selection.id) { - *cursor.column_mut() = *column + editor.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_with(|map, selection| { + let mut cursor = selection.head(); + if motion.linewise() { + if let Some(column) = original_columns.get(&selection.id) { + *cursor.column_mut() = *column + } } - } - cursor = map.clip_point(cursor, Bias::Left); - selection.collapse_to(cursor, selection.goal) + cursor = map.clip_point(cursor, Bias::Left); + selection.collapse_to(cursor, selection.goal) + }); }); }); }); diff --git a/crates/vim/src/vim_test_context.rs b/crates/vim/src/vim_test_context.rs index 09470c31c8..4122c46059 100644 --- a/crates/vim/src/vim_test_context.rs +++ b/crates/vim/src/vim_test_context.rs @@ -3,7 +3,7 @@ use std::ops::{Deref, Range}; use collections::BTreeMap; use itertools::{Either, Itertools}; -use editor::display_map::ToDisplayPoint; +use editor::{display_map::ToDisplayPoint, Autoscroll}; use gpui::{json::json, keymap::Keystroke, ViewHandle}; use indoc::indoc; use language::Selection; @@ -128,7 +128,9 @@ impl<'a> VimTestContext<'a> { let (unmarked_text, markers) = marked_text(&text); editor.set_text(unmarked_text, cx); let cursor_offset = markers[0]; - editor.replace_selections_with(cx, |map| cursor_offset.to_display_point(map)); + editor.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.replace_cursors_with(|map| vec![cursor_offset.to_display_point(map)]) + }); }) } @@ -197,7 +199,8 @@ impl<'a> VimTestContext<'a> { let (empty_selections, reverse_selections, forward_selections) = self.editor.read_with(self.cx, |editor, cx| { let (empty_selections, non_empty_selections): (Vec<_>, Vec<_>) = editor - .local_selections::(cx) + .selections + .all::(cx) .into_iter() .partition_map(|selection| { if selection.is_empty() { diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index 9c41263317..4d32d38c30 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -1,4 +1,4 @@ -use editor::Bias; +use editor::{Autoscroll, Bias}; use gpui::{actions, MutableAppContext, ViewContext}; use workspace::Workspace; @@ -14,23 +14,25 @@ pub fn init(cx: &mut MutableAppContext) { pub fn visual_motion(motion: Motion, cx: &mut MutableAppContext) { Vim::update(cx, |vim, cx| { vim.update_active_editor(cx, |editor, cx| { - editor.move_selections(cx, |map, selection| { - let (new_head, goal) = motion.move_point(map, selection.head(), selection.goal); - let new_head = map.clip_at_line_end(new_head); - let was_reversed = selection.reversed; - selection.set_head(new_head, goal); + editor.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.move_with(|map, selection| { + let (new_head, goal) = motion.move_point(map, selection.head(), selection.goal); + let new_head = map.clip_at_line_end(new_head); + let was_reversed = selection.reversed; + selection.set_head(new_head, goal); - if was_reversed && !selection.reversed { - // Head was at the start of the selection, and now is at the end. We need to move the start - // back by one if possible in order to compensate for this change. - *selection.start.column_mut() = selection.start.column().saturating_sub(1); - selection.start = map.clip_point(selection.start, Bias::Left); - } else if !was_reversed && selection.reversed { - // Head was at the end of the selection, and now is at the start. We need to move the end - // forward by one if possible in order to compensate for this change. - *selection.end.column_mut() = selection.end.column() + 1; - selection.end = map.clip_point(selection.end, Bias::Left); - } + if was_reversed && !selection.reversed { + // Head was at the start of the selection, and now is at the end. We need to move the start + // back by one if possible in order to compensate for this change. + *selection.start.column_mut() = selection.start.column().saturating_sub(1); + selection.start = map.clip_point(selection.start, Bias::Left); + } else if !was_reversed && selection.reversed { + // Head was at the end of the selection, and now is at the start. We need to move the end + // forward by one if possible in order to compensate for this change. + *selection.end.column_mut() = selection.end.column() + 1; + selection.end = map.clip_point(selection.end, Bias::Left); + } + }); }); }); }); @@ -40,13 +42,15 @@ pub fn change(_: &mut Workspace, _: &VisualChange, cx: &mut ViewContext() .unwrap(); editor1.update(cx, |editor, cx| { - editor.select_display_ranges(&[DisplayPoint::new(10, 0)..DisplayPoint::new(10, 0)], cx); + editor.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.select_display_ranges([DisplayPoint::new(10, 0)..DisplayPoint::new(10, 0)]) + }); }); let editor2 = workspace .update(cx, |w, cx| w.open_path(file2.clone(), true, cx)) @@ -980,10 +982,9 @@ mod tests { editor3 .update(cx, |editor, cx| { - editor.select_display_ranges( - &[DisplayPoint::new(12, 0)..DisplayPoint::new(12, 0)], - cx, - ); + editor.change_selections(Some(Autoscroll::Fit), cx, |s| { + s.select_display_ranges([DisplayPoint::new(12, 0)..DisplayPoint::new(12, 0)]) + }); editor.newline(&Default::default(), cx); editor.newline(&Default::default(), cx); editor.move_down(&Default::default(), cx); @@ -1124,34 +1125,37 @@ mod tests { // Modify file to collapse multiple nav history entries into the same location. // Ensure we don't visit the same location twice when navigating. editor1.update(cx, |editor, cx| { - editor.select_display_ranges(&[DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)], cx) + editor.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)]) + }) }); for _ in 0..5 { editor1.update(cx, |editor, cx| { - editor - .select_display_ranges(&[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)], cx); + editor.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)]) + }); }); editor1.update(cx, |editor, cx| { - editor.select_display_ranges( - &[DisplayPoint::new(13, 0)..DisplayPoint::new(13, 0)], - cx, - ) + editor.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(13, 0)..DisplayPoint::new(13, 0)]) + }) }); } editor1.update(cx, |editor, cx| { editor.transact(cx, |editor, cx| { - editor.select_display_ranges( - &[DisplayPoint::new(2, 0)..DisplayPoint::new(14, 0)], - cx, - ); + editor.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(14, 0)]) + }); editor.insert("", cx); }) }); editor1.update(cx, |editor, cx| { - editor.select_display_ranges(&[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)], cx) + editor.change_selections(None, cx, |s| { + s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]) + }) }); workspace .update(cx, |w, cx| Pane::go_back(w, None, cx)) @@ -1177,7 +1181,7 @@ mod tests { let editor = item.downcast::().unwrap(); let (selections, scroll_position) = editor.update(cx, |editor, cx| { ( - editor.selected_display_ranges(cx), + editor.selections.display_ranges(cx), editor.scroll_position(cx), ) });