Fix *Map randomized tests (#64527)

Closes https://github.com/zed-industries/zed/issues/64444

Ensures that with `ITERATIONS=200000 OPERATIONS=30`, each of the
following tests pass: `test_random_inlays`, `test_random_folds`,
`test_random_tabs`, `test_random_wraps`, `test_random_display_map`,
`test_random_blocks`, and 12/14 shards of the diff test.

Each comment contains the bug and the fix explanation.

Release Notes:

- N/A
This commit is contained in:
Kirill Bulatov authored and GitHub committed 2026-09-23 16:13:31 +00:00
1 parent 9183ba4c55
commit eb2f43d982
6 files changed
+156 -37

No files matched your search

+10 -1
View File
@@ -1408,6 +1408,10 @@ fn compare_hunks(
let mut last_unchanged_new_hunk_end: Option<text::Anchor> = None;
let mut has_changes = false;
let mut extended_end_candidate: Option<text::Anchor> = None;
let base_text_changed = old_base_text.remote_id() != new_base_text.remote_id()
|| new_base_text
.version()
.changed_since(old_base_text.version());
loop {
match (new_cursor.item(), old_cursor.item()) {
@@ -1437,7 +1441,12 @@ fn compare_hunks(
new_cursor.next();
}
Ordering::Equal => {
if new_hunk != old_hunk {
let base_text_differs = base_text_changed
&& !old_base_text
.chars_for_range(old_hunk.diff_base_byte_range.clone())
.eq(new_base_text
.chars_for_range(new_hunk.diff_base_byte_range.clone()));
if new_hunk != old_hunk || base_text_differs {
has_changes = true;
extended_end_candidate = None;
start.get_or_insert(new_hunk.buffer_range.start);
+1 -6
View File
@@ -1135,7 +1135,6 @@ impl BlockMap {
// For each of these blocks, insert a new isomorphic transform preceding the block,
// and then insert the block itself.
let mut just_processed_folded_buffer = false;
for (block_placement, block) in blocks_in_edit.drain(..) {
let span =
ztracing::debug_span!("for block in edits", block_height = block.height());
@@ -1157,12 +1156,8 @@ impl BlockMap {
continue;
};
rows_before_block = delta;
just_processed_folded_buffer = false;
}
&BlockPlacement::Near(position) | &BlockPlacement::Below(position) => {
if just_processed_folded_buffer {
continue;
}
let Some(delta) = (position + RowDelta(1)).checked_sub(input_rows) else {
continue;
};
@@ -1174,7 +1169,6 @@ impl BlockMap {
};
rows_before_block = delta;
summary.input_rows = WrapRow(1) + (*range.end() - *range.start());
just_processed_folded_buffer = matches!(block, Block::FoldedBuffer { .. });
}
}
@@ -1627,6 +1621,7 @@ impl BlockMap {
});
blocks.dedup_by(|right, left| match (left.0.clone(), right.0.clone()) {
(BlockPlacement::Replace(range), BlockPlacement::Above(row))
| (BlockPlacement::Replace(range), BlockPlacement::Near(row))
| (BlockPlacement::Replace(range), BlockPlacement::Below(row)) => range.contains(&row),
(BlockPlacement::Replace(range_a), BlockPlacement::Replace(range_b)) => {
if range_a.end() >= range_b.start() && range_a.start() <= range_b.end() {
+84 -21
View File
@@ -518,14 +518,11 @@ impl FoldMap {
((edit.new.start + edit.old_len()).0.0 as isize + delta) as usize,
));
let anchor = inlay_snapshot
.buffer
.anchor_before(inlay_snapshot.to_buffer_offset(edit.new.start));
let mut folds_cursor = self
.snapshot
.folds
.cursor::<FoldRange>(&inlay_snapshot.buffer);
folds_cursor.seek(&FoldRange(anchor..Anchor::Max), Bias::Left);
folds_cursor.seek(&inlay_snapshot.to_buffer_offset(edit.new.start), Bias::Left);
let mut folds = iter::from_fn({
let inlay_snapshot = &inlay_snapshot;
@@ -634,6 +631,8 @@ impl FoldMap {
let mut fold_edits = Vec::with_capacity(inlay_edits.len());
{
let old_len = self.snapshot.inlay_snapshot.len();
let new_len = inlay_snapshot.len();
let mut old_transforms = self
.snapshot
.transforms
@@ -641,34 +640,92 @@ impl FoldMap {
let mut new_transforms =
new_transforms.cursor::<Dimensions<InlayOffset, FoldOffset>>(());
for mut edit in inlay_edits {
old_transforms.seek(&edit.old.start, Bias::Left);
if old_transforms.item().is_some_and(|t| t.is_fold()) {
edit.old.start = old_transforms.start().0;
let mut widened_edits = Vec::<InlayEdit>::with_capacity(inlay_edits.len());
let mut inlay_edits_iter = inlay_edits.into_iter().peekable();
while let Some(mut edit) = inlay_edits_iter.next() {
let previous_edit = widened_edits.last();
let mut merge_with_previous =
previous_edit.is_some_and(|previous| previous.old.end >= edit.old.start);
if !merge_with_previous {
let mut limit = match previous_edit {
Some(previous) => (edit.old.start - previous.old.end)
.min(edit.new.start - previous.new.end),
None => edit.old.start.0.0.min(edit.new.start.0.0),
};
loop {
old_transforms.seek(&edit.old.start, Bias::Left);
new_transforms.seek(&edit.new.start, Bias::Left);
let old_pull = if old_transforms.item().is_some_and(|t| t.is_fold()) {
edit.old.start - old_transforms.start().0
} else {
0
};
let new_pull = if new_transforms.item().is_some_and(|t| t.is_fold()) {
edit.new.start - new_transforms.start().0
} else {
0
};
let pull = old_pull.max(new_pull).min(limit);
if pull == 0 {
break;
}
edit.old.start -= pull;
edit.new.start -= pull;
limit -= pull;
}
merge_with_previous = previous_edit.is_some() && limit == 0;
}
let mut limit = match inlay_edits_iter.peek() {
Some(next) => {
(next.old.start - edit.old.end).min(next.new.start - edit.new.end)
}
None => (old_len - edit.old.end).min(new_len - edit.new.end),
};
loop {
old_transforms.seek_forward(&edit.old.end, Bias::Right);
new_transforms.seek_forward(&edit.new.end, Bias::Right);
let old_extension = if old_transforms.item().is_some_and(|t| t.is_fold()) {
old_transforms.end().0 - edit.old.end
} else {
0
};
let new_extension = if new_transforms.item().is_some_and(|t| t.is_fold()) {
new_transforms.end().0 - edit.new.end
} else {
0
};
let extension = old_extension.max(new_extension).min(limit);
if extension == 0 {
break;
}
edit.old.end += extension;
edit.new.end += extension;
limit -= extension;
}
if merge_with_previous {
if let Some(previous) = widened_edits.last_mut() {
previous.old.end = edit.old.end;
previous.new.end = edit.new.end;
}
} else {
widened_edits.push(edit);
}
}
for edit in widened_edits {
old_transforms.seek(&edit.old.start, Bias::Left);
let old_start =
old_transforms.start().1.0 + (edit.old.start - old_transforms.start().0);
old_transforms.seek_forward(&edit.old.end, Bias::Right);
if old_transforms.item().is_some_and(|t| t.is_fold()) {
old_transforms.next();
edit.old.end = old_transforms.start().0;
}
let old_end =
old_transforms.start().1.0 + (edit.old.end - old_transforms.start().0);
new_transforms.seek(&edit.new.start, Bias::Left);
if new_transforms.item().is_some_and(|t| t.is_fold()) {
edit.new.start = new_transforms.start().0;
}
let new_start =
new_transforms.start().1.0 + (edit.new.start - new_transforms.start().0);
new_transforms.seek_forward(&edit.new.end, Bias::Right);
if new_transforms.item().is_some_and(|t| t.is_fold()) {
new_transforms.next();
edit.new.end = new_transforms.start().0;
}
let new_end =
new_transforms.start().1.0 + (edit.new.end - new_transforms.start().0);
@@ -1360,6 +1417,12 @@ impl sum_tree::SeekTarget<'_, FoldSummary, FoldRange> for FoldRange {
}
}
impl sum_tree::SeekTarget<'_, FoldSummary, FoldRange> for MultiBufferOffset {
fn cmp(&self, cursor_location: &FoldRange, buffer: &MultiBufferSnapshot) -> Ordering {
Ord::cmp(self, &cursor_location.start.to_offset(buffer))
}
}
impl<'a> sum_tree::Dimension<'a, FoldSummary> for MultiBufferOffset {
fn zero(_cx: &MultiBufferSnapshot) -> Self {
Default::default()
+1 -1
View File
@@ -1137,7 +1137,7 @@ impl WrapPointCursor<'_> {
#[ztracing::instrument(skip_all)]
pub fn map(&mut self, point: TabPoint) -> WrapPoint {
let cursor = &mut self.cursor;
if cursor.did_seek() {
if cursor.did_seek() && point >= cursor.start().0 {
cursor.seek_forward(&point, Bias::Right);
} else {
cursor.seek(&point, Bias::Right);
+1 -1
View File
@@ -4958,7 +4958,7 @@ impl MultiBufferSnapshot {
base_text_byte_range.start..base_text_offset,
);
position.0.add_text_dim(&position_in_hunk);
} else if at_transform_end {
} else if at_transform_end && base_text_offset > base_text_byte_range.end {
// diff_base offset falls outside this hunk's range;
// advance to see if the next transform is a better fit.
diff_transforms.next();
+59 -7
View File
@@ -4,7 +4,7 @@ use gpui::{App, AppContext, Context, Entity};
use itertools::Itertools;
use language::{Buffer, BufferEditSource, BufferSnapshot};
use rope::Point;
use sum_tree::{Dimensions, SumTree};
use sum_tree::{Cursor, Dimensions, SumTree};
use text::{Bias, BufferId, Edit, OffsetRangeExt, Patch};
use util::rel_path::RelPath;
use ztracing::instrument;
@@ -460,6 +460,28 @@ impl MultiBuffer {
}
reused_excerpt
} else {
let context = next_excerpt.context.to_offset(buffer_snapshot);
let old_start = cursor.position.1;
let new_start = new_excerpts.summary().len();
let text_len = context.end - context.start;
let first_char_len = buffer_snapshot
.chars_at(context.start)
.next()
.map_or(0, char::len_utf8)
.min(text_len);
let last_char_len = buffer_snapshot
.reversed_chars_at(context.end)
.next()
.map_or(0, char::len_utf8)
.min(text_len);
for range in [0..first_char_len, text_len - last_char_len..text_len] {
patch.push_maybe_empty(Edit {
old: old_start + MultiBufferOffset(range.start)
..old_start + MultiBufferOffset(range.end),
new: new_start + MultiBufferOffset(range.start)
..new_start + MultiBufferOffset(range.end),
});
}
Excerpt::new(
path_key.clone(),
path_key_index,
@@ -590,6 +612,8 @@ impl MultiBuffer {
new_excerpts.append(suffix, ());
drop(cursor);
let edits =
include_preceding_separators(&snapshot.excerpts, &new_excerpts, patch.into_inner());
snapshot.excerpts = new_excerpts;
snapshot.buffers.insert(
buffer_id,
@@ -618,11 +642,7 @@ impl MultiBuffer {
snapshot.trailing_excerpt_update_count += 1;
}
let edits = Self::sync_diff_transforms(
&mut snapshot,
patch.into_inner(),
DiffChangeKind::BufferEdited,
);
let edits = Self::sync_diff_transforms(&mut snapshot, edits, DiffChangeKind::BufferEdited);
if !edits.is_empty() {
self.subscriptions.publish(edits);
cx.emit(Event::Edited {
@@ -795,6 +815,8 @@ impl MultiBuffer {
);
}
let excerpt_edits =
include_preceding_separators(&snapshot.excerpts, &new_excerpts, excerpt_edits);
snapshot.excerpts = new_excerpts;
let edits =
@@ -863,10 +885,12 @@ impl MultiBuffer {
old: edit_start..edit_end,
new: edit_start..edit_start,
};
let excerpt_edits =
include_preceding_separators(&snapshot.excerpts, &new_excerpts, vec![edit]);
snapshot.excerpts = new_excerpts;
let edits =
Self::sync_diff_transforms(&mut snapshot, vec![edit], DiffChangeKind::BufferEdited);
Self::sync_diff_transforms(&mut snapshot, excerpt_edits, DiffChangeKind::BufferEdited);
if !edits.is_empty() {
self.subscriptions.publish(edits);
}
@@ -878,3 +902,31 @@ impl MultiBuffer {
cx.notify();
}
}
fn include_preceding_separators(
old_excerpts: &SumTree<Excerpt>,
new_excerpts: &SumTree<Excerpt>,
edits: Vec<Edit<ExcerptOffset>>,
) -> Vec<Edit<ExcerptOffset>> {
let mut old_cursor = old_excerpts.cursor::<ExcerptOffset>(());
let mut new_cursor = new_excerpts.cursor::<ExcerptOffset>(());
let mut widened = Patch::empty();
for mut edit in edits {
if follows_separator(&mut old_cursor, edit.old.start)
&& follows_separator(&mut new_cursor, edit.new.start)
{
edit.old.start.0.0 -= 1;
edit.new.start.0.0 -= 1;
}
widened.push_maybe_empty(edit);
}
widened.into_inner()
}
fn follows_separator(cursor: &mut Cursor<Excerpt, ExcerptOffset>, offset: ExcerptOffset) -> bool {
cursor.seek(&offset, Bias::Left);
cursor.end() == offset
&& cursor
.item()
.is_some_and(|excerpt| excerpt.has_trailing_newline)
}