Fix panic when folding immediately after a tab (#49920) (cherry-pick to preview) (#50054)

zed-zippy[bot] and Conrad Irwin created

Cherry-pick of #49920 to preview

----
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

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>

Change summary

crates/editor/src/display_map/tab_map.rs | 63 ++++++++++++++++++++-----
1 file changed, 49 insertions(+), 14 deletions(-)

Detailed changes

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<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();