Detailed changes
@@ -14397,12 +14397,8 @@ async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut gpui::TestAppContex
let buffer = multibuffer.as_singleton().unwrap();
let change_set = cx.new(|cx| {
let mut change_set = BufferChangeSet::new(&buffer, cx);
- change_set.recalculate_diff_sync(
- base_text.into(),
- buffer.read(cx).text_snapshot(),
- true,
- cx,
- );
+ let _ =
+ change_set.set_base_text(base_text.into(), buffer.read(cx).text_snapshot(), cx);
change_set
});
@@ -14412,6 +14408,7 @@ async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut gpui::TestAppContex
buffer.read(cx).remote_id()
})
});
+ cx.run_until_parked();
cx.assert_state_with_diff(
indoc! { "
@@ -35,6 +35,7 @@ util.workspace = true
unindent.workspace = true
serde_json.workspace = true
pretty_assertions.workspace = true
+text = {workspace = true, features = ["test-support"]}
[features]
test-support = []
@@ -1,5 +1,5 @@
use rope::Rope;
-use std::{iter, ops::Range};
+use std::{cmp, iter, ops::Range};
use sum_tree::SumTree;
use text::{Anchor, BufferSnapshot, OffsetRangeExt, Point};
@@ -25,7 +25,7 @@ pub struct DiffHunk {
}
/// We store [`InternalDiffHunk`]s internally so we don't need to store the additional row range.
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq, Eq)]
struct InternalDiffHunk {
buffer_range: Range<Anchor>,
diff_base_byte_range: Range<usize>,
@@ -187,6 +187,69 @@ impl BufferDiff {
})
}
+ pub fn compare(&self, old: &Self, new_snapshot: &BufferSnapshot) -> Option<Range<Anchor>> {
+ let mut new_cursor = self.tree.cursor::<()>(new_snapshot);
+ let mut old_cursor = old.tree.cursor::<()>(new_snapshot);
+ old_cursor.next(new_snapshot);
+ new_cursor.next(new_snapshot);
+ let mut start = None;
+ let mut end = None;
+
+ loop {
+ match (new_cursor.item(), old_cursor.item()) {
+ (Some(new_hunk), Some(old_hunk)) => {
+ match new_hunk
+ .buffer_range
+ .start
+ .cmp(&old_hunk.buffer_range.start, new_snapshot)
+ {
+ cmp::Ordering::Less => {
+ start.get_or_insert(new_hunk.buffer_range.start);
+ end.replace(new_hunk.buffer_range.end);
+ new_cursor.next(new_snapshot);
+ }
+ cmp::Ordering::Equal => {
+ if new_hunk != old_hunk {
+ start.get_or_insert(new_hunk.buffer_range.start);
+ if old_hunk
+ .buffer_range
+ .end
+ .cmp(&new_hunk.buffer_range.end, new_snapshot)
+ .is_ge()
+ {
+ end.replace(old_hunk.buffer_range.end);
+ } else {
+ end.replace(new_hunk.buffer_range.end);
+ }
+ }
+
+ new_cursor.next(new_snapshot);
+ old_cursor.next(new_snapshot);
+ }
+ cmp::Ordering::Greater => {
+ start.get_or_insert(old_hunk.buffer_range.start);
+ end.replace(old_hunk.buffer_range.end);
+ old_cursor.next(new_snapshot);
+ }
+ }
+ }
+ (Some(new_hunk), None) => {
+ start.get_or_insert(new_hunk.buffer_range.start);
+ end.replace(new_hunk.buffer_range.end);
+ new_cursor.next(new_snapshot);
+ }
+ (None, Some(old_hunk)) => {
+ start.get_or_insert(old_hunk.buffer_range.start);
+ end.replace(old_hunk.buffer_range.end);
+ old_cursor.next(new_snapshot);
+ }
+ (None, None) => break,
+ }
+ }
+
+ start.zip(end).map(|(start, end)| start..end)
+ }
+
#[cfg(test)]
fn clear(&mut self, buffer: &text::BufferSnapshot) {
self.tree = SumTree::new(buffer);
@@ -427,4 +490,128 @@ mod tests {
],
);
}
+
+ #[test]
+ fn test_buffer_diff_compare() {
+ let base_text = "
+ zero
+ one
+ two
+ three
+ four
+ five
+ six
+ seven
+ eight
+ nine
+ "
+ .unindent();
+
+ let buffer_text_1 = "
+ one
+ three
+ four
+ five
+ SIX
+ seven
+ eight
+ NINE
+ "
+ .unindent();
+
+ let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text_1);
+
+ let empty_diff = BufferDiff::new(&buffer);
+ let diff_1 = BufferDiff::build(&base_text, &buffer);
+ let range = diff_1.compare(&empty_diff, &buffer).unwrap();
+ assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0));
+
+ // Edit does not affect the diff.
+ buffer.edit_via_marked_text(
+ &"
+ one
+ three
+ four
+ five
+ «SIX.5»
+ seven
+ eight
+ NINE
+ "
+ .unindent(),
+ );
+ let diff_2 = BufferDiff::build(&base_text, &buffer);
+ assert_eq!(None, diff_2.compare(&diff_1, &buffer));
+
+ // Edit turns a deletion hunk into a modification.
+ buffer.edit_via_marked_text(
+ &"
+ one
+ «THREE»
+ four
+ five
+ SIX.5
+ seven
+ eight
+ NINE
+ "
+ .unindent(),
+ );
+ let diff_3 = BufferDiff::build(&base_text, &buffer);
+ let range = diff_3.compare(&diff_2, &buffer).unwrap();
+ assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(2, 0));
+
+ // Edit turns a modification hunk into a deletion.
+ buffer.edit_via_marked_text(
+ &"
+ one
+ THREE
+ four
+ five«»
+ seven
+ eight
+ NINE
+ "
+ .unindent(),
+ );
+ let diff_4 = BufferDiff::build(&base_text, &buffer);
+ let range = diff_4.compare(&diff_3, &buffer).unwrap();
+ assert_eq!(range.to_point(&buffer), Point::new(3, 4)..Point::new(4, 0));
+
+ // Edit introduces a new insertion hunk.
+ buffer.edit_via_marked_text(
+ &"
+ one
+ THREE
+ four«
+ FOUR.5
+ »five
+ seven
+ eight
+ NINE
+ "
+ .unindent(),
+ );
+ let diff_5 = BufferDiff::build(&base_text, &buffer);
+ let range = diff_5.compare(&diff_4, &buffer).unwrap();
+ assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(4, 0));
+
+ // Edit removes a hunk.
+ buffer.edit_via_marked_text(
+ &"
+ one
+ THREE
+ four
+ FOUR.5
+ five
+ seven
+ eight
+ «nine»
+ "
+ .unindent(),
+ );
+ let diff_6 = BufferDiff::build(&base_text, &buffer);
+ let range = diff_6.compare(&diff_5, &buffer).unwrap();
+ assert_eq!(range.to_point(&buffer), Point::new(7, 0)..Point::new(8, 0));
+ }
}
@@ -21,7 +21,7 @@ use language::{
TextDimension, TextObject, ToOffset as _, ToPoint as _, TransactionId, TreeSitterOptions,
Unclipped,
};
-use project::buffer_store::BufferChangeSet;
+use project::buffer_store::{BufferChangeSet, BufferChangeSetEvent};
use rope::DimensionPair;
use smallvec::SmallVec;
use smol::future::yield_now;
@@ -434,7 +434,6 @@ struct BufferEdit {
#[derive(Clone, Copy, Debug, PartialEq)]
enum DiffChangeKind {
BufferEdited,
- ExcerptsChanged,
DiffUpdated { base_changed: bool },
ExpandOrCollapseHunks { expand: bool },
}
@@ -546,8 +545,14 @@ impl MultiBuffer {
diff_bases.insert(
*buffer_id,
ChangeSetState {
- _subscription: new_cx
- .observe(&change_set_state.change_set, Self::buffer_diff_changed),
+ _subscription: new_cx.subscribe(
+ &change_set_state.change_set,
+ |this, change_set, event, cx| match event {
+ BufferChangeSetEvent::DiffChanged { changed_range } => {
+ this.buffer_diff_changed(change_set, changed_range.clone(), cx)
+ }
+ },
+ ),
change_set: change_set_state.change_set.clone(),
},
);
@@ -1603,7 +1608,7 @@ impl MultiBuffer {
old: edit_start..edit_start,
new: edit_start..edit_end,
}],
- DiffChangeKind::ExcerptsChanged,
+ DiffChangeKind::BufferEdited,
);
cx.emit(Event::Edited {
singleton_buffer_edited: false,
@@ -1636,7 +1641,7 @@ impl MultiBuffer {
old: start..prev_len,
new: start..start,
}],
- DiffChangeKind::ExcerptsChanged,
+ DiffChangeKind::BufferEdited,
);
cx.emit(Event::Edited {
singleton_buffer_edited: false,
@@ -1909,7 +1914,7 @@ impl MultiBuffer {
snapshot.trailing_excerpt_update_count += 1;
}
- self.sync_diff_transforms(snapshot, edits, DiffChangeKind::ExcerptsChanged);
+ self.sync_diff_transforms(snapshot, edits, DiffChangeKind::BufferEdited);
cx.emit(Event::Edited {
singleton_buffer_edited: false,
edited_buffer: None,
@@ -1998,22 +2003,26 @@ impl MultiBuffer {
});
}
- fn buffer_diff_changed(&mut self, change_set: Entity<BufferChangeSet>, cx: &mut Context<Self>) {
+ fn buffer_diff_changed(
+ &mut self,
+ change_set: Entity<BufferChangeSet>,
+ range: Range<text::Anchor>,
+ cx: &mut Context<Self>,
+ ) {
let change_set = change_set.read(cx);
let buffer_id = change_set.buffer_id;
let diff = change_set.diff_to_buffer.clone();
let base_text = change_set.base_text.clone();
self.sync(cx);
let mut snapshot = self.snapshot.borrow_mut();
- let base_text_version_changed =
- snapshot
- .diffs
- .get(&buffer_id)
- .map_or(true, |diff_snapshot| {
- change_set.base_text.as_ref().map_or(true, |base_text| {
- base_text.remote_id() != diff_snapshot.base_text.remote_id()
- })
- });
+ let base_text_changed = snapshot
+ .diffs
+ .get(&buffer_id)
+ .map_or(true, |diff_snapshot| {
+ change_set.base_text.as_ref().map_or(true, |base_text| {
+ base_text.remote_id() != diff_snapshot.base_text.remote_id()
+ })
+ });
if let Some(base_text) = base_text {
snapshot.diffs.insert(
@@ -2026,26 +2035,44 @@ impl MultiBuffer {
} else {
snapshot.diffs.remove(&buffer_id);
}
+ let buffers = self.buffers.borrow();
+ let Some(buffer_state) = buffers.get(&buffer_id) else {
+ return;
+ };
+
+ let diff_change_range = range.to_offset(buffer_state.buffer.read(cx));
let mut excerpt_edits = Vec::new();
- for locator in self
- .buffers
- .borrow()
- .get(&buffer_id)
- .map(|state| &state.excerpts)
- .into_iter()
- .flatten()
- {
+ for locator in &buffer_state.excerpts {
let mut cursor = snapshot
.excerpts
.cursor::<(Option<&Locator>, ExcerptOffset)>(&());
cursor.seek_forward(&Some(locator), Bias::Left, &());
if let Some(excerpt) = cursor.item() {
if excerpt.locator == *locator {
- let excerpt_range = cursor.start().1..cursor.end(&()).1;
+ let excerpt_buffer_range = excerpt.range.context.to_offset(&excerpt.buffer);
+ if diff_change_range.end < excerpt_buffer_range.start
+ || diff_change_range.start > excerpt_buffer_range.end
+ {
+ continue;
+ }
+ let excerpt_start = cursor.start().1;
+ let excerpt_len = ExcerptOffset::new(excerpt.text_summary.len);
+ let diff_change_start_in_excerpt = ExcerptOffset::new(
+ diff_change_range
+ .start
+ .saturating_sub(excerpt_buffer_range.start),
+ );
+ let diff_change_end_in_excerpt = ExcerptOffset::new(
+ diff_change_range
+ .end
+ .saturating_sub(excerpt_buffer_range.start),
+ );
+ let edit_start = excerpt_start + diff_change_start_in_excerpt.min(excerpt_len);
+ let edit_end = excerpt_start + diff_change_end_in_excerpt.min(excerpt_len);
excerpt_edits.push(Edit {
- old: excerpt_range.clone(),
- new: excerpt_range.clone(),
+ old: edit_start..edit_end,
+ new: edit_start..edit_end,
});
}
}
@@ -2055,7 +2082,7 @@ impl MultiBuffer {
snapshot,
excerpt_edits,
DiffChangeKind::DiffUpdated {
- base_changed: base_text_version_changed,
+ base_changed: base_text_changed,
},
);
cx.emit(Event::Edited {
@@ -2145,11 +2172,18 @@ impl MultiBuffer {
pub fn add_change_set(&mut self, change_set: Entity<BufferChangeSet>, cx: &mut Context<Self>) {
let buffer_id = change_set.read(cx).buffer_id;
- self.buffer_diff_changed(change_set.clone(), cx);
+ self.buffer_diff_changed(change_set.clone(), text::Anchor::MIN..text::Anchor::MAX, cx);
self.diff_bases.insert(
buffer_id,
ChangeSetState {
- _subscription: cx.observe(&change_set, Self::buffer_diff_changed),
+ _subscription: cx.subscribe(
+ &change_set,
+ |this, change_set, event, cx| match event {
+ BufferChangeSetEvent::DiffChanged { changed_range } => {
+ this.buffer_diff_changed(change_set, changed_range.clone(), cx);
+ }
+ },
+ ),
change_set,
},
);
@@ -2329,7 +2363,7 @@ impl MultiBuffer {
drop(cursor);
snapshot.excerpts = new_excerpts;
- self.sync_diff_transforms(snapshot, edits, DiffChangeKind::ExcerptsChanged);
+ self.sync_diff_transforms(snapshot, edits, DiffChangeKind::BufferEdited);
cx.emit(Event::Edited {
singleton_buffer_edited: false,
edited_buffer: None,
@@ -2430,7 +2464,7 @@ impl MultiBuffer {
drop(cursor);
snapshot.excerpts = new_excerpts;
- self.sync_diff_transforms(snapshot, edits, DiffChangeKind::ExcerptsChanged);
+ self.sync_diff_transforms(snapshot, edits, DiffChangeKind::BufferEdited);
cx.emit(Event::Edited {
singleton_buffer_edited: false,
edited_buffer: None,
@@ -2595,63 +2629,52 @@ impl MultiBuffer {
let edit_old_start = old_diff_transforms.start().1 + edit_start_overshoot;
let edit_new_start = (edit_old_start as isize + output_delta) as usize;
- if change_kind == DiffChangeKind::BufferEdited {
- self.interpolate_diff_transforms_for_edit(
- &edit,
- &excerpts,
- &mut old_diff_transforms,
- &mut new_diff_transforms,
- &mut end_of_current_insert,
- );
- } else {
- self.recompute_diff_transforms_for_edit(
- &edit,
- &mut excerpts,
- &mut old_diff_transforms,
- &mut new_diff_transforms,
- &mut end_of_current_insert,
- &mut old_expanded_hunks,
- &snapshot,
- change_kind,
- );
- }
-
- self.push_buffer_content_transform(
- &snapshot,
+ let changed_diff_hunks = self.recompute_diff_transforms_for_edit(
+ &edit,
+ &mut excerpts,
+ &mut old_diff_transforms,
&mut new_diff_transforms,
- edit.new.end,
- end_of_current_insert,
+ &mut end_of_current_insert,
+ &mut old_expanded_hunks,
+ &snapshot,
+ change_kind,
);
// Compute the end of the edit in output coordinates.
- let edit_end_overshoot = (edit.old.end - old_diff_transforms.start().0).value;
- let edit_old_end = old_diff_transforms.start().1 + edit_end_overshoot;
- let edit_new_end = new_diff_transforms.summary().output.len;
+ let edit_old_end_overshoot = edit.old.end - old_diff_transforms.start().0;
+ let edit_new_end_overshoot = edit.new.end - new_diff_transforms.summary().excerpt_len();
+ let edit_old_end = old_diff_transforms.start().1 + edit_old_end_overshoot.value;
+ let edit_new_end =
+ new_diff_transforms.summary().output.len + edit_new_end_overshoot.value;
let output_edit = Edit {
old: edit_old_start..edit_old_end,
new: edit_new_start..edit_new_end,
};
- output_delta += (output_edit.new.end - output_edit.new.start) as isize
- - (output_edit.old.end - output_edit.old.start) as isize;
- output_edits.push(output_edit);
+ output_delta += (output_edit.new.end - output_edit.new.start) as isize;
+ output_delta -= (output_edit.old.end - output_edit.old.start) as isize;
+ if changed_diff_hunks || matches!(change_kind, DiffChangeKind::BufferEdited) {
+ output_edits.push(output_edit);
+ }
// If this is the last edit that intersects the current diff transform,
- // then preserve a suffix of the this diff transform.
+ // then recreate the content up to the end of this transform, to prepare
+ // for reusing additional slices of the old transforms.
if excerpt_edits.peek().map_or(true, |next_edit| {
next_edit.old.start >= old_diff_transforms.end(&()).0
}) {
+ let mut excerpt_offset = edit.new.end;
if old_diff_transforms.start().0 < edit.old.end {
- let suffix = old_diff_transforms.end(&()).0 - edit.old.end;
- let transform_end = new_diff_transforms.summary().excerpt_len() + suffix;
- self.push_buffer_content_transform(
- &snapshot,
- &mut new_diff_transforms,
- transform_end,
- end_of_current_insert,
- );
+ excerpt_offset += old_diff_transforms.end(&()).0 - edit.old.end;
old_diff_transforms.next(&());
}
+ old_expanded_hunks.clear();
+ self.push_buffer_content_transform(
+ &snapshot,
+ &mut new_diff_transforms,
+ excerpt_offset,
+ end_of_current_insert,
+ );
at_transform_boundary = true;
}
}
@@ -2691,7 +2714,7 @@ impl MultiBuffer {
old_expanded_hunks: &mut HashSet<(ExcerptId, text::Anchor)>,
snapshot: &MultiBufferSnapshot,
change_kind: DiffChangeKind,
- ) {
+ ) -> bool {
log::trace!(
"recomputing diff transform for edit {:?} => {:?}",
edit.old.start.value..edit.old.end.value,
@@ -2699,11 +2722,7 @@ impl MultiBuffer {
);
// Record which hunks were previously expanded.
- old_expanded_hunks.clear();
while let Some(item) = old_diff_transforms.item() {
- if old_diff_transforms.end(&()).0 > edit.old.end {
- break;
- }
if let Some(hunk_anchor) = item.hunk_anchor() {
log::trace!(
"previously expanded hunk at {}",
@@ -2711,10 +2730,22 @@ impl MultiBuffer {
);
old_expanded_hunks.insert(hunk_anchor);
}
+ if old_diff_transforms.end(&()).0 > edit.old.end {
+ break;
+ }
old_diff_transforms.next(&());
}
+ // Avoid querying diff hunks if there's no possibility of hunks being expanded.
+ if old_expanded_hunks.is_empty()
+ && change_kind == DiffChangeKind::BufferEdited
+ && !self.all_diff_hunks_expanded
+ {
+ return false;
+ }
+
// Visit each excerpt that intersects the edit.
+ let mut did_expand_hunks = false;
while let Some(excerpt) = excerpts.item() {
if excerpt.text_summary.len == 0 {
if excerpts.end(&()) <= edit.new.end {
@@ -2754,8 +2785,10 @@ impl MultiBuffer {
+ ExcerptOffset::new(
hunk_buffer_range.start.saturating_sub(excerpt_buffer_start),
);
- let hunk_excerpt_end = excerpt_start
- + ExcerptOffset::new(hunk_buffer_range.end - excerpt_buffer_start);
+ let hunk_excerpt_end = excerpt_end.min(
+ excerpt_start
+ + ExcerptOffset::new(hunk_buffer_range.end - excerpt_buffer_start),
+ );
self.push_buffer_content_transform(
snapshot,
@@ -2787,7 +2820,11 @@ impl MultiBuffer {
};
if should_expand_hunk {
- log::trace!("expanding hunk at {}", hunk_excerpt_start.value);
+ did_expand_hunks = true;
+ log::trace!(
+ "expanding hunk {:?}",
+ hunk_excerpt_start.value..hunk_excerpt_end.value,
+ );
if !hunk.diff_base_byte_range.is_empty()
&& hunk_buffer_range.start >= edit_buffer_start
@@ -2833,68 +2870,8 @@ impl MultiBuffer {
break;
}
}
- }
-
- fn interpolate_diff_transforms_for_edit(
- &self,
- edit: &Edit<TypedOffset<Excerpt>>,
- excerpts: &Cursor<Excerpt, TypedOffset<Excerpt>>,
- old_diff_transforms: &mut Cursor<DiffTransform, (TypedOffset<Excerpt>, usize)>,
- new_diff_transforms: &mut SumTree<DiffTransform>,
- end_of_current_insert: &mut Option<(TypedOffset<Excerpt>, ExcerptId, text::Anchor)>,
- ) {
- log::trace!(
- "interpolating diff transform for edit {:?} => {:?}",
- edit.old.start.value..edit.old.end.value,
- edit.new.start.value..edit.new.end.value
- );
-
- // Preserve deleted hunks immediately preceding edits.
- if let Some(transform) = old_diff_transforms.item() {
- if old_diff_transforms.start().0 == edit.old.start {
- if let DiffTransform::DeletedHunk { hunk_anchor, .. } = transform {
- if excerpts
- .item()
- .map_or(false, |excerpt| hunk_anchor.1.is_valid(&excerpt.buffer))
- {
- self.push_diff_transform(new_diff_transforms, transform.clone());
- old_diff_transforms.next(&());
- }
- }
- }
- }
-
- let edit_start_transform = old_diff_transforms.item();
-
- // When an edit starts within an inserted hunks, extend the hunk
- // to include the lines of the edit.
- if let Some((
- DiffTransform::BufferContent {
- inserted_hunk_anchor: Some(inserted_hunk_anchor),
- ..
- },
- excerpt,
- )) = edit_start_transform.zip(excerpts.item())
- {
- let buffer = &excerpt.buffer;
- if inserted_hunk_anchor.1.is_valid(buffer) {
- let excerpt_start = *excerpts.start();
- let excerpt_end = excerpt_start + ExcerptOffset::new(excerpt.text_summary.len);
- let excerpt_buffer_start = excerpt.range.context.start.to_offset(buffer);
- let edit_buffer_end =
- excerpt_buffer_start + edit.new.end.value.saturating_sub(excerpt_start.value);
- let edit_buffer_end_point = buffer.offset_to_point(edit_buffer_end);
- let edited_buffer_line_end =
- buffer.point_to_offset(edit_buffer_end_point + Point::new(1, 0));
- let edited_line_end = excerpt_start
- + ExcerptOffset::new(edited_buffer_line_end - excerpt_buffer_start);
- let hunk_end = edited_line_end.min(excerpt_end);
- *end_of_current_insert =
- Some((hunk_end, inserted_hunk_anchor.0, inserted_hunk_anchor.1));
- }
- }
- old_diff_transforms.seek_forward(&edit.old.end, Bias::Right, &());
+ did_expand_hunks || !old_expanded_hunks.is_empty()
}
fn append_diff_transforms(
@@ -354,16 +354,17 @@ fn test_excerpt_boundaries_and_clipping(cx: &mut App) {
}
#[gpui::test]
-fn test_diff_boundary_anchors(cx: &mut App) {
+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(cx).snapshot();
+ let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
let change_set = cx.new(|cx| {
let mut change_set = BufferChangeSet::new(&buffer, cx);
- change_set.recalculate_diff_sync(base_text.into(), snapshot.text, true, cx);
+ let _ = change_set.set_base_text(base_text.into(), snapshot.text, cx);
change_set
});
+ cx.run_until_parked();
let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
multibuffer.update(cx, |multibuffer, cx| {
multibuffer.add_change_set(change_set, cx)
@@ -375,9 +376,9 @@ fn test_diff_boundary_anchors(cx: &mut App) {
multibuffer.set_all_diff_hunks_expanded(cx);
(before, after)
});
- cx.background_executor().run_until_parked();
+ cx.run_until_parked();
- let snapshot = multibuffer.read(cx).snapshot(cx);
+ 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());
@@ -410,9 +411,10 @@ fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
let change_set = cx.new(|cx| {
let mut change_set = BufferChangeSet::new(&buffer, cx);
let snapshot = buffer.read(cx).snapshot();
- change_set.recalculate_diff_sync(base_text.into(), snapshot.text, true, cx);
+ let _ = change_set.set_base_text(base_text.into(), snapshot.text, cx);
change_set
});
+ cx.run_until_parked();
let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
(multibuffer.snapshot(cx), multibuffer.subscribe())
@@ -507,10 +509,11 @@ fn test_editing_text_in_diff_hunks(cx: &mut TestAppContext) {
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();
- change_set.recalculate_diff_sync(base_text.into(), snapshot.text, true, 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 multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
@@ -586,14 +589,6 @@ fn test_editing_text_in_diff_hunks(cx: &mut TestAppContext) {
);
multibuffer.update(cx, |multibuffer, cx| multibuffer.undo(cx));
- change_set.update(cx, |change_set, cx| {
- change_set.recalculate_diff_sync(
- base_text.into(),
- buffer.read(cx).text_snapshot(),
- true,
- cx,
- );
- });
assert_new_snapshot(
&multibuffer,
&mut snapshot,
@@ -1861,7 +1856,7 @@ impl ReferenceMultibuffer {
.buffer_range
.end
.cmp(&excerpt.range.start, &buffer)
- .is_le();
+ .is_lt();
let hunk_follows_excerpt = hunk
.buffer_range
.start
@@ -2064,7 +2059,7 @@ impl ReferenceMultibuffer {
}
#[gpui::test(iterations = 100)]
-fn test_random_multibuffer(cx: &mut App, mut rng: StdRng) {
+async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
let operations = env::var("OPERATIONS")
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
.unwrap_or(10);
@@ -2085,7 +2080,7 @@ fn test_random_multibuffer(cx: &mut App, mut rng: StdRng) {
buf.randomly_edit(&mut rng, edit_count, cx);
needs_diff_calculation = true;
});
- reference.diffs_updated(cx);
+ cx.update(|cx| reference.diffs_updated(cx));
}
15..=19 if !reference.excerpts.is_empty() => {
multibuffer.update(cx, |multibuffer, cx| {
@@ -2119,10 +2114,11 @@ fn test_random_multibuffer(cx: &mut App, mut rng: StdRng) {
break;
};
let id = excerpt.id;
- reference.remove_excerpt(id, cx);
+ cx.update(|cx| reference.remove_excerpt(id, cx));
ids_to_remove.push(id);
}
- let snapshot = multibuffer.read(cx).read(cx);
+ let snapshot =
+ multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
ids_to_remove.sort_unstable_by(|a, b| a.cmp(b, &snapshot));
drop(snapshot);
multibuffer.update(cx, |multibuffer, cx| {
@@ -2130,7 +2126,8 @@ fn test_random_multibuffer(cx: &mut App, mut rng: StdRng) {
});
}
30..=39 if !reference.excerpts.is_empty() => {
- let multibuffer = multibuffer.read(cx).read(cx);
+ let multibuffer =
+ multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
let offset =
multibuffer.clip_offset(rng.gen_range(0..=multibuffer.len()), Bias::Left);
let bias = if rng.gen() { Bias::Left } else { Bias::Right };
@@ -2139,7 +2136,8 @@ fn test_random_multibuffer(cx: &mut App, mut rng: StdRng) {
anchors.sort_by(|a, b| a.cmp(b, &multibuffer));
}
40..=44 if !anchors.is_empty() => {
- let multibuffer = multibuffer.read(cx).read(cx);
+ let multibuffer =
+ multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
let prev_len = anchors.len();
anchors = multibuffer
.refresh_anchors(&anchors)
@@ -2189,12 +2187,7 @@ fn test_random_multibuffer(cx: &mut App, mut rng: StdRng) {
"recalculating diff for buffer {:?}",
snapshot.remote_id(),
);
- change_set.recalculate_diff_sync(
- change_set.base_text.clone().unwrap().text(),
- snapshot.text,
- false,
- cx,
- )
+ change_set.recalculate_diff(snapshot.text, cx)
});
}
reference.diffs_updated(cx);
@@ -2208,15 +2201,17 @@ fn test_random_multibuffer(cx: &mut App, mut rng: StdRng) {
.collect::<String>();
let buffer = cx.new(|cx| Buffer::local(base_text.clone(), cx));
- let snapshot = buffer.read(cx).snapshot();
- let change_set = cx.new(|cx| {
- let mut change_set = BufferChangeSet::new(&buffer, cx);
- change_set.recalculate_diff_sync(base_text, snapshot.text, true, cx);
- change_set
- });
+ 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();
- reference.add_change_set(change_set.clone(), cx);
multibuffer.update(cx, |multibuffer, cx| {
+ reference.add_change_set(change_set.clone(), cx);
multibuffer.add_change_set(change_set, cx)
});
buffers.push(buffer);
@@ -2225,12 +2220,6 @@ fn test_random_multibuffer(cx: &mut App, mut rng: StdRng) {
buffers.choose(&mut rng).unwrap()
};
- let buffer = buffer_handle.read(cx);
- let end_row = rng.gen_range(0..=buffer.max_point().row);
- let start_row = rng.gen_range(0..=end_row);
- let end_ix = buffer.point_to_offset(Point::new(end_row, 0));
- let start_ix = buffer.point_to_offset(Point::new(start_row, 0));
- let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix);
let prev_excerpt_ix = rng.gen_range(0..=reference.excerpts.len());
let prev_excerpt_id = reference
.excerpts
@@ -2238,15 +2227,25 @@ fn test_random_multibuffer(cx: &mut App, mut rng: StdRng) {
.map_or(ExcerptId::max(), |e| e.id);
let excerpt_ix = (prev_excerpt_ix + 1).min(reference.excerpts.len());
- log::info!(
- "Inserting excerpt at {} of {} for buffer {}: {:?}[{:?}] = {:?}",
- excerpt_ix,
- reference.excerpts.len(),
- buffer_handle.read(cx).remote_id(),
- buffer.text(),
- start_ix..end_ix,
- &buffer.text()[start_ix..end_ix]
- );
+ let (range, anchor_range) = buffer_handle.read_with(cx, |buffer, _| {
+ let end_row = rng.gen_range(0..=buffer.max_point().row);
+ let start_row = rng.gen_range(0..=end_row);
+ let end_ix = buffer.point_to_offset(Point::new(end_row, 0));
+ let start_ix = buffer.point_to_offset(Point::new(start_row, 0));
+ let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix);
+
+ log::info!(
+ "Inserting excerpt at {} of {} for buffer {}: {:?}[{:?}] = {:?}",
+ excerpt_ix,
+ reference.excerpts.len(),
+ buffer.remote_id(),
+ buffer.text(),
+ start_ix..end_ix,
+ &buffer.text()[start_ix..end_ix]
+ );
+
+ (start_ix..end_ix, anchor_range)
+ });
let excerpt_id = multibuffer.update(cx, |multibuffer, cx| {
multibuffer
@@ -2254,7 +2253,7 @@ fn test_random_multibuffer(cx: &mut App, mut rng: StdRng) {
prev_excerpt_id,
buffer_handle.clone(),
[ExcerptRange {
- context: start_ix..end_ix,
+ context: range,
primary: None,
}],
cx,
@@ -2277,7 +2276,7 @@ fn test_random_multibuffer(cx: &mut App, mut rng: StdRng) {
})
}
- let snapshot = multibuffer.read(cx).snapshot(cx);
+ let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
let actual_text = snapshot.text();
let actual_boundary_rows = snapshot
.excerpt_boundaries_in_range(0..)
@@ -2287,7 +2286,7 @@ fn test_random_multibuffer(cx: &mut App, mut rng: StdRng) {
let actual_diff = format_diff(&actual_text, &actual_row_infos, &actual_boundary_rows);
let (expected_text, expected_row_infos, expected_boundary_rows) =
- reference.expected_content(cx);
+ cx.update(|cx| reference.expected_content(cx));
let expected_diff =
format_diff(&expected_text, &expected_row_infos, &expected_boundary_rows);
@@ -2404,7 +2403,7 @@ fn test_random_multibuffer(cx: &mut App, mut rng: StdRng) {
}
}
- let snapshot = multibuffer.read(cx).snapshot(cx);
+ let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
for (old_snapshot, subscription) in old_versions {
let edits = subscription.consume().into_inner();
@@ -69,6 +69,10 @@ pub struct BufferChangeSet {
pub language_registry: Option<Arc<LanguageRegistry>>,
}
+pub enum BufferChangeSetEvent {
+ DiffChanged { changed_range: Range<text::Anchor> },
+}
+
enum BufferStoreState {
Local(LocalBufferStore),
Remote(RemoteBufferStore),
@@ -2201,6 +2205,8 @@ impl BufferStore {
}
}
+impl EventEmitter<BufferChangeSetEvent> for BufferChangeSet {}
+
impl BufferChangeSet {
pub fn new(buffer: &Entity<Buffer>, cx: &mut Context<Self>) -> Self {
cx.subscribe(buffer, |this, buffer, event, cx| match event {
@@ -2318,69 +2324,53 @@ impl BufferChangeSet {
let (tx, rx) = oneshot::channel();
self.diff_updated_futures.push(tx);
self.recalculate_diff_task = Some(cx.spawn(|this, mut cx| async move {
- let new_base_text = if base_text_changed {
- let base_text_rope: Rope = base_text.as_str().into();
- let snapshot = this.update(&mut cx, |this, cx| {
- language::Buffer::build_snapshot(
+ let (old_diff, new_base_text) = this.update(&mut cx, |this, cx| {
+ let new_base_text = if base_text_changed {
+ let base_text_rope: Rope = base_text.as_str().into();
+ let snapshot = language::Buffer::build_snapshot(
base_text_rope,
this.language.clone(),
this.language_registry.clone(),
cx,
- )
- })?;
- Some(cx.background_executor().spawn(snapshot).await)
- } else {
- None
- };
- let diff = cx
- .background_executor()
- .spawn({
- let buffer_snapshot = buffer_snapshot.clone();
- async move { BufferDiff::build(&base_text, &buffer_snapshot) }
- })
- .await;
+ );
+ cx.background_executor()
+ .spawn(async move { Some(snapshot.await) })
+ } else {
+ Task::ready(None)
+ };
+ (this.diff_to_buffer.clone(), new_base_text)
+ })?;
+
+ let diff = cx.background_executor().spawn(async move {
+ let new_diff = BufferDiff::build(&base_text, &buffer_snapshot);
+ let changed_range = if base_text_changed {
+ Some(text::Anchor::MIN..text::Anchor::MAX)
+ } else {
+ new_diff.compare(&old_diff, &buffer_snapshot)
+ };
+ (new_diff, changed_range)
+ });
+
+ let (new_base_text, (diff, changed_range)) = futures::join!(new_base_text, diff);
+
this.update(&mut cx, |this, cx| {
if let Some(new_base_text) = new_base_text {
this.base_text = Some(new_base_text)
}
this.diff_to_buffer = diff;
+
this.recalculate_diff_task.take();
for tx in this.diff_updated_futures.drain(..) {
tx.send(()).ok();
}
- cx.notify();
+ if let Some(changed_range) = changed_range {
+ cx.emit(BufferChangeSetEvent::DiffChanged { changed_range });
+ }
})?;
Ok(())
}));
rx
}
-
- #[cfg(any(test, feature = "test-support"))]
- pub fn recalculate_diff_sync(
- &mut self,
- mut base_text: String,
- buffer_snapshot: text::BufferSnapshot,
- base_text_changed: bool,
- cx: &mut Context<Self>,
- ) {
- LineEnding::normalize(&mut base_text);
- let diff = BufferDiff::build(&base_text, &buffer_snapshot);
- if base_text_changed {
- self.base_text = Some(
- cx.background_executor()
- .clone()
- .block(Buffer::build_snapshot(
- base_text.into(),
- self.language.clone(),
- self.language_registry.clone(),
- cx,
- )),
- );
- }
- self.diff_to_buffer = diff;
- self.recalculate_diff_task.take();
- cx.notify();
- }
}
impl OpenBuffer {
@@ -13,7 +13,7 @@ path = "src/text.rs"
doctest = false
[features]
-test-support = ["rand"]
+test-support = ["rand", "util/test-support"]
[dependencies]
anyhow.workspace = true