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:
parent
b5c38e9a09
commit
773a3b335e
8 changed files with 472 additions and 120 deletions
|
@ -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();
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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);
|
BlockPoint::new(cursor.start().1 .0, 0)
|
||||||
if wrap_start == wrap_point {
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 => {
|
||||||
Ordering::Less => i += 1,
|
match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
|
||||||
Ordering::Equal => break,
|
Ordering::Less => i += 1,
|
||||||
Ordering::Greater => break,
|
Ordering::Equal => 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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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();
|
||||||
.collect();
|
let selections = iter::from_fn(move || {
|
||||||
(display_map, selections)
|
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> {
|
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
|
||||||
id: s.id,
|
.map(move |s| {
|
||||||
start: summaries.next().unwrap(),
|
let start = summaries.next().unwrap();
|
||||||
end: summaries.next().unwrap(),
|
let end = summaries.next().unwrap();
|
||||||
reversed: s.reversed,
|
|
||||||
goal: s.goal,
|
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<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,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue