diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 01655dd12e60b38ffc8c91bb58e04099faeedbbc..a07f8a887ac42c51de2c4e9486488b5480b77413 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3058,7 +3058,7 @@ impl Editor { self.buffer.update(cx, |buffer, cx| { buffer.set_active_selections( &selection_anchors, - self.selections.line_mode, + self.selections.line_mode(), self.cursor_shape, cx, ) @@ -6900,7 +6900,7 @@ impl Editor { if !EditorSettings::get_global(cx).selection_highlight { return None; } - if self.selections.count() != 1 || self.selections.line_mode { + if self.selections.count() != 1 || self.selections.line_mode() { return None; } let selection = self.selections.newest::(cx); @@ -12269,7 +12269,7 @@ impl Editor { let mut is_first = true; for selection in &mut selections { let is_entire_line = - (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode; + (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode(); if is_entire_line { selection.start = Point::new(selection.start.row, 0); if !selection.is_empty() && selection.end.column == 0 { @@ -12369,7 +12369,7 @@ impl Editor { for selection in &selections { let mut start = selection.start; let mut end = selection.end; - let is_entire_line = selection.is_empty() || self.selections.line_mode; + let is_entire_line = selection.is_empty() || self.selections.line_mode(); if is_entire_line { start = Point::new(start.row, 0); end = cmp::min(max_point, Point::new(end.row + 1, 0)); @@ -21357,7 +21357,7 @@ impl Editor { if self.leader_id.is_none() { buffer.set_active_selections( &self.selections.disjoint_anchors_arc(), - self.selections.line_mode, + self.selections.line_mode(), self.cursor_shape, cx, ); diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 7a1a6f49830d657d2c3aa0f1e2eeabe205031075..df1640a716b3e5dae648f33aabac4d0aefecb02e 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -5370,8 +5370,8 @@ async fn test_manipulate_text(cx: &mut TestAppContext) { «HeLlO, wOrLD!ˇ» "}); - // Test selections with `line_mode = true`. - cx.update_editor(|editor, _window, _cx| editor.selections.line_mode = true); + // Test selections with `line_mode() = true`. + cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true)); cx.set_state(indoc! {" «The quick brown fox jumps over diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 90dd2a599a16705e0eb49319ec562d505ca58399..04a479a776ec7bdd6a29cfe998eafab9e6ccd7c0 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1371,7 +1371,7 @@ impl EditorElement { let layout = SelectionLayout::new( selection, - editor.selections.line_mode, + editor.selections.line_mode(), editor.cursor_shape, &snapshot.display_snapshot, is_newest, @@ -3149,7 +3149,7 @@ impl EditorElement { let newest = editor.selections.newest::(cx); SelectionLayout::new( newest, - editor.selections.line_mode, + editor.selections.line_mode(), editor.cursor_shape, &snapshot.display_snapshot, true, diff --git a/crates/editor/src/hover_links.rs b/crates/editor/src/hover_links.rs index 8177a4dca1607cff57cb513d743dd2e975b675d4..c2a0108915ac5c4f4baef814b2fe7753ce1e2e62 100644 --- a/crates/editor/src/hover_links.rs +++ b/crates/editor/src/hover_links.rs @@ -666,9 +666,7 @@ pub(crate) fn find_url( ) -> Option<(Range, String)> { const LIMIT: usize = 2048; - let Ok(snapshot) = buffer.read_with(&cx, |buffer, _| buffer.snapshot()) else { - return None; - }; + let snapshot = buffer.read_with(&cx, |buffer, _| buffer.snapshot()).ok()?; let offset = position.to_offset(&snapshot); let mut token_start = offset; diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 3e592e4bdcbca2a0e9679f9e79e7d3f65e95895a..f882db7c198bcabe22333cbbc620359695bb5f03 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -190,7 +190,7 @@ impl FollowableItem for Editor { self.buffer.update(cx, |buffer, cx| { buffer.set_active_selections( &self.selections.disjoint_anchors_arc(), - self.selections.line_mode, + self.selections.line_mode(), self.cursor_shape, cx, ); diff --git a/crates/editor/src/selections_collection.rs b/crates/editor/src/selections_collection.rs index 4343443ff8c4cb4e388984c9014b13ddc8726523..7fb44ab4b413f319e5b27983489fdc21dd8b92ce 100644 --- a/crates/editor/src/selections_collection.rs +++ b/crates/editor/src/selections_collection.rs @@ -1,6 +1,6 @@ use std::{ cell::Ref, - cmp, iter, mem, + cmp, fmt, iter, mem, ops::{Deref, DerefMut, Range, Sub}, sync::Arc, }; @@ -29,7 +29,7 @@ pub struct SelectionsCollection { display_map: Entity, buffer: Entity, next_selection_id: usize, - pub line_mode: bool, + line_mode: bool, /// The non-pending, non-overlapping selections. /// The [SelectionsCollection::pending] selection could possibly overlap these disjoint: Arc<[Selection]>, @@ -424,6 +424,14 @@ impl SelectionsCollection { pub fn next_selection_id(&self) -> usize { self.next_selection_id } + + pub fn line_mode(&self) -> bool { + self.line_mode + } + + pub fn set_line_mode(&mut self, line_mode: bool) { + self.line_mode = line_mode; + } } pub struct MutableSelectionsCollection<'a> { @@ -914,10 +922,10 @@ fn selection_to_anchor_selection( where T: ToOffset + Ord, { - let end_bias = if selection.end > selection.start { - Bias::Left - } else { + let end_bias = if selection.start == selection.end { Bias::Right + } else { + Bias::Left }; Selection { id: selection.id, @@ -928,49 +936,59 @@ where } } -// Panics if passed selections are not in order -fn resolve_selections_display<'a>( +fn resolve_selections_point<'a>( selections: impl 'a + IntoIterator>, map: &'a DisplaySnapshot, -) -> impl 'a + Iterator> { +) -> impl 'a + Iterator> { let (to_summarize, selections) = selections.into_iter().tee(); let mut summaries = map .buffer_snapshot .summaries_for_anchors::(to_summarize.flat_map(|s| [&s.start, &s.end])) .into_iter(); - let mut selections = selections - .map(move |s| { - let start = summaries.next().unwrap(); - let end = summaries.next().unwrap(); - - let display_start = map.point_to_display_point(start, Bias::Left); - let display_end = if start == end { - map.point_to_display_point(end, Bias::Right) - } else { - map.point_to_display_point(end, Bias::Left) - }; + selections.map(move |s| { + let start = summaries.next().unwrap(); + let end = summaries.next().unwrap(); + assert!(start <= end, "start: {:?}, end: {:?}", start, end); + Selection { + id: s.id, + start, + end, + reversed: s.reversed, + goal: s.goal, + } + }) +} - Selection { - id: s.id, - start: display_start, - end: display_end, - reversed: s.reversed, - goal: s.goal, - } - }) - .peekable(); - iter::from_fn(move || { - let mut selection = selections.next()?; - while let Some(next_selection) = selections.peek() { - if selection.end >= next_selection.start { - selection.end = cmp::max(selection.end, next_selection.end); - selections.next(); +// Panics if passed selections are not in order +fn resolve_selections_display<'a>( + selections: impl 'a + IntoIterator>, + map: &'a DisplaySnapshot, +) -> impl 'a + Iterator> { + let selections = resolve_selections_point(selections, map).map(move |s| { + let display_start = map.point_to_display_point(s.start, Bias::Left); + let display_end = map.point_to_display_point( + s.end, + if s.start == s.end { + Bias::Right } else { - break; - } + Bias::Left + }, + ); + assert!( + display_start <= display_end, + "display_start: {:?}, display_end: {:?}", + display_start, + display_end + ); + Selection { + id: s.id, + start: display_start, + end: display_end, + reversed: s.reversed, + goal: s.goal, } - Some(selection) - }) + }); + coalesce_selections(selections) } // Panics if passed selections are not in order @@ -988,11 +1006,13 @@ where .dimensions_from_points::(to_convert.flat_map(|s| { let start = map.display_point_to_point(s.start, Bias::Left); let end = map.display_point_to_point(s.end, Bias::Right); + assert!(start <= end, "start: {:?}, end: {:?}", start, end); [start, end] })); selections.map(move |s| { let start = converted_endpoints.next().unwrap(); let end = converted_endpoints.next().unwrap(); + assert!(start <= end, "start: {:?}, end: {:?}", start, end); Selection { id: s.id, start, @@ -1002,3 +1022,33 @@ where } }) } + +fn coalesce_selections( + selections: impl Iterator>, +) -> impl Iterator> { + let mut selections = selections.peekable(); + iter::from_fn(move || { + let mut selection = selections.next()?; + while let Some(next_selection) = selections.peek() { + if selection.end >= next_selection.start { + if selection.reversed == next_selection.reversed { + selection.end = cmp::max(selection.end, next_selection.end); + selections.next(); + } else { + selection.end = cmp::max(selection.start, next_selection.start); + break; + } + } else { + break; + } + } + assert!( + selection.start <= selection.end, + "selection.start: {:?}, selection.end: {:?}, selection.reversed: {:?}", + selection.start, + selection.end, + selection.reversed + ); + Some(selection) + }) +} diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 1c2ea0dccbc6e84d7a871f7a4d30fb6c6949044e..26c7535bc3530031d037d6068042d5437f4091bc 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -822,7 +822,7 @@ impl Vim { editor.set_collapse_matches(false); editor.set_input_enabled(true); editor.set_autoindent(true); - editor.selections.line_mode = false; + editor.selections.set_line_mode(false); editor.unregister_addon::(); editor.set_relative_line_number(None, cx); if let Some(vim) = Vim::globals(cx).focused_vim() @@ -1787,7 +1787,9 @@ impl Vim { editor.set_collapse_matches(true); editor.set_input_enabled(vim.editor_input_enabled()); editor.set_autoindent(vim.should_autoindent()); - editor.selections.line_mode = matches!(vim.mode, Mode::VisualLine); + editor + .selections + .set_line_mode(matches!(vim.mode, Mode::VisualLine)); let hide_edit_predictions = !matches!(vim.mode, Mode::Insert | Mode::Replace); editor.set_edit_predictions_hidden_for_vim_mode(hide_edit_predictions, window, cx); diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index 35bc1eba2c900cd7c8f370629e0585584bc92d59..bcb8c610f43be440a7b4840a62e7f7afc6b27c41 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -609,8 +609,8 @@ impl Vim { self.store_visual_marks(window, cx); self.update_editor(cx, |vim, editor, cx| { let mut original_columns: HashMap<_, _> = Default::default(); - let line_mode = line_mode || editor.selections.line_mode; - editor.selections.line_mode = false; + let line_mode = line_mode || editor.selections.line_mode(); + editor.selections.set_line_mode(false); editor.transact(window, cx, |editor, window, cx| { editor.change_selections(Default::default(), window, cx, |s| { @@ -692,7 +692,7 @@ impl Vim { pub fn visual_yank(&mut self, line_mode: bool, window: &mut Window, cx: &mut Context) { self.store_visual_marks(window, cx); self.update_editor(cx, |vim, editor, cx| { - let line_mode = line_mode || editor.selections.line_mode; + let line_mode = line_mode || editor.selections.line_mode(); // For visual line mode, adjust selections to avoid yanking the next line when on \n if line_mode && vim.mode != Mode::VisualBlock { @@ -710,7 +710,7 @@ impl Vim { }); } - editor.selections.line_mode = line_mode; + editor.selections.set_line_mode(line_mode); let kind = if line_mode { MotionKind::Linewise } else {