Fix crash in BlockMap::sync when there are inlay hints w/ newlines ri… (#25598)

Max Brunsfeld and Cole Miller created

Closes https://github.com/zed-industries/zed/issues/25377

Release Notes:

- Fixed a crash that could happen when typing in the assistant panel
with edit predictions enabled.

---------

Co-authored-by: Cole Miller <m@cole-miller.net>

Change summary

crates/editor/src/display_map.rs           | 61 ++++++++++++++++++++++++
crates/editor/src/display_map/block_map.rs | 11 ++-
2 files changed, 68 insertions(+), 4 deletions(-)

Detailed changes

crates/editor/src/display_map.rs 🔗

@@ -1911,6 +1911,67 @@ pub mod tests {
         );
     }
 
+    #[gpui::test]
+    fn test_inlays_with_newlines_after_blocks(cx: &mut gpui::TestAppContext) {
+        cx.update(|cx| init_test(cx, |_| {}));
+
+        let buffer = cx.new(|cx| Buffer::local("a", cx));
+        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
+        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
+
+        let font_size = px(14.0);
+        let map = cx.new(|cx| {
+            DisplayMap::new(
+                buffer.clone(),
+                font("Helvetica"),
+                font_size,
+                None,
+                true,
+                1,
+                1,
+                1,
+                FoldPlaceholder::test(),
+                cx,
+            )
+        });
+
+        map.update(cx, |map, cx| {
+            map.insert_blocks(
+                [BlockProperties {
+                    placement: BlockPlacement::Above(
+                        buffer_snapshot.anchor_before(Point::new(0, 0)),
+                    ),
+                    height: 2,
+                    style: BlockStyle::Sticky,
+                    render: Arc::new(|_| div().into_any()),
+                    priority: 0,
+                }],
+                cx,
+            );
+        });
+        map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\na"));
+
+        map.update(cx, |map, cx| {
+            map.splice_inlays(
+                &[],
+                vec![Inlay {
+                    id: InlayId::InlineCompletion(0),
+                    position: buffer_snapshot.anchor_after(0),
+                    text: "\n".into(),
+                }],
+                cx,
+            );
+        });
+        map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\n\na"));
+
+        // Regression test: updating the display map does not crash when a
+        // block is immediately followed by a multi-line inlay.
+        buffer.update(cx, |buffer, cx| {
+            buffer.edit([(1..1, "b")], None, cx);
+        });
+        map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\n\nab"));
+    }
+
     #[gpui::test]
     async fn test_chunks(cx: &mut gpui::TestAppContext) {
         let text = r#"

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

@@ -638,10 +638,13 @@ impl BlockMap {
                 self.custom_blocks[start_block_ix..end_block_ix]
                     .iter()
                     .filter_map(|block| {
-                        Some((
-                            block.placement.to_wrap_row(wrap_snapshot)?,
-                            Block::Custom(block.clone()),
-                        ))
+                        let placement = block.placement.to_wrap_row(wrap_snapshot)?;
+                        if let BlockPlacement::Above(row) = placement {
+                            if row < new_start {
+                                return None;
+                            }
+                        }
+                        Some((placement, Block::Custom(block.clone())))
                     }),
             );