git: Compute and synchronize diffs from HEAD (#23626)
This PR builds on #21258 to make it possible to use HEAD as a diff base. The buffer store is extended to support holding multiple change sets, and collab gains support for synchronizing the committed text of files when any collaborator requires it. Not implemented in this PR: - Exposing the diff from HEAD to the user - Decorating the diff from HEAD with information about which hunks are staged `test_random_multibuffer` now fails first at `SEED=13277`, similar to the previous high-water mark, but with various bugs in the multibuffer logic now shaken out. Release Notes: - N/A --------- Co-authored-by: Max <max@zed.dev> Co-authored-by: Ben <ben@zed.dev> Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com> Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com> Co-authored-by: Conrad <conrad@zed.dev>
This commit is contained in:
parent
871f98bc4d
commit
5704b50fb1
29 changed files with 2799 additions and 603 deletions
|
@ -28,7 +28,7 @@ use smol::future::yield_now;
|
|||
use std::{
|
||||
any::type_name,
|
||||
borrow::Cow,
|
||||
cell::{Ref, RefCell, RefMut},
|
||||
cell::{Ref, RefCell},
|
||||
cmp, fmt,
|
||||
future::Future,
|
||||
io,
|
||||
|
@ -290,6 +290,7 @@ impl ExcerptBoundary {
|
|||
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct RowInfo {
|
||||
pub buffer_id: Option<BufferId>,
|
||||
pub buffer_row: Option<u32>,
|
||||
pub multibuffer_row: Option<MultiBufferRow>,
|
||||
pub diff_status: Option<git::diff::DiffHunkStatus>,
|
||||
|
@ -1742,7 +1743,7 @@ impl MultiBuffer {
|
|||
}
|
||||
|
||||
self.sync_diff_transforms(
|
||||
snapshot,
|
||||
&mut snapshot,
|
||||
vec![Edit {
|
||||
old: edit_start..edit_start,
|
||||
new: edit_start..edit_end,
|
||||
|
@ -1775,7 +1776,7 @@ impl MultiBuffer {
|
|||
snapshot.has_conflict = false;
|
||||
|
||||
self.sync_diff_transforms(
|
||||
snapshot,
|
||||
&mut snapshot,
|
||||
vec![Edit {
|
||||
old: start..prev_len,
|
||||
new: start..start,
|
||||
|
@ -2053,7 +2054,7 @@ impl MultiBuffer {
|
|||
snapshot.trailing_excerpt_update_count += 1;
|
||||
}
|
||||
|
||||
self.sync_diff_transforms(snapshot, edits, DiffChangeKind::BufferEdited);
|
||||
self.sync_diff_transforms(&mut snapshot, edits, DiffChangeKind::BufferEdited);
|
||||
cx.emit(Event::Edited {
|
||||
singleton_buffer_edited: false,
|
||||
edited_buffer: None,
|
||||
|
@ -2218,7 +2219,7 @@ impl MultiBuffer {
|
|||
}
|
||||
|
||||
self.sync_diff_transforms(
|
||||
snapshot,
|
||||
&mut snapshot,
|
||||
excerpt_edits,
|
||||
DiffChangeKind::DiffUpdated {
|
||||
base_changed: base_text_changed,
|
||||
|
@ -2388,7 +2389,7 @@ impl MultiBuffer {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.sync(cx);
|
||||
let snapshot = self.snapshot.borrow_mut();
|
||||
let mut snapshot = self.snapshot.borrow_mut();
|
||||
let mut excerpt_edits = Vec::new();
|
||||
for range in ranges.iter() {
|
||||
let end_excerpt_id = range.end.excerpt_id;
|
||||
|
@ -2422,7 +2423,7 @@ impl MultiBuffer {
|
|||
}
|
||||
|
||||
self.sync_diff_transforms(
|
||||
snapshot,
|
||||
&mut snapshot,
|
||||
excerpt_edits,
|
||||
DiffChangeKind::ExpandOrCollapseHunks { expand },
|
||||
);
|
||||
|
@ -2491,7 +2492,7 @@ impl MultiBuffer {
|
|||
drop(cursor);
|
||||
snapshot.excerpts = new_excerpts;
|
||||
|
||||
self.sync_diff_transforms(snapshot, edits, DiffChangeKind::BufferEdited);
|
||||
self.sync_diff_transforms(&mut snapshot, edits, DiffChangeKind::BufferEdited);
|
||||
cx.emit(Event::Edited {
|
||||
singleton_buffer_edited: false,
|
||||
edited_buffer: None,
|
||||
|
@ -2592,7 +2593,7 @@ impl MultiBuffer {
|
|||
drop(cursor);
|
||||
snapshot.excerpts = new_excerpts;
|
||||
|
||||
self.sync_diff_transforms(snapshot, edits, DiffChangeKind::BufferEdited);
|
||||
self.sync_diff_transforms(&mut snapshot, edits, DiffChangeKind::BufferEdited);
|
||||
cx.emit(Event::Edited {
|
||||
singleton_buffer_edited: false,
|
||||
edited_buffer: None,
|
||||
|
@ -2705,12 +2706,12 @@ impl MultiBuffer {
|
|||
drop(cursor);
|
||||
snapshot.excerpts = new_excerpts;
|
||||
|
||||
self.sync_diff_transforms(snapshot, edits, DiffChangeKind::BufferEdited);
|
||||
self.sync_diff_transforms(&mut snapshot, edits, DiffChangeKind::BufferEdited);
|
||||
}
|
||||
|
||||
fn sync_diff_transforms(
|
||||
&self,
|
||||
mut snapshot: RefMut<MultiBufferSnapshot>,
|
||||
snapshot: &mut MultiBufferSnapshot,
|
||||
excerpt_edits: Vec<text::Edit<ExcerptOffset>>,
|
||||
change_kind: DiffChangeKind,
|
||||
) {
|
||||
|
@ -2791,11 +2792,23 @@ impl MultiBuffer {
|
|||
if excerpt_edits.peek().map_or(true, |next_edit| {
|
||||
next_edit.old.start >= old_diff_transforms.end(&()).0
|
||||
}) {
|
||||
let keep_next_old_transform = (old_diff_transforms.start().0 >= edit.old.end)
|
||||
&& match old_diff_transforms.item() {
|
||||
Some(DiffTransform::BufferContent {
|
||||
inserted_hunk_anchor: Some(hunk_anchor),
|
||||
..
|
||||
}) => excerpts
|
||||
.item()
|
||||
.is_some_and(|excerpt| hunk_anchor.1.is_valid(&excerpt.buffer)),
|
||||
_ => true,
|
||||
};
|
||||
|
||||
let mut excerpt_offset = edit.new.end;
|
||||
if old_diff_transforms.start().0 < edit.old.end {
|
||||
if !keep_next_old_transform {
|
||||
excerpt_offset += old_diff_transforms.end(&()).0 - edit.old.end;
|
||||
old_diff_transforms.next(&());
|
||||
}
|
||||
|
||||
old_expanded_hunks.clear();
|
||||
self.push_buffer_content_transform(
|
||||
&snapshot,
|
||||
|
@ -2894,12 +2907,14 @@ impl MultiBuffer {
|
|||
buffer.anchor_before(edit_buffer_start)..buffer.anchor_after(edit_buffer_end);
|
||||
|
||||
for hunk in diff.hunks_intersecting_range(edit_anchor_range, buffer) {
|
||||
let hunk_buffer_range = hunk.buffer_range.to_offset(buffer);
|
||||
|
||||
let hunk_anchor = (excerpt.id, hunk.buffer_range.start);
|
||||
if !hunk_anchor.1.is_valid(buffer) {
|
||||
if hunk_buffer_range.start < excerpt_buffer_start {
|
||||
log::trace!("skipping hunk that starts before excerpt");
|
||||
continue;
|
||||
}
|
||||
|
||||
let hunk_buffer_range = hunk.buffer_range.to_offset(buffer);
|
||||
let hunk_excerpt_start = excerpt_start
|
||||
+ ExcerptOffset::new(
|
||||
hunk_buffer_range.start.saturating_sub(excerpt_buffer_start),
|
||||
|
@ -2941,8 +2956,9 @@ impl MultiBuffer {
|
|||
if should_expand_hunk {
|
||||
did_expand_hunks = true;
|
||||
log::trace!(
|
||||
"expanding hunk {:?}",
|
||||
"expanding hunk {:?}, excerpt:{:?}",
|
||||
hunk_excerpt_start.value..hunk_excerpt_end.value,
|
||||
excerpt.id
|
||||
);
|
||||
|
||||
if !hunk.diff_base_byte_range.is_empty()
|
||||
|
@ -3389,12 +3405,12 @@ impl MultiBufferSnapshot {
|
|||
self.diff_hunks_in_range(Anchor::min()..Anchor::max())
|
||||
}
|
||||
|
||||
pub fn diff_hunks_in_range<T: ToOffset>(
|
||||
pub fn diff_hunks_in_range<T: ToPoint>(
|
||||
&self,
|
||||
range: Range<T>,
|
||||
) -> impl Iterator<Item = MultiBufferDiffHunk> + '_ {
|
||||
let range = range.start.to_offset(self)..range.end.to_offset(self);
|
||||
self.lift_buffer_metadata(range.clone(), move |buffer, buffer_range| {
|
||||
let query_range = range.start.to_point(self)..range.end.to_point(self);
|
||||
self.lift_buffer_metadata(query_range.clone(), move |buffer, buffer_range| {
|
||||
let diff = self.diffs.get(&buffer.remote_id())?;
|
||||
let buffer_start = buffer.anchor_before(buffer_range.start);
|
||||
let buffer_end = buffer.anchor_after(buffer_range.end);
|
||||
|
@ -3409,19 +3425,25 @@ impl MultiBufferSnapshot {
|
|||
}),
|
||||
)
|
||||
})
|
||||
.map(|(range, hunk, excerpt)| {
|
||||
.filter_map(move |(range, hunk, excerpt)| {
|
||||
if range.start != range.end
|
||||
&& range.end == query_range.start
|
||||
&& !hunk.row_range.is_empty()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let end_row = if range.end.column == 0 {
|
||||
range.end.row
|
||||
} else {
|
||||
range.end.row + 1
|
||||
};
|
||||
MultiBufferDiffHunk {
|
||||
Some(MultiBufferDiffHunk {
|
||||
row_range: MultiBufferRow(range.start.row)..MultiBufferRow(end_row),
|
||||
buffer_id: excerpt.buffer_id,
|
||||
excerpt_id: excerpt.id,
|
||||
buffer_range: hunk.buffer_range.clone(),
|
||||
diff_base_byte_range: hunk.diff_base_byte_range.clone(),
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -3560,8 +3582,8 @@ impl MultiBufferSnapshot {
|
|||
/// multi-buffer coordinates.
|
||||
fn lift_buffer_metadata<'a, D, M, I>(
|
||||
&'a self,
|
||||
range: Range<usize>,
|
||||
get_buffer_metadata: impl 'a + Fn(&'a BufferSnapshot, Range<usize>) -> Option<I>,
|
||||
query_range: Range<D>,
|
||||
get_buffer_metadata: impl 'a + Fn(&'a BufferSnapshot, Range<D>) -> Option<I>,
|
||||
) -> impl Iterator<Item = (Range<D>, M, &'a Excerpt)> + 'a
|
||||
where
|
||||
I: Iterator<Item = (Range<D>, M)> + 'a,
|
||||
|
@ -3569,18 +3591,19 @@ impl MultiBufferSnapshot {
|
|||
{
|
||||
let max_position = D::from_text_summary(&self.text_summary());
|
||||
let mut current_excerpt_metadata: Option<(ExcerptId, I)> = None;
|
||||
let mut cursor = self.cursor::<DimensionPair<usize, D>>();
|
||||
let mut cursor = self.cursor::<D>();
|
||||
|
||||
// Find the excerpt and buffer offset where the given range ends.
|
||||
cursor.seek(&DimensionPair {
|
||||
key: range.end,
|
||||
value: None,
|
||||
});
|
||||
cursor.seek(&query_range.end);
|
||||
let mut range_end = None;
|
||||
while let Some(region) = cursor.region() {
|
||||
if region.is_main_buffer {
|
||||
let mut buffer_end = region.buffer_range.start.key;
|
||||
let overshoot = range.end.saturating_sub(region.range.start.key);
|
||||
let mut buffer_end = region.buffer_range.start;
|
||||
let overshoot = if query_range.end > region.range.start {
|
||||
query_range.end - region.range.start
|
||||
} else {
|
||||
D::default()
|
||||
};
|
||||
buffer_end.add_assign(&overshoot);
|
||||
range_end = Some((region.excerpt.id, buffer_end));
|
||||
break;
|
||||
|
@ -3588,13 +3611,10 @@ impl MultiBufferSnapshot {
|
|||
cursor.next();
|
||||
}
|
||||
|
||||
cursor.seek(&DimensionPair {
|
||||
key: range.start,
|
||||
value: None,
|
||||
});
|
||||
cursor.seek(&query_range.start);
|
||||
|
||||
if let Some(region) = cursor.region().filter(|region| !region.is_main_buffer) {
|
||||
if region.range.start.key > 0 {
|
||||
if region.range.start > D::zero(&()) {
|
||||
cursor.prev()
|
||||
}
|
||||
}
|
||||
|
@ -3613,14 +3633,18 @@ impl MultiBufferSnapshot {
|
|||
// and retrieve the metadata for the resulting range.
|
||||
else {
|
||||
let region = cursor.region()?;
|
||||
let buffer_start = if region.is_main_buffer {
|
||||
let start_overshoot = range.start.saturating_sub(region.range.start.key);
|
||||
(region.buffer_range.start.key + start_overshoot)
|
||||
.min(region.buffer_range.end.key)
|
||||
let mut buffer_start;
|
||||
if region.is_main_buffer {
|
||||
buffer_start = region.buffer_range.start;
|
||||
if query_range.start > region.range.start {
|
||||
let overshoot = query_range.start - region.range.start;
|
||||
buffer_start.add_assign(&overshoot);
|
||||
}
|
||||
buffer_start = buffer_start.min(region.buffer_range.end);
|
||||
} else {
|
||||
cursor.main_buffer_position()?.key
|
||||
buffer_start = cursor.main_buffer_position()?;
|
||||
};
|
||||
let mut buffer_end = excerpt.range.context.end.to_offset(&excerpt.buffer);
|
||||
let mut buffer_end = excerpt.range.context.end.summary::<D>(&excerpt.buffer);
|
||||
if let Some((end_excerpt_id, end_buffer_offset)) = range_end {
|
||||
if excerpt.id == end_excerpt_id {
|
||||
buffer_end = buffer_end.min(end_buffer_offset);
|
||||
|
@ -3637,53 +3661,56 @@ impl MultiBufferSnapshot {
|
|||
};
|
||||
|
||||
// Visit each metadata item.
|
||||
if let Some((range, metadata)) = metadata_iter.and_then(Iterator::next) {
|
||||
if let Some((metadata_buffer_range, metadata)) = metadata_iter.and_then(Iterator::next)
|
||||
{
|
||||
// Find the multibuffer regions that contain the start and end of
|
||||
// the metadata item's range.
|
||||
if range.start > D::default() {
|
||||
if metadata_buffer_range.start > D::default() {
|
||||
while let Some(region) = cursor.region() {
|
||||
if !region.is_main_buffer
|
||||
|| region.buffer.remote_id() == excerpt.buffer_id
|
||||
&& region.buffer_range.end.value.unwrap() < range.start
|
||||
if region.is_main_buffer
|
||||
&& (region.buffer_range.end >= metadata_buffer_range.start
|
||||
|| cursor.is_at_end_of_excerpt())
|
||||
{
|
||||
cursor.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
cursor.next();
|
||||
}
|
||||
}
|
||||
let start_region = cursor.region()?;
|
||||
while let Some(region) = cursor.region() {
|
||||
if !region.is_main_buffer
|
||||
|| region.buffer.remote_id() == excerpt.buffer_id
|
||||
&& region.buffer_range.end.value.unwrap() <= range.end
|
||||
if region.is_main_buffer
|
||||
&& (region.buffer_range.end > metadata_buffer_range.end
|
||||
|| cursor.is_at_end_of_excerpt())
|
||||
{
|
||||
cursor.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
cursor.next();
|
||||
}
|
||||
let end_region = cursor
|
||||
.region()
|
||||
.filter(|region| region.buffer.remote_id() == excerpt.buffer_id);
|
||||
let end_region = cursor.region();
|
||||
|
||||
// Convert the metadata item's range into multibuffer coordinates.
|
||||
let mut start = start_region.range.start.value.unwrap();
|
||||
let region_buffer_start = start_region.buffer_range.start.value.unwrap();
|
||||
if start_region.is_main_buffer && range.start > region_buffer_start {
|
||||
start.add_assign(&(range.start - region_buffer_start));
|
||||
}
|
||||
let mut end = max_position;
|
||||
if let Some(end_region) = end_region {
|
||||
end = end_region.range.start.value.unwrap();
|
||||
debug_assert!(end_region.is_main_buffer);
|
||||
let region_buffer_start = end_region.buffer_range.start.value.unwrap();
|
||||
if range.end > region_buffer_start {
|
||||
end.add_assign(&(range.end - region_buffer_start));
|
||||
}
|
||||
let mut start_position = start_region.range.start;
|
||||
let region_buffer_start = start_region.buffer_range.start;
|
||||
if start_region.is_main_buffer && metadata_buffer_range.start > region_buffer_start
|
||||
{
|
||||
start_position.add_assign(&(metadata_buffer_range.start - region_buffer_start));
|
||||
start_position = start_position.min(start_region.range.end);
|
||||
}
|
||||
|
||||
return Some((start..end, metadata, excerpt));
|
||||
let mut end_position = max_position;
|
||||
if let Some(end_region) = &end_region {
|
||||
end_position = end_region.range.start;
|
||||
debug_assert!(end_region.is_main_buffer);
|
||||
let region_buffer_start = end_region.buffer_range.start;
|
||||
if metadata_buffer_range.end > region_buffer_start {
|
||||
end_position.add_assign(&(metadata_buffer_range.end - region_buffer_start));
|
||||
}
|
||||
end_position = end_position.min(end_region.range.end);
|
||||
}
|
||||
|
||||
if start_position <= query_range.end && end_position >= query_range.start {
|
||||
return Some((start_position..end_position, metadata, excerpt));
|
||||
}
|
||||
}
|
||||
// When there are no more metadata items for this excerpt, move to the next excerpt.
|
||||
else {
|
||||
|
@ -4509,7 +4536,16 @@ impl MultiBufferSnapshot {
|
|||
}
|
||||
|
||||
let excerpt_start_position = D::from_text_summary(&cursor.start().text);
|
||||
if let Some(excerpt) = cursor.item().filter(|excerpt| excerpt.id == excerpt_id) {
|
||||
if let Some(excerpt) = cursor.item() {
|
||||
if excerpt.id != excerpt_id {
|
||||
let position = self.resolve_summary_for_anchor(
|
||||
&Anchor::min(),
|
||||
excerpt_start_position,
|
||||
&mut diff_transforms_cursor,
|
||||
);
|
||||
summaries.extend(excerpt_anchors.map(|_| position));
|
||||
continue;
|
||||
}
|
||||
let excerpt_buffer_start =
|
||||
excerpt.range.context.start.summary::<D>(&excerpt.buffer);
|
||||
let excerpt_buffer_end = excerpt.range.context.end.summary::<D>(&excerpt.buffer);
|
||||
|
@ -5525,7 +5561,7 @@ impl MultiBufferSnapshot {
|
|||
buffer_id: BufferId,
|
||||
group_id: usize,
|
||||
) -> impl Iterator<Item = DiagnosticEntry<Point>> + '_ {
|
||||
self.lift_buffer_metadata(0..self.len(), move |buffer, _| {
|
||||
self.lift_buffer_metadata(Point::zero()..self.max_point(), move |buffer, _| {
|
||||
if buffer.remote_id() != buffer_id {
|
||||
return None;
|
||||
};
|
||||
|
@ -5538,15 +5574,19 @@ impl MultiBufferSnapshot {
|
|||
.map(|(range, diagnostic, _)| DiagnosticEntry { diagnostic, range })
|
||||
}
|
||||
|
||||
pub fn diagnostics_in_range<'a, T, O>(
|
||||
pub fn diagnostics_in_range<'a, T>(
|
||||
&'a self,
|
||||
range: Range<T>,
|
||||
) -> impl Iterator<Item = DiagnosticEntry<O>> + 'a
|
||||
) -> impl Iterator<Item = DiagnosticEntry<T>> + 'a
|
||||
where
|
||||
T: 'a + ToOffset,
|
||||
O: 'a + text::FromAnchor + Copy + TextDimension + Ord + Sub<O, Output = O> + fmt::Debug,
|
||||
T: 'a
|
||||
+ text::ToOffset
|
||||
+ text::FromAnchor
|
||||
+ TextDimension
|
||||
+ Ord
|
||||
+ Sub<T, Output = T>
|
||||
+ fmt::Debug,
|
||||
{
|
||||
let range = range.start.to_offset(self)..range.end.to_offset(self);
|
||||
self.lift_buffer_metadata(range, move |buffer, buffer_range| {
|
||||
Some(
|
||||
buffer
|
||||
|
@ -6036,6 +6076,24 @@ where
|
|||
self.cached_region.clone()
|
||||
}
|
||||
|
||||
fn is_at_end_of_excerpt(&mut self) -> bool {
|
||||
if self.diff_transforms.end(&()).1 < self.excerpts.end(&()) {
|
||||
return false;
|
||||
} else if self.diff_transforms.end(&()).1 > self.excerpts.end(&())
|
||||
|| self.diff_transforms.item().is_none()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
self.diff_transforms.next(&());
|
||||
let next_transform = self.diff_transforms.item();
|
||||
self.diff_transforms.prev(&());
|
||||
|
||||
next_transform.map_or(true, |next_transform| {
|
||||
matches!(next_transform, DiffTransform::BufferContent { .. })
|
||||
})
|
||||
}
|
||||
|
||||
fn main_buffer_position(&self) -> Option<D> {
|
||||
let excerpt = self.excerpts.item()?;
|
||||
let buffer = &excerpt.buffer;
|
||||
|
@ -6879,6 +6937,7 @@ impl<'a> Iterator for MultiBufferRows<'a> {
|
|||
if self.is_empty && self.point.row == 0 {
|
||||
self.point += Point::new(1, 0);
|
||||
return Some(RowInfo {
|
||||
buffer_id: None,
|
||||
buffer_row: Some(0),
|
||||
multibuffer_row: Some(MultiBufferRow(0)),
|
||||
diff_status: None,
|
||||
|
@ -6906,6 +6965,7 @@ impl<'a> Iterator for MultiBufferRows<'a> {
|
|||
.to_point(&last_excerpt.buffer)
|
||||
.row;
|
||||
return Some(RowInfo {
|
||||
buffer_id: Some(last_excerpt.buffer_id),
|
||||
buffer_row: Some(last_row),
|
||||
multibuffer_row: Some(multibuffer_row),
|
||||
diff_status: None,
|
||||
|
@ -6919,6 +6979,7 @@ impl<'a> Iterator for MultiBufferRows<'a> {
|
|||
let overshoot = self.point - region.range.start;
|
||||
let buffer_point = region.buffer_range.start + overshoot;
|
||||
let result = Some(RowInfo {
|
||||
buffer_id: Some(region.buffer.remote_id()),
|
||||
buffer_row: Some(buffer_point.row),
|
||||
multibuffer_row: Some(MultiBufferRow(self.point.row)),
|
||||
diff_status: if region.is_inserted_hunk && self.point < region.range.end {
|
||||
|
|
|
@ -19,12 +19,14 @@ fn init_logger() {
|
|||
#[gpui::test]
|
||||
fn test_empty_singleton(cx: &mut App) {
|
||||
let buffer = cx.new(|cx| Buffer::local("", cx));
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
|
||||
let snapshot = multibuffer.read(cx).snapshot(cx);
|
||||
assert_eq!(snapshot.text(), "");
|
||||
assert_eq!(
|
||||
snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>(),
|
||||
[RowInfo {
|
||||
buffer_id: Some(buffer_id),
|
||||
buffer_row: Some(0),
|
||||
multibuffer_row: Some(MultiBufferRow(0)),
|
||||
diff_status: None
|
||||
|
@ -359,13 +361,7 @@ fn test_diff_boundary_anchors(cx: &mut TestAppContext) {
|
|||
let base_text = "one\ntwo\nthree\n";
|
||||
let text = "one\nthree\n";
|
||||
let buffer = cx.new(|cx| Buffer::local(text, cx));
|
||||
let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
|
||||
let change_set = cx.new(|cx| {
|
||||
let mut change_set = BufferChangeSet::new(&buffer, cx);
|
||||
let _ = change_set.set_base_text(base_text.into(), snapshot.text, cx);
|
||||
change_set
|
||||
});
|
||||
cx.run_until_parked();
|
||||
let change_set = cx.new(|cx| BufferChangeSet::new_with_base_text(base_text, &buffer, cx));
|
||||
let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.add_change_set(change_set, cx)
|
||||
|
@ -382,7 +378,7 @@ fn test_diff_boundary_anchors(cx: &mut TestAppContext) {
|
|||
let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
|
||||
let actual_text = snapshot.text();
|
||||
let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
|
||||
let actual_diff = format_diff(&actual_text, &actual_row_infos, &Default::default());
|
||||
let actual_diff = format_diff(&actual_text, &actual_row_infos, &Default::default(), None);
|
||||
pretty_assertions::assert_eq!(
|
||||
actual_diff,
|
||||
indoc! {
|
||||
|
@ -409,13 +405,7 @@ fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
|
|||
let base_text = "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\n";
|
||||
let text = "one\nfour\nseven\n";
|
||||
let buffer = cx.new(|cx| Buffer::local(text, cx));
|
||||
let change_set = cx.new(|cx| {
|
||||
let mut change_set = BufferChangeSet::new(&buffer, cx);
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let _ = change_set.set_base_text(base_text.into(), snapshot.text, cx);
|
||||
change_set
|
||||
});
|
||||
cx.run_until_parked();
|
||||
let change_set = cx.new(|cx| BufferChangeSet::new_with_base_text(base_text, &buffer, cx));
|
||||
let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
|
||||
(multibuffer.snapshot(cx), multibuffer.subscribe())
|
||||
|
@ -508,13 +498,7 @@ fn test_editing_text_in_diff_hunks(cx: &mut TestAppContext) {
|
|||
let base_text = "one\ntwo\nfour\nfive\nsix\nseven\n";
|
||||
let text = "one\ntwo\nTHREE\nfour\nfive\nseven\n";
|
||||
let buffer = cx.new(|cx| Buffer::local(text, cx));
|
||||
let change_set = cx.new(|cx| {
|
||||
let mut change_set = BufferChangeSet::new(&buffer, cx);
|
||||
let snapshot = buffer.read(cx).text_snapshot();
|
||||
let _ = change_set.set_base_text(base_text.into(), snapshot, cx);
|
||||
change_set
|
||||
});
|
||||
cx.run_until_parked();
|
||||
let change_set = cx.new(|cx| BufferChangeSet::new_with_base_text(&base_text, &buffer, cx));
|
||||
let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
|
||||
|
||||
let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
|
||||
|
@ -995,12 +979,7 @@ fn test_empty_diff_excerpt(cx: &mut TestAppContext) {
|
|||
let buffer = cx.new(|cx| Buffer::local("", cx));
|
||||
let base_text = "a\nb\nc";
|
||||
|
||||
let change_set = cx.new(|cx| {
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let mut change_set = BufferChangeSet::new(&buffer, cx);
|
||||
let _ = change_set.set_base_text(base_text.into(), snapshot.text, cx);
|
||||
change_set
|
||||
});
|
||||
let change_set = cx.new(|cx| BufferChangeSet::new_with_base_text(base_text, &buffer, cx));
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.set_all_diff_hunks_expanded(cx);
|
||||
multibuffer.add_change_set(change_set.clone(), cx);
|
||||
|
@ -1040,7 +1019,7 @@ fn test_empty_diff_excerpt(cx: &mut TestAppContext) {
|
|||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "a\nb\nc")], None, cx);
|
||||
change_set.update(cx, |change_set, cx| {
|
||||
let _ = change_set.recalculate_diff(buffer.snapshot().text, cx);
|
||||
change_set.recalculate_diff_sync(buffer.snapshot().text, cx);
|
||||
});
|
||||
assert_eq!(buffer.text(), "a\nb\nc")
|
||||
});
|
||||
|
@ -1052,7 +1031,7 @@ fn test_empty_diff_excerpt(cx: &mut TestAppContext) {
|
|||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.undo(cx);
|
||||
change_set.update(cx, |change_set, cx| {
|
||||
let _ = change_set.recalculate_diff(buffer.snapshot().text, cx);
|
||||
change_set.recalculate_diff_sync(buffer.snapshot().text, cx);
|
||||
});
|
||||
assert_eq!(buffer.text(), "")
|
||||
});
|
||||
|
@ -1294,8 +1273,7 @@ fn test_basic_diff_hunks(cx: &mut TestAppContext) {
|
|||
);
|
||||
|
||||
let buffer = cx.new(|cx| Buffer::local(text, cx));
|
||||
let change_set =
|
||||
cx.new(|cx| BufferChangeSet::new_with_base_text(base_text.to_string(), &buffer, cx));
|
||||
let change_set = cx.new(|cx| BufferChangeSet::new_with_base_text(base_text, &buffer, cx));
|
||||
cx.run_until_parked();
|
||||
|
||||
let multibuffer = cx.new(|cx| {
|
||||
|
@ -1485,8 +1463,8 @@ fn test_basic_diff_hunks(cx: &mut TestAppContext) {
|
|||
assert_line_indents(&snapshot);
|
||||
|
||||
// Recalculate the diff, changing the first diff hunk.
|
||||
let _ = change_set.update(cx, |change_set, cx| {
|
||||
change_set.recalculate_diff(buffer.read(cx).text_snapshot(), cx)
|
||||
change_set.update(cx, |change_set, cx| {
|
||||
change_set.recalculate_diff_sync(buffer.read(cx).text_snapshot(), cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
assert_new_snapshot(
|
||||
|
@ -1538,8 +1516,7 @@ fn test_repeatedly_expand_a_diff_hunk(cx: &mut TestAppContext) {
|
|||
);
|
||||
|
||||
let buffer = cx.new(|cx| Buffer::local(text, cx));
|
||||
let change_set =
|
||||
cx.new(|cx| BufferChangeSet::new_with_base_text(base_text.to_string(), &buffer, cx));
|
||||
let change_set = cx.new(|cx| BufferChangeSet::new_with_base_text(base_text, &buffer, cx));
|
||||
cx.run_until_parked();
|
||||
|
||||
let multibuffer = cx.new(|cx| {
|
||||
|
@ -1840,10 +1817,8 @@ fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) {
|
|||
|
||||
let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
|
||||
let buffer_2 = cx.new(|cx| Buffer::local(text_2, cx));
|
||||
let change_set_1 =
|
||||
cx.new(|cx| BufferChangeSet::new_with_base_text(base_text_1.to_string(), &buffer_1, cx));
|
||||
let change_set_2 =
|
||||
cx.new(|cx| BufferChangeSet::new_with_base_text(base_text_2.to_string(), &buffer_2, cx));
|
||||
let change_set_1 = cx.new(|cx| BufferChangeSet::new_with_base_text(base_text_1, &buffer_1, cx));
|
||||
let change_set_2 = cx.new(|cx| BufferChangeSet::new_with_base_text(base_text_2, &buffer_2, cx));
|
||||
cx.run_until_parked();
|
||||
|
||||
let multibuffer = cx.new(|cx| {
|
||||
|
@ -2028,6 +2003,7 @@ struct ReferenceMultibuffer {
|
|||
change_sets: HashMap<BufferId, Entity<BufferChangeSet>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ReferenceExcerpt {
|
||||
id: ExcerptId,
|
||||
buffer: Entity<Buffer>,
|
||||
|
@ -2037,6 +2013,7 @@ struct ReferenceExcerpt {
|
|||
|
||||
#[derive(Debug)]
|
||||
struct ReferenceRegion {
|
||||
buffer_id: Option<BufferId>,
|
||||
range: Range<usize>,
|
||||
buffer_start: Option<Point>,
|
||||
status: Option<DiffHunkStatus>,
|
||||
|
@ -2117,37 +2094,26 @@ impl ReferenceMultibuffer {
|
|||
};
|
||||
let diff = change_set.read(cx).diff_to_buffer.clone();
|
||||
let excerpt_range = excerpt.range.to_offset(&buffer);
|
||||
if excerpt_range.is_empty() {
|
||||
return;
|
||||
}
|
||||
for hunk in diff.hunks_intersecting_range(range, &buffer) {
|
||||
let hunk_range = hunk.buffer_range.to_offset(&buffer);
|
||||
let hunk_precedes_excerpt = hunk
|
||||
.buffer_range
|
||||
.end
|
||||
.cmp(&excerpt.range.start, &buffer)
|
||||
.is_lt();
|
||||
let hunk_follows_excerpt = hunk
|
||||
.buffer_range
|
||||
.start
|
||||
.cmp(&excerpt.range.end, &buffer)
|
||||
.is_ge();
|
||||
if hunk_precedes_excerpt || hunk_follows_excerpt {
|
||||
if hunk_range.start < excerpt_range.start || hunk_range.start > excerpt_range.end {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Err(ix) = excerpt
|
||||
.expanded_diff_hunks
|
||||
.binary_search_by(|anchor| anchor.cmp(&hunk.buffer_range.start, &buffer))
|
||||
{
|
||||
log::info!(
|
||||
"expanding diff hunk {:?}. excerpt: {:?}",
|
||||
"expanding diff hunk {:?}. excerpt:{:?}, excerpt range:{:?}",
|
||||
hunk_range,
|
||||
excerpt_id,
|
||||
excerpt_range
|
||||
);
|
||||
excerpt
|
||||
.expanded_diff_hunks
|
||||
.insert(ix, hunk.buffer_range.start);
|
||||
} else {
|
||||
log::trace!("hunk {hunk_range:?} already expanded in excerpt {excerpt_id:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2170,17 +2136,12 @@ impl ReferenceMultibuffer {
|
|||
.peekable();
|
||||
|
||||
while let Some(hunk) = hunks.next() {
|
||||
if !hunk.buffer_range.start.is_valid(&buffer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore hunks that are outside the excerpt range.
|
||||
let mut hunk_range = hunk.buffer_range.to_offset(buffer);
|
||||
|
||||
hunk_range.end = hunk_range.end.min(buffer_range.end);
|
||||
if hunk_range.start > buffer_range.end
|
||||
|| hunk_range.end < buffer_range.start
|
||||
|| buffer_range.is_empty()
|
||||
{
|
||||
if hunk_range.start > buffer_range.end || hunk_range.start < buffer_range.start {
|
||||
log::trace!("skipping hunk outside excerpt range");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -2188,6 +2149,12 @@ impl ReferenceMultibuffer {
|
|||
expanded_anchor.to_offset(&buffer).max(buffer_range.start)
|
||||
== hunk_range.start.max(buffer_range.start)
|
||||
}) {
|
||||
log::trace!("skipping a hunk that's not marked as expanded");
|
||||
continue;
|
||||
}
|
||||
|
||||
if !hunk.buffer_range.start.is_valid(&buffer) {
|
||||
log::trace!("skipping hunk with deleted start: {:?}", hunk.row_range);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -2196,6 +2163,7 @@ impl ReferenceMultibuffer {
|
|||
let len = text.len();
|
||||
text.extend(buffer.text_for_range(offset..hunk_range.start));
|
||||
regions.push(ReferenceRegion {
|
||||
buffer_id: Some(buffer.remote_id()),
|
||||
range: len..text.len(),
|
||||
buffer_start: Some(buffer.offset_to_point(offset)),
|
||||
status: None,
|
||||
|
@ -2212,6 +2180,7 @@ impl ReferenceMultibuffer {
|
|||
let len = text.len();
|
||||
text.push_str(&base_text);
|
||||
regions.push(ReferenceRegion {
|
||||
buffer_id: Some(base_buffer.remote_id()),
|
||||
range: len..text.len(),
|
||||
buffer_start: Some(
|
||||
base_buffer.offset_to_point(hunk.diff_base_byte_range.start),
|
||||
|
@ -2228,6 +2197,7 @@ impl ReferenceMultibuffer {
|
|||
let len = text.len();
|
||||
text.extend(buffer.text_for_range(offset..hunk_range.end));
|
||||
regions.push(ReferenceRegion {
|
||||
buffer_id: Some(buffer.remote_id()),
|
||||
range: len..text.len(),
|
||||
buffer_start: Some(buffer.offset_to_point(offset)),
|
||||
status: Some(DiffHunkStatus::Added),
|
||||
|
@ -2241,6 +2211,7 @@ impl ReferenceMultibuffer {
|
|||
text.extend(buffer.text_for_range(offset..buffer_range.end));
|
||||
text.push('\n');
|
||||
regions.push(ReferenceRegion {
|
||||
buffer_id: Some(buffer.remote_id()),
|
||||
range: len..text.len(),
|
||||
buffer_start: Some(buffer.offset_to_point(offset)),
|
||||
status: None,
|
||||
|
@ -2250,6 +2221,7 @@ impl ReferenceMultibuffer {
|
|||
// Remove final trailing newline.
|
||||
if self.excerpts.is_empty() {
|
||||
regions.push(ReferenceRegion {
|
||||
buffer_id: None,
|
||||
range: 0..1,
|
||||
buffer_start: Some(Point::new(0, 0)),
|
||||
status: None,
|
||||
|
@ -2273,6 +2245,7 @@ impl ReferenceMultibuffer {
|
|||
+ text[region.range.start..ix].matches('\n').count() as u32
|
||||
});
|
||||
RowInfo {
|
||||
buffer_id: region.buffer_id,
|
||||
diff_status: region.status,
|
||||
buffer_row,
|
||||
multibuffer_row: Some(MultiBufferRow(
|
||||
|
@ -2348,6 +2321,7 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
|
|||
buffer.update(cx, |buf, cx| {
|
||||
let edit_count = rng.gen_range(1..5);
|
||||
buf.randomly_edit(&mut rng, edit_count, cx);
|
||||
log::info!("buffer text:\n{}", buf.text());
|
||||
needs_diff_calculation = true;
|
||||
});
|
||||
cx.update(|cx| reference.diffs_updated(cx));
|
||||
|
@ -2440,7 +2414,11 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
|
|||
let range = snapshot.anchor_in_excerpt(excerpt.id, start).unwrap()
|
||||
..snapshot.anchor_in_excerpt(excerpt.id, end).unwrap();
|
||||
|
||||
log::info!("expanding diff hunks for excerpt {:?}", excerpt_ix);
|
||||
log::info!(
|
||||
"expanding diff hunks in range {:?} (excerpt id {:?}) index {excerpt_ix:?})",
|
||||
range.to_offset(&snapshot),
|
||||
excerpt.id
|
||||
);
|
||||
reference.expand_diff_hunks(excerpt.id, start..end, cx);
|
||||
multibuffer.expand_diff_hunks(vec![range], cx);
|
||||
});
|
||||
|
@ -2457,7 +2435,7 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
|
|||
"recalculating diff for buffer {:?}",
|
||||
snapshot.remote_id(),
|
||||
);
|
||||
change_set.recalculate_diff(snapshot.text, cx)
|
||||
change_set.recalculate_diff_sync(snapshot.text, cx);
|
||||
});
|
||||
}
|
||||
reference.diffs_updated(cx);
|
||||
|
@ -2471,14 +2449,8 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
|
|||
.collect::<String>();
|
||||
|
||||
let buffer = cx.new(|cx| Buffer::local(base_text.clone(), cx));
|
||||
let change_set = cx.new(|cx| BufferChangeSet::new(&buffer, cx));
|
||||
change_set
|
||||
.update(cx, |change_set, cx| {
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
change_set.set_base_text(base_text, snapshot.text, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let change_set =
|
||||
cx.new(|cx| BufferChangeSet::new_with_base_text(&base_text, &buffer, cx));
|
||||
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
reference.add_change_set(change_set.clone(), cx);
|
||||
|
@ -2553,12 +2525,28 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
|
|||
.filter_map(|b| if b.next.is_some() { Some(b.row) } else { None })
|
||||
.collect::<HashSet<_>>();
|
||||
let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
|
||||
let actual_diff = format_diff(&actual_text, &actual_row_infos, &actual_boundary_rows);
|
||||
|
||||
let (expected_text, expected_row_infos, expected_boundary_rows) =
|
||||
cx.update(|cx| reference.expected_content(cx));
|
||||
let expected_diff =
|
||||
format_diff(&expected_text, &expected_row_infos, &expected_boundary_rows);
|
||||
|
||||
let has_diff = actual_row_infos
|
||||
.iter()
|
||||
.any(|info| info.diff_status.is_some())
|
||||
|| expected_row_infos
|
||||
.iter()
|
||||
.any(|info| info.diff_status.is_some());
|
||||
let actual_diff = format_diff(
|
||||
&actual_text,
|
||||
&actual_row_infos,
|
||||
&actual_boundary_rows,
|
||||
Some(has_diff),
|
||||
);
|
||||
let expected_diff = format_diff(
|
||||
&expected_text,
|
||||
&expected_row_infos,
|
||||
&expected_boundary_rows,
|
||||
Some(has_diff),
|
||||
);
|
||||
|
||||
log::info!("Multibuffer content:\n{}", actual_diff);
|
||||
|
||||
|
@ -2569,8 +2557,8 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
|
|||
actual_text.split('\n').count()
|
||||
);
|
||||
pretty_assertions::assert_eq!(actual_diff, expected_diff);
|
||||
pretty_assertions::assert_eq!(actual_row_infos, expected_row_infos);
|
||||
pretty_assertions::assert_eq!(actual_text, expected_text);
|
||||
pretty_assertions::assert_eq!(actual_row_infos, expected_row_infos);
|
||||
|
||||
for _ in 0..5 {
|
||||
let start_row = rng.gen_range(0..=expected_row_infos.len());
|
||||
|
@ -2937,8 +2925,10 @@ fn format_diff(
|
|||
text: &str,
|
||||
row_infos: &Vec<RowInfo>,
|
||||
boundary_rows: &HashSet<MultiBufferRow>,
|
||||
has_diff: Option<bool>,
|
||||
) -> String {
|
||||
let has_diff = row_infos.iter().any(|info| info.diff_status.is_some());
|
||||
let has_diff =
|
||||
has_diff.unwrap_or_else(|| row_infos.iter().any(|info| info.diff_status.is_some()));
|
||||
text.split('\n')
|
||||
.enumerate()
|
||||
.zip(row_infos)
|
||||
|
@ -3002,7 +2992,7 @@ fn assert_new_snapshot(
|
|||
let line_infos = new_snapshot
|
||||
.row_infos(MultiBufferRow(0))
|
||||
.collect::<Vec<_>>();
|
||||
let actual_diff = format_diff(&actual_text, &line_infos, &Default::default());
|
||||
let actual_diff = format_diff(&actual_text, &line_infos, &Default::default(), None);
|
||||
pretty_assertions::assert_eq!(actual_diff, expected_diff);
|
||||
check_edits(
|
||||
snapshot,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue