@@ -508,12 +508,9 @@ struct BufferState {
struct DiffState {
diff: Entity<BufferDiff>,
- /// Whether the [`MultiBuffer`] this is associated with is inverted (i.e.
- /// showing additions as deletions). This is used in the side-by-side diff
- /// view.
- ///
- /// The [`DiffState`]s in a [`MultiBuffer`] must either be all inverted, or
- // all not inverted.
+ /// If set, this diff is "inverted" (i.e. showing additions as deletions).
+ /// This is used in the side-by-side diff view. The main_buffer is the
+ /// editable buffer, while the excerpt shows the base text.
main_buffer: Option<WeakEntity<Buffer>>,
_subscription: gpui::Subscription,
}
@@ -613,6 +610,7 @@ pub struct MultiBufferSnapshot {
is_dirty: bool,
has_deleted_file: bool,
has_conflict: bool,
+ has_inverted_diff: bool,
/// immutable fields
singleton: bool,
excerpt_ids: SumTree<ExcerptIdMapping>,
@@ -1870,6 +1868,7 @@ impl MultiBuffer {
is_dirty,
has_deleted_file,
has_conflict,
+ has_inverted_diff,
singleton: _,
excerpt_ids: _,
replaced_excerpts,
@@ -1885,6 +1884,7 @@ impl MultiBuffer {
*is_dirty = false;
*has_deleted_file = false;
*has_conflict = false;
+ *has_inverted_diff = false;
replaced_excerpts.clear();
let edits = Self::sync_diff_transforms(
@@ -2182,6 +2182,14 @@ impl MultiBuffer {
snapshot.diffs.remove(buffer_id);
}
+ // Recalculate has_inverted_diff after removing diffs
+ if !removed_buffer_ids.is_empty() {
+ snapshot.has_inverted_diff = snapshot
+ .diffs
+ .iter()
+ .any(|(_, diff)| diff.main_buffer.is_some());
+ }
+
if changed_trailing_excerpt {
snapshot.trailing_excerpt_update_count += 1;
}
@@ -2544,8 +2552,6 @@ impl MultiBuffer {
}
pub fn add_diff(&mut self, diff: Entity<BufferDiff>, cx: &mut Context<Self>) {
- debug_assert!(self.diffs.values().all(|diff| diff.main_buffer.is_none()));
-
let buffer_id = diff.read(cx).buffer_id;
self.buffer_diff_changed(
diff.clone(),
@@ -2561,10 +2567,9 @@ impl MultiBuffer {
main_buffer: Entity<Buffer>,
cx: &mut Context<Self>,
) {
- debug_assert!(self.diffs.values().all(|diff| diff.main_buffer.is_some()));
-
let base_text_buffer_id = diff.read(cx).base_text(cx).remote_id();
let diff_change_range = 0..diff.read(cx).base_text(cx).len();
+ self.snapshot.get_mut().has_inverted_diff = true;
self.inverted_buffer_diff_changed(
diff.clone(),
diff_change_range,
@@ -2940,6 +2945,7 @@ impl MultiBuffer {
is_dirty,
has_deleted_file,
has_conflict,
+ has_inverted_diff: _,
singleton: _,
excerpt_ids: _,
replaced_excerpts: _,
@@ -3228,10 +3234,12 @@ impl MultiBuffer {
}
// Avoid querying diff hunks if there's no possibility of hunks being expanded.
+ // For inverted diffs, hunks are always shown, so we can't skip this.
let all_diff_hunks_expanded = snapshot.all_diff_hunks_expanded;
if old_expanded_hunks.is_empty()
&& change_kind == DiffChangeKind::BufferEdited
&& !all_diff_hunks_expanded
+ && !snapshot.has_inverted_diff
{
return false;
}
@@ -2295,6 +2295,7 @@ async fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) {
struct ReferenceMultibuffer {
excerpts: Vec<ReferenceExcerpt>,
diffs: HashMap<BufferId, Entity<BufferDiff>>,
+ inverted_diffs: HashMap<BufferId, (Entity<BufferDiff>, text::BufferSnapshot)>,
}
#[derive(Debug)]
@@ -2355,6 +2356,7 @@ impl ReferenceMultibuffer {
.any(|excerpt| excerpt.buffer.read(cx).remote_id() == id)
{
self.diffs.remove(&id);
+ self.inverted_diffs.remove(&id);
}
}
@@ -2392,6 +2394,12 @@ impl ReferenceMultibuffer {
.unwrap();
let buffer = excerpt.buffer.read(cx).snapshot();
let buffer_id = buffer.remote_id();
+
+ // Skip inverted excerpts - hunks are always expanded
+ if self.inverted_diffs.contains_key(&buffer_id) {
+ return;
+ }
+
let Some(diff) = self.diffs.get(&buffer_id) else {
return;
};
@@ -2431,107 +2439,186 @@ impl ReferenceMultibuffer {
for excerpt in &self.excerpts {
excerpt_boundary_rows.insert(MultiBufferRow(text.matches('\n').count() as u32));
let buffer = excerpt.buffer.read(cx);
+ let buffer_id = buffer.remote_id();
let buffer_range = excerpt.range.to_offset(buffer);
- let diff = self
- .diffs
- .get(&buffer.remote_id())
- .unwrap()
- .read(cx)
- .snapshot(cx);
- let base_buffer = diff.base_text();
-
- let mut offset = buffer_range.start;
- let hunks = diff
- .hunks_intersecting_range(excerpt.range.clone(), buffer)
- .peekable();
-
- for hunk in hunks {
- // 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.start < buffer_range.start {
- log::trace!("skipping hunk outside excerpt range");
- continue;
- }
- if !excerpt.expanded_diff_hunks.iter().any(|expanded_anchor| {
- 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 let Some((diff, main_buffer_snapshot)) = self.inverted_diffs.get(&buffer_id) {
+ // INVERTED DIFF: Show base text with deleted regions marked
+ let diff_snapshot = diff.read(cx).snapshot(cx);
- if !hunk.buffer_range.start.is_valid(buffer) {
- log::trace!("skipping hunk with deleted start: {:?}", hunk.range);
- continue;
- }
+ let mut offset = buffer_range.start;
+ for hunk in diff_snapshot
+ .hunks_intersecting_base_text_range(buffer_range.clone(), main_buffer_snapshot)
+ {
+ let hunk_base_range = hunk.diff_base_byte_range.clone();
- if hunk_range.start >= offset {
- // Add the buffer text before the hunk
- let len = text.len();
- text.extend(buffer.text_for_range(offset..hunk_range.start));
- if text.len() > len {
- regions.push(ReferenceRegion {
- buffer_id: Some(buffer.remote_id()),
- range: len..text.len(),
- buffer_range: Some((offset..hunk_range.start).to_point(&buffer)),
- status: None,
- excerpt_id: Some(excerpt.id),
- });
+ // Match actual multibuffer behavior: skip hunks that start before excerpt
+ if hunk_base_range.start < buffer_range.start {
+ continue;
}
- // Add the deleted text for the hunk.
- if !hunk.diff_base_byte_range.is_empty() {
- let mut base_text = base_buffer
- .text_for_range(hunk.diff_base_byte_range.clone())
- .collect::<String>();
- if !base_text.ends_with('\n') {
- base_text.push('\n');
+ // Clamp to excerpt range
+ let hunk_start = hunk_base_range.start.max(buffer_range.start);
+ let hunk_end = hunk_base_range.end.min(buffer_range.end);
+
+ if hunk_start > buffer_range.end {
+ continue;
+ }
+
+ // Add buffer text before the hunk (no status)
+ if hunk_start > offset {
+ let len = text.len();
+ text.extend(buffer.text_for_range(offset..hunk_start));
+ if text.len() > len {
+ regions.push(ReferenceRegion {
+ buffer_id: Some(buffer_id),
+ range: len..text.len(),
+ buffer_range: Some((offset..hunk_start).to_point(&buffer)),
+ status: None,
+ excerpt_id: Some(excerpt.id),
+ });
}
+ }
+
+ // Add the "deleted" region (base text that's not in main)
+ if hunk_end > hunk_start {
let len = text.len();
- text.push_str(&base_text);
+ text.extend(buffer.text_for_range(hunk_start..hunk_end));
regions.push(ReferenceRegion {
- buffer_id: Some(base_buffer.remote_id()),
+ buffer_id: Some(buffer_id),
range: len..text.len(),
- buffer_range: Some(hunk.diff_base_byte_range.to_point(&base_buffer)),
+ buffer_range: Some((hunk_start..hunk_end).to_point(&buffer)),
status: Some(DiffHunkStatus::deleted(hunk.secondary_status)),
excerpt_id: Some(excerpt.id),
});
}
- offset = hunk_range.start;
+ offset = hunk_end.max(offset);
}
- // Add the inserted text for the hunk.
- if hunk_range.end > offset {
+ // Add remaining buffer text
+ if offset < buffer_range.end {
let len = text.len();
- text.extend(buffer.text_for_range(offset..hunk_range.end));
- let range = len..text.len();
- let region = ReferenceRegion {
- buffer_id: Some(buffer.remote_id()),
- range,
- buffer_range: Some((offset..hunk_range.end).to_point(&buffer)),
- status: Some(DiffHunkStatus::added(hunk.secondary_status)),
+ text.extend(buffer.text_for_range(offset..buffer_range.end));
+ text.push('\n');
+ regions.push(ReferenceRegion {
+ buffer_id: Some(buffer_id),
+ range: len..text.len(),
+ buffer_range: Some((offset..buffer_range.end).to_point(&buffer)),
+ status: None,
excerpt_id: Some(excerpt.id),
- };
- offset = hunk_range.end;
- regions.push(region);
+ });
+ } else {
+ text.push('\n');
+ regions.push(ReferenceRegion {
+ buffer_id: Some(buffer_id),
+ range: text.len() - 1..text.len(),
+ buffer_range: Some((buffer_range.end..buffer_range.end).to_point(&buffer)),
+ status: None,
+ excerpt_id: Some(excerpt.id),
+ });
}
- }
+ } else {
+ // NON-INVERTED: Existing logic (show main buffer, insert deleted content)
+ let diff = self.diffs.get(&buffer_id).unwrap().read(cx).snapshot(cx);
+ let base_buffer = diff.base_text();
- // Add the buffer text for the rest of the excerpt.
- let len = text.len();
- 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_range: Some((offset..buffer_range.end).to_point(&buffer)),
- status: None,
- excerpt_id: Some(excerpt.id),
- });
+ let mut offset = buffer_range.start;
+ let hunks = diff
+ .hunks_intersecting_range(excerpt.range.clone(), buffer)
+ .peekable();
+
+ for hunk in hunks {
+ // 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.start < buffer_range.start
+ {
+ log::trace!("skipping hunk outside excerpt range");
+ continue;
+ }
+
+ if !excerpt.expanded_diff_hunks.iter().any(|expanded_anchor| {
+ 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.range);
+ continue;
+ }
+
+ if hunk_range.start >= offset {
+ // Add the buffer text before the hunk
+ let len = text.len();
+ text.extend(buffer.text_for_range(offset..hunk_range.start));
+ if text.len() > len {
+ regions.push(ReferenceRegion {
+ buffer_id: Some(buffer_id),
+ range: len..text.len(),
+ buffer_range: Some((offset..hunk_range.start).to_point(&buffer)),
+ status: None,
+ excerpt_id: Some(excerpt.id),
+ });
+ }
+
+ // Add the deleted text for the hunk.
+ if !hunk.diff_base_byte_range.is_empty() {
+ let mut base_text = base_buffer
+ .text_for_range(hunk.diff_base_byte_range.clone())
+ .collect::<String>();
+ if !base_text.ends_with('\n') {
+ base_text.push('\n');
+ }
+ let len = text.len();
+ text.push_str(&base_text);
+ regions.push(ReferenceRegion {
+ buffer_id: Some(base_buffer.remote_id()),
+ range: len..text.len(),
+ buffer_range: Some(
+ hunk.diff_base_byte_range.to_point(&base_buffer),
+ ),
+ status: Some(DiffHunkStatus::deleted(hunk.secondary_status)),
+ excerpt_id: Some(excerpt.id),
+ });
+ }
+
+ offset = hunk_range.start;
+ }
+
+ // Add the inserted text for the hunk.
+ if hunk_range.end > offset {
+ let len = text.len();
+ text.extend(buffer.text_for_range(offset..hunk_range.end));
+ let range = len..text.len();
+ let region = ReferenceRegion {
+ buffer_id: Some(buffer_id),
+ range,
+ buffer_range: Some((offset..hunk_range.end).to_point(&buffer)),
+ status: Some(DiffHunkStatus::added(hunk.secondary_status)),
+ excerpt_id: Some(excerpt.id),
+ };
+ offset = hunk_range.end;
+ regions.push(region);
+ }
+ }
+
+ // Add the buffer text for the rest of the excerpt.
+ let len = text.len();
+ text.extend(buffer.text_for_range(offset..buffer_range.end));
+ text.push('\n');
+ regions.push(ReferenceRegion {
+ buffer_id: Some(buffer_id),
+ range: len..text.len(),
+ buffer_range: Some((offset..buffer_range.end).to_point(&buffer)),
+ status: None,
+ excerpt_id: Some(excerpt.id),
+ });
+ }
}
// Remove final trailing newline.
@@ -2635,9 +2722,18 @@ impl ReferenceMultibuffer {
fn diffs_updated(&mut self, cx: &App) {
for excerpt in &mut self.excerpts {
let buffer = excerpt.buffer.read(cx).snapshot();
- let excerpt_range = excerpt.range.to_offset(&buffer);
let buffer_id = buffer.remote_id();
- let diff = self.diffs.get(&buffer_id).unwrap().read(cx).snapshot(cx);
+
+ // Skip inverted diff excerpts - hunks are always expanded
+ if self.inverted_diffs.contains_key(&buffer_id) {
+ continue;
+ }
+
+ let excerpt_range = excerpt.range.to_offset(&buffer);
+ let Some(diff) = self.diffs.get(&buffer_id) else {
+ continue;
+ };
+ let diff = diff.read(cx).snapshot(cx);
let mut hunks = diff.hunks_in_row_range(0..u32::MAX, &buffer).peekable();
excerpt.expanded_diff_hunks.retain(|hunk_anchor| {
if !hunk_anchor.is_valid(&buffer) {
@@ -2665,6 +2761,29 @@ impl ReferenceMultibuffer {
let buffer_id = diff.read(cx).buffer_id;
self.diffs.insert(buffer_id, diff);
}
+
+ fn add_inverted_diff(
+ &mut self,
+ diff: Entity<BufferDiff>,
+ main_buffer_snapshot: text::BufferSnapshot,
+ cx: &App,
+ ) {
+ let base_text_buffer_id = diff.read(cx).base_text(cx).remote_id();
+ self.inverted_diffs
+ .insert(base_text_buffer_id, (diff, main_buffer_snapshot));
+ }
+
+ fn update_inverted_diff_snapshot(
+ &mut self,
+ diff: &Entity<BufferDiff>,
+ main_buffer_snapshot: text::BufferSnapshot,
+ cx: &App,
+ ) {
+ let base_text_buffer_id = diff.read(cx).base_text(cx).remote_id();
+ if let Some((_, stored_snapshot)) = self.inverted_diffs.get_mut(&base_text_buffer_id) {
+ *stored_snapshot = main_buffer_snapshot;
+ }
+ }
}
#[gpui::test(iterations = 100)]
@@ -2754,6 +2873,8 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
let mut anchors = Vec::new();
let mut old_versions = Vec::new();
let mut needs_diff_calculation = false;
+ // Maps main_buffer_id -> diff for inverted diffs (needed for recalculation when main buffer is edited)
+ let mut inverted_diff_main_buffers: HashMap<BufferId, Entity<BufferDiff>> = HashMap::default();
for _ in 0..operations {
match rng.random_range(0..100) {
0..=14 if !buffers.is_empty() => {
@@ -2852,121 +2973,236 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
}
45..=55 if !reference.excerpts.is_empty() => {
multibuffer.update(cx, |multibuffer, cx| {
- let snapshot = multibuffer.snapshot(cx);
- let excerpt_ix = rng.random_range(0..reference.excerpts.len());
- let excerpt = &reference.excerpts[excerpt_ix];
- let start = excerpt.range.start;
- let end = excerpt.range.end;
- let range = snapshot.anchor_in_excerpt(excerpt.id, start).unwrap()
- ..snapshot.anchor_in_excerpt(excerpt.id, end).unwrap();
-
- log::info!(
- "expanding diff hunks in range {:?} (excerpt id {:?}, index {excerpt_ix:?}, buffer id {:?})",
- range.to_offset(&snapshot),
- excerpt.id,
- excerpt.buffer.read(cx).remote_id(),
- );
- reference.expand_diff_hunks(excerpt.id, start..end, cx);
- multibuffer.expand_diff_hunks(vec![range], cx);
- });
+ let snapshot = multibuffer.snapshot(cx);
+ let excerpt_ix = rng.random_range(0..reference.excerpts.len());
+ let excerpt = &reference.excerpts[excerpt_ix];
+
+ // Skip inverted excerpts - hunks can't be collapsed
+ let buffer_id = excerpt.buffer.read(cx).remote_id();
+ if reference.inverted_diffs.contains_key(&buffer_id) {
+ return;
+ }
+
+ let start = excerpt.range.start;
+ let end = excerpt.range.end;
+ let range = snapshot.anchor_in_excerpt(excerpt.id, start).unwrap()
+ ..snapshot.anchor_in_excerpt(excerpt.id, end).unwrap();
+
+ log::info!(
+ "expanding diff hunks in range {:?} (excerpt id {:?}, index {excerpt_ix:?}, buffer id {:?})",
+ range.to_offset(&snapshot),
+ excerpt.id,
+ buffer_id,
+ );
+ reference.expand_diff_hunks(excerpt.id, start..end, cx);
+ multibuffer.expand_diff_hunks(vec![range], cx);
+ });
}
56..=85 if needs_diff_calculation => {
multibuffer.update(cx, |multibuffer, cx| {
for buffer in multibuffer.all_buffers() {
let snapshot = buffer.read(cx).snapshot();
- multibuffer.diff_for(snapshot.remote_id()).unwrap().update(
- cx,
- |diff, cx| {
+ let buffer_id = snapshot.remote_id();
+
+ // Recalculate non-inverted diff if exists
+ if let Some(diff) = multibuffer.diff_for(buffer_id) {
+ diff.update(cx, |diff, cx| {
+ log::info!("recalculating diff for buffer {:?}", buffer_id,);
+ diff.recalculate_diff_sync(&snapshot.text, cx);
+ });
+ }
+
+ // Recalculate inverted diff if this is a main buffer for one
+ if let Some(inverted_diff) = inverted_diff_main_buffers.get(&buffer_id) {
+ inverted_diff.update(cx, |diff, cx| {
log::info!(
- "recalculating diff for buffer {:?}",
- snapshot.remote_id(),
+ "recalculating inverted diff for main buffer {:?}",
+ buffer_id,
);
diff.recalculate_diff_sync(&snapshot.text, cx);
- },
- );
+ });
+ // Update the stored snapshot in the reference model
+ reference.update_inverted_diff_snapshot(
+ inverted_diff,
+ snapshot.text.clone(),
+ cx,
+ );
+ }
}
reference.diffs_updated(cx);
needs_diff_calculation = false;
});
}
_ => {
- let buffer_handle = if buffers.is_empty() || rng.random_bool(0.4) {
- let mut base_text = util::RandomCharIter::new(&mut rng)
+ // Decide if we're creating a new buffer or reusing an existing one
+ let create_new_buffer = buffers.is_empty() || rng.random_bool(0.4);
+ // Decide if this should be an inverted diff excerpt (only for new buffers, 30% chance)
+ let create_inverted = create_new_buffer && rng.random_bool(0.3);
+
+ if create_inverted {
+ // Create a new main buffer
+ let mut main_buffer_text = util::RandomCharIter::new(&mut rng)
.take(256)
.collect::<String>();
+ let main_buffer = cx.new(|cx| Buffer::local(main_buffer_text.clone(), cx));
+ text::LineEnding::normalize(&mut main_buffer_text);
+ let main_buffer_id = main_buffer.read_with(cx, |buffer, _| buffer.remote_id());
+ base_texts.insert(main_buffer_id, main_buffer_text);
+ buffers.push(main_buffer.clone());
- let buffer = cx.new(|cx| Buffer::local(base_text.clone(), cx));
- text::LineEnding::normalize(&mut base_text);
- base_texts.insert(
- buffer.read_with(cx, |buffer, _| buffer.remote_id()),
- base_text,
- );
- buffers.push(buffer);
- buffers.last().unwrap()
- } else {
- buffers.choose(&mut rng).unwrap()
- };
+ // Generate different base text for the inverted diff
+ let inverted_base_text: String =
+ util::RandomCharIter::new(&mut rng).take(256).collect();
- let prev_excerpt_ix = rng.random_range(0..=reference.excerpts.len());
- let prev_excerpt_id = reference
- .excerpts
- .get(prev_excerpt_ix)
- .map_or(ExcerptId::max(), |e| e.id);
- let excerpt_ix = (prev_excerpt_ix + 1).min(reference.excerpts.len());
+ let diff = cx.new(|cx| {
+ BufferDiff::new_with_base_text(
+ &inverted_base_text,
+ &main_buffer.read(cx).text_snapshot(),
+ cx,
+ )
+ });
- let (range, anchor_range) = buffer_handle.read_with(cx, |buffer, _| {
- let end_row = rng.random_range(0..=buffer.max_point().row);
- let start_row = rng.random_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 base_text_buffer = diff.read_with(cx, |diff, _| diff.base_text_buffer());
+
+ // Track for recalculation when main buffer is edited
+ inverted_diff_main_buffers.insert(main_buffer_id, diff.clone());
+
+ let prev_excerpt_ix = rng.random_range(0..=reference.excerpts.len());
+ let prev_excerpt_id = reference
+ .excerpts
+ .get(prev_excerpt_ix)
+ .map_or(ExcerptId::max(), |e| e.id);
+ let excerpt_ix = (prev_excerpt_ix + 1).min(reference.excerpts.len());
+
+ // Create excerpt from base_text_buffer
+ let (range, anchor_range) = base_text_buffer.read_with(cx, |buffer, _| {
+ let end_row = rng.random_range(0..=buffer.max_point().row);
+ let start_row = rng.random_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 inverted excerpt at {} of {} for base_text_buffer {}: {:?}[{:?}] = {:?}",
+ excerpt_ix,
+ reference.excerpts.len(),
+ buffer.remote_id(),
+ buffer.text(),
+ start_ix..end_ix,
+ &buffer.text()[start_ix..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
+ .insert_excerpts_after(
+ prev_excerpt_id,
+ base_text_buffer.clone(),
+ [ExcerptRange::new(range.clone())],
+ cx,
+ )
+ .pop()
+ .unwrap()
+ });
+
+ reference.insert_excerpt_after(
+ prev_excerpt_id,
+ excerpt_id,
+ (base_text_buffer.clone(), anchor_range),
);
- (start_ix..end_ix, anchor_range)
- });
+ let main_buffer_snapshot =
+ main_buffer.read_with(cx, |buf, _| buf.text_snapshot());
+ multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.add_inverted_diff(diff.clone(), main_buffer.clone(), cx);
+ });
+ cx.update(|cx| {
+ reference.add_inverted_diff(diff, main_buffer_snapshot, cx);
+ });
+ } else {
+ // Non-inverted: existing logic
+ let buffer_handle = if create_new_buffer {
+ let mut base_text = util::RandomCharIter::new(&mut rng)
+ .take(256)
+ .collect::<String>();
- let excerpt_id = multibuffer.update(cx, |multibuffer, cx| {
- multibuffer
- .insert_excerpts_after(
- prev_excerpt_id,
- buffer_handle.clone(),
- [ExcerptRange::new(range.clone())],
- cx,
- )
- .pop()
- .unwrap()
- });
+ let buffer = cx.new(|cx| Buffer::local(base_text.clone(), cx));
+ text::LineEnding::normalize(&mut base_text);
+ base_texts.insert(
+ buffer.read_with(cx, |buffer, _| buffer.remote_id()),
+ base_text,
+ );
+ buffers.push(buffer);
+ buffers.last().unwrap()
+ } else {
+ buffers.choose(&mut rng).unwrap()
+ };
- reference.insert_excerpt_after(
- prev_excerpt_id,
- excerpt_id,
- (buffer_handle.clone(), anchor_range),
- );
+ let prev_excerpt_ix = rng.random_range(0..=reference.excerpts.len());
+ let prev_excerpt_id = reference
+ .excerpts
+ .get(prev_excerpt_ix)
+ .map_or(ExcerptId::max(), |e| e.id);
+ let excerpt_ix = (prev_excerpt_ix + 1).min(reference.excerpts.len());
+
+ let (range, anchor_range) = buffer_handle.read_with(cx, |buffer, _| {
+ let end_row = rng.random_range(0..=buffer.max_point().row);
+ let start_row = rng.random_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]
+ );
- multibuffer.update(cx, |multibuffer, cx| {
- let id = buffer_handle.read(cx).remote_id();
- if multibuffer.diff_for(id).is_none() {
- let base_text = base_texts.get(&id).unwrap();
- let diff = cx.new(|cx| {
- BufferDiff::new_with_base_text(
- base_text,
- &buffer_handle.read(cx).text_snapshot(),
+ (start_ix..end_ix, anchor_range)
+ });
+
+ let excerpt_id = multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer
+ .insert_excerpts_after(
+ prev_excerpt_id,
+ buffer_handle.clone(),
+ [ExcerptRange::new(range.clone())],
cx,
)
- });
- reference.add_diff(diff.clone(), cx);
- multibuffer.add_diff(diff, cx)
- }
- });
+ .pop()
+ .unwrap()
+ });
+
+ reference.insert_excerpt_after(
+ prev_excerpt_id,
+ excerpt_id,
+ (buffer_handle.clone(), anchor_range),
+ );
+
+ multibuffer.update(cx, |multibuffer, cx| {
+ let id = buffer_handle.read(cx).remote_id();
+ if multibuffer.diff_for(id).is_none() {
+ let base_text = base_texts.get(&id).unwrap();
+ let diff = cx.new(|cx| {
+ BufferDiff::new_with_base_text(
+ base_text,
+ &buffer_handle.read(cx).text_snapshot(),
+ cx,
+ )
+ });
+ reference.add_diff(diff.clone(), cx);
+ multibuffer.add_diff(diff, cx)
+ }
+ });
+ }
}
}
@@ -3052,7 +3288,12 @@ fn check_multibuffer(
expected_row_infos
.into_iter()
.filter_map(|info| {
- if info.diff_status.is_some_and(|status| status.is_deleted()) {
+ // For inverted diffs, deleted rows are visible and should be counted.
+ // Only filter out deleted rows that are NOT from inverted diffs.
+ let is_inverted_diff = info
+ .buffer_id
+ .is_some_and(|id| reference.inverted_diffs.contains_key(&id));
+ if info.diff_status.is_some_and(|status| status.is_deleted()) && !is_inverted_diff {
None
} else {
info.buffer_row