Fix panic calling blocks_intersecting_buffer_range with an empty range (#28049)
Previously, when comparing a block with an empty range to an empty query range in non-inclusive mode, our binary search logic could end up computing an inverted range, causing a panic. This commit adds special casing when comparing empty blocks with empty ranges. cc @as-cii: I'm realizing that the approach to searching for the intersecting replacement blocks makes some invalid assumptions about the ordering of replace decorations. They aren't ordered at all by their end range. @maxbrunsfeld and I are wondering if long term, we should remove replace decorations and find another solution for folding buffers in multi buffers. Release Notes: - Fixed an occasional panic that would occur when navigating to the next change hunk with a pending inline transformation present. Co-authored-by: Peter Tripp <petertripp@gmail.com>
This commit is contained in:
parent
ec40e2d85c
commit
8b5ea05163
1 changed files with 46 additions and 9 deletions
|
@ -1235,21 +1235,21 @@ impl BlockMapWriter<'_> {
|
||||||
) -> &[Arc<CustomBlock>] {
|
) -> &[Arc<CustomBlock>] {
|
||||||
let wrap_snapshot = self.0.wrap_snapshot.borrow();
|
let wrap_snapshot = self.0.wrap_snapshot.borrow();
|
||||||
let buffer = wrap_snapshot.buffer_snapshot();
|
let buffer = wrap_snapshot.buffer_snapshot();
|
||||||
let start_block_ix = match self.0.custom_blocks.binary_search_by(|probe| {
|
|
||||||
probe
|
let start_block_ix = match self.0.custom_blocks.binary_search_by(|block| {
|
||||||
.end()
|
let block_end = block.end().to_offset(buffer);
|
||||||
.to_offset(buffer)
|
block_end.cmp(&range.start).then_with(|| {
|
||||||
.cmp(&range.start)
|
if inclusive || (range.is_empty() && block.start().to_offset(buffer) == block_end) {
|
||||||
.then(if inclusive {
|
|
||||||
Ordering::Greater
|
Ordering::Greater
|
||||||
} else {
|
} else {
|
||||||
Ordering::Less
|
Ordering::Less
|
||||||
})
|
}
|
||||||
|
})
|
||||||
}) {
|
}) {
|
||||||
Ok(ix) | Err(ix) => ix,
|
Ok(ix) | Err(ix) => ix,
|
||||||
};
|
};
|
||||||
let end_block_ix = match self.0.custom_blocks.binary_search_by(|probe| {
|
let end_block_ix = match self.0.custom_blocks.binary_search_by(|block| {
|
||||||
probe
|
block
|
||||||
.start()
|
.start()
|
||||||
.to_offset(buffer)
|
.to_offset(buffer)
|
||||||
.cmp(&range.end)
|
.cmp(&range.end)
|
||||||
|
@ -1261,6 +1261,7 @@ impl BlockMapWriter<'_> {
|
||||||
}) {
|
}) {
|
||||||
Ok(ix) | Err(ix) => ix,
|
Ok(ix) | Err(ix) => ix,
|
||||||
};
|
};
|
||||||
|
|
||||||
&self.0.custom_blocks[start_block_ix..end_block_ix]
|
&self.0.custom_blocks[start_block_ix..end_block_ix]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3505,6 +3506,42 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn test_remove_intersecting_replace_blocks_edge_case(cx: &mut gpui::TestAppContext) {
|
||||||
|
cx.update(init_test);
|
||||||
|
|
||||||
|
let text = "abc\ndef\nghi\njkl\nmno";
|
||||||
|
let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
|
||||||
|
let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
|
||||||
|
let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||||
|
let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||||
|
let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||||
|
let (_wrap_map, wraps_snapshot) =
|
||||||
|
cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
|
||||||
|
let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
|
||||||
|
|
||||||
|
let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
|
||||||
|
let _block_id = writer.insert(vec![BlockProperties {
|
||||||
|
style: BlockStyle::Fixed,
|
||||||
|
placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
|
||||||
|
height: 1,
|
||||||
|
render: Arc::new(|_| div().into_any()),
|
||||||
|
priority: 0,
|
||||||
|
}])[0];
|
||||||
|
|
||||||
|
let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
|
||||||
|
assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
|
||||||
|
|
||||||
|
let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
|
||||||
|
writer.remove_intersecting_replace_blocks(
|
||||||
|
[buffer_snapshot.anchor_after(Point::new(1, 0))
|
||||||
|
..buffer_snapshot.anchor_after(Point::new(1, 0))],
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
|
||||||
|
assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
|
||||||
|
}
|
||||||
|
|
||||||
fn init_test(cx: &mut gpui::App) {
|
fn init_test(cx: &mut gpui::App) {
|
||||||
let settings = SettingsStore::test(cx);
|
let settings = SettingsStore::test(cx);
|
||||||
cx.set_global(settings);
|
cx.set_global(settings);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue