Detailed changes
@@ -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::<Stateful<Div>>().unwrap();
@@ -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<u32> {
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]
@@ -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)
}
}
}
@@ -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::<usize, _>(new_anchor_selections, &snapshot)
+ let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
+ let new_selections = resolve_selections::<usize, _>(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)
})
}
@@ -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);
@@ -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<Point>],
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<Selection<Point>> = 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<SelectionLayout>)],
+ block_start_rows: &HashSet<DisplayRow>,
visible_display_row_range: Range<DisplayRow>,
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<CustomBlockId, u32>,
+ selections: &[Selection<Point>],
cx: &mut WindowContext,
) -> (AnyElement, Size<Pixels>) {
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<Point>],
cx: &mut WindowContext,
) -> Result<Vec<BlockLayout>, HashMap<CustomBlockId, u32>> {
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<BlockLayout>,
+ block_starts: &mut HashSet<DisplayRow>,
hitbox: &Hitbox,
line_height: Pixels,
scroll_pixel_position: gpui::Point<Pixels>,
@@ -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<Selection<Point>> =
+ 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,
@@ -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<Selection<D>> {
- self.pending_anchor()
- .as_ref()
- .map(|pending| pending.map(|p| p.summary::<D>(&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<SelectMode> {
@@ -111,12 +111,10 @@ impl SelectionsCollection {
where
D: 'a + TextDimension + Ord + Sub<D, Output = D>,
{
+ let map = self.display_map(cx);
let disjoint_anchors = &self.disjoint;
- let mut disjoint =
- resolve_multiple::<D, _>(disjoint_anchors.iter(), &self.buffer(cx)).peekable();
-
+ let mut disjoint = resolve_selections::<D, _>(disjoint_anchors.iter(), &map).peekable();
let mut pending_opt = self.pending::<D>(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<D, Output = D> + 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<Selection<DisplayPoint>>) {
- let display_map = self.display_map(cx);
- let selections = self
- .all::<Point>(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<Anchor> {
@@ -241,15 +262,18 @@ impl SelectionsCollection {
&self,
cx: &mut AppContext,
) -> Selection<D> {
- let buffer = self.buffer(cx);
- self.newest_anchor().map(|p| p.summary::<D>(&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<DisplayPoint> {
- 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<D> {
- let buffer = self.buffer(cx);
- self.oldest_anchor().map(|p| p.summary::<D>(&buffer))
+ let map = self.display_map(cx);
+ let selection = resolve_selections([self.oldest_anchor()], &map)
+ .next()
+ .unwrap();
+ selection
}
pub fn first_anchor(&self) -> Selection<Anchor> {
@@ -538,9 +565,9 @@ impl<'a> MutableSelectionsCollection<'a> {
}
pub fn select_anchors(&mut self, selections: Vec<Selection<Anchor>>) {
- let buffer = self.buffer.read(self.cx).snapshot(self.cx);
+ let map = self.display_map();
let resolved_selections =
- resolve_multiple::<usize, _>(&selections, &buffer).collect::<Vec<_>>();
+ resolve_selections::<usize, _>(&selections, &map).collect::<Vec<_>>();
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::<Point>(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::<usize>(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>(
+fn resolve_selections_display<'a>(
+ selections: impl 'a + IntoIterator<Item = &'a Selection<Anchor>>,
+ map: &'a DisplaySnapshot,
+) -> impl 'a + Iterator<Item = Selection<DisplayPoint>> {
+ let (to_summarize, selections) = selections.into_iter().tee();
+ let mut summaries = map
+ .buffer_snapshot
+ .summaries_for_anchors::<Point, _>(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)
+ };
+
+ 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,
- snapshot: &MultiBufferSnapshot,
+ map: &'a DisplaySnapshot,
) -> impl 'a + Iterator<Item = Selection<D>>
where
- D: TextDimension + Ord + Sub<D, Output = D>,
+ D: TextDimension + Clone + Ord + Sub<D, Output = D>,
I: 'a + IntoIterator<Item = &'a Selection<Anchor>>,
{
- let (to_summarize, selections) = selections.into_iter().tee();
- let mut summaries = snapshot
- .summaries_for_anchors::<D, _>(
- to_summarize
- .flat_map(|s| [&s.start, &s.end])
- .collect::<Vec<_>>(),
- )
- .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 (to_convert, selections) = resolve_selections_display(selections, map).tee();
+ let mut converted_endpoints =
+ map.buffer_snapshot
+ .dimensions_from_points::<D>(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,
+ }
})
}
@@ -3083,6 +3083,58 @@ impl MultiBufferSnapshot {
summaries
}
+ pub fn dimensions_from_points<'a, D>(
+ &'a self,
+ points: impl 'a + IntoIterator<Item = Point>,
+ ) -> impl 'a + Iterator<Item = D>
+ where
+ D: TextDimension,
+ {
+ let mut cursor = self.excerpts.cursor::<TextSummary>(&());
+ let mut memoized_source_start: Option<Point> = 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::<D, _>(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<Item = &'a Anchor>,
@@ -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)