Fix panic when folding immediately after a tab (#49920)
fixes ZED-57B Before you mark this PR as ready for review, make sure that you have: - [ ] Added a solid test coverage and/or screenshots from doing manual testing - [ ] Done a self-review taking into account security and performance aspects - [ ] Aligned any UI changes with the [UI checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) Release Notes: - Fixed a (rare) panic when a fold was created immediately following a tab character
This commit is contained in:
1 parent
3333c79da1
commit
9e87fefe3f
1 file changed
+49
-14
@@ -604,19 +604,16 @@ impl<'a> Iterator for TabChunks<'a> {
|
||||
|
||||
#[ztracing::instrument(skip_all)]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.chunk.text.is_empty() {
|
||||
if let Some(chunk) = self.fold_chunks.next() {
|
||||
self.chunk = chunk;
|
||||
if self.inside_leading_tab {
|
||||
self.chunk.text = &self.chunk.text[1..];
|
||||
self.chunk.tabs >>= 1;
|
||||
self.chunk.chars >>= 1;
|
||||
self.chunk.newlines >>= 1;
|
||||
self.inside_leading_tab = false;
|
||||
self.input_column += 1;
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
while self.chunk.text.is_empty() {
|
||||
let chunk = self.fold_chunks.next()?;
|
||||
self.chunk = chunk;
|
||||
if self.inside_leading_tab {
|
||||
self.chunk.text = &self.chunk.text[1..];
|
||||
self.chunk.tabs >>= 1;
|
||||
self.chunk.chars >>= 1;
|
||||
self.chunk.newlines >>= 1;
|
||||
self.inside_leading_tab = false;
|
||||
self.input_column += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -710,7 +707,7 @@ mod tests {
|
||||
use crate::{
|
||||
MultiBuffer,
|
||||
display_map::{
|
||||
fold_map::{FoldMap, FoldOffset},
|
||||
fold_map::{FoldMap, FoldOffset, FoldPlaceholder},
|
||||
inlay_map::InlayMap,
|
||||
},
|
||||
};
|
||||
@@ -1090,6 +1087,44 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_empty_chunk_after_leading_tab_trim(cx: &mut gpui::App) {
|
||||
// We fold "hello" (offsets 1..6) so the fold map creates a
|
||||
// transform boundary at offset 1, producing a 1-byte fold chunk
|
||||
// for the tab.
|
||||
let text = "\thello";
|
||||
let buffer = MultiBuffer::build_simple(text, cx);
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
|
||||
let mut fold_map = FoldMap::new(inlay_snapshot.clone()).0;
|
||||
|
||||
let (mut writer, _, _) = fold_map.write(inlay_snapshot.clone(), vec![]);
|
||||
writer.fold(vec![(
|
||||
MultiBufferOffset(1)..MultiBufferOffset(6),
|
||||
FoldPlaceholder::test(),
|
||||
)]);
|
||||
let (fold_snapshot, _) = fold_map.read(inlay_snapshot, vec![]);
|
||||
|
||||
let tab_size = NonZeroU32::new(4).unwrap();
|
||||
let (_, tab_snapshot) = TabMap::new(fold_snapshot, tab_size);
|
||||
|
||||
// The tab at column 0 expands to 4 spaces (columns 0‥4).
|
||||
// Seek starting at column 2 (middle of that tab) so that
|
||||
// `inside_leading_tab = true` and `to_next_stop = 2`.
|
||||
// Set the end just past the tab expansion so the iterator must
|
||||
// process the tab byte from the fold chunk.
|
||||
let max = tab_snapshot.max_point();
|
||||
let start = TabPoint::new(0, 2);
|
||||
let end = max;
|
||||
|
||||
// This should not panic.
|
||||
let result: String = tab_snapshot
|
||||
.chunks(start..end, false, Highlights::default())
|
||||
.map(|c| c.text)
|
||||
.collect();
|
||||
assert!(!result.is_empty());
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
fn test_random_tabs(cx: &mut gpui::App, mut rng: StdRng) {
|
||||
let tab_size = NonZeroU32::new(rng.random_range(1..=4)).unwrap();
|
||||
|
||||
Reference in new issue
Block a user