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 33d1d52677..1536ceae92 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -3001,7 +3001,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(true, cx, |s| s.select_ranges([13..13], None)); editor.handle_input(&Input(".".into()), cx); cx.focus(&editor_b); }); @@ -4213,7 +4213,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(true, cx, |s| { + s.select_ranges([Point::new(1, 31)..Point::new(1, 31)], None) + }); cx.focus(&editor_b); }); @@ -4450,7 +4452,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(true, cx, |s| s.select_ranges([7..7], None)); editor.rename(&Rename, cx).unwrap() }); @@ -5470,8 +5472,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(true, cx, |s| s.select_ranges([0..1], None)) + }); + editor_a2.update(cx_a, |editor, cx| { + editor.change_selections(true, cx, |s| s.select_ranges([2..3], None)) + }); workspace_b .update(cx_b, |workspace, cx| { workspace @@ -5536,7 +5542,7 @@ 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(true, cx, |s| s.select_ranges([1..1, 2..2], None)); }); editor_b1 .condition(cx_b, |editor, cx| { @@ -5550,7 +5556,7 @@ mod tests { .await; editor_a1.update(cx_a, |editor, cx| { - editor.select_ranges([3..3], None, cx); + editor.change_selections(true, cx, |s| s.select_ranges([3..3], None)); editor.set_scroll_position(vec2f(0., 100.), cx); }); editor_b1 diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index eb8fae2ab2..31807f07d8 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -417,8 +417,11 @@ 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(true, cx, |s| s.refresh()); + selections = editor + .selections + .interleaved::(&editor.buffer().read(cx).read(cx)); } // If any selection has lost its position, move it to start of the next primary diagnostic. @@ -441,7 +444,9 @@ impl ProjectDiagnosticsEditor { } } } - editor.update_selections(selections, None, cx); + editor.change_selections(true, cx, |s| { + s.select(selections, None); + }); Some(()) }); diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index ef99cbf5a8..abc0b13181 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::(&buffer.read(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..b01bee0ab4 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,8 +50,7 @@ use std::{ any::TypeId, borrow::Cow, cmp::{self, Ordering, Reverse}, - iter::{self, FromIterator}, - mem, + iter, mem, ops::{Deref, DerefMut, Range, RangeInclusive, Sub}, sync::Arc, time::{Duration, Instant}, @@ -377,9 +377,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 +427,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 +519,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, @@ -949,19 +941,8 @@ impl Editor { 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: SelectionsCollection::new(), columnar_selection_tail: None, - next_selection_id: 1, add_selections_state: None, select_next_state: None, selection_history: Default::default(), @@ -1204,12 +1185,15 @@ 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::(&display_map.buffer_snapshot); 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 + .interleaved::(&display_map.buffer_snapshot); first_cursor_top = selections .first() .unwrap() @@ -1269,7 +1253,9 @@ 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 + .interleaved::(&display_map.buffer_snapshot); let mut target_left; let mut target_right; @@ -1318,22 +1304,91 @@ impl Editor { } } - pub fn replace_selections_with( + pub fn change_selections( &mut self, + local: bool, 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, + change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R, + ) -> R { + let old_cursor_position = self.selections.newest_anchor().head(); + self.push_to_selection_history(); + + #[allow(deprecated)] + // Using deprecated to prevent using change_with outside of this function + let (autoscroll, result) = + self.selections + .change_with(self.display_map.clone(), self.buffer.clone(), cx, change); + + if let Some(autoscroll) = autoscroll { + self.request_autoscroll(autoscroll, cx); } - .map(|display_point| display_point.to_point(&display_map)); - self.update_selections(vec![selection], None, cx); + + if self.focused && self.leader_replica_id.is_none() { + self.buffer.update(cx, |buffer, cx| { + buffer.set_active_selections(&self.selections.disjoint_anchors(), 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 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(); + + result } pub fn display_selections( @@ -1342,61 +1397,14 @@ impl Editor { ) -> (DisplaySnapshot, Vec>) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let selections = self - .local_selections::(cx) + .selections + .interleaved::(&display_map.buffer_snapshot) .into_iter() .map(|selection| selection.map(|point| point.to_display_point(&display_map))) .collect(); (display_map, selections) } - 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); - } - - 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); - }); - } - - 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) - }); - } - pub fn edit(&mut self, edits: I, cx: &mut ViewContext) where I: IntoIterator, T)>, @@ -1450,29 +1458,34 @@ 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) + .selections + .newest::(&display_map.buffer_snapshot) .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(); - if position >= tail { - pending.selection.start = tail_anchor.clone(); - } else { - pending.selection.end = tail_anchor.clone(); - pending.selection.reversed = true; - } + self.change_selections(true, cx, |s| { + let mut pending = s + .pending_mut() + .as_mut() + .expect("extend_selection not called with pending selection"); - match &mut pending.mode { - SelectMode::Word(range) | SelectMode::Line(range) => { - *range = tail_anchor.clone()..tail_anchor + if position >= tail { + pending.selection.start = tail_anchor.clone(); + } else { + pending.selection.end = tail_anchor.clone(); + pending.selection.reversed = true; } - _ => {} - } - self.set_selections(self.selections.clone(), Some(pending), true, cx); + match &mut pending.mode { + SelectMode::Word(range) | SelectMode::Line(range) => { + *range = tail_anchor.clone()..tail_anchor + } + _ => {} + } + }); } fn begin_selection( @@ -1489,7 +1502,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 +1540,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(true, 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( @@ -1574,7 +1566,8 @@ 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) + .selections + .newest::(&display_map.buffer_snapshot) .tail(); self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail)); @@ -1599,14 +1592,14 @@ 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 { + match &self.selections.pending_mode().unwrap() { 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 +1655,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(true, cx, |s| { + s.pending_mut().as_mut().unwrap().selection = pending; + }); } else { log::error!("update_selection dispatched with no pending selection"); return; @@ -1682,9 +1678,14 @@ 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 + .interleaved::(&self.buffer.read(cx).snapshot(cx)); + self.change_selections(true, cx, |s| { + s.select(selections, None); + s.clear_pending(); + }); } } @@ -1702,7 +1703,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 +1712,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(true, cx, |s| { + s.select_ranges(selection_ranges, None); + }); 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,28 +1752,10 @@ 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); + if self.change_selections(true, cx, |s| s.try_cancel()) { 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); - return; - } } cx.propagate_action(); @@ -1783,7 +1766,8 @@ impl Editor { &self, cx: &AppContext, ) -> Vec> { - self.local_selections::(cx) + self.selections + .interleaved::(&self.buffer.read(cx).read(cx)) .iter() .map(|s| { if s.reversed { @@ -1801,12 +1785,9 @@ impl Editor { .display_map .update(cx, |display_map, cx| display_map.snapshot(cx)); self.selections + .disjoint_anchors() .iter() - .chain( - self.pending_selection - .as_ref() - .map(|pending| &pending.selection), - ) + .chain(self.selections.pending_anchor().as_ref()) .map(|s| { if s.reversed { s.end.to_display_point(&display_map)..s.start.to_display_point(&display_map) @@ -1817,68 +1798,6 @@ impl Editor { .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,8 +1819,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 buffer = this.buffer.read(cx).snapshot(cx); + let selections = this.selections.interleaved::(&buffer); selections .iter() .map(|selection| { @@ -1947,9 +1866,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,25 +1879,22 @@ impl Editor { this.buffer.update(cx, |buffer, cx| { buffer.edit_with_autoindent(edits, cx); + }); + 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(); - 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(); + this.change_selections(true, cx, |s| { + s.select(new_selections, Some(Autoscroll::Fit)) }); this.request_autoscroll(Autoscroll::Fit, cx); @@ -1985,7 +1904,9 @@ impl Editor { 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 + .interleaved::(&this.buffer.read(cx).snapshot(cx)); let selection_anchors = this.buffer.update(cx, |buffer, cx| { let anchors = { let snapshot = buffer.read(cx); @@ -2019,12 +1940,15 @@ impl Editor { }) .collect() }; - this.update_selections(selections, Some(Autoscroll::Fit), cx); + + this.change_selections(true, cx, |s| { + s.select(selections, Some(Autoscroll::Fit)); + }) }); } 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,51 +1968,52 @@ impl Editor { .cloned() { if self - .local_selections::(cx) + .selections + .interleaved::(&snapshot) .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(true, cx, |s| s.select_anchors(selections, None)); + true } else { false } } fn autoclose_bracket_pairs(&mut self, cx: &mut ViewContext) { - let selections = self.local_selections::(cx); + let snapshot = self.buffer.read(cx).snapshot(cx); + let selections = self.selections.interleaved::(&snapshot); let mut bracket_pair_state = None; let mut new_selections = None; self.buffer.update(cx, |buffer, cx| { @@ -2155,7 +2080,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 +2102,9 @@ impl Editor { }); if let Some(new_selections) = new_selections { - self.update_selections(new_selections, None, cx); + self.change_selections(true, cx, |s| { + s.select(new_selections, None); + }); } if let Some(bracket_pair_state) = bracket_pair_state { self.autoclose_stack.push(bracket_pair_state); @@ -2185,7 +2112,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.interleaved::(&buffer); let autoclose_pair = if let Some(autoclose_pair) = self.autoclose_stack.last() { autoclose_pair } else { @@ -2197,7 +2125,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 +2147,9 @@ impl Editor { }) .collect(); self.autoclose_stack.pop(); - self.update_selections(new_selections, Some(Autoscroll::Fit), cx); + self.change_selections(true, cx, |s| { + s.select(new_selections, Some(Autoscroll::Fit)); + }); true } else { false @@ -2252,7 +2181,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) @@ -2357,8 +2286,10 @@ impl Editor { 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 selections = self + .selections + .interleaved::(&self.buffer.read(cx).snapshot(cx)); + let newest_selection = self.selections.newest_anchor(); if newest_selection.start.buffer_id != Some(buffer_handle.id()) { return None; } @@ -2524,7 +2455,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 +2516,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 +2551,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 +2739,9 @@ impl Editor { }); if let Some(tabstop) = tabstops.first() { - self.select_ranges(tabstop.iter().cloned(), Some(Autoscroll::Fit), cx); + self.change_selections(true, cx, |s| { + s.select_ranges(tabstop.iter().cloned(), Some(Autoscroll::Fit)); + }); self.snippet_stack.push(SnippetState { active_index: 0, ranges: tabstops, @@ -2827,14 +2760,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 +2774,25 @@ 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(); + self.change_selections(true, cx, |s| { + s.select_anchor_ranges( + current_ranges.into_iter().cloned(), + Some(Autoscroll::Fit), + ) + }); - // Remove the snippet state when moving to the last tabstop. - if snippet.active_index + 1 == snippet.ranges.len() { - self.snippet_stack.pop(); + // 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 +2806,10 @@ 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 + .interleaved::(&display_map.buffer_snapshot); for selection in &mut selections { if selection.is_empty() { let old_head = selection.head(); @@ -2911,18 +2836,20 @@ impl Editor { } self.transact(cx, |this, cx| { - this.update_selections(selections, Some(Autoscroll::Fit), cx); + this.change_selections(true, cx, |s| s.select(selections, Some(Autoscroll::Fit))); 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(true, 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 +2868,9 @@ impl Editor { return; } - let mut selections = self.local_selections::(cx); + let mut selections = self + .selections + .interleaved::(&self.buffer.read(cx).read(cx)); if selections.iter().all(|s| s.is_empty()) { self.transact(cx, |this, cx| { this.buffer.update(cx, |buffer, cx| { @@ -2966,7 +2895,9 @@ impl Editor { selection.end = selection.start; } }); - this.update_selections(selections, Some(Autoscroll::Fit), cx); + this.change_selections(true, cx, |s| { + s.select(selections, Some(Autoscroll::Fit)); + }); }); } else { self.indent(&Indent, cx); @@ -2974,7 +2905,9 @@ impl Editor { } pub fn indent(&mut self, _: &Indent, cx: &mut ViewContext) { - let mut selections = self.local_selections::(cx); + let mut selections = self + .selections + .interleaved::(&self.buffer.read(cx).read(cx)); self.transact(cx, |this, cx| { let mut last_indent = None; this.buffer.update(cx, |buffer, cx| { @@ -3029,13 +2962,17 @@ impl Editor { } }); - this.update_selections(selections, Some(Autoscroll::Fit), cx); + this.change_selections(true, cx, |s| { + s.select(selections, Some(Autoscroll::Fit)); + }); }); } 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 + .interleaved::(&display_map.buffer_snapshot); let mut deletion_ranges = Vec::new(); let mut last_outdent = None; { @@ -3078,18 +3015,18 @@ impl Editor { cx, ); }); - this.update_selections( - this.local_selections::(cx), - Some(Autoscroll::Fit), - cx, - ); + let snapshot = this.buffer.read(cx).snapshot(cx); + this.change_selections(true, cx, |s| { + s.select(s.interleaved::(&snapshot), Some(Autoscroll::Fit)) + }); }); } 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 + .interleaved::(&display_map.buffer_snapshot); let mut new_cursors = Vec::new(); let mut edit_ranges = Vec::new(); @@ -3109,6 +3046,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 +3098,17 @@ impl Editor { } }) .collect(); - this.update_selections(new_selections, Some(Autoscroll::Fit), cx); + + this.change_selections(true, cx, |s| { + s.select(new_selections, Some(Autoscroll::Fit)); + }); }); } 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.interleaved::(buffer); let mut edits = Vec::new(); let mut selections_iter = selections.iter().peekable(); @@ -3212,7 +3153,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.interleaved::(&buffer); let mut selections = selections.iter().peekable(); let mut contiguous_row_selections = Vec::new(); let mut new_selections = Vec::new(); @@ -3310,7 +3251,9 @@ impl Editor { } }); this.fold_ranges(refold_ranges, cx); - this.update_selections(new_selections, Some(Autoscroll::Fit), cx); + this.change_selections(true, cx, |s| { + s.select(new_selections, Some(Autoscroll::Fit)); + }) }); } @@ -3322,7 +3265,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.interleaved::(&buffer); let mut selections = selections.iter().peekable(); let mut contiguous_row_selections = Vec::new(); let mut new_selections = Vec::new(); @@ -3413,62 +3356,68 @@ impl Editor { } }); this.fold_ranges(refold_ranges, cx); - this.update_selections(new_selections, Some(Autoscroll::Fit), cx); + this.change_selections(true, cx, |s| { + s.select(new_selections, Some(Autoscroll::Fit)) + }); }); } 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(true, 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 buffer = this.buffer.read(cx).snapshot(cx); + this.change_selections(true, cx, |s| { + s.select(s.interleaved::(&buffer), Some(Autoscroll::Fit)); + }); }); } 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.interleaved::(&buffer); 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 +3439,20 @@ impl Editor { } self.transact(cx, |this, cx| { - this.update_selections(selections, Some(Autoscroll::Fit), cx); + this.change_selections(true, cx, |s| { + s.select(selections, Some(Autoscroll::Fit)); + }); 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 buffer = self.buffer.read(cx).read(cx); + let selections = self.selections.interleaved::(&buffer); 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 +3482,9 @@ 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 + .interleaved::(&this.buffer.read(cx).read(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 +3537,12 @@ 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 + .interleaved::(&this.buffer.read(cx).read(cx)); + this.change_selections(true, cx, |s| { + s.select(selections, Some(Autoscroll::Fit)) + }); } else { this.insert(&clipboard_text, cx); } @@ -3596,7 +3553,11 @@ 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(true, cx, |s| { + // TODO: move to SelectionsCollection to preserve selection + // invariants without rechecking + s.select_anchors(selections.to_vec(), None); + }); } self.request_autoscroll(Autoscroll::Fit, cx); cx.emit(Event::Edited); @@ -3607,7 +3568,11 @@ 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(true, cx, |s| { + // TODO: move to SelectionsCollection to preserve selection + // invariants without rechecking + s.select_anchors(selections.to_vec(), None); + }); } self.request_autoscroll(Autoscroll::Fit, cx); cx.emit(Event::Edited); @@ -3620,37 +3585,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(true, 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(true, 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(true, 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(true, 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 +3638,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(true, 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(true, 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 +3669,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(true, 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(true, cx, |s| { + s.move_heads_with(|map, head, goal| movement::down(map, head, goal, false)) + }); } pub fn move_to_previous_word_start( @@ -3714,12 +3691,14 @@ impl Editor { _: &MoveToPreviousWordStart, cx: &mut ViewContext, ) { - self.move_cursors(cx, |map, head, _| { - ( - movement::previous_word_start(map, head), - SelectionGoal::None, - ) - }); + self.change_selections(true, 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 +3706,14 @@ impl Editor { _: &MoveToPreviousSubwordStart, cx: &mut ViewContext, ) { - self.move_cursors(cx, |map, head, _| { - ( - movement::previous_subword_start(map, head), - SelectionGoal::None, - ) - }); + self.change_selections(true, 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 +3721,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(true, 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 +3736,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(true, 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 +3752,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(true, 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 +3770,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(true, 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(true, 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 +3795,19 @@ impl Editor { _: &MoveToNextSubwordEnd, cx: &mut ViewContext, ) { - self.move_cursors(cx, |map, head, _| { - (movement::next_subword_end(map, head), SelectionGoal::None) - }); + self.change_selections(true, 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(true, 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 +3815,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(true, 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(true, 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 +3842,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(true, 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 +3859,14 @@ impl Editor { _: &MoveToBeginningOfLine, cx: &mut ViewContext, ) { - self.move_cursors(cx, |map, head, _| { - ( - movement::line_beginning(map, head, true), - SelectionGoal::None, - ) - }); + self.change_selections(true, 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 +3874,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(true, cx, |s| { + s.move_heads_with(|map, head, _| { + ( + movement::line_beginning(map, head, action.stop_at_soft_wraps), + SelectionGoal::None, + ) + }); }); } @@ -3885,8 +3890,10 @@ impl Editor { cx: &mut ViewContext, ) { self.transact(cx, |this, cx| { - this.move_selections(cx, |_, selection| { - selection.reversed = true; + this.change_selections(true, cx, |s| { + s.move_with(|_, selection| { + selection.reversed = true; + }); }); this.select_to_beginning_of_line( @@ -3900,9 +3907,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(true, 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 +3919,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(true, 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 +3959,20 @@ 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(true, cx, |s| { + s.select_ranges(vec![0..0], Some(Autoscroll::Fit)); + }); } 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::(&self.buffer.read(cx).read(cx)); selection.set_head(Point::zero(), SelectionGoal::None); - self.update_selections(vec![selection], Some(Autoscroll::Fit), cx); + + self.change_selections(true, cx, |s| { + s.select(vec![selection], Some(Autoscroll::Fit)); + }); } pub fn move_to_end(&mut self, _: &MoveToEnd, cx: &mut ViewContext) { @@ -3971,14 +3982,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(true, cx, |s| { + s.select_ranges(vec![cursor..cursor], Some(Autoscroll::Fit)) + }); } pub fn set_nav_history(&mut self, nav_history: Option) { @@ -4019,25 +4025,26 @@ 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::(&buffer); + selection.set_head(buffer.len(), SelectionGoal::None); + self.change_selections(true, cx, |s| { + s.select(vec![selection], Some(Autoscroll::Fit)); + }); } 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(true, cx, |s| { + s.select_ranges(vec![0..end], Some(Autoscroll::Fit)); + }); } 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 + .interleaved::(&display_map.buffer_snapshot); let max_point = display_map.buffer_snapshot.max_point(); for selection in &mut selections { let rows = selection.spanned_rows(true, &display_map); @@ -4045,7 +4052,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(true, cx, |s| { + s.select(selections, Some(Autoscroll::Fit)); + }); } pub fn split_selection_into_lines( @@ -4054,33 +4063,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 buffer = self.buffer.read(cx).read(cx); + let selections = self.selections.interleaved::(&buffer); 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(true, cx, |s| { + s.select_ranges(new_selection_ranges, Some(Autoscroll::Fit)); + }); } pub fn add_selection_above(&mut self, _: &AddSelectionAbove, cx: &mut ViewContext) { @@ -4094,7 +4093,9 @@ 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 + .interleaved::(&display_map.buffer_snapshot); 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(); @@ -4179,7 +4180,9 @@ impl Editor { state.stack.pop(); } - self.update_selections(new_selections, Some(Autoscroll::Newest), cx); + self.change_selections(true, cx, |s| { + s.select(new_selections, Some(Autoscroll::Fit)); + }); if state.stack.len() > 1 { self.add_selections_state = Some(state); } @@ -4189,7 +4192,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.interleaved::(&buffer); 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 +4228,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(true, 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, Some(Autoscroll::Newest)); }); - self.unfold_ranges([next_selected_range], false, cx); - self.update_selections(selections, Some(Autoscroll::Newest), cx); } else { select_next_state.done = true; } @@ -4268,7 +4262,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(true, cx, |s| { + s.select(selections, Some(Autoscroll::Newest)); + }); self.select_next_state = Some(select_state); } else { let query = buffer @@ -4286,7 +4282,9 @@ 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 + .interleaved::(&this.buffer.read(cx).snapshot(cx)); let mut all_selection_lines_are_comments = true; let mut edit_ranges = Vec::new(); let mut last_toggled_row = None; @@ -4387,11 +4385,12 @@ impl Editor { } }); - this.update_selections( - this.local_selections::(cx), - Some(Autoscroll::Fit), - cx, - ); + let selections = this + .selections + .interleaved::(&this.buffer.read(cx).read(cx)); + this.change_selections(true, cx, |s| { + s.select(selections, Some(Autoscroll::Fit)); + }); }); } @@ -4400,9 +4399,12 @@ 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 + .interleaved::(&buffer) + .into_boxed_slice(); let mut stack = mem::take(&mut self.select_larger_syntax_node_stack); let mut selected_larger_node = false; @@ -4435,7 +4437,9 @@ impl Editor { if selected_larger_node { stack.push(old_selections); - self.update_selections(new_selections, Some(Autoscroll::Fit), cx); + self.change_selections(true, cx, |s| { + s.select(new_selections, Some(Autoscroll::Fit)); + }); } self.select_larger_syntax_node_stack = stack; } @@ -4447,7 +4451,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(true, cx, |s| { + s.select(selections.to_vec(), Some(Autoscroll::Fit)); + }); } self.select_larger_syntax_node_stack = stack; } @@ -4457,8 +4463,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.interleaved::(&buffer); for selection in &mut selections { if let Some((open_range, close_range)) = buffer.enclosing_bracket_ranges(selection.start..selection.end) @@ -4476,14 +4482,20 @@ impl Editor { } } - self.update_selections(selections, Some(Autoscroll::Fit), cx); + self.change_selections(true, cx, |s| { + s.select(selections, Some(Autoscroll::Fit)); + }); } 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(true, cx, |s| { + // TODO: Move to selections so selections invariants can be preserved rather than + // rechecking them. + s.select_anchors(entry.selections.to_vec(), None) + }); self.select_next_state = entry.select_next_state; self.add_selections_state = entry.add_selections_state; self.request_autoscroll(Autoscroll::Newest, cx); @@ -4495,7 +4507,11 @@ 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(true, cx, |s| { + // TODO: Move to selections so selections invariants can be preserved rather than + // rechecking them. + s.select_anchors(entry.selections.to_vec(), None) + }); self.select_next_state = entry.select_next_state; self.add_selections_state = entry.add_selections_state; self.request_autoscroll(Autoscroll::Newest, cx); @@ -4513,7 +4529,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::(&buffer); let mut active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| { active_diagnostics .primary_range @@ -4550,17 +4566,18 @@ impl Editor { if let Some((primary_range, group_id)) = group { self.activate_diagnostics(group_id, cx); - self.update_selections( - vec![Selection { - id: selection.id, - start: primary_range.start, - end: primary_range.start, - reversed: false, - goal: SelectionGoal::None, - }], - Some(Autoscroll::Center), - cx, - ); + self.change_selections(true, 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), + ); + }); break; } else { // Cycle around to the start of the buffer, potentially moving back to the start of @@ -4599,13 +4616,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::(&buffer.read(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 +4640,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(true, cx, |s| { + s.select_ranges([range], Some(Autoscroll::Center)); + }); + nav_history.borrow_mut().enable(); }); } @@ -4643,8 +4663,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::(&buffer.read(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 +4733,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 +4922,12 @@ 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 rename_buffer = rename_editor.buffer.read(cx); + let cursor_in_rename_editor = rename_editor + .selections + .newest::(&rename_buffer.read(cx)) + .head(); // Update the selection to match the position of the selection inside // the rename editor. @@ -4913,17 +4938,14 @@ 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, - ); + let new_selection = Selection { + id: self.selections.newest_anchor().id, + start: cursor_in_editor, + end: cursor_in_editor, + reversed: false, + goal: SelectionGoal::None, + }; + self.change_selections(true, cx, |s| s.select(vec![new_selection], None)); } Some(rename) @@ -5046,8 +5068,9 @@ impl Editor { 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)); + // TODO: Don't expose next_selection_id Some(Selection { - id: post_inc(&mut self.next_selection_id), + id: post_inc(&mut self.selections.next_selection_id), start: start.to_point(display_map), end: end.to_point(display_map), reversed, @@ -5061,430 +5084,19 @@ impl Editor { } } - 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)) + self.change_selections(false, cx, |s| { + s.select_anchors(selections, None); }); - - // 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 }); } 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 +5129,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 +5139,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 +5159,10 @@ 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 + .interleaved::(&display_map.buffer_snapshot); 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 +5184,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.interleaved::(buffer); let ranges = selections .iter() .map(|s| { @@ -5630,7 +5244,9 @@ impl Editor { } pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext) { - let selections = self.local_selections::(cx); + let selections = self + .selections + .interleaved::(&self.buffer.read(cx).read(cx)); let ranges = selections.into_iter().map(|s| s.start..s.end); self.fold_ranges(ranges, cx); } @@ -6007,7 +5623,7 @@ impl Editor { } let mut new_selections_by_buffer = HashMap::default(); - for selection in editor.local_selections::(cx) { + for selection in editor.selections.interleaved::(&buffer.read(cx)) { for (buffer, mut range) in buffer.range_to_buffer_ranges(selection.start..selection.end, cx) { @@ -6022,7 +5638,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 +5650,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(true, cx, |s| { + s.select_ranges(ranges, Some(Autoscroll::Newest)); + }); }); } @@ -6134,7 +5752,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 +6189,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 +6294,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(true, cx, |s| s.select_ranges([0..0], None)); + editor.backspace(&Backspace, cx); }); assert_eq!(mem::take(&mut *events.borrow_mut()), []); @@ -6693,21 +6312,22 @@ mod tests { editor.update(cx, |editor, cx| { editor.start_transaction_at(now, cx); - editor.select_ranges([2..4], None, cx); + editor.change_selections(true, cx, |s| s.select_ranges([2..4], None)); + 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]); editor.start_transaction_at(now, cx); - editor.select_ranges([4..5], None, cx); + editor.change_selections(true, cx, |s| s.select_ranges([4..5], None)); 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]); now += group_interval + Duration::from_millis(1); - editor.select_ranges([2..2], None, cx); + editor.change_selections(true, cx, |s| s.select_ranges([2..2], None)); // Simulate an edit in another editor buffer.update(cx, |buffer, cx| { @@ -6861,13 +6481,19 @@ 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(true, cx, |s| { + s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]) + }); + editor.change_selections(true, 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(true, 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()); @@ -7014,7 +6640,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(true, cx, |s| { + s.select_display_ranges([DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)]); + }); view.fold(&Fold, cx); assert_eq!( view.display_text(cx), @@ -7130,7 +6758,9 @@ mod tests { &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)] ); - view.select_display_ranges(&[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)], cx); + view.change_selections(true, 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), @@ -7252,7 +6882,9 @@ 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(true, cx, |s| { + s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]); + }); view.move_down(&MoveDown, cx); assert_eq!( view.selected_display_ranges(cx), @@ -7297,13 +6929,12 @@ 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(true, 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| { @@ -7458,13 +7089,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(true, 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,7 +7200,9 @@ 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(true, cx, |s| { + s.select_display_ranges([DisplayPoint::new(1, 7)..DisplayPoint::new(1, 7)]); + }); view.move_to_next_word_end(&MoveToNextWordEnd, cx); assert_eq!( @@ -7619,7 +7251,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(true, cx, |s| s.select_ranges(ranges, None)); editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx); assert_eq!(editor.text(cx), " four"); }); @@ -7632,30 +7264,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(true, 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(true, 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 +7299,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(true, 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 +7332,15 @@ mod tests { let (_, editor) = cx.add_window(Default::default(), |cx| { let mut editor = build_editor(buffer.clone(), cx); - editor.select_ranges( - [ - Point::new(2, 4)..Point::new(2, 5), - Point::new(5, 4)..Point::new(5, 5), - ], - None, - cx, - ); + editor.change_selections(true, cx, |s| { + s.select_ranges( + [ + Point::new(2, 4)..Point::new(2, 5), + Point::new(5, 4)..Point::new(5, 5), + ], + None, + ) + }); editor }); @@ -7773,7 +7403,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(true, cx, |s| s.select_ranges([3..4, 11..12, 19..20], None)); editor }); @@ -8022,23 +7652,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(true, 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(true, 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 +7679,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 +7694,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(true, 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,14 +7719,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( - &[ + view.change_selections(true, 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!( @@ -8115,7 +7741,9 @@ 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(true, 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!( @@ -8131,15 +7759,14 @@ 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(true, 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!( @@ -8156,13 +7783,12 @@ 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(true, 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!( @@ -8189,15 +7815,14 @@ mod tests { ], cx, ); - view.select_display_ranges( - &[ + view.change_selections(true, 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" @@ -8287,7 +7912,9 @@ mod tests { }], cx, ); - editor.select_ranges([Point::new(2, 0)..Point::new(2, 0)], None, cx); + editor.change_selections(true, cx, |s| { + s.select_ranges([Point::new(2, 0)..Point::new(2, 0)], None) + }); editor.move_line_down(&MoveLineDown, cx); }); } @@ -8299,7 +7926,7 @@ 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(true, cx, |s| s.select_ranges([1..1], None)); editor.transpose(&Default::default(), cx); assert_eq!(editor.text(cx), "bac"); assert_eq!(editor.selected_ranges(cx), [2..2]); @@ -8319,12 +7946,12 @@ 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(true, cx, |s| s.select_ranges([3..3], None)); editor.transpose(&Default::default(), cx); assert_eq!(editor.text(cx), "acb\nde"); assert_eq!(editor.selected_ranges(cx), [3..3]); - editor.select_ranges([4..4], None, cx); + editor.change_selections(true, cx, |s| s.select_ranges([4..4], None)); editor.transpose(&Default::default(), cx); assert_eq!(editor.text(cx), "acbd\ne"); assert_eq!(editor.selected_ranges(cx), [5..5]); @@ -8344,7 +7971,7 @@ 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(true, cx, |s| s.select_ranges([1..1, 2..2, 4..4], None)); editor.transpose(&Default::default(), cx); assert_eq!(editor.text(cx), "bacd\ne"); assert_eq!(editor.selected_ranges(cx), [2..2, 3..3, 5..5]); @@ -8372,7 +7999,7 @@ 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(true, cx, |s| s.select_ranges([4..4], None)); editor.transpose(&Default::default(), cx); assert_eq!(editor.text(cx), "🏀🍐✋"); assert_eq!(editor.selected_ranges(cx), [8..8]); @@ -8400,14 +8027,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(true, cx, |s| { + s.select_ranges(vec![0..7, 11..17, 22..27], None) + }); 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(true, cx, |s| { + s.select_ranges(vec![4..4, 9..9, 13..13], None) + }); view.paste(&Paste, cx); assert_eq!(view.display_text(cx), "two one✅ four three six five "); assert_eq!( @@ -8424,7 +8055,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(true, cx, |s| s.select_ranges(vec![0..0, 31..31], None)); view.handle_input(&Input("( ".into()), cx); view.paste(&Paste, cx); view.handle_input(&Input(") ".into()), cx); @@ -8435,7 +8066,7 @@ mod tests { }); view.update(cx, |view, cx| { - view.select_ranges(vec![0..0], None, cx); + view.change_selections(true, cx, |s| s.select_ranges(vec![0..0], None)); view.handle_input(&Input("123\n4567\n89\n".into()), cx); assert_eq!( view.display_text(cx), @@ -8445,14 +8076,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(true, 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,14 +8093,13 @@ 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(true, 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), @@ -8488,21 +8117,22 @@ 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(true, 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(true, 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), @@ -8539,15 +8169,14 @@ 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(true, 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), @@ -8592,15 +8221,14 @@ mod tests { ], cx, ); - view.select_display_ranges( - &[ + view.change_selections(true, 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"); }); @@ -8622,7 +8250,9 @@ mod tests { }); view.update(cx, |view, cx| { - view.select_display_ranges(&[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 1)], cx); + view.change_selections(true, 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), @@ -8651,7 +8281,9 @@ 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(true, 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); @@ -8721,7 +8353,9 @@ mod tests { }); view.update(cx, |view, cx| { - view.select_display_ranges(&[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)], cx); + view.change_selections(true, 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); @@ -8762,7 +8396,9 @@ mod tests { }); view.update(cx, |view, cx| { - view.select_display_ranges(&[DisplayPoint::new(0, 1)..DisplayPoint::new(1, 4)], cx); + view.change_selections(true, 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), @@ -8800,7 +8436,9 @@ mod tests { }); view.update(cx, |view, cx| { - view.select_display_ranges(&[DisplayPoint::new(4, 3)..DisplayPoint::new(1, 1)], cx); + view.change_selections(true, 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); @@ -8837,7 +8475,9 @@ 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(true, cx, |s| { + s.select_ranges([ranges[1].start + 1..ranges[1].start + 1], None) + }); view.select_next( &SelectNext { replace_newest: false, @@ -8902,14 +8542,13 @@ mod tests { .await; view.update(cx, |view, cx| { - view.select_display_ranges( - &[ + view.change_selections(true, 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!( @@ -9062,7 +8701,7 @@ mod tests { .await; editor.update(cx, |editor, cx| { - editor.select_ranges([5..5, 8..8, 9..9], None, cx); + editor.change_selections(true, cx, |s| s.select_ranges([5..5, 8..8, 9..9], None)); editor.newline(&Newline, cx); assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n"); assert_eq!( @@ -9116,13 +8755,12 @@ mod tests { .await; view.update(cx, |view, cx| { - view.select_display_ranges( - &[ + view.change_selections(true, 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 +8806,12 @@ mod tests { ); view.undo(&Undo, cx); - view.select_display_ranges( - &[ + view.change_selections(true, 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 +8827,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(true, 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 +8843,9 @@ mod tests { ); view.undo(&Undo, cx); - view.select_display_ranges(&[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1)], cx); + view.change_selections(true, 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), @@ -9227,105 +8868,88 @@ 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.selected_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.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 +9113,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(true, cx, |s| { + s.select_ranges([Point::new(0, 3)..Point::new(0, 3)], None) + }); editor.handle_input(&Input(".".to_string()), cx); }); @@ -9542,14 +9168,15 @@ mod tests { ); editor.update(cx, |editor, cx| { - editor.select_ranges( - [ - Point::new(1, 3)..Point::new(1, 3), - Point::new(2, 5)..Point::new(2, 5), - ], - None, - cx, - ); + editor.change_selections(true, cx, |s| { + s.select_ranges( + [ + Point::new(1, 3)..Point::new(1, 3), + Point::new(2, 5)..Point::new(2, 5), + ], + None, + ) + }); editor.handle_input(&Input(" ".to_string()), cx); assert!(editor.context_menu.is_none()); @@ -9702,13 +9329,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(true, 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 +9350,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(true, 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 +9367,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(true, 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,14 +9407,15 @@ 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( - [ - Point::new(0, 0)..Point::new(0, 0), - Point::new(1, 0)..Point::new(1, 0), - ], - None, - cx, - ); + view.change_selections(true, cx, |s| { + s.select_ranges( + [ + Point::new(0, 0)..Point::new(0, 0), + Point::new(1, 0)..Point::new(1, 0), + ], + None, + ) + }); view.handle_input(&Input("X".to_string()), cx); assert_eq!(view.text(cx), "Xaaaa\nXbbbb"); @@ -9820,7 +9451,7 @@ mod tests { b|bb|b cccc"}); assert_eq!(view.text(cx), expected_text); - view.select_ranges(selection_ranges, None, cx); + view.change_selections(true, cx, |s| s.select_ranges(selection_ranges, None)); view.handle_input(&Input("X".to_string()), cx); @@ -9874,7 +9505,9 @@ 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(true, cx, |s| { + s.select_ranges([Point::new(1, 3)..Point::new(1, 3)], None) + }); editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx); assert_eq!( editor.selected_ranges(cx), @@ -9888,7 +9521,9 @@ 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(true, cx, |s| { + s.refresh(); + }); assert_eq!( editor.selected_ranges(cx), [ @@ -9913,7 +9548,9 @@ mod tests { // Refreshing selections will relocate the first selection to the original buffer // location. - editor.refresh_selections(cx); + editor.change_selections(true, cx, |s| { + s.refresh(); + }); assert_eq!( editor.selected_ranges(cx), [ @@ -9921,7 +9558,7 @@ mod tests { Point::new(0, 3)..Point::new(0, 3) ] ); - assert!(editor.pending_selection.is_some()); + assert!(editor.selections.pending_anchor().is_some()); }); } @@ -9970,12 +9607,14 @@ mod tests { ); // Ensure we don't panic when selections are refreshed and that the pending selection is finalized. - editor.refresh_selections(cx); + editor.change_selections(true, cx, |s| { + s.refresh(); + }); assert_eq!( editor.selected_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 +9657,13 @@ mod tests { .await; view.update(cx, |view, cx| { - view.select_display_ranges( - &[ + view.change_selections(true, 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,7 +9796,7 @@ mod tests { // Update the selections only leader.update(cx, |leader, cx| { - leader.select_ranges([1..1], None, cx); + leader.change_selections(true, cx, |s| s.select_ranges([1..1], None)); }); follower.update(cx, |follower, cx| { follower @@ -10183,7 +9821,7 @@ mod tests { // Update the selections and scroll position leader.update(cx, |leader, cx| { - leader.select_ranges([0..0], None, cx); + leader.change_selections(true, cx, |s| s.select_ranges([0..0], None)); leader.request_autoscroll(Autoscroll::Newest, cx); leader.set_scroll_position(vec2f(1.5, 3.5), cx); }); @@ -10199,7 +9837,7 @@ mod tests { // Creating a pending selection that precedes another selection leader.update(cx, |leader, cx| { - leader.select_ranges([1..1], None, cx); + leader.change_selections(true, cx, |s| s.select_ranges([1..1], None)); leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx); }); follower.update(cx, |follower, cx| { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index a04d27a8bb..dcef624d01 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -957,8 +957,9 @@ 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 local_selections = view + .selections + .interleaved_in_range(start_anchor..end_anchor, &display_map.buffer_snapshot); for selection in &local_selections { let is_empty = selection.start == selection.end; let selection_start = snapshot.prev_line_boundary(selection.start).1; @@ -1041,7 +1042,8 @@ impl Element for EditorElement { } let newest_selection_head = view - .newest_selection_with_snapshot::(&snapshot.buffer_snapshot) + .selections + .newest::(&snapshot.buffer_snapshot) .head() .to_display_point(&snapshot); diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 120826321b..b87719c130 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 @@ -252,7 +258,7 @@ impl Item for Editor { } else { buffer.clip_point(data.cursor_position, Bias::Left) }; - let newest_selection = self.newest_selection_with_snapshot::(&buffer); + let newest_selection = self.selections.newest::(&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(true, cx, |s| { + s.select_ranges([offset..offset], Some(Autoscroll::Fit)) + }); 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.interleaved::(&buffer) { 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..fc2f563099 --- /dev/null +++ b/crates/editor/src/selections_collection.rs @@ -0,0 +1,703 @@ +use std::{ + iter, mem, + ops::{Deref, Range, Sub}, + sync::Arc, +}; + +use collections::HashMap; +use gpui::{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, Autoscroll, DisplayPoint, ExcerptId, MultiBuffer, MultiBufferSnapshot, SelectMode, + ToOffset, +}; + +#[derive(Clone)] +pub struct PendingSelection { + pub selection: Selection, + pub mode: SelectMode, +} + +pub struct SelectionsCollection { + pub next_selection_id: usize, + disjoint: Arc<[Selection]>, + pending: Option, +} + +impl SelectionsCollection { + pub fn new() -> Self { + Self { + 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, + }), + } + } + + 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, + snapshot: &MultiBufferSnapshot, + ) -> Option> { + self.pending_anchor() + .as_ref() + .map(|pending| pending.map(|p| p.summary::(&snapshot))) + } + + pub fn pending_mode(&self) -> Option { + self.pending.as_ref().map(|pending| pending.mode.clone()) + } + + pub fn interleaved<'a, D>(&self, buffer: &MultiBufferSnapshot) -> Vec> + where + D: 'a + TextDimension + Ord + Sub, + { + let anchor_disjoint = &self.disjoint; + let mut disjoint = resolve_multiple::(anchor_disjoint.iter(), &buffer).peekable(); + + let mut pending_opt = self.pending::(&buffer); + + 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 interleaved_in_range<'a>( + &self, + range: Range, + buffer: &MultiBufferSnapshot, + ) -> Vec> { + 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, + }; + + fn point_selection( + selection: &Selection, + buffer: &MultiBufferSnapshot, + ) -> Selection { + let start = crate::ToPoint::to_point(&selection.start, &buffer); + let end = crate::ToPoint::to_point(&selection.end, &buffer); + Selection { + id: selection.id, + start, + end, + reversed: selection.reversed, + goal: selection.goal, + } + } + + self.disjoint[start_ix..end_ix] + .iter() + .chain(self.pending.as_ref().map(|pending| &pending.selection)) + .map(|s| point_selection(s, &buffer)) + .collect() + } + + pub fn newest_anchor(&self) -> &Selection { + self.pending + .as_ref() + .map(|s| &s.selection) + .or_else(|| self.disjoint.iter().max_by_key(|s| s.id)) + .unwrap() + } + + pub fn newest>( + &self, + snapshot: &MultiBufferSnapshot, + ) -> Selection { + resolve(self.newest_anchor(), snapshot) + } + + pub fn oldest_anchor(&self) -> &Selection { + self.disjoint + .iter() + .min_by_key(|s| s.id) + .or_else(|| self.pending.as_ref().map(|p| &p.selection)) + .unwrap() + } + + pub fn oldest>( + &self, + snapshot: &MultiBufferSnapshot, + ) -> Selection { + resolve(self.oldest_anchor(), snapshot) + } + + pub fn first>( + &self, + snapshot: &MultiBufferSnapshot, + ) -> Selection { + self.interleaved(&snapshot).first().unwrap().clone() + } + + pub fn last>( + &self, + snapshot: &MultiBufferSnapshot, + ) -> Selection { + self.interleaved(&snapshot).last().unwrap().clone() + } + + // NOTE do not use. This should only be called from Editor::change_selections. + #[deprecated] + pub fn change_with( + &mut self, + display_map: ModelHandle, + buffer: ModelHandle, + cx: &mut MutableAppContext, + change: impl FnOnce(&mut MutableSelectionsCollection) -> R, + ) -> (Option, R) { + let mut mutable_collection = MutableSelectionsCollection { + collection: self, + autoscroll: None, + display_map, + buffer, + 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" + ); + (mutable_collection.autoscroll, result) + } +} + +pub struct MutableSelectionsCollection<'a> { + collection: &'a mut SelectionsCollection, + pub autoscroll: Option, + buffer: ModelHandle, + display_map: ModelHandle, + cx: &'a mut MutableAppContext, +} + +impl<'a> MutableSelectionsCollection<'a> { + 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 pending_mut(&mut self) -> &mut Option { + &mut self.collection.pending + } + + pub fn try_cancel(&mut self) -> bool { + let buffer = self.buffer.read(self.cx).snapshot(self.cx); + 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, &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, autoscroll: Option) + where + T: 'a + + ToOffset + + ToPoint + + TextDimension + + Ord + + Sub + + std::marker::Copy + + std::fmt::Debug, + { + let buffer = self.buffer.read(self.cx).snapshot(self.cx); + let mut selections = self.interleaved(&buffer); + 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 + }; + selections.push(Selection { + id: post_inc(&mut self.collection.next_selection_id), + start, + end, + reversed, + goal: SelectionGoal::None, + }); + self.select(selections, autoscroll); + } + + pub fn select(&mut self, mut selections: Vec>, autoscroll: Option) + 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.autoscroll = autoscroll.or(self.autoscroll.take()); + + 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, + mut selections: Vec>, + autoscroll: Option, + ) { + let buffer = self.buffer.read(self.cx).snapshot(self.cx); + selections.sort_unstable_by(|a, b| a.start.cmp(&b.start, &buffer)); + + // 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; + } + } + + self.autoscroll = autoscroll.or(self.autoscroll.take()); + + self.collection.disjoint = Arc::from_iter( + selections + .into_iter() + .map(|selection| reset_biases(selection, &buffer)), + ); + + self.collection.pending = None; + } + + pub fn select_ranges(&mut self, ranges: I, autoscroll: Option) + 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, autoscroll) + } + + pub fn select_anchor_ranges>>( + &mut self, + ranges: I, + autoscroll: Option, + ) { + 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, autoscroll) + } + + #[cfg(any(test, feature = "test-support"))] + pub fn select_display_ranges(&mut self, ranges: T) + where + T: IntoIterator>, + { + let display_map = self.display_map.update(self.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.collection.next_selection_id), + start: start.to_point(&display_map), + end: end.to_point(&display_map), + reversed, + goal: SelectionGoal::None, + } + }) + .collect(); + self.select(selections, None); + } + + pub fn move_with( + &mut self, + mut move_selection: impl FnMut(&DisplaySnapshot, &mut Selection), + ) { + let display_map = self.display_map.update(self.cx, |map, cx| map.snapshot(cx)); + let selections = self + .interleaved::(&display_map.buffer_snapshot) + .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, Some(Autoscroll::Fit)) + } + + 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.update(self.cx, |map, cx| map.snapshot(cx)); + 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, None); + } + + /// 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 { + // TODO: Pull disjoint constraint out of update_selections so we don't have to + // store the pending_selection here. + let buffer = self.buffer.read(self.cx).snapshot(self.cx); + let mut pending = self.collection.pending.take(); + let mut selections_with_lost_position = HashMap::default(); + + let anchors_with_status = buffer.refresh_anchors( + self.disjoint + .iter() + .flat_map(|selection| [&selection.start, &selection.end]), + ); + 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() { + self.select::( + resolve_multiple(adjusted_disjoint.iter(), &buffer).collect(), + None, + ); + } + + if let Some(pending) = pending.as_mut() { + 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, + 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 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.cmp(&selection.start, buffer).is_gt() { + 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..407733c771 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(true, cx, |s| s.select_ranges(text_ranges, None)); } pub fn assert_text_with_selections( diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index 3f8aa933ba..2f7219aa6f 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(&buffer).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(true, cx, |s| { + s.select_ranges([position..position], Some(Autoscroll::Center)) + }); } }); cx.emit(Event::Dismissed); diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index 19c044e65f..ff0dfa5145 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(true, cx, |s| { + s.select_ranges([len..len], Some(Autoscroll::Center)) + }); if len > 0 { editor.insert("\n\n", cx); } diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index 5658cf2011..7383b4b3a9 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::(&buffer).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(true, cx, |s| { + s.select_ranges([position..position], Some(Autoscroll::Center)) + }); } }); cx.emit(Event::Dismissed); diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 157ea8ef73..80530ff5e6 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(true, cx, |s| { + s.select_ranges([position..position], Some(Autoscroll::Center)) + }); }); }); Ok::<_, anyhow::Error>(()) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 549edf89e7..95ef808ab0 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -227,7 +227,8 @@ impl BufferSearchBar { .display_snapshot; let selection = editor .read(cx) - .newest_selection_with_snapshot::(&display_map.buffer_snapshot); + .selections + .newest::(&display_map.buffer_snapshot); let mut text: String; if selection.start == selection.end { @@ -387,14 +388,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(true, cx, |s| { + s.select_ranges([range_to_select], Some(Autoscroll::Fit)) + }); } }); } @@ -535,11 +538,12 @@ 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()], - Some(Autoscroll::Fit), - cx, - ); + editor.change_selections(true, cx, |s| { + s.select_ranges( + [ranges[match_ix].clone()], + Some(Autoscroll::Fit), + ) + }); } } @@ -564,7 +568,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,7 +725,9 @@ mod tests { }); editor.update(cx, |editor, cx| { - editor.select_display_ranges(&[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)], cx); + editor.change_selections(true, 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)); @@ -804,7 +810,9 @@ 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(true, 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)); @@ -821,7 +829,9 @@ 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(true, 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)); @@ -838,7 +848,9 @@ 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(true, 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)); @@ -855,7 +867,9 @@ 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(true, 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)); @@ -872,7 +886,9 @@ 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(true, 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)); diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index cbd373c468..1e363ebf15 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(true, cx, |s| { + s.select_ranges([range_to_select], Some(Autoscroll::Fit)) + }); }); } } @@ -476,8 +478,10 @@ 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(true, cx, |s| { + s.select_ranges([cursor.clone()..cursor], None) + }); }); cx.focus(&self.results_editor); } @@ -489,7 +493,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(true, cx, |s| { + s.select_ranges(match_ranges.first().cloned(), Some(Autoscroll::Fit)) + }); } editor.highlight_background::( match_ranges, @@ -510,7 +516,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 { diff --git a/crates/vim/src/insert.rs b/crates/vim/src/insert.rs index afaeda17b0..69912cff18 100644 --- a/crates/vim/src/insert.rs +++ b/crates/vim/src/insert.rs @@ -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(true, 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..7c4ac6a20c 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -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(true, 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 = 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(true, 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(true, 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..cfcfa8d2b4 100644 --- a/crates/vim/src/vim_test_context.rs +++ b/crates/vim/src/vim_test_context.rs @@ -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(true, 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 + .interleaved::(&editor.buffer().read(cx).read(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..c1b2771f7a 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -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(true, 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(true, 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)) @@ -979,10 +981,9 @@ mod tests { editor3 .update(cx, |editor, cx| { - editor.select_display_ranges( - &[DisplayPoint::new(12, 0)..DisplayPoint::new(12, 0)], - cx, - ); + editor.change_selections(true, 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); @@ -1123,34 +1124,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(true, 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(true, 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(true, 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(true, 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(true, 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))