diff --git a/crates/diagnostics/src/diagnostics_tests.rs b/crates/diagnostics/src/diagnostics_tests.rs index 1daffffb4e..ffb7414d3d 100644 --- a/crates/diagnostics/src/diagnostics_tests.rs +++ b/crates/diagnostics/src/diagnostics_tests.rs @@ -986,6 +986,7 @@ fn editor_blocks( em_width: px(0.), max_width: px(0.), block_id, + selected: false, editor_style: &editor::EditorStyle::default(), }); let element = element.downcast_mut::>().unwrap(); diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index c11cbec328..ed616ac49a 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -660,7 +660,7 @@ impl DisplaySnapshot { new_start..new_end } - fn point_to_display_point(&self, point: MultiBufferPoint, bias: Bias) -> DisplayPoint { + pub fn point_to_display_point(&self, point: MultiBufferPoint, bias: Bias) -> DisplayPoint { let inlay_point = self.inlay_snapshot.to_inlay_point(point); let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias); let tab_point = self.tab_snapshot.to_tab_point(fold_point); @@ -669,7 +669,7 @@ impl DisplaySnapshot { DisplayPoint(block_point) } - fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point { + pub fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point { self.inlay_snapshot .to_buffer_point(self.display_point_to_inlay_point(point, bias)) } @@ -691,7 +691,7 @@ impl DisplaySnapshot { fn display_point_to_inlay_point(&self, point: DisplayPoint, bias: Bias) -> InlayPoint { let block_point = point.0; - let wrap_point = self.block_snapshot.to_wrap_point(block_point); + let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias); let tab_point = self.wrap_snapshot.to_tab_point(wrap_point); let fold_point = self.tab_snapshot.to_fold_point(tab_point, bias).0; fold_point.to_inlay_point(&self.fold_snapshot) @@ -699,7 +699,7 @@ impl DisplaySnapshot { pub fn display_point_to_fold_point(&self, point: DisplayPoint, bias: Bias) -> FoldPoint { let block_point = point.0; - let wrap_point = self.block_snapshot.to_wrap_point(block_point); + let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias); let tab_point = self.wrap_snapshot.to_tab_point(wrap_point); self.tab_snapshot.to_fold_point(tab_point, bias).0 } @@ -990,7 +990,7 @@ impl DisplaySnapshot { pub fn soft_wrap_indent(&self, display_row: DisplayRow) -> Option { let wrap_row = self .block_snapshot - .to_wrap_point(BlockPoint::new(display_row.0, 0)) + .to_wrap_point(BlockPoint::new(display_row.0, 0), Bias::Left) .row(); self.wrap_snapshot.soft_wrap_indent(wrap_row) } @@ -1222,7 +1222,7 @@ impl DisplayPoint { } pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize { - let wrap_point = map.block_snapshot.to_wrap_point(self.0); + let wrap_point = map.block_snapshot.to_wrap_point(self.0, bias); let tab_point = map.wrap_snapshot.to_tab_point(wrap_point); let fold_point = map.tab_snapshot.to_fold_point(tab_point, bias).0; let inlay_point = fold_point.to_inlay_point(&map.fold_snapshot); @@ -2048,6 +2048,112 @@ pub mod tests { ); } + #[gpui::test] + async fn test_point_translation_with_replace_blocks(cx: &mut gpui::TestAppContext) { + cx.background_executor + .set_block_on_ticks(usize::MAX..=usize::MAX); + + cx.update(|cx| init_test(cx, |_| {})); + + let buffer = cx.update(|cx| MultiBuffer::build_simple("abcde\nfghij\nklmno\npqrst", cx)); + let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx)); + let map = cx.new_model(|cx| { + DisplayMap::new( + buffer.clone(), + font("Courier"), + px(16.0), + None, + true, + 1, + 1, + 0, + FoldPlaceholder::test(), + cx, + ) + }); + + let snapshot = map.update(cx, |map, cx| { + map.insert_blocks( + [BlockProperties { + placement: BlockPlacement::Replace( + buffer_snapshot.anchor_before(Point::new(1, 2)) + ..buffer_snapshot.anchor_after(Point::new(2, 3)), + ), + height: 4, + style: BlockStyle::Fixed, + render: Box::new(|_| div().into_any()), + priority: 0, + }], + cx, + ); + map.snapshot(cx) + }); + + assert_eq!(snapshot.text(), "abcde\n\n\n\n\npqrst"); + + let point_to_display_points = [ + (Point::new(1, 0), DisplayPoint::new(DisplayRow(1), 0)), + (Point::new(2, 0), DisplayPoint::new(DisplayRow(1), 0)), + (Point::new(3, 0), DisplayPoint::new(DisplayRow(5), 0)), + ]; + for (buffer_point, display_point) in point_to_display_points { + assert_eq!( + snapshot.point_to_display_point(buffer_point, Bias::Left), + display_point, + "point_to_display_point({:?}, Bias::Left)", + buffer_point + ); + assert_eq!( + snapshot.point_to_display_point(buffer_point, Bias::Right), + display_point, + "point_to_display_point({:?}, Bias::Right)", + buffer_point + ); + } + + let display_points_to_points = [ + ( + DisplayPoint::new(DisplayRow(1), 0), + Point::new(1, 0), + Point::new(2, 5), + ), + ( + DisplayPoint::new(DisplayRow(2), 0), + Point::new(1, 0), + Point::new(2, 5), + ), + ( + DisplayPoint::new(DisplayRow(3), 0), + Point::new(1, 0), + Point::new(2, 5), + ), + ( + DisplayPoint::new(DisplayRow(4), 0), + Point::new(1, 0), + Point::new(2, 5), + ), + ( + DisplayPoint::new(DisplayRow(5), 0), + Point::new(3, 0), + Point::new(3, 0), + ), + ]; + for (display_point, left_buffer_point, right_buffer_point) in display_points_to_points { + assert_eq!( + snapshot.display_point_to_point(display_point, Bias::Left), + left_buffer_point, + "display_point_to_point({:?}, Bias::Left)", + display_point + ); + assert_eq!( + snapshot.display_point_to_point(display_point, Bias::Right), + right_buffer_point, + "display_point_to_point({:?}, Bias::Right)", + display_point + ); + } + } + // todo(linux) fails due to pixel differences in text rendering #[cfg(target_os = "macos")] #[gpui::test] diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index a7d0ca9c63..250c914fe8 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -265,6 +265,7 @@ pub struct BlockContext<'a, 'b> { pub em_width: Pixels, pub line_height: Pixels, pub block_id: BlockId, + pub selected: bool, pub editor_style: &'b EditorStyle, } @@ -1311,7 +1312,6 @@ impl BlockSnapshot { let (output_start_row, input_start_row) = cursor.start(); let (output_end_row, input_end_row) = cursor.end(&()); let output_start = Point::new(output_start_row.0, 0); - let output_end = Point::new(output_end_row.0, 0); let input_start = Point::new(input_start_row.0, 0); let input_end = Point::new(input_end_row.0, 0); @@ -1319,10 +1319,10 @@ impl BlockSnapshot { Some(Block::Custom(block)) if matches!(block.placement, BlockPlacement::Replace(_)) => { - if bias == Bias::Left { + if ((bias == Bias::Left || search_left) && output_start <= point.0) + || (!search_left && output_start >= point.0) + { return BlockPoint(output_start); - } else { - return BlockPoint(Point::new(output_end.row - 1, 0)); } } None => { @@ -1364,12 +1364,7 @@ impl BlockSnapshot { cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &()); if let Some(transform) = cursor.item() { if transform.block.is_some() { - let wrap_start = WrapPoint::new(cursor.start().0 .0, 0); - if wrap_start == wrap_point { - BlockPoint::new(cursor.start().1 .0, 0) - } else { - BlockPoint::new(cursor.end(&()).1 .0 - 1, 0) - } + BlockPoint::new(cursor.start().1 .0, 0) } else { let (input_start_row, output_start_row) = cursor.start(); let input_start = Point::new(input_start_row.0, 0); @@ -1382,7 +1377,7 @@ impl BlockSnapshot { } } - pub fn to_wrap_point(&self, block_point: BlockPoint) -> WrapPoint { + pub fn to_wrap_point(&self, block_point: BlockPoint, bias: Bias) -> WrapPoint { let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&()); cursor.seek(&BlockRow(block_point.row), Bias::Right, &()); if let Some(transform) = cursor.item() { @@ -1391,7 +1386,9 @@ impl BlockSnapshot { if block.place_below() { let wrap_row = cursor.start().1 .0 - 1; WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row)) - } else if block.place_above() || block_point.row == cursor.start().0 .0 { + } else if block.place_above() { + WrapPoint::new(cursor.start().1 .0, 0) + } else if bias == Bias::Left { WrapPoint::new(cursor.start().1 .0, 0) } else { let wrap_row = cursor.end(&()).1 .0 - 1; @@ -1766,19 +1763,19 @@ mod tests { ); assert_eq!( - snapshot.to_wrap_point(BlockPoint::new(0, 3)), + snapshot.to_wrap_point(BlockPoint::new(0, 3), Bias::Left), WrapPoint::new(0, 3) ); assert_eq!( - snapshot.to_wrap_point(BlockPoint::new(1, 0)), + snapshot.to_wrap_point(BlockPoint::new(1, 0), Bias::Left), WrapPoint::new(1, 0) ); assert_eq!( - snapshot.to_wrap_point(BlockPoint::new(3, 0)), + snapshot.to_wrap_point(BlockPoint::new(3, 0), Bias::Left), WrapPoint::new(1, 0) ); assert_eq!( - snapshot.to_wrap_point(BlockPoint::new(7, 0)), + snapshot.to_wrap_point(BlockPoint::new(7, 0), Bias::Left), WrapPoint::new(3, 3) ); @@ -2616,10 +2613,15 @@ mod tests { // Ensure that conversion between block points and wrap points is stable. for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() { - let original_wrap_point = WrapPoint::new(row, 0); - let block_point = blocks_snapshot.to_block_point(original_wrap_point); - let wrap_point = blocks_snapshot.to_wrap_point(block_point); - assert_eq!(blocks_snapshot.to_block_point(wrap_point), block_point); + let wrap_point = WrapPoint::new(row, 0); + let block_point = blocks_snapshot.to_block_point(wrap_point); + let left_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Left); + let right_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Right); + assert_eq!(blocks_snapshot.to_block_point(left_wrap_point), block_point); + assert_eq!( + blocks_snapshot.to_block_point(right_wrap_point), + block_point + ); } let mut block_point = BlockPoint::new(0, 0); @@ -2627,10 +2629,12 @@ mod tests { let left_point = blocks_snapshot.clip_point(block_point, Bias::Left); let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left); assert_eq!( - blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(left_point)), + blocks_snapshot + .to_block_point(blocks_snapshot.to_wrap_point(left_point, Bias::Left)), left_point, - "wrap point: {:?}", - blocks_snapshot.to_wrap_point(left_point) + "block point: {:?}, wrap point: {:?}", + block_point, + blocks_snapshot.to_wrap_point(left_point, Bias::Left) ); assert_eq!( left_buffer_point, @@ -2642,10 +2646,12 @@ mod tests { let right_point = blocks_snapshot.clip_point(block_point, Bias::Right); let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right); assert_eq!( - blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(right_point)), + blocks_snapshot + .to_block_point(blocks_snapshot.to_wrap_point(right_point, Bias::Right)), right_point, - "wrap point: {:?}", - blocks_snapshot.to_wrap_point(right_point) + "block point: {:?}, wrap point: {:?}", + block_point, + blocks_snapshot.to_wrap_point(right_point, Bias::Right) ); assert_eq!( right_buffer_point, @@ -2681,7 +2687,8 @@ mod tests { impl BlockSnapshot { fn to_point(&self, point: BlockPoint, bias: Bias) -> Point { - self.wrap_snapshot.to_point(self.to_wrap_point(point), bias) + self.wrap_snapshot + .to_point(self.to_wrap_point(point, bias), bias) } } } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 27bccb40b3..de5c00bad1 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -131,7 +131,9 @@ use project::{ use rand::prelude::*; use rpc::{proto::*, ErrorExt}; use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide}; -use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection}; +use selections_collection::{ + resolve_selections, MutableSelectionsCollection, SelectionsCollection, +}; use serde::{Deserialize, Serialize}; use settings::{update_settings_file, Settings, SettingsLocation, SettingsStore}; use smallvec::SmallVec; @@ -3484,8 +3486,8 @@ impl Editor { } let new_anchor_selections = new_selections.iter().map(|e| &e.0); let new_selection_deltas = new_selections.iter().map(|e| e.1); - let snapshot = this.buffer.read(cx).read(cx); - let new_selections = resolve_multiple::(new_anchor_selections, &snapshot) + let map = this.display_map.update(cx, |map, cx| map.snapshot(cx)); + let new_selections = resolve_selections::(new_anchor_selections, &map) .zip(new_selection_deltas) .map(|(selection, delta)| Selection { id: selection.id, @@ -3498,18 +3500,20 @@ impl Editor { let mut i = 0; for (position, delta, selection_id, pair) in new_autoclose_regions { - let position = position.to_offset(&snapshot) + delta; - let start = snapshot.anchor_before(position); - let end = snapshot.anchor_after(position); + let position = position.to_offset(&map.buffer_snapshot) + delta; + let start = map.buffer_snapshot.anchor_before(position); + let end = map.buffer_snapshot.anchor_after(position); while let Some(existing_state) = this.autoclose_regions.get(i) { - match existing_state.range.start.cmp(&start, &snapshot) { + match existing_state.range.start.cmp(&start, &map.buffer_snapshot) { Ordering::Less => i += 1, Ordering::Greater => break, - Ordering::Equal => match end.cmp(&existing_state.range.end, &snapshot) { - Ordering::Less => i += 1, - Ordering::Equal => break, - Ordering::Greater => break, - }, + Ordering::Equal => { + match end.cmp(&existing_state.range.end, &map.buffer_snapshot) { + Ordering::Less => i += 1, + Ordering::Equal => break, + Ordering::Greater => break, + } + } } } this.autoclose_regions.insert( @@ -3522,7 +3526,6 @@ impl Editor { ); } - drop(snapshot); let had_active_inline_completion = this.has_active_inline_completion(cx); this.change_selections_inner(Some(Autoscroll::fit()), false, cx, |s| { s.select(new_selections) @@ -4038,7 +4041,7 @@ impl Editor { } } - (selection.clone(), enclosing) + (selection, enclosing) }) } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 92d62fac7f..43e7ff74e6 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -3989,6 +3989,76 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) { }); } +#[gpui::test] +async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) { + init_test(cx, |_| {}); + + let mut cx = EditorTestContext::new(cx).await; + cx.set_state( + &" + ˇzero + one + two + three + four + five + " + .unindent(), + ); + + // Create a four-line block that replaces three lines of text. + cx.update_editor(|editor, cx| { + let snapshot = editor.snapshot(cx); + let snapshot = &snapshot.buffer_snapshot; + let placement = BlockPlacement::Replace( + snapshot.anchor_after(Point::new(1, 0))..snapshot.anchor_after(Point::new(3, 0)), + ); + editor.insert_blocks( + [BlockProperties { + placement, + height: 4, + style: BlockStyle::Sticky, + render: Box::new(|_| gpui::div().into_any_element()), + priority: 0, + }], + None, + cx, + ); + }); + + // Move down so that the cursor touches the block. + cx.update_editor(|editor, cx| { + editor.move_down(&Default::default(), cx); + }); + cx.assert_editor_state( + &" + zero + «one + two + threeˇ» + four + five + " + .unindent(), + ); + + // Move down past the block. + cx.update_editor(|editor, cx| { + editor.move_down(&Default::default(), cx); + }); + cx.assert_editor_state( + &" + zero + one + two + three + ˇfour + five + " + .unindent(), + ); +} + #[gpui::test] fn test_transpose(cx: &mut TestAppContext) { init_test(cx, |_| {}); @@ -4182,7 +4252,7 @@ async fn test_rewrap(cx: &mut TestAppContext) { // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis - // porttitor id. Aliquam id accumsan eros.ˇˇˇˇ + // porttitor id. Aliquam id accumsan eros.ˇ "}; cx.set_state(unwrapped_text); @@ -4212,7 +4282,7 @@ async fn test_rewrap(cx: &mut TestAppContext) { let wrapped_text = indoc! {" // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus - // auctor, eu lacinia sapien scelerisque.ˇˇ + // auctor, eu lacinia sapien scelerisque.ˇ // // Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, @@ -4220,7 +4290,7 @@ async fn test_rewrap(cx: &mut TestAppContext) { // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id - // vulputate turpis porttitor id. Aliquam id accumsan eros.ˇˇ + // vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ "}; cx.set_state(unwrapped_text); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index a3d634378a..2c10273930 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -25,7 +25,7 @@ use crate::{ MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, }; use client::ParticipantIndex; -use collections::{BTreeMap, HashMap}; +use collections::{BTreeMap, HashMap, HashSet}; use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid}; use gpui::Subscription; use gpui::{ @@ -809,10 +809,12 @@ impl EditorElement { cx.notify() } + #[allow(clippy::too_many_arguments)] fn layout_selections( &self, start_anchor: Anchor, end_anchor: Anchor, + local_selections: &[Selection], snapshot: &EditorSnapshot, start_row: DisplayRow, end_row: DisplayRow, @@ -827,13 +829,9 @@ impl EditorElement { let mut newest_selection_head = None; self.editor.update(cx, |editor, cx| { if editor.show_local_selections { - let mut local_selections: Vec> = editor - .selections - .disjoint_in_range(start_anchor..end_anchor, cx); - local_selections.extend(editor.selections.pending(cx)); let mut layouts = Vec::new(); let newest = editor.selections.newest(cx); - for selection in local_selections.drain(..) { + for selection in local_selections.iter().cloned() { let is_empty = selection.start == selection.end; let is_newest = selection == newest; @@ -996,6 +994,7 @@ impl EditorElement { &self, snapshot: &EditorSnapshot, selections: &[(PlayerColor, Vec)], + block_start_rows: &HashSet, visible_display_row_range: Range, line_layouts: &[LineWithInvisibles], text_hitbox: &Hitbox, @@ -1015,7 +1014,10 @@ impl EditorElement { let cursor_position = selection.head; let in_range = visible_display_row_range.contains(&cursor_position.row()); - if (selection.is_local && !editor.show_local_cursors(cx)) || !in_range { + if (selection.is_local && !editor.show_local_cursors(cx)) + || !in_range + || block_start_rows.contains(&cursor_position.row()) + { continue; } @@ -2068,14 +2070,14 @@ impl EditorElement { editor_width: Pixels, scroll_width: &mut Pixels, resized_blocks: &mut HashMap, + selections: &[Selection], cx: &mut WindowContext, ) -> (AnyElement, Size) { let mut element = match block { Block::Custom(block) => { - let align_to = block - .start() - .to_point(&snapshot.buffer_snapshot) - .to_display_point(snapshot); + let block_start = block.start().to_point(&snapshot.buffer_snapshot); + let block_end = block.end().to_point(&snapshot.buffer_snapshot); + let align_to = block_start.to_display_point(snapshot); let anchor_x = text_x + if rows.contains(&align_to.row()) { line_layouts[align_to.row().minus(rows.start) as usize] @@ -2085,6 +2087,18 @@ impl EditorElement { .x_for_index(align_to.column() as usize) }; + let selected = selections + .binary_search_by(|selection| { + if selection.end <= block_start { + Ordering::Less + } else if selection.start >= block_end { + Ordering::Greater + } else { + Ordering::Equal + } + }) + .is_ok(); + div() .size_full() .child(block.render(&mut BlockContext { @@ -2094,6 +2108,7 @@ impl EditorElement { line_height, em_width, block_id, + selected, max_width: text_hitbox.size.width.max(*scroll_width), editor_style: &self.style, })) @@ -2431,6 +2446,7 @@ impl EditorElement { text_x: Pixels, line_height: Pixels, line_layouts: &[LineWithInvisibles], + selections: &[Selection], cx: &mut WindowContext, ) -> Result, HashMap> { let (fixed_blocks, non_fixed_blocks) = snapshot @@ -2467,6 +2483,7 @@ impl EditorElement { editor_width, scroll_width, &mut resized_blocks, + selections, cx, ); fixed_block_max_width = fixed_block_max_width.max(element_size.width + em_width); @@ -2511,6 +2528,7 @@ impl EditorElement { editor_width, scroll_width, &mut resized_blocks, + selections, cx, ); @@ -2556,6 +2574,7 @@ impl EditorElement { editor_width, scroll_width, &mut resized_blocks, + selections, cx, ); @@ -2584,6 +2603,7 @@ impl EditorElement { fn layout_blocks( &self, blocks: &mut Vec, + block_starts: &mut HashSet, hitbox: &Hitbox, line_height: Pixels, scroll_pixel_position: gpui::Point, @@ -2591,6 +2611,7 @@ impl EditorElement { ) { for block in blocks { let mut origin = if let Some(row) = block.row { + block_starts.insert(row); hitbox.origin + point( Pixels::ZERO, @@ -5101,9 +5122,19 @@ impl Element for EditorElement { cx, ); + let local_selections: Vec> = + self.editor.update(cx, |editor, cx| { + let mut selections = editor + .selections + .disjoint_in_range(start_anchor..end_anchor, cx); + selections.extend(editor.selections.pending(cx)); + selections + }); + let (selections, active_rows, newest_selection_head) = self.layout_selections( start_anchor, end_anchor, + &local_selections, &snapshot, start_row, end_row, @@ -5176,6 +5207,7 @@ impl Element for EditorElement { gutter_dimensions.full_width(), line_height, &line_layouts, + &local_selections, cx, ) }); @@ -5315,9 +5347,11 @@ impl Element for EditorElement { cx, ); + let mut block_start_rows = HashSet::default(); cx.with_element_namespace("blocks", |cx| { self.layout_blocks( &mut blocks, + &mut block_start_rows, &hitbox, line_height, scroll_pixel_position, @@ -5334,6 +5368,7 @@ impl Element for EditorElement { let visible_cursors = self.layout_visible_cursors( &snapshot, &selections, + &block_start_rows, start_row..end_row, &line_layouts, &text_hitbox, diff --git a/crates/editor/src/selections_collection.rs b/crates/editor/src/selections_collection.rs index 8e1c12b8cd..1edfc6a4fb 100644 --- a/crates/editor/src/selections_collection.rs +++ b/crates/editor/src/selections_collection.rs @@ -1,6 +1,6 @@ use std::{ cell::Ref, - iter, mem, + cmp, iter, mem, ops::{Deref, DerefMut, Range, Sub}, sync::Arc, }; @@ -98,9 +98,9 @@ impl SelectionsCollection { &self, cx: &mut AppContext, ) -> Option> { - self.pending_anchor() - .as_ref() - .map(|pending| pending.map(|p| p.summary::(&self.buffer(cx)))) + let map = self.display_map(cx); + let selection = resolve_selections(self.pending_anchor().as_ref(), &map).next(); + selection } pub(crate) fn pending_mode(&self) -> Option { @@ -111,12 +111,10 @@ impl SelectionsCollection { where D: 'a + TextDimension + Ord + Sub, { + let map = self.display_map(cx); let disjoint_anchors = &self.disjoint; - let mut disjoint = - resolve_multiple::(disjoint_anchors.iter(), &self.buffer(cx)).peekable(); - + let mut disjoint = resolve_selections::(disjoint_anchors.iter(), &map).peekable(); let mut pending_opt = self.pending::(cx); - iter::from_fn(move || { if let Some(pending) = pending_opt.as_mut() { while let Some(next_selection) = disjoint.peek() { @@ -199,34 +197,57 @@ impl SelectionsCollection { where D: 'a + TextDimension + Ord + Sub + std::fmt::Debug, { - let buffer = self.buffer(cx); + let map = self.display_map(cx); let start_ix = match self .disjoint - .binary_search_by(|probe| probe.end.cmp(&range.start, &buffer)) + .binary_search_by(|probe| probe.end.cmp(&range.start, &map.buffer_snapshot)) { Ok(ix) | Err(ix) => ix, }; let end_ix = match self .disjoint - .binary_search_by(|probe| probe.start.cmp(&range.end, &buffer)) + .binary_search_by(|probe| probe.start.cmp(&range.end, &map.buffer_snapshot)) { Ok(ix) => ix + 1, Err(ix) => ix, }; - resolve_multiple(&self.disjoint[start_ix..end_ix], &buffer).collect() + resolve_selections(&self.disjoint[start_ix..end_ix], &map).collect() } pub fn all_display( &self, cx: &mut AppContext, ) -> (DisplaySnapshot, Vec>) { - let display_map = self.display_map(cx); - let selections = self - .all::(cx) - .into_iter() - .map(|selection| selection.map(|point| point.to_display_point(&display_map))) - .collect(); - (display_map, selections) + let map = self.display_map(cx); + let disjoint_anchors = &self.disjoint; + let mut disjoint = resolve_selections_display(disjoint_anchors.iter(), &map).peekable(); + let mut pending_opt = + resolve_selections_display(self.pending_anchor().as_ref(), &map).next(); + let selections = 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(); + (map, selections) } pub fn newest_anchor(&self) -> &Selection { @@ -241,15 +262,18 @@ impl SelectionsCollection { &self, cx: &mut AppContext, ) -> Selection { - let buffer = self.buffer(cx); - self.newest_anchor().map(|p| p.summary::(&buffer)) + let map = self.display_map(cx); + let selection = resolve_selections([self.newest_anchor()], &map) + .next() + .unwrap(); + selection } pub fn newest_display(&self, cx: &mut AppContext) -> Selection { - let display_map = self.display_map(cx); - let selection = self - .newest_anchor() - .map(|point| point.to_display_point(&display_map)); + let map = self.display_map(cx); + let selection = resolve_selections_display([self.newest_anchor()], &map) + .next() + .unwrap(); selection } @@ -265,8 +289,11 @@ impl SelectionsCollection { &self, cx: &mut AppContext, ) -> Selection { - let buffer = self.buffer(cx); - self.oldest_anchor().map(|p| p.summary::(&buffer)) + let map = self.display_map(cx); + let selection = resolve_selections([self.oldest_anchor()], &map) + .next() + .unwrap(); + selection } pub fn first_anchor(&self) -> Selection { @@ -538,9 +565,9 @@ impl<'a> MutableSelectionsCollection<'a> { } pub fn select_anchors(&mut self, selections: Vec>) { - let buffer = self.buffer.read(self.cx).snapshot(self.cx); + let map = self.display_map(); let resolved_selections = - resolve_multiple::(&selections, &buffer).collect::>(); + resolve_selections::(&selections, &map).collect::>(); self.select(resolved_selections); } @@ -650,20 +677,16 @@ impl<'a> MutableSelectionsCollection<'a> { ) { let mut changed = false; let display_map = self.display_map(); - let selections = self - .collection - .all::(self.cx) + let (_, selections) = self.collection.all_display(self.cx); + let selections = selections .into_iter() .map(|selection| { - let mut moved_selection = - selection.map(|point| point.to_display_point(&display_map)); + let mut moved_selection = selection.clone(); move_selection(&display_map, &mut moved_selection); - let moved_selection = - moved_selection.map(|display_point| display_point.to_point(&display_map)); if selection != moved_selection { changed = true; } - moved_selection + moved_selection.map(|display_point| display_point.to_point(&display_map)) }) .collect(); @@ -804,8 +827,8 @@ impl<'a> MutableSelectionsCollection<'a> { .collect(); if !adjusted_disjoint.is_empty() { - let resolved_selections = - resolve_multiple(adjusted_disjoint.iter(), &self.buffer()).collect(); + let map = self.display_map(); + let resolved_selections = resolve_selections(adjusted_disjoint.iter(), &map).collect(); self.select::(resolved_selections); } @@ -849,27 +872,76 @@ impl<'a> DerefMut for MutableSelectionsCollection<'a> { } // Panics if passed selections are not in order -pub(crate) fn resolve_multiple<'a, D, I>( - selections: I, - snapshot: &MultiBufferSnapshot, -) -> impl 'a + Iterator> -where - D: TextDimension + Ord + Sub, - I: 'a + IntoIterator>, -{ +fn resolve_selections_display<'a>( + selections: impl 'a + IntoIterator>, + map: &'a DisplaySnapshot, +) -> impl 'a + Iterator> { let (to_summarize, selections) = selections.into_iter().tee(); - let mut summaries = snapshot - .summaries_for_anchors::( - to_summarize - .flat_map(|s| [&s.start, &s.end]) - .collect::>(), - ) + let mut summaries = map + .buffer_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, + 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) + }; + + 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(); + } else { + break; + } + } + Some(selection) + }) +} + +// Panics if passed selections are not in order +pub(crate) fn resolve_selections<'a, D, I>( + selections: I, + map: &'a DisplaySnapshot, +) -> impl 'a + Iterator> +where + D: TextDimension + Clone + Ord + Sub, + I: 'a + IntoIterator>, +{ + let (to_convert, selections) = resolve_selections_display(selections, map).tee(); + let mut converted_endpoints = + map.buffer_snapshot + .dimensions_from_points::(to_convert.flat_map(|s| { + let start = map.display_point_to_point(s.start, Bias::Left); + let end = map.display_point_to_point(s.end, Bias::Right); + [start, end] + })); + selections.map(move |s| { + let start = converted_endpoints.next().unwrap(); + let end = converted_endpoints.next().unwrap(); + Selection { + id: s.id, + start, + end, + reversed: s.reversed, + goal: s.goal, + } }) } diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index f4bdafc985..31a5b32300 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -3083,6 +3083,58 @@ impl MultiBufferSnapshot { summaries } + pub fn dimensions_from_points<'a, D>( + &'a self, + points: impl 'a + IntoIterator, + ) -> impl 'a + Iterator + where + D: TextDimension, + { + let mut cursor = self.excerpts.cursor::(&()); + let mut memoized_source_start: Option = None; + let mut points = points.into_iter(); + std::iter::from_fn(move || { + let point = points.next()?; + + // Clear the memoized source start if the point is in a different excerpt than previous. + if memoized_source_start.map_or(false, |_| point >= cursor.end(&()).lines) { + memoized_source_start = None; + } + + // Now determine where the excerpt containing the point starts in its source buffer. + // We'll use this value to calculate overshoot next. + let source_start = if let Some(source_start) = memoized_source_start { + source_start + } else { + cursor.seek_forward(&point, Bias::Right, &()); + if let Some(excerpt) = cursor.item() { + let source_start = excerpt.range.context.start.to_point(&excerpt.buffer); + memoized_source_start = Some(source_start); + source_start + } else { + return Some(D::from_text_summary(cursor.start())); + } + }; + + // First, assume the output dimension is at least the start of the excerpt containing the point + let mut output = D::from_text_summary(cursor.start()); + + // If the point lands within its excerpt, calculate and add the overshoot in dimension D. + if let Some(excerpt) = cursor.item() { + let overshoot = point - cursor.start().lines; + if !overshoot.is_zero() { + let end_in_excerpt = source_start + overshoot; + output.add_assign( + &excerpt + .buffer + .text_summary_for_range::(source_start..end_in_excerpt), + ); + } + } + Some(output) + }) + } + pub fn refresh_anchors<'a, I>(&'a self, anchors: I) -> Vec<(usize, Anchor, bool)> where I: 'a + IntoIterator, @@ -4706,6 +4758,12 @@ impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for usize { } } +impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, TextSummary> for Point { + fn cmp(&self, cursor_location: &TextSummary, _: &()) -> cmp::Ordering { + Ord::cmp(self, &cursor_location.lines) + } +} + impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, Option<&'a Locator>> for Locator { fn cmp(&self, cursor_location: &Option<&'a Locator>, _: &()) -> cmp::Ordering { Ord::cmp(&Some(self), cursor_location)