Skip to content

Commit

Permalink
Expand selections to Replace block boundaries (#20092)
Browse files Browse the repository at this point in the history
Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <[email protected]>
Co-authored-by: Max Brunsfeld <[email protected]>
Co-authored-by: Antonio <[email protected]>
Co-authored-by: Max <[email protected]>
  • Loading branch information
5 people authored Nov 1, 2024
1 parent b5c38e9 commit 773a3b3
Show file tree
Hide file tree
Showing 8 changed files with 469 additions and 117 deletions.
1 change: 1 addition & 0 deletions crates/diagnostics/src/diagnostics_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
118 changes: 112 additions & 6 deletions crates/editor/src/display_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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))
}
Expand All @@ -691,15 +691,15 @@ 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)
}

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
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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]
Expand Down
61 changes: 34 additions & 27 deletions crates/editor/src/display_map/block_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}

Expand Down Expand Up @@ -1311,18 +1312,17 @@ 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);

match transform.block.as_ref() {
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 => {
Expand Down Expand Up @@ -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);
Expand All @@ -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() {
Expand All @@ -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;
Expand Down Expand Up @@ -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)
);

Expand Down Expand Up @@ -2616,21 +2613,28 @@ 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);
for c in expected_text.chars() {
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,
Expand All @@ -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,
Expand Down Expand Up @@ -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)
}
}
}
31 changes: 17 additions & 14 deletions crates/editor/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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(
Expand All @@ -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)
Expand Down Expand Up @@ -4038,7 +4041,7 @@ impl Editor {
}
}

(selection.clone(), enclosing)
(selection, enclosing)
})
}

Expand Down
Loading

0 comments on commit 773a3b3

Please sign in to comment.