mirror of
https://github.com/zed-industries/zed.git
synced 2026-10-28 08:55:27 +08:00
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:
1 parent
9183ba4c55
commit
eb2f43d982
6 files changed
+156
-37
No files matched your search
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
Reference in new issue
Block a user