Remove empty line when collapsing diagnostics (#39459)

Vinicius da Motta created

Closes #39028

Fixed empty lines appearing when collapsing files with diagnostic
messages in the diagnostics panel.

Added a flag to track when processing a `FoldedBuffer` and skip
`Near/Below` blocks (diagnostic messages) that immediately follow it.
This prevents diagnostics from rendering as empty lines when their file
is collapsed.

Before:
<img width="1489" height="429" alt="before"
src="https://github.com/user-attachments/assets/5e233290-1f6e-403c-a6b3-a65107586d01"
/>

After:
<img width="981" height="270" alt="after"
src="https://github.com/user-attachments/assets/a877b651-6b7f-4441-805c-38ea41e73a18"
/>

Release Notes:
- Fixed empty lines when collapsing files with diagnostics in the
diagnostics panel

Change summary

crates/editor/src/display_map/block_map.rs | 96 ++++++++++++++++++++++++
1 file changed, 96 insertions(+)

Detailed changes

crates/editor/src/display_map/block_map.rs 🔗

@@ -689,6 +689,7 @@ 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 mut summary = TransformSummary {
                     input_rows: 0,
@@ -701,8 +702,12 @@ impl BlockMap {
                 match block_placement {
                     BlockPlacement::Above(position) => {
                         rows_before_block = position.0 - new_transforms.summary().input_rows;
+                        just_processed_folded_buffer = false;
                     }
                     BlockPlacement::Near(position) | BlockPlacement::Below(position) => {
+                        if just_processed_folded_buffer {
+                            continue;
+                        }
                         if position.0 + 1 < new_transforms.summary().input_rows {
                             continue;
                         }
@@ -711,6 +716,7 @@ impl BlockMap {
                     BlockPlacement::Replace(range) => {
                         rows_before_block = range.start().0 - new_transforms.summary().input_rows;
                         summary.input_rows = range.end().0 - range.start().0 + 1;
+                        just_processed_folded_buffer = matches!(block, Block::FoldedBuffer { .. });
                     }
                 }
 
@@ -3566,6 +3572,96 @@ mod tests {
         assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
     }
 
+    #[gpui::test]
+    fn test_folded_buffer_with_near_blocks(cx: &mut gpui::TestAppContext) {
+        cx.update(init_test);
+
+        let text = "line 1\nline 2\nline 3";
+        let buffer = cx.update(|cx| {
+            MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(2, 6)])], cx)
+        });
+        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
+        let buffer_ids = buffer_snapshot
+            .excerpts()
+            .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
+            .dedup()
+            .collect::<Vec<_>>();
+        assert_eq!(buffer_ids.len(), 1);
+        let buffer_id = buffer_ids[0];
+
+        let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
+        let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
+        let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
+        let (_, wrap_snapshot) =
+            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
+        let mut block_map = BlockMap::new(wrap_snapshot.clone(), 1, 1);
+
+        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
+        writer.insert(vec![BlockProperties {
+            style: BlockStyle::Fixed,
+            placement: BlockPlacement::Near(buffer_snapshot.anchor_after(Point::new(0, 0))),
+            height: Some(1),
+            render: Arc::new(|_| div().into_any()),
+            priority: 0,
+        }]);
+
+        let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
+        assert_eq!(blocks_snapshot.text(), "\nline 1\n\nline 2\nline 3");
+
+        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
+        buffer.read_with(cx, |buffer, cx| {
+            writer.fold_buffers([buffer_id], buffer, cx);
+        });
+
+        let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default());
+        assert_eq!(blocks_snapshot.text(), "");
+    }
+
+    #[gpui::test]
+    fn test_folded_buffer_with_near_blocks_on_last_line(cx: &mut gpui::TestAppContext) {
+        cx.update(init_test);
+
+        let text = "line 1\nline 2\nline 3\nline 4";
+        let buffer = cx.update(|cx| {
+            MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(3, 6)])], cx)
+        });
+        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
+        let buffer_ids = buffer_snapshot
+            .excerpts()
+            .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
+            .dedup()
+            .collect::<Vec<_>>();
+        assert_eq!(buffer_ids.len(), 1);
+        let buffer_id = buffer_ids[0];
+
+        let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
+        let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
+        let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
+        let (_, wrap_snapshot) =
+            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
+        let mut block_map = BlockMap::new(wrap_snapshot.clone(), 1, 1);
+
+        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
+        writer.insert(vec![BlockProperties {
+            style: BlockStyle::Fixed,
+            placement: BlockPlacement::Near(buffer_snapshot.anchor_after(Point::new(3, 6))),
+            height: Some(1),
+            render: Arc::new(|_| div().into_any()),
+            priority: 0,
+        }]);
+
+        let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
+        assert_eq!(blocks_snapshot.text(), "\nline 1\nline 2\nline 3\nline 4\n");
+
+        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
+        buffer.read_with(cx, |buffer, cx| {
+            writer.fold_buffers([buffer_id], buffer, cx);
+        });
+
+        let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default());
+        assert_eq!(blocks_snapshot.text(), "");
+    }
+
     fn init_test(cx: &mut gpui::App) {
         let settings = SettingsStore::test(cx);
         cx.set_global(settings);