git: Refactor buffer_diff point translation APIs for more efficient side-by-side diff syncing (#48237)
The side-by-side diff heavily relies on a primitive from `buffer_diff` that converts a point on one side of the diff to a range of points on the other side. The way this primitive is set up on main is pretty naive--every time we call `points_to_base_text_points` (or `base_text_points_to_points`), we need to iterate over all hunks in the diff. That's particularly bad for the case of constructing a new side-by-side diff starting from a multibuffer, because we call those APIs once per excerpt, and the number of excerpts is ~equal to the number of hunks. This PR changes the point translation APIs exposed by `buffer_diff` to make it easier to use them efficiently in `editor`. The new shape is a pair of functions that return a patch that can be used to translate from the main buffer to the base text or vice versa. When syncing edits through the block map that touch several excerpts for the same buffer, we can reuse this patch for excerpts after the first--so when building a new side-by-side diff, we'll iterate over each hunk just once. The shape of the new APIs also sets us up to scale down to cases like editing on the right-hand side of the diff: we can pass in a point range and give them permission to return an approximate patch that's only guaranteed to give the correct results when used with points in that range. For edits that only affect one excerpt, and given how the project diff is set up, that should allow us to skip iterating over most of the hunks in a buffer. Release Notes: - N/A --------- Co-authored-by: cameron <cameron.studdstreet@gmail.com>
This commit is contained in:
1 parent
0d99eb5d40
commit
6d2ed6c3f0
6 files changed
+348
-1615
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -150,12 +150,11 @@ type TextHighlights = TreeMap<HighlightKey, Arc<(HighlightStyle, Vec<Range<Ancho
|
||||
type InlayHighlights = TreeMap<TypeId, TreeMap<InlayId, (HighlightStyle, InlayHighlight)>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MultiBufferRowMapping {
|
||||
pub first_group: Option<Range<MultiBufferPoint>>,
|
||||
pub boundaries: Vec<(MultiBufferPoint, Range<MultiBufferPoint>)>,
|
||||
pub prev_boundary: Option<(MultiBufferPoint, Range<MultiBufferPoint>)>,
|
||||
pub source_excerpt_end: MultiBufferPoint,
|
||||
pub target_excerpt_end: MultiBufferPoint,
|
||||
pub struct CompanionExcerptPatch {
|
||||
pub patch: Patch<MultiBufferPoint>,
|
||||
pub edited_range: Range<MultiBufferPoint>,
|
||||
pub source_excerpt_range: Range<MultiBufferPoint>,
|
||||
pub target_excerpt_range: Range<MultiBufferPoint>,
|
||||
}
|
||||
|
||||
pub type ConvertMultiBufferRows = fn(
|
||||
@@ -163,7 +162,7 @@ pub type ConvertMultiBufferRows = fn(
|
||||
&MultiBufferSnapshot,
|
||||
&MultiBufferSnapshot,
|
||||
(Bound<MultiBufferPoint>, Bound<MultiBufferPoint>),
|
||||
) -> Vec<MultiBufferRowMapping>;
|
||||
) -> Vec<CompanionExcerptPatch>;
|
||||
|
||||
/// Decides how text in a [`MultiBuffer`] should be displayed in a buffer, handling inlay hints,
|
||||
/// folding, hard tabs, soft wrapping, custom blocks (like diagnostics), and highlighting.
|
||||
@@ -233,7 +232,7 @@ impl Companion {
|
||||
companion_snapshot: &MultiBufferSnapshot,
|
||||
our_snapshot: &MultiBufferSnapshot,
|
||||
bounds: (Bound<MultiBufferPoint>, Bound<MultiBufferPoint>),
|
||||
) -> Vec<MultiBufferRowMapping> {
|
||||
) -> Vec<CompanionExcerptPatch> {
|
||||
let (excerpt_map, convert_fn) = if display_map_id == self.rhs_display_map_id {
|
||||
(&self.rhs_excerpt_to_lhs_excerpt, self.rhs_rows_to_lhs_rows)
|
||||
} else {
|
||||
@@ -242,19 +241,32 @@ impl Companion {
|
||||
convert_fn(excerpt_map, companion_snapshot, our_snapshot, bounds)
|
||||
}
|
||||
|
||||
pub(crate) fn convert_rows_from_companion(
|
||||
pub(crate) fn convert_point_from_companion(
|
||||
&self,
|
||||
display_map_id: EntityId,
|
||||
our_snapshot: &MultiBufferSnapshot,
|
||||
companion_snapshot: &MultiBufferSnapshot,
|
||||
bounds: (Bound<MultiBufferPoint>, Bound<MultiBufferPoint>),
|
||||
) -> Vec<MultiBufferRowMapping> {
|
||||
point: MultiBufferPoint,
|
||||
) -> Range<MultiBufferPoint> {
|
||||
let (excerpt_map, convert_fn) = if display_map_id == self.rhs_display_map_id {
|
||||
(&self.lhs_excerpt_to_rhs_excerpt, self.lhs_rows_to_rhs_rows)
|
||||
} else {
|
||||
(&self.rhs_excerpt_to_lhs_excerpt, self.rhs_rows_to_lhs_rows)
|
||||
};
|
||||
convert_fn(excerpt_map, our_snapshot, companion_snapshot, bounds)
|
||||
|
||||
let excerpt = convert_fn(
|
||||
excerpt_map,
|
||||
our_snapshot,
|
||||
companion_snapshot,
|
||||
(Bound::Included(point), Bound::Included(point)),
|
||||
)
|
||||
.into_iter()
|
||||
.next();
|
||||
|
||||
let Some(excerpt) = excerpt else {
|
||||
return Point::zero()..our_snapshot.max_point();
|
||||
};
|
||||
excerpt.patch.edit_for_old_position(point).new
|
||||
}
|
||||
|
||||
pub(crate) fn companion_excerpt_to_excerpt(
|
||||
|
||||
@@ -656,36 +656,25 @@ impl BlockMap {
|
||||
.to_point(WrapPoint::new(edit.new.end, 0), Bias::Left);
|
||||
|
||||
let my_start = companion
|
||||
.convert_rows_from_companion(
|
||||
.convert_point_from_companion(
|
||||
display_map_id,
|
||||
wrap_snapshot.buffer_snapshot(),
|
||||
companion_new_snapshot.buffer_snapshot(),
|
||||
(
|
||||
Bound::Included(companion_start),
|
||||
Bound::Included(companion_start),
|
||||
),
|
||||
companion_start,
|
||||
)
|
||||
.first()
|
||||
.and_then(|t| t.boundaries.first())
|
||||
.map(|(_, range)| range.start)
|
||||
.unwrap_or(wrap_snapshot.buffer_snapshot().max_point());
|
||||
.start;
|
||||
let my_end = companion
|
||||
.convert_rows_from_companion(
|
||||
.convert_point_from_companion(
|
||||
display_map_id,
|
||||
wrap_snapshot.buffer_snapshot(),
|
||||
companion_new_snapshot.buffer_snapshot(),
|
||||
(
|
||||
Bound::Included(companion_end),
|
||||
Bound::Included(companion_end),
|
||||
),
|
||||
companion_end,
|
||||
)
|
||||
.first()
|
||||
.and_then(|t| t.boundaries.last())
|
||||
.map(|(_, range)| range.end)
|
||||
.unwrap_or(wrap_snapshot.buffer_snapshot().max_point());
|
||||
.end;
|
||||
|
||||
let mut my_start = wrap_snapshot.make_wrap_point(my_start, Bias::Left);
|
||||
let mut my_end = wrap_snapshot.make_wrap_point(my_end, Bias::Left);
|
||||
// TODO(split-diff) should use trailing_excerpt_update_count for the second case
|
||||
if my_end.column() > 0 || my_end == wrap_snapshot.max_point() {
|
||||
*my_end.row_mut() += 1;
|
||||
*my_end.column_mut() = 0;
|
||||
@@ -1104,7 +1093,7 @@ impl BlockMap {
|
||||
let our_buffer = wrap_snapshot.buffer_snapshot();
|
||||
let companion_buffer = companion_snapshot.buffer_snapshot();
|
||||
|
||||
let row_mappings = companion.convert_rows_to_companion(
|
||||
let patches = companion.convert_rows_to_companion(
|
||||
display_map_id,
|
||||
companion_buffer,
|
||||
our_buffer,
|
||||
@@ -1129,14 +1118,27 @@ impl BlockMap {
|
||||
|
||||
let mut result = Vec::new();
|
||||
|
||||
for row_mapping in row_mappings {
|
||||
let mut iter = row_mapping.boundaries.iter().cloned().peekable();
|
||||
// old approach: buffer_diff computed the patch, and then passed a chosen sequence of points through it, returning the results
|
||||
// new approach: buffer_diff gives us the patch directly, and then we pass through the points we are interested in
|
||||
for excerpt in patches {
|
||||
let mut source_points = (excerpt.edited_range.start.row..=excerpt.edited_range.end.row)
|
||||
.map(|row| MultiBufferPoint::new(row, 0))
|
||||
.chain(if excerpt.edited_range.end.column > 0 {
|
||||
Some(excerpt.edited_range.end)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.peekable();
|
||||
let last_source_point = if excerpt.edited_range.end.column > 0 {
|
||||
excerpt.edited_range.end
|
||||
} else {
|
||||
MultiBufferPoint::new(excerpt.edited_range.end.row, 0)
|
||||
};
|
||||
|
||||
let Some(((first_boundary, first_range), first_group)) =
|
||||
iter.peek().cloned().zip(row_mapping.first_group.clone())
|
||||
else {
|
||||
let Some(first_point) = source_points.peek().copied() else {
|
||||
continue;
|
||||
};
|
||||
let edit_for_first_point = excerpt.patch.edit_for_old_position(first_point);
|
||||
|
||||
// Because we calculate spacers based on differences in wrap row
|
||||
// counts between the RHS and LHS for corresponding buffer points,
|
||||
@@ -1145,12 +1147,20 @@ impl BlockMap {
|
||||
// counts should have been balanced already by spacers above this
|
||||
// edit, so we only need to insert spacers for when the difference
|
||||
// in counts diverges from that baseline value.
|
||||
let (our_baseline, their_baseline) = if first_group.start < first_boundary {
|
||||
(first_group.start, first_range.start)
|
||||
} else if let Some((prev_boundary, prev_range)) = row_mapping.prev_boundary {
|
||||
(prev_boundary, prev_range.end)
|
||||
let (our_baseline, their_baseline) = if edit_for_first_point.old.start < first_point {
|
||||
// Case 1: We are inside a hunk/group--take the start of the hunk/group on both sides as the baseline.
|
||||
(
|
||||
edit_for_first_point.old.start,
|
||||
edit_for_first_point.new.start,
|
||||
)
|
||||
} else if first_point.row > excerpt.source_excerpt_range.start.row {
|
||||
// Case 2: We are not inside a hunk/group--go back by one row to find the baseline.
|
||||
let prev_point = Point::new(first_point.row - 1, 0);
|
||||
let edit_for_prev_point = excerpt.patch.edit_for_old_position(prev_point);
|
||||
(prev_point, edit_for_prev_point.new.end)
|
||||
} else {
|
||||
(first_boundary, first_range.start)
|
||||
// Case 3: We are at the start of the excerpt--no previous row to use as the baseline.
|
||||
(first_point, edit_for_first_point.new.start)
|
||||
};
|
||||
let our_baseline = wrap_snapshot
|
||||
.make_wrap_point(our_baseline, Bias::Left)
|
||||
@@ -1161,14 +1171,17 @@ impl BlockMap {
|
||||
|
||||
let mut delta = their_baseline.0 as i32 - our_baseline.0 as i32;
|
||||
|
||||
if first_group.start < first_boundary {
|
||||
let mut current_boundary = first_boundary;
|
||||
let current_range = first_range;
|
||||
while let Some((next_boundary, next_range)) = iter.peek().cloned()
|
||||
&& next_range.end <= current_range.end
|
||||
{
|
||||
iter.next();
|
||||
current_boundary = next_boundary;
|
||||
// If we started out in the middle of a hunk/group, work up to the end of that group to set up the main loop below.
|
||||
if edit_for_first_point.old.start < first_point {
|
||||
let mut current_boundary = first_point;
|
||||
let current_range = edit_for_first_point.new;
|
||||
while let Some(next_point) = source_points.peek().cloned() {
|
||||
let edit_for_next_point = excerpt.patch.edit_for_old_position(next_point);
|
||||
if edit_for_next_point.new.end > current_range.end {
|
||||
break;
|
||||
}
|
||||
source_points.next();
|
||||
current_boundary = next_point;
|
||||
}
|
||||
|
||||
let (new_delta, spacer) =
|
||||
@@ -1187,13 +1200,14 @@ impl BlockMap {
|
||||
}
|
||||
}
|
||||
|
||||
while let Some((boundary, range)) = iter.next() {
|
||||
let mut current_boundary = boundary;
|
||||
let current_range = range;
|
||||
// Main loop: process one hunk/group at a time, possibly inserting spacers before and after.
|
||||
while let Some(source_point) = source_points.next() {
|
||||
let mut current_boundary = source_point;
|
||||
let current_range = excerpt.patch.edit_for_old_position(current_boundary).new;
|
||||
|
||||
// This can only occur at the end of an excerpt.
|
||||
if current_boundary.column > 0 {
|
||||
debug_assert_eq!(current_boundary, row_mapping.source_excerpt_end);
|
||||
debug_assert_eq!(current_boundary, excerpt.source_excerpt_range.end);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1202,9 +1216,12 @@ impl BlockMap {
|
||||
determine_spacer(current_boundary, current_range.start, delta);
|
||||
delta = delta_at_start;
|
||||
|
||||
while let Some((next_boundary, next_range)) = iter.peek()
|
||||
&& next_range.end <= current_range.end
|
||||
{
|
||||
while let Some(next_point) = source_points.peek().copied() {
|
||||
let edit_for_next_point = excerpt.patch.edit_for_old_position(next_point);
|
||||
if edit_for_next_point.new.end > current_range.end {
|
||||
break;
|
||||
}
|
||||
|
||||
if let Some((wrap_row, height)) = spacer_at_start.take() {
|
||||
result.push((
|
||||
BlockPlacement::Above(wrap_row),
|
||||
@@ -1216,13 +1233,13 @@ impl BlockMap {
|
||||
));
|
||||
}
|
||||
|
||||
current_boundary = *next_boundary;
|
||||
iter.next();
|
||||
current_boundary = next_point;
|
||||
source_points.next();
|
||||
}
|
||||
|
||||
// This can only occur at the end of an excerpt.
|
||||
if current_boundary.column > 0 {
|
||||
debug_assert_eq!(current_boundary, row_mapping.source_excerpt_end);
|
||||
debug_assert_eq!(current_boundary, excerpt.source_excerpt_range.end);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1254,10 +1271,9 @@ impl BlockMap {
|
||||
}
|
||||
}
|
||||
|
||||
let (last_boundary, _last_range) = row_mapping.boundaries.last().cloned().unwrap();
|
||||
if last_boundary == row_mapping.source_excerpt_end {
|
||||
if last_source_point == excerpt.source_excerpt_range.end {
|
||||
let (_new_delta, spacer) =
|
||||
determine_spacer(last_boundary, row_mapping.target_excerpt_end, delta);
|
||||
determine_spacer(last_source_point, excerpt.target_excerpt_range.end, delta);
|
||||
if let Some((wrap_row, height)) = spacer {
|
||||
result.push((
|
||||
BlockPlacement::Below(wrap_row),
|
||||
|
||||
+10
-15
@@ -22742,29 +22742,24 @@ impl Editor {
|
||||
buffer_ranges.last()
|
||||
}?;
|
||||
|
||||
let start_row_in_buffer = text::ToPoint::to_point(&range.start, buffer).row;
|
||||
let end_row_in_buffer = text::ToPoint::to_point(&range.end, buffer).row;
|
||||
let buffer_range = range.to_point(buffer);
|
||||
|
||||
let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
|
||||
let selection = start_row_in_buffer..end_row_in_buffer;
|
||||
|
||||
return Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection));
|
||||
return Some((
|
||||
multi_buffer.buffer(buffer.remote_id()).unwrap(),
|
||||
buffer_range.start.row..buffer_range.end.row,
|
||||
));
|
||||
};
|
||||
|
||||
let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
|
||||
let (mut translated, _, _) = buffer_diff_snapshot.points_to_base_text_points(
|
||||
[
|
||||
Point::new(start_row_in_buffer, 0),
|
||||
Point::new(end_row_in_buffer, 0),
|
||||
],
|
||||
buffer,
|
||||
);
|
||||
let start_row = translated.next().unwrap().start.row;
|
||||
let end_row = translated.next().unwrap().end.row;
|
||||
let start =
|
||||
buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.start, buffer);
|
||||
let end =
|
||||
buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.end, buffer);
|
||||
|
||||
Some((
|
||||
multi_buffer.buffer(buffer.remote_id()).unwrap(),
|
||||
start_row..end_row,
|
||||
start.row..end.row,
|
||||
))
|
||||
});
|
||||
|
||||
|
||||
+140
-160
@@ -1,4 +1,4 @@
|
||||
use std::ops::{Bound, Range};
|
||||
use std::ops::{Bound, Range, RangeInclusive};
|
||||
|
||||
use buffer_diff::{BufferDiff, BufferDiffSnapshot};
|
||||
use collections::HashMap;
|
||||
@@ -6,19 +6,19 @@ use feature_flags::{FeatureFlag, FeatureFlagAppExt as _};
|
||||
use gpui::{Action, AppContext as _, Entity, EventEmitter, Focusable, Subscription, WeakEntity};
|
||||
use language::{Buffer, Capability};
|
||||
use multi_buffer::{
|
||||
Anchor, BufferOffset, ExcerptId, ExcerptRange, ExpandExcerptDirection, MultiBuffer,
|
||||
MultiBufferPoint, MultiBufferSnapshot, PathKey,
|
||||
Anchor, ExcerptId, ExcerptRange, ExpandExcerptDirection, MultiBuffer, MultiBufferPoint,
|
||||
MultiBufferSnapshot, PathKey,
|
||||
};
|
||||
use project::Project;
|
||||
use rope::Point;
|
||||
use text::{OffsetRangeExt as _, ToPoint as _};
|
||||
use text::{OffsetRangeExt as _, Patch, ToPoint as _};
|
||||
use ui::{
|
||||
App, Context, InteractiveElement as _, IntoElement as _, ParentElement as _, Render,
|
||||
Styled as _, Window, div,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
display_map::MultiBufferRowMapping,
|
||||
display_map::CompanionExcerptPatch,
|
||||
split_editor_view::{SplitEditorState, SplitEditorView},
|
||||
};
|
||||
use workspace::{ActivatePaneLeft, ActivatePaneRight, Item, Workspace};
|
||||
@@ -35,17 +35,13 @@ pub(crate) fn convert_lhs_rows_to_rhs(
|
||||
rhs_snapshot: &MultiBufferSnapshot,
|
||||
lhs_snapshot: &MultiBufferSnapshot,
|
||||
lhs_bounds: (Bound<MultiBufferPoint>, Bound<MultiBufferPoint>),
|
||||
) -> Vec<MultiBufferRowMapping> {
|
||||
convert_rows(
|
||||
) -> Vec<CompanionExcerptPatch> {
|
||||
patches_for_range(
|
||||
lhs_excerpt_to_rhs_excerpt,
|
||||
lhs_snapshot,
|
||||
rhs_snapshot,
|
||||
lhs_bounds,
|
||||
|diff, points, buffer| {
|
||||
let (points, first_group, prev_boundary) =
|
||||
diff.base_text_points_to_points(points, buffer);
|
||||
(points.collect(), first_group, prev_boundary)
|
||||
},
|
||||
|diff, range, buffer| diff.patch_for_base_text_range(range, buffer),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -54,182 +50,171 @@ pub(crate) fn convert_rhs_rows_to_lhs(
|
||||
lhs_snapshot: &MultiBufferSnapshot,
|
||||
rhs_snapshot: &MultiBufferSnapshot,
|
||||
rhs_bounds: (Bound<MultiBufferPoint>, Bound<MultiBufferPoint>),
|
||||
) -> Vec<MultiBufferRowMapping> {
|
||||
convert_rows(
|
||||
) -> Vec<CompanionExcerptPatch> {
|
||||
patches_for_range(
|
||||
rhs_excerpt_to_lhs_excerpt,
|
||||
rhs_snapshot,
|
||||
lhs_snapshot,
|
||||
rhs_bounds,
|
||||
|diff, points, buffer| {
|
||||
let (points, first_group, prev_boundary) =
|
||||
diff.points_to_base_text_points(points, buffer);
|
||||
(points.collect(), first_group, prev_boundary)
|
||||
},
|
||||
|diff, range, buffer| diff.patch_for_buffer_range(range, buffer),
|
||||
)
|
||||
}
|
||||
|
||||
fn convert_rows<F>(
|
||||
fn patches_for_range<F>(
|
||||
excerpt_map: &HashMap<ExcerptId, ExcerptId>,
|
||||
source_snapshot: &MultiBufferSnapshot,
|
||||
target_snapshot: &MultiBufferSnapshot,
|
||||
source_bounds: (Bound<MultiBufferPoint>, Bound<MultiBufferPoint>),
|
||||
translate_fn: F,
|
||||
) -> Vec<MultiBufferRowMapping>
|
||||
) -> Vec<CompanionExcerptPatch>
|
||||
where
|
||||
F: Fn(
|
||||
&BufferDiffSnapshot,
|
||||
Vec<Point>,
|
||||
&text::BufferSnapshot,
|
||||
) -> (
|
||||
Vec<Range<Point>>,
|
||||
Option<Range<Point>>,
|
||||
Option<(Point, Range<Point>)>,
|
||||
),
|
||||
F: Fn(&BufferDiffSnapshot, RangeInclusive<Point>, &text::BufferSnapshot) -> Patch<Point>,
|
||||
{
|
||||
let mut result = Vec::new();
|
||||
let mut patches = HashMap::default();
|
||||
|
||||
for (buffer, buffer_offset_range, source_excerpt_id) in
|
||||
for (source_buffer, buffer_offset_range, source_excerpt_id) in
|
||||
source_snapshot.range_to_buffer_ranges(source_bounds)
|
||||
{
|
||||
if let Some(translation) = convert_excerpt_rows(
|
||||
excerpt_map,
|
||||
let target_excerpt_id = excerpt_map.get(&source_excerpt_id).copied().unwrap();
|
||||
let target_buffer = target_snapshot
|
||||
.buffer_for_excerpt(target_excerpt_id)
|
||||
.unwrap();
|
||||
let patch = patches.entry(source_buffer.remote_id()).or_insert_with(|| {
|
||||
let diff = source_snapshot
|
||||
.diff_for_buffer_id(source_buffer.remote_id())
|
||||
.unwrap();
|
||||
let rhs_buffer = if source_buffer.remote_id() == diff.base_text().remote_id() {
|
||||
&target_buffer
|
||||
} else {
|
||||
source_buffer
|
||||
};
|
||||
// TODO(split-diff) pass only the union of the ranges for the affected excerpts
|
||||
translate_fn(diff, Point::zero()..=source_buffer.max_point(), rhs_buffer)
|
||||
});
|
||||
let buffer_point_range = buffer_offset_range.to_point(source_buffer);
|
||||
|
||||
// TODO(split-diff) maybe narrow the patch to only the edited part of the excerpt
|
||||
// (less useful for project diff, but important if we want to do singleton side-by-side diff)
|
||||
result.push(patch_for_excerpt(
|
||||
source_snapshot,
|
||||
target_snapshot,
|
||||
source_excerpt_id,
|
||||
buffer,
|
||||
buffer_offset_range,
|
||||
&translate_fn,
|
||||
) {
|
||||
result.push(translation);
|
||||
}
|
||||
target_excerpt_id,
|
||||
source_buffer,
|
||||
target_buffer,
|
||||
patch,
|
||||
buffer_point_range,
|
||||
));
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn convert_excerpt_rows<F>(
|
||||
excerpt_map: &HashMap<ExcerptId, ExcerptId>,
|
||||
fn patch_for_excerpt(
|
||||
source_snapshot: &MultiBufferSnapshot,
|
||||
target_snapshot: &MultiBufferSnapshot,
|
||||
source_excerpt_id: ExcerptId,
|
||||
target_excerpt_id: ExcerptId,
|
||||
source_buffer: &text::BufferSnapshot,
|
||||
source_buffer_range: Range<BufferOffset>,
|
||||
translate_fn: F,
|
||||
) -> Option<MultiBufferRowMapping>
|
||||
where
|
||||
F: Fn(
|
||||
&BufferDiffSnapshot,
|
||||
Vec<Point>,
|
||||
&text::BufferSnapshot,
|
||||
) -> (
|
||||
Vec<Range<Point>>,
|
||||
Option<Range<Point>>,
|
||||
Option<(Point, Range<Point>)>,
|
||||
),
|
||||
{
|
||||
let target_excerpt_id = excerpt_map.get(&source_excerpt_id).copied()?;
|
||||
let target_buffer = target_snapshot.buffer_for_excerpt(target_excerpt_id)?;
|
||||
|
||||
let diff = source_snapshot.diff_for_buffer_id(source_buffer.remote_id())?;
|
||||
let rhs_buffer = if source_buffer.remote_id() == diff.base_text().remote_id() {
|
||||
&target_buffer
|
||||
} else {
|
||||
source_buffer
|
||||
};
|
||||
|
||||
let local_start = source_buffer.offset_to_point(source_buffer_range.start.0);
|
||||
let local_end = source_buffer.offset_to_point(source_buffer_range.end.0);
|
||||
|
||||
let mut input_points: Vec<Point> = (local_start.row..=local_end.row)
|
||||
.map(|row| Point::new(row, 0))
|
||||
.collect();
|
||||
if local_end.column > 0 {
|
||||
input_points.push(local_end);
|
||||
}
|
||||
|
||||
let (translated_ranges, first_group, prev_boundary) =
|
||||
translate_fn(&diff, input_points.clone(), rhs_buffer);
|
||||
|
||||
let source_multibuffer_range = source_snapshot.range_for_excerpt(source_excerpt_id)?;
|
||||
target_buffer: &text::BufferSnapshot,
|
||||
patch: &Patch<Point>,
|
||||
source_edited_range: Range<Point>,
|
||||
) -> CompanionExcerptPatch {
|
||||
let source_multibuffer_range = source_snapshot
|
||||
.range_for_excerpt(source_excerpt_id)
|
||||
.unwrap();
|
||||
let source_excerpt_start_in_multibuffer = source_multibuffer_range.start;
|
||||
let source_context_range = source_snapshot.context_range_for_excerpt(source_excerpt_id)?;
|
||||
let source_context_range = source_snapshot
|
||||
.context_range_for_excerpt(source_excerpt_id)
|
||||
.unwrap();
|
||||
let source_excerpt_start_in_buffer = source_context_range.start.to_point(&source_buffer);
|
||||
let source_excerpt_end_in_buffer = source_context_range.end.to_point(&source_buffer);
|
||||
let target_multibuffer_range = target_snapshot.range_for_excerpt(target_excerpt_id)?;
|
||||
let target_multibuffer_range = target_snapshot
|
||||
.range_for_excerpt(target_excerpt_id)
|
||||
.unwrap();
|
||||
let target_excerpt_start_in_multibuffer = target_multibuffer_range.start;
|
||||
let target_context_range = target_snapshot.context_range_for_excerpt(target_excerpt_id)?;
|
||||
let target_context_range = target_snapshot
|
||||
.context_range_for_excerpt(target_excerpt_id)
|
||||
.unwrap();
|
||||
let target_excerpt_start_in_buffer = target_context_range.start.to_point(&target_buffer);
|
||||
let target_excerpt_end_in_buffer = target_context_range.end.to_point(&target_buffer);
|
||||
|
||||
let boundaries: Vec<_> = input_points
|
||||
.into_iter()
|
||||
.zip(translated_ranges)
|
||||
.map(|(source_buffer_point, target_range)| {
|
||||
let source_multibuffer_point = source_excerpt_start_in_multibuffer
|
||||
+ (source_buffer_point - source_excerpt_start_in_buffer.min(source_buffer_point));
|
||||
|
||||
let clamped_target_start = target_range
|
||||
let edits = patch
|
||||
.edits()
|
||||
.iter()
|
||||
.skip_while(|edit| edit.old.end < source_excerpt_start_in_buffer)
|
||||
.take_while(|edit| edit.old.start <= source_excerpt_end_in_buffer)
|
||||
.map(|edit| {
|
||||
let clamped_source_start = edit
|
||||
.old
|
||||
.start
|
||||
.max(source_excerpt_start_in_buffer)
|
||||
.min(source_excerpt_end_in_buffer);
|
||||
let clamped_source_end = edit
|
||||
.old
|
||||
.end
|
||||
.max(source_excerpt_start_in_buffer)
|
||||
.min(source_excerpt_end_in_buffer);
|
||||
let source_multibuffer_start = source_excerpt_start_in_multibuffer
|
||||
+ (clamped_source_start - source_excerpt_start_in_buffer);
|
||||
let source_multibuffer_end = source_excerpt_start_in_multibuffer
|
||||
+ (clamped_source_end - source_excerpt_start_in_buffer);
|
||||
let clamped_target_start = edit
|
||||
.new
|
||||
.start
|
||||
.max(target_excerpt_start_in_buffer)
|
||||
.min(target_excerpt_end_in_buffer);
|
||||
let clamped_target_end = target_range
|
||||
let clamped_target_end = edit
|
||||
.new
|
||||
.end
|
||||
.max(target_excerpt_start_in_buffer)
|
||||
.min(target_excerpt_end_in_buffer);
|
||||
|
||||
let target_multibuffer_start = target_excerpt_start_in_multibuffer
|
||||
+ (clamped_target_start - target_excerpt_start_in_buffer);
|
||||
|
||||
let target_multibuffer_end = target_excerpt_start_in_multibuffer
|
||||
+ (clamped_target_end - target_excerpt_start_in_buffer);
|
||||
text::Edit {
|
||||
old: source_multibuffer_start..source_multibuffer_end,
|
||||
new: target_multibuffer_start..target_multibuffer_end,
|
||||
}
|
||||
});
|
||||
|
||||
(
|
||||
source_multibuffer_point,
|
||||
target_multibuffer_start..target_multibuffer_end,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let first_group = first_group.map(|first_group| {
|
||||
let start = source_excerpt_start_in_multibuffer
|
||||
+ (first_group.start - source_excerpt_start_in_buffer.min(first_group.start));
|
||||
let end = source_excerpt_start_in_multibuffer
|
||||
+ (first_group.end - source_excerpt_start_in_buffer.min(first_group.end));
|
||||
start..end
|
||||
});
|
||||
let edits = [text::Edit {
|
||||
old: source_excerpt_start_in_multibuffer..source_excerpt_start_in_multibuffer,
|
||||
new: target_excerpt_start_in_multibuffer..target_excerpt_start_in_multibuffer,
|
||||
}]
|
||||
.into_iter()
|
||||
.chain(edits);
|
||||
|
||||
let prev_boundary = prev_boundary.map(|(source_buffer_point, target_range)| {
|
||||
let source_multibuffer_point = source_excerpt_start_in_multibuffer
|
||||
+ (source_buffer_point - source_excerpt_start_in_buffer.min(source_buffer_point));
|
||||
let mut merged_edits: Vec<text::Edit<Point>> = Vec::new();
|
||||
for edit in edits {
|
||||
if let Some(last) = merged_edits.last_mut() {
|
||||
if edit.new.start <= last.new.end {
|
||||
last.old.end = last.old.end.max(edit.old.end);
|
||||
last.new.end = last.new.end.max(edit.new.end);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
merged_edits.push(edit);
|
||||
}
|
||||
|
||||
let clamped_target_start = target_range
|
||||
.start
|
||||
.max(target_excerpt_start_in_buffer)
|
||||
.min(target_excerpt_end_in_buffer);
|
||||
let clamped_target_end = target_range
|
||||
.end
|
||||
.max(target_excerpt_start_in_buffer)
|
||||
.min(target_excerpt_end_in_buffer);
|
||||
let edited_range = source_excerpt_start_in_multibuffer
|
||||
+ (source_edited_range.start - source_excerpt_start_in_buffer)
|
||||
..source_excerpt_start_in_multibuffer
|
||||
+ (source_edited_range.end - source_excerpt_start_in_buffer);
|
||||
|
||||
let target_multibuffer_start = target_excerpt_start_in_multibuffer
|
||||
+ (clamped_target_start - target_excerpt_start_in_buffer);
|
||||
let target_multibuffer_end = target_excerpt_start_in_multibuffer
|
||||
+ (clamped_target_end - target_excerpt_start_in_buffer);
|
||||
let source_excerpt_end = source_excerpt_start_in_multibuffer
|
||||
+ (source_excerpt_end_in_buffer - source_excerpt_start_in_buffer);
|
||||
let target_excerpt_end = target_excerpt_start_in_multibuffer
|
||||
+ (target_excerpt_end_in_buffer - target_excerpt_start_in_buffer);
|
||||
|
||||
(
|
||||
source_multibuffer_point,
|
||||
target_multibuffer_start..target_multibuffer_end,
|
||||
)
|
||||
});
|
||||
|
||||
Some(MultiBufferRowMapping {
|
||||
boundaries,
|
||||
first_group,
|
||||
prev_boundary,
|
||||
source_excerpt_end: source_excerpt_start_in_multibuffer
|
||||
+ (source_excerpt_end_in_buffer - source_excerpt_start_in_buffer),
|
||||
target_excerpt_end: target_excerpt_start_in_multibuffer
|
||||
+ (target_excerpt_end_in_buffer - target_excerpt_start_in_buffer),
|
||||
})
|
||||
CompanionExcerptPatch {
|
||||
patch: Patch::new(merged_edits),
|
||||
edited_range,
|
||||
source_excerpt_range: source_excerpt_start_in_multibuffer..source_excerpt_end,
|
||||
target_excerpt_range: target_excerpt_start_in_multibuffer..target_excerpt_end,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SplitDiffFeatureFlag;
|
||||
@@ -623,24 +608,16 @@ impl SplittableEditor {
|
||||
let source_snapshot = source_multibuffer.read(cx).snapshot(cx);
|
||||
let target_snapshot = target_multibuffer.read(cx).snapshot(cx);
|
||||
|
||||
let target_point = target_editor.update(cx, |target_editor, cx| {
|
||||
let target_range = target_editor.update(cx, |target_editor, cx| {
|
||||
target_editor.display_map.update(cx, |display_map, cx| {
|
||||
let display_map_id = cx.entity_id();
|
||||
display_map.companion().unwrap().update(cx, |companion, _| {
|
||||
companion
|
||||
.convert_rows_from_companion(
|
||||
display_map_id,
|
||||
&target_snapshot,
|
||||
&source_snapshot,
|
||||
(Bound::Included(source_point), Bound::Included(source_point)),
|
||||
)
|
||||
.first()
|
||||
.unwrap()
|
||||
.boundaries
|
||||
.first()
|
||||
.unwrap()
|
||||
.1
|
||||
.start
|
||||
companion.convert_point_from_companion(
|
||||
display_map_id,
|
||||
&target_snapshot,
|
||||
&source_snapshot,
|
||||
source_point,
|
||||
)
|
||||
})
|
||||
})
|
||||
});
|
||||
@@ -648,7 +625,7 @@ impl SplittableEditor {
|
||||
target_editor.update(cx, |editor, cx| {
|
||||
editor.set_suppress_selection_callback(true);
|
||||
editor.change_selections(crate::SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([target_point..target_point]);
|
||||
s.select_ranges([target_range]);
|
||||
});
|
||||
editor.set_suppress_selection_callback(false);
|
||||
});
|
||||
@@ -1501,14 +1478,17 @@ impl LhsEditor {
|
||||
.into_iter()
|
||||
.map(|(_, excerpt_range)| {
|
||||
let point_range_to_base_text_point_range = |range: Range<Point>| {
|
||||
let (mut translated, _, _) = diff_snapshot.points_to_base_text_points(
|
||||
[Point::new(range.start.row, 0), Point::new(range.end.row, 0)],
|
||||
main_buffer,
|
||||
);
|
||||
let start_row = translated.next().unwrap().start.row;
|
||||
let end_row = translated.next().unwrap().end.row;
|
||||
let end_column = diff_snapshot.base_text().line_len(end_row);
|
||||
Point::new(start_row, 0)..Point::new(end_row, end_column)
|
||||
let start = diff_snapshot
|
||||
.buffer_point_to_base_text_range(
|
||||
Point::new(range.start.row, 0),
|
||||
main_buffer,
|
||||
)
|
||||
.start;
|
||||
let end = diff_snapshot
|
||||
.buffer_point_to_base_text_range(Point::new(range.end.row, 0), main_buffer)
|
||||
.end;
|
||||
let end_column = diff_snapshot.base_text().line_len(end.row);
|
||||
Point::new(start.row, 0)..Point::new(end.row, end_column)
|
||||
};
|
||||
let rhs = excerpt_range.primary.to_point(main_buffer);
|
||||
let context = excerpt_range.context.to_point(main_buffer);
|
||||
|
||||
@@ -225,6 +225,46 @@ where
|
||||
old
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the edit that touches the given old position.
|
||||
///
|
||||
/// An edit is considered to touch the given old position if edit.old.start <= old <= edit.old.end (note, inclusive on the right).
|
||||
///
|
||||
/// If there are no edits touching the given old position, an empty edit with appropriate (empty) old and new ranges is returned.
|
||||
pub fn edit_for_old_position(&self, old: T) -> Edit<T> {
|
||||
let edits = self.edits();
|
||||
|
||||
let ix = match edits.binary_search_by(|probe| probe.old.start.cmp(&old)) {
|
||||
Ok(ix) => ix,
|
||||
Err(ix) => {
|
||||
if ix == 0 {
|
||||
return Edit {
|
||||
old: old..old,
|
||||
new: old..old,
|
||||
};
|
||||
} else {
|
||||
ix - 1
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(edit) = edits.get(ix) {
|
||||
if old > edit.old.end {
|
||||
let translated = edit.new.end + (old - edit.old.end);
|
||||
Edit {
|
||||
new: translated..translated,
|
||||
old: old..old,
|
||||
}
|
||||
} else {
|
||||
edit.clone()
|
||||
}
|
||||
} else {
|
||||
Edit {
|
||||
old: old..old,
|
||||
new: old..old,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Patch<T> {
|
||||
|
||||
Reference in new issue
Block a user