Expand selections to Replace block boundaries (#20092)

Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
Co-authored-by: Antonio <antonio@zed.dev>
Co-authored-by: Max <max@zed.dev>
This commit is contained in:
Richard Feldman 2024-11-01 16:10:19 -04:00 committed by GitHub
parent b5c38e9a09
commit 773a3b335e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 472 additions and 120 deletions

View file

@ -986,6 +986,7 @@ fn editor_blocks(
em_width: px(0.), em_width: px(0.),
max_width: px(0.), max_width: px(0.),
block_id, block_id,
selected: false,
editor_style: &editor::EditorStyle::default(), editor_style: &editor::EditorStyle::default(),
}); });
let element = element.downcast_mut::<Stateful<Div>>().unwrap(); let element = element.downcast_mut::<Stateful<Div>>().unwrap();

View file

@ -660,7 +660,7 @@ impl DisplaySnapshot {
new_start..new_end 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 inlay_point = self.inlay_snapshot.to_inlay_point(point);
let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias); let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
let tab_point = self.tab_snapshot.to_tab_point(fold_point); let tab_point = self.tab_snapshot.to_tab_point(fold_point);
@ -669,7 +669,7 @@ impl DisplaySnapshot {
DisplayPoint(block_point) 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 self.inlay_snapshot
.to_buffer_point(self.display_point_to_inlay_point(point, bias)) .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 { fn display_point_to_inlay_point(&self, point: DisplayPoint, bias: Bias) -> InlayPoint {
let block_point = point.0; 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 tab_point = self.wrap_snapshot.to_tab_point(wrap_point);
let fold_point = self.tab_snapshot.to_fold_point(tab_point, bias).0; let fold_point = self.tab_snapshot.to_fold_point(tab_point, bias).0;
fold_point.to_inlay_point(&self.fold_snapshot) 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 { pub fn display_point_to_fold_point(&self, point: DisplayPoint, bias: Bias) -> FoldPoint {
let block_point = point.0; 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 tab_point = self.wrap_snapshot.to_tab_point(wrap_point);
self.tab_snapshot.to_fold_point(tab_point, bias).0 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> { pub fn soft_wrap_indent(&self, display_row: DisplayRow) -> Option<u32> {
let wrap_row = self let wrap_row = self
.block_snapshot .block_snapshot
.to_wrap_point(BlockPoint::new(display_row.0, 0)) .to_wrap_point(BlockPoint::new(display_row.0, 0), Bias::Left)
.row(); .row();
self.wrap_snapshot.soft_wrap_indent(wrap_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 { 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 tab_point = map.wrap_snapshot.to_tab_point(wrap_point);
let fold_point = map.tab_snapshot.to_fold_point(tab_point, bias).0; let fold_point = map.tab_snapshot.to_fold_point(tab_point, bias).0;
let inlay_point = fold_point.to_inlay_point(&map.fold_snapshot); 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 // todo(linux) fails due to pixel differences in text rendering
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
#[gpui::test] #[gpui::test]

View file

@ -265,6 +265,7 @@ pub struct BlockContext<'a, 'b> {
pub em_width: Pixels, pub em_width: Pixels,
pub line_height: Pixels, pub line_height: Pixels,
pub block_id: BlockId, pub block_id: BlockId,
pub selected: bool,
pub editor_style: &'b EditorStyle, pub editor_style: &'b EditorStyle,
} }
@ -1311,7 +1312,6 @@ impl BlockSnapshot {
let (output_start_row, input_start_row) = cursor.start(); let (output_start_row, input_start_row) = cursor.start();
let (output_end_row, input_end_row) = cursor.end(&()); let (output_end_row, input_end_row) = cursor.end(&());
let output_start = Point::new(output_start_row.0, 0); 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_start = Point::new(input_start_row.0, 0);
let input_end = Point::new(input_end_row.0, 0); let input_end = Point::new(input_end_row.0, 0);
@ -1319,10 +1319,10 @@ impl BlockSnapshot {
Some(Block::Custom(block)) Some(Block::Custom(block))
if matches!(block.placement, BlockPlacement::Replace(_)) => 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); return BlockPoint(output_start);
} else {
return BlockPoint(Point::new(output_end.row - 1, 0));
} }
} }
None => { None => {
@ -1364,12 +1364,7 @@ impl BlockSnapshot {
cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &()); cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &());
if let Some(transform) = cursor.item() { if let Some(transform) = cursor.item() {
if transform.block.is_some() { 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) BlockPoint::new(cursor.start().1 .0, 0)
} else {
BlockPoint::new(cursor.end(&()).1 .0 - 1, 0)
}
} else { } else {
let (input_start_row, output_start_row) = cursor.start(); let (input_start_row, output_start_row) = cursor.start();
let input_start = Point::new(input_start_row.0, 0); 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)>(&()); let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
cursor.seek(&BlockRow(block_point.row), Bias::Right, &()); cursor.seek(&BlockRow(block_point.row), Bias::Right, &());
if let Some(transform) = cursor.item() { if let Some(transform) = cursor.item() {
@ -1391,7 +1386,9 @@ impl BlockSnapshot {
if block.place_below() { if block.place_below() {
let wrap_row = cursor.start().1 .0 - 1; let wrap_row = cursor.start().1 .0 - 1;
WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row)) 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) WrapPoint::new(cursor.start().1 .0, 0)
} else { } else {
let wrap_row = cursor.end(&()).1 .0 - 1; let wrap_row = cursor.end(&()).1 .0 - 1;
@ -1766,19 +1763,19 @@ mod tests {
); );
assert_eq!( assert_eq!(
snapshot.to_wrap_point(BlockPoint::new(0, 3)), snapshot.to_wrap_point(BlockPoint::new(0, 3), Bias::Left),
WrapPoint::new(0, 3) WrapPoint::new(0, 3)
); );
assert_eq!( assert_eq!(
snapshot.to_wrap_point(BlockPoint::new(1, 0)), snapshot.to_wrap_point(BlockPoint::new(1, 0), Bias::Left),
WrapPoint::new(1, 0) WrapPoint::new(1, 0)
); );
assert_eq!( assert_eq!(
snapshot.to_wrap_point(BlockPoint::new(3, 0)), snapshot.to_wrap_point(BlockPoint::new(3, 0), Bias::Left),
WrapPoint::new(1, 0) WrapPoint::new(1, 0)
); );
assert_eq!( assert_eq!(
snapshot.to_wrap_point(BlockPoint::new(7, 0)), snapshot.to_wrap_point(BlockPoint::new(7, 0), Bias::Left),
WrapPoint::new(3, 3) WrapPoint::new(3, 3)
); );
@ -2616,10 +2613,15 @@ mod tests {
// Ensure that conversion between block points and wrap points is stable. // Ensure that conversion between block points and wrap points is stable.
for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() { for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() {
let original_wrap_point = WrapPoint::new(row, 0); let wrap_point = WrapPoint::new(row, 0);
let block_point = blocks_snapshot.to_block_point(original_wrap_point); let block_point = blocks_snapshot.to_block_point(wrap_point);
let wrap_point = blocks_snapshot.to_wrap_point(block_point); let left_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Left);
assert_eq!(blocks_snapshot.to_block_point(wrap_point), block_point); 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); 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_point = blocks_snapshot.clip_point(block_point, Bias::Left);
let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left); let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left);
assert_eq!( 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, left_point,
"wrap point: {:?}", "block point: {:?}, wrap point: {:?}",
blocks_snapshot.to_wrap_point(left_point) block_point,
blocks_snapshot.to_wrap_point(left_point, Bias::Left)
); );
assert_eq!( assert_eq!(
left_buffer_point, left_buffer_point,
@ -2642,10 +2646,12 @@ mod tests {
let right_point = blocks_snapshot.clip_point(block_point, Bias::Right); let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right); let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right);
assert_eq!( 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, right_point,
"wrap point: {:?}", "block point: {:?}, wrap point: {:?}",
blocks_snapshot.to_wrap_point(right_point) block_point,
blocks_snapshot.to_wrap_point(right_point, Bias::Right)
); );
assert_eq!( assert_eq!(
right_buffer_point, right_buffer_point,
@ -2681,7 +2687,8 @@ mod tests {
impl BlockSnapshot { impl BlockSnapshot {
fn to_point(&self, point: BlockPoint, bias: Bias) -> Point { 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)
} }
} }
} }

View file

@ -131,7 +131,9 @@ use project::{
use rand::prelude::*; use rand::prelude::*;
use rpc::{proto::*, ErrorExt}; use rpc::{proto::*, ErrorExt};
use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide}; 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 serde::{Deserialize, Serialize};
use settings::{update_settings_file, Settings, SettingsLocation, SettingsStore}; use settings::{update_settings_file, Settings, SettingsLocation, SettingsStore};
use smallvec::SmallVec; use smallvec::SmallVec;
@ -3484,8 +3486,8 @@ impl Editor {
} }
let new_anchor_selections = new_selections.iter().map(|e| &e.0); let new_anchor_selections = new_selections.iter().map(|e| &e.0);
let new_selection_deltas = new_selections.iter().map(|e| e.1); let new_selection_deltas = new_selections.iter().map(|e| e.1);
let snapshot = this.buffer.read(cx).read(cx); let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
let new_selections = resolve_multiple::<usize, _>(new_anchor_selections, &snapshot) let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
.zip(new_selection_deltas) .zip(new_selection_deltas)
.map(|(selection, delta)| Selection { .map(|(selection, delta)| Selection {
id: selection.id, id: selection.id,
@ -3498,18 +3500,20 @@ impl Editor {
let mut i = 0; let mut i = 0;
for (position, delta, selection_id, pair) in new_autoclose_regions { for (position, delta, selection_id, pair) in new_autoclose_regions {
let position = position.to_offset(&snapshot) + delta; let position = position.to_offset(&map.buffer_snapshot) + delta;
let start = snapshot.anchor_before(position); let start = map.buffer_snapshot.anchor_before(position);
let end = snapshot.anchor_after(position); let end = map.buffer_snapshot.anchor_after(position);
while let Some(existing_state) = this.autoclose_regions.get(i) { 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::Less => i += 1,
Ordering::Greater => break, Ordering::Greater => break,
Ordering::Equal => match end.cmp(&existing_state.range.end, &snapshot) { Ordering::Equal => {
match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
Ordering::Less => i += 1, Ordering::Less => i += 1,
Ordering::Equal => break, Ordering::Equal => break,
Ordering::Greater => break, Ordering::Greater => break,
}, }
}
} }
} }
this.autoclose_regions.insert( this.autoclose_regions.insert(
@ -3522,7 +3526,6 @@ impl Editor {
); );
} }
drop(snapshot);
let had_active_inline_completion = this.has_active_inline_completion(cx); let had_active_inline_completion = this.has_active_inline_completion(cx);
this.change_selections_inner(Some(Autoscroll::fit()), false, cx, |s| { this.change_selections_inner(Some(Autoscroll::fit()), false, cx, |s| {
s.select(new_selections) s.select(new_selections)
@ -4038,7 +4041,7 @@ impl Editor {
} }
} }
(selection.clone(), enclosing) (selection, enclosing)
}) })
} }

View file

@ -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] #[gpui::test]
fn test_transpose(cx: &mut TestAppContext) { fn test_transpose(cx: &mut TestAppContext) {
init_test(cx, |_| {}); 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 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
// dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
// viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis // 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); cx.set_state(unwrapped_text);
@ -4212,7 +4282,7 @@ async fn test_rewrap(cx: &mut TestAppContext) {
let wrapped_text = indoc! {" let wrapped_text = indoc! {"
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
// purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus // 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 // Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
// tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, // 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 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
// nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
// porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id // 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); cx.set_state(unwrapped_text);

View file

@ -25,7 +25,7 @@ use crate::{
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
}; };
use client::ParticipantIndex; use client::ParticipantIndex;
use collections::{BTreeMap, HashMap}; use collections::{BTreeMap, HashMap, HashSet};
use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid}; use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid};
use gpui::Subscription; use gpui::Subscription;
use gpui::{ use gpui::{
@ -809,10 +809,12 @@ impl EditorElement {
cx.notify() cx.notify()
} }
#[allow(clippy::too_many_arguments)]
fn layout_selections( fn layout_selections(
&self, &self,
start_anchor: Anchor, start_anchor: Anchor,
end_anchor: Anchor, end_anchor: Anchor,
local_selections: &[Selection<Point>],
snapshot: &EditorSnapshot, snapshot: &EditorSnapshot,
start_row: DisplayRow, start_row: DisplayRow,
end_row: DisplayRow, end_row: DisplayRow,
@ -827,13 +829,9 @@ impl EditorElement {
let mut newest_selection_head = None; let mut newest_selection_head = None;
self.editor.update(cx, |editor, cx| { self.editor.update(cx, |editor, cx| {
if editor.show_local_selections { 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 mut layouts = Vec::new();
let newest = editor.selections.newest(cx); 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_empty = selection.start == selection.end;
let is_newest = selection == newest; let is_newest = selection == newest;
@ -996,6 +994,7 @@ impl EditorElement {
&self, &self,
snapshot: &EditorSnapshot, snapshot: &EditorSnapshot,
selections: &[(PlayerColor, Vec<SelectionLayout>)], selections: &[(PlayerColor, Vec<SelectionLayout>)],
block_start_rows: &HashSet<DisplayRow>,
visible_display_row_range: Range<DisplayRow>, visible_display_row_range: Range<DisplayRow>,
line_layouts: &[LineWithInvisibles], line_layouts: &[LineWithInvisibles],
text_hitbox: &Hitbox, text_hitbox: &Hitbox,
@ -1015,7 +1014,10 @@ impl EditorElement {
let cursor_position = selection.head; let cursor_position = selection.head;
let in_range = visible_display_row_range.contains(&cursor_position.row()); 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; continue;
} }
@ -2068,14 +2070,14 @@ impl EditorElement {
editor_width: Pixels, editor_width: Pixels,
scroll_width: &mut Pixels, scroll_width: &mut Pixels,
resized_blocks: &mut HashMap<CustomBlockId, u32>, resized_blocks: &mut HashMap<CustomBlockId, u32>,
selections: &[Selection<Point>],
cx: &mut WindowContext, cx: &mut WindowContext,
) -> (AnyElement, Size<Pixels>) { ) -> (AnyElement, Size<Pixels>) {
let mut element = match block { let mut element = match block {
Block::Custom(block) => { Block::Custom(block) => {
let align_to = block let block_start = block.start().to_point(&snapshot.buffer_snapshot);
.start() let block_end = block.end().to_point(&snapshot.buffer_snapshot);
.to_point(&snapshot.buffer_snapshot) let align_to = block_start.to_display_point(snapshot);
.to_display_point(snapshot);
let anchor_x = text_x let anchor_x = text_x
+ if rows.contains(&align_to.row()) { + if rows.contains(&align_to.row()) {
line_layouts[align_to.row().minus(rows.start) as usize] line_layouts[align_to.row().minus(rows.start) as usize]
@ -2085,6 +2087,18 @@ impl EditorElement {
.x_for_index(align_to.column() as usize) .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() div()
.size_full() .size_full()
.child(block.render(&mut BlockContext { .child(block.render(&mut BlockContext {
@ -2094,6 +2108,7 @@ impl EditorElement {
line_height, line_height,
em_width, em_width,
block_id, block_id,
selected,
max_width: text_hitbox.size.width.max(*scroll_width), max_width: text_hitbox.size.width.max(*scroll_width),
editor_style: &self.style, editor_style: &self.style,
})) }))
@ -2431,6 +2446,7 @@ impl EditorElement {
text_x: Pixels, text_x: Pixels,
line_height: Pixels, line_height: Pixels,
line_layouts: &[LineWithInvisibles], line_layouts: &[LineWithInvisibles],
selections: &[Selection<Point>],
cx: &mut WindowContext, cx: &mut WindowContext,
) -> Result<Vec<BlockLayout>, HashMap<CustomBlockId, u32>> { ) -> Result<Vec<BlockLayout>, HashMap<CustomBlockId, u32>> {
let (fixed_blocks, non_fixed_blocks) = snapshot let (fixed_blocks, non_fixed_blocks) = snapshot
@ -2467,6 +2483,7 @@ impl EditorElement {
editor_width, editor_width,
scroll_width, scroll_width,
&mut resized_blocks, &mut resized_blocks,
selections,
cx, cx,
); );
fixed_block_max_width = fixed_block_max_width.max(element_size.width + em_width); fixed_block_max_width = fixed_block_max_width.max(element_size.width + em_width);
@ -2511,6 +2528,7 @@ impl EditorElement {
editor_width, editor_width,
scroll_width, scroll_width,
&mut resized_blocks, &mut resized_blocks,
selections,
cx, cx,
); );
@ -2556,6 +2574,7 @@ impl EditorElement {
editor_width, editor_width,
scroll_width, scroll_width,
&mut resized_blocks, &mut resized_blocks,
selections,
cx, cx,
); );
@ -2584,6 +2603,7 @@ impl EditorElement {
fn layout_blocks( fn layout_blocks(
&self, &self,
blocks: &mut Vec<BlockLayout>, blocks: &mut Vec<BlockLayout>,
block_starts: &mut HashSet<DisplayRow>,
hitbox: &Hitbox, hitbox: &Hitbox,
line_height: Pixels, line_height: Pixels,
scroll_pixel_position: gpui::Point<Pixels>, scroll_pixel_position: gpui::Point<Pixels>,
@ -2591,6 +2611,7 @@ impl EditorElement {
) { ) {
for block in blocks { for block in blocks {
let mut origin = if let Some(row) = block.row { let mut origin = if let Some(row) = block.row {
block_starts.insert(row);
hitbox.origin hitbox.origin
+ point( + point(
Pixels::ZERO, Pixels::ZERO,
@ -5101,9 +5122,19 @@ impl Element for EditorElement {
cx, 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( let (selections, active_rows, newest_selection_head) = self.layout_selections(
start_anchor, start_anchor,
end_anchor, end_anchor,
&local_selections,
&snapshot, &snapshot,
start_row, start_row,
end_row, end_row,
@ -5176,6 +5207,7 @@ impl Element for EditorElement {
gutter_dimensions.full_width(), gutter_dimensions.full_width(),
line_height, line_height,
&line_layouts, &line_layouts,
&local_selections,
cx, cx,
) )
}); });
@ -5315,9 +5347,11 @@ impl Element for EditorElement {
cx, cx,
); );
let mut block_start_rows = HashSet::default();
cx.with_element_namespace("blocks", |cx| { cx.with_element_namespace("blocks", |cx| {
self.layout_blocks( self.layout_blocks(
&mut blocks, &mut blocks,
&mut block_start_rows,
&hitbox, &hitbox,
line_height, line_height,
scroll_pixel_position, scroll_pixel_position,
@ -5334,6 +5368,7 @@ impl Element for EditorElement {
let visible_cursors = self.layout_visible_cursors( let visible_cursors = self.layout_visible_cursors(
&snapshot, &snapshot,
&selections, &selections,
&block_start_rows,
start_row..end_row, start_row..end_row,
&line_layouts, &line_layouts,
&text_hitbox, &text_hitbox,

View file

@ -1,6 +1,6 @@
use std::{ use std::{
cell::Ref, cell::Ref,
iter, mem, cmp, iter, mem,
ops::{Deref, DerefMut, Range, Sub}, ops::{Deref, DerefMut, Range, Sub},
sync::Arc, sync::Arc,
}; };
@ -98,9 +98,9 @@ impl SelectionsCollection {
&self, &self,
cx: &mut AppContext, cx: &mut AppContext,
) -> Option<Selection<D>> { ) -> Option<Selection<D>> {
self.pending_anchor() let map = self.display_map(cx);
.as_ref() let selection = resolve_selections(self.pending_anchor().as_ref(), &map).next();
.map(|pending| pending.map(|p| p.summary::<D>(&self.buffer(cx)))) selection
} }
pub(crate) fn pending_mode(&self) -> Option<SelectMode> { pub(crate) fn pending_mode(&self) -> Option<SelectMode> {
@ -111,12 +111,10 @@ impl SelectionsCollection {
where where
D: 'a + TextDimension + Ord + Sub<D, Output = D>, D: 'a + TextDimension + Ord + Sub<D, Output = D>,
{ {
let map = self.display_map(cx);
let disjoint_anchors = &self.disjoint; let disjoint_anchors = &self.disjoint;
let mut disjoint = let mut disjoint = resolve_selections::<D, _>(disjoint_anchors.iter(), &map).peekable();
resolve_multiple::<D, _>(disjoint_anchors.iter(), &self.buffer(cx)).peekable();
let mut pending_opt = self.pending::<D>(cx); let mut pending_opt = self.pending::<D>(cx);
iter::from_fn(move || { iter::from_fn(move || {
if let Some(pending) = pending_opt.as_mut() { if let Some(pending) = pending_opt.as_mut() {
while let Some(next_selection) = disjoint.peek() { while let Some(next_selection) = disjoint.peek() {
@ -199,34 +197,57 @@ impl SelectionsCollection {
where where
D: 'a + TextDimension + Ord + Sub<D, Output = D> + std::fmt::Debug, 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 let start_ix = match self
.disjoint .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, Ok(ix) | Err(ix) => ix,
}; };
let end_ix = match self let end_ix = match self
.disjoint .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, Ok(ix) => ix + 1,
Err(ix) => ix, 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( pub fn all_display(
&self, &self,
cx: &mut AppContext, cx: &mut AppContext,
) -> (DisplaySnapshot, Vec<Selection<DisplayPoint>>) { ) -> (DisplaySnapshot, Vec<Selection<DisplayPoint>>) {
let display_map = self.display_map(cx); let map = self.display_map(cx);
let selections = self let disjoint_anchors = &self.disjoint;
.all::<Point>(cx) let mut disjoint = resolve_selections_display(disjoint_anchors.iter(), &map).peekable();
.into_iter() let mut pending_opt =
.map(|selection| selection.map(|point| point.to_display_point(&display_map))) 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(); .collect();
(display_map, selections) (map, selections)
} }
pub fn newest_anchor(&self) -> &Selection<Anchor> { pub fn newest_anchor(&self) -> &Selection<Anchor> {
@ -241,15 +262,18 @@ impl SelectionsCollection {
&self, &self,
cx: &mut AppContext, cx: &mut AppContext,
) -> Selection<D> { ) -> Selection<D> {
let buffer = self.buffer(cx); let map = self.display_map(cx);
self.newest_anchor().map(|p| p.summary::<D>(&buffer)) let selection = resolve_selections([self.newest_anchor()], &map)
.next()
.unwrap();
selection
} }
pub fn newest_display(&self, cx: &mut AppContext) -> Selection<DisplayPoint> { pub fn newest_display(&self, cx: &mut AppContext) -> Selection<DisplayPoint> {
let display_map = self.display_map(cx); let map = self.display_map(cx);
let selection = self let selection = resolve_selections_display([self.newest_anchor()], &map)
.newest_anchor() .next()
.map(|point| point.to_display_point(&display_map)); .unwrap();
selection selection
} }
@ -265,8 +289,11 @@ impl SelectionsCollection {
&self, &self,
cx: &mut AppContext, cx: &mut AppContext,
) -> Selection<D> { ) -> Selection<D> {
let buffer = self.buffer(cx); let map = self.display_map(cx);
self.oldest_anchor().map(|p| p.summary::<D>(&buffer)) let selection = resolve_selections([self.oldest_anchor()], &map)
.next()
.unwrap();
selection
} }
pub fn first_anchor(&self) -> Selection<Anchor> { 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>>) { 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 = let resolved_selections =
resolve_multiple::<usize, _>(&selections, &buffer).collect::<Vec<_>>(); resolve_selections::<usize, _>(&selections, &map).collect::<Vec<_>>();
self.select(resolved_selections); self.select(resolved_selections);
} }
@ -650,20 +677,16 @@ impl<'a> MutableSelectionsCollection<'a> {
) { ) {
let mut changed = false; let mut changed = false;
let display_map = self.display_map(); let display_map = self.display_map();
let selections = self let (_, selections) = self.collection.all_display(self.cx);
.collection let selections = selections
.all::<Point>(self.cx)
.into_iter() .into_iter()
.map(|selection| { .map(|selection| {
let mut moved_selection = let mut moved_selection = selection.clone();
selection.map(|point| point.to_display_point(&display_map));
move_selection(&display_map, &mut moved_selection); 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 { if selection != moved_selection {
changed = true; changed = true;
} }
moved_selection moved_selection.map(|display_point| display_point.to_point(&display_map))
}) })
.collect(); .collect();
@ -804,8 +827,8 @@ impl<'a> MutableSelectionsCollection<'a> {
.collect(); .collect();
if !adjusted_disjoint.is_empty() { if !adjusted_disjoint.is_empty() {
let resolved_selections = let map = self.display_map();
resolve_multiple(adjusted_disjoint.iter(), &self.buffer()).collect(); let resolved_selections = resolve_selections(adjusted_disjoint.iter(), &map).collect();
self.select::<usize>(resolved_selections); self.select::<usize>(resolved_selections);
} }
@ -849,27 +872,76 @@ impl<'a> DerefMut for MutableSelectionsCollection<'a> {
} }
// Panics if passed selections are not in order // Panics if passed selections are not in order
pub(crate) fn resolve_multiple<'a, D, I>( fn resolve_selections_display<'a>(
selections: I, selections: impl 'a + IntoIterator<Item = &'a Selection<Anchor>>,
snapshot: &MultiBufferSnapshot, map: &'a DisplaySnapshot,
) -> impl 'a + Iterator<Item = Selection<D>> ) -> impl 'a + Iterator<Item = Selection<DisplayPoint>> {
where
D: TextDimension + Ord + Sub<D, Output = D>,
I: 'a + IntoIterator<Item = &'a Selection<Anchor>>,
{
let (to_summarize, selections) = selections.into_iter().tee(); let (to_summarize, selections) = selections.into_iter().tee();
let mut summaries = snapshot let mut summaries = map
.summaries_for_anchors::<D, _>( .buffer_snapshot
to_summarize .summaries_for_anchors::<Point, _>(to_summarize.flat_map(|s| [&s.start, &s.end]))
.flat_map(|s| [&s.start, &s.end])
.collect::<Vec<_>>(),
)
.into_iter(); .into_iter();
selections.map(move |s| Selection { 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, id: s.id,
start: summaries.next().unwrap(), start: display_start,
end: summaries.next().unwrap(), end: display_end,
reversed: s.reversed, reversed: s.reversed,
goal: s.goal, 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<Item = Selection<D>>
where
D: TextDimension + Clone + Ord + Sub<D, Output = D>,
I: 'a + IntoIterator<Item = &'a Selection<Anchor>>,
{
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,
}
}) })
} }

View file

@ -3083,6 +3083,58 @@ impl MultiBufferSnapshot {
summaries 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)> pub fn refresh_anchors<'a, I>(&'a self, anchors: I) -> Vec<(usize, Anchor, bool)>
where where
I: 'a + IntoIterator<Item = &'a Anchor>, 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 { impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, Option<&'a Locator>> for Locator {
fn cmp(&self, cursor_location: &Option<&'a Locator>, _: &()) -> cmp::Ordering { fn cmp(&self, cursor_location: &Option<&'a Locator>, _: &()) -> cmp::Ordering {
Ord::cmp(&Some(self), cursor_location) Ord::cmp(&Some(self), cursor_location)