diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 45c52db37ee1e9e0e1e25b745a42a7338e2e714e..5ff3979e7da848ddba98f5b6f8d1ea26ad990a81 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -604,19 +604,16 @@ impl<'a> Iterator for TabChunks<'a> { #[ztracing::instrument(skip_all)] fn next(&mut self) -> Option { - 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();