git: Follow-up fixes for custom blocks in the side-by-side diff (#48747)

Cole Miller created

- Ensure that both sides are passed the appropriate companion data to
preserve spacers when syncing
- Remove companion handling in codepaths related to range folding, since
this isn't supported in the side-by-side diff
- Move handling of buffer folding into the block map
- Rework `set_companion` to handle both `DisplayMap`s at once
- DRY some code around block map syncing in the `DisplayMap`

TODO:

- [x] diagnose and fix issue that causes balancing blocks not to render
properly when they are adjacent to spacers (e.g. merge conflict buttons)
- [x] clear balancing blocks when clearing companion
- [x] additional tests: interaction between spacers and balancing
blocks, resizing

Release Notes:

- N/A

Change summary

crates/editor/src/display_map.rs           | 889 ++++++-----------------
crates/editor/src/display_map/block_map.rs | 505 +++++++-----
crates/editor/src/split.rs                 | 760 ++++++++++++++++++-
crates/editor/src/test.rs                  |  15 
crates/text/src/patch.rs                   |   4 
5 files changed, 1,222 insertions(+), 951 deletions(-)

Detailed changes

crates/editor/src/display_map.rs 🔗

@@ -111,6 +111,7 @@ use ui::{SharedString, px};
 use unicode_segmentation::UnicodeSegmentation;
 use ztracing::instrument;
 
+use std::cell::RefCell;
 use std::{
     any::TypeId,
     borrow::Cow,
@@ -228,39 +229,34 @@ pub struct DisplayMap {
     lsp_folding_crease_ids: HashMap<BufferId, Vec<CreaseId>>,
 }
 
-// test change
-
 pub(crate) struct Companion {
     rhs_display_map_id: EntityId,
-    rhs_folded_buffers: HashSet<BufferId>,
     rhs_buffer_to_lhs_buffer: HashMap<BufferId, BufferId>,
     lhs_buffer_to_rhs_buffer: HashMap<BufferId, BufferId>,
     rhs_excerpt_to_lhs_excerpt: HashMap<ExcerptId, ExcerptId>,
     lhs_excerpt_to_rhs_excerpt: HashMap<ExcerptId, ExcerptId>,
     rhs_rows_to_lhs_rows: ConvertMultiBufferRows,
     lhs_rows_to_rhs_rows: ConvertMultiBufferRows,
-    rhs_custom_blocks_to_lhs_custom_blocks: HashMap<CustomBlockId, CustomBlockId>,
-    lhs_custom_blocks_to_rhs_custom_blocks: HashMap<CustomBlockId, CustomBlockId>,
+    rhs_custom_block_to_balancing_block: RefCell<HashMap<CustomBlockId, CustomBlockId>>,
+    lhs_custom_block_to_balancing_block: RefCell<HashMap<CustomBlockId, CustomBlockId>>,
 }
 
 impl Companion {
     pub(crate) fn new(
         rhs_display_map_id: EntityId,
-        rhs_folded_buffers: HashSet<BufferId>,
         rhs_rows_to_lhs_rows: ConvertMultiBufferRows,
         lhs_rows_to_rhs_rows: ConvertMultiBufferRows,
     ) -> Self {
         Self {
             rhs_display_map_id,
-            rhs_folded_buffers,
             rhs_buffer_to_lhs_buffer: Default::default(),
             lhs_buffer_to_rhs_buffer: Default::default(),
             rhs_excerpt_to_lhs_excerpt: Default::default(),
             lhs_excerpt_to_rhs_excerpt: Default::default(),
             rhs_rows_to_lhs_rows,
             lhs_rows_to_rhs_rows,
-            rhs_custom_blocks_to_lhs_custom_blocks: Default::default(),
-            lhs_custom_blocks_to_rhs_custom_blocks: Default::default(),
+            rhs_custom_block_to_balancing_block: Default::default(),
+            lhs_custom_block_to_balancing_block: Default::default(),
         }
     }
 
@@ -268,37 +264,17 @@ impl Companion {
         self.rhs_display_map_id == display_map_id
     }
 
-    pub(crate) fn companion_custom_block_to_custom_block(
+    pub(crate) fn custom_block_to_balancing_block(
         &self,
         display_map_id: EntityId,
-    ) -> &HashMap<CustomBlockId, CustomBlockId> {
+    ) -> &RefCell<HashMap<CustomBlockId, CustomBlockId>> {
         if self.is_rhs(display_map_id) {
-            &self.lhs_custom_blocks_to_rhs_custom_blocks
+            &self.rhs_custom_block_to_balancing_block
         } else {
-            &self.rhs_custom_blocks_to_lhs_custom_blocks
+            &self.lhs_custom_block_to_balancing_block
         }
     }
 
-    pub(crate) fn add_custom_block_mapping(
-        &mut self,
-        lhs_id: CustomBlockId,
-        rhs_id: CustomBlockId,
-    ) {
-        self.lhs_custom_blocks_to_rhs_custom_blocks
-            .insert(lhs_id, rhs_id);
-        self.rhs_custom_blocks_to_lhs_custom_blocks
-            .insert(rhs_id, lhs_id);
-    }
-
-    pub(crate) fn remove_custom_block_mapping(
-        &mut self,
-        lhs_id: &CustomBlockId,
-        rhs_id: &CustomBlockId,
-    ) {
-        self.lhs_custom_blocks_to_rhs_custom_blocks.remove(lhs_id);
-        self.rhs_custom_blocks_to_lhs_custom_blocks.remove(rhs_id);
-    }
-
     pub(crate) fn convert_rows_to_companion(
         &self,
         display_map_id: EntityId,
@@ -342,6 +318,34 @@ impl Companion {
         excerpt.patch.edit_for_old_position(point).new
     }
 
+    pub(crate) fn convert_point_to_companion(
+        &self,
+        display_map_id: EntityId,
+        our_snapshot: &MultiBufferSnapshot,
+        companion_snapshot: &MultiBufferSnapshot,
+        point: MultiBufferPoint,
+    ) -> Range<MultiBufferPoint> {
+        let (excerpt_map, convert_fn) = if self.is_rhs(display_map_id) {
+            (&self.rhs_excerpt_to_lhs_excerpt, self.rhs_rows_to_lhs_rows)
+        } else {
+            (&self.lhs_excerpt_to_rhs_excerpt, self.lhs_rows_to_rhs_rows)
+        };
+
+        let excerpt = convert_fn(
+            excerpt_map,
+            companion_snapshot,
+            our_snapshot,
+            (Bound::Included(point), Bound::Included(point)),
+        )
+        .into_iter()
+        .next();
+
+        let Some(excerpt) = excerpt else {
+            return Point::zero()..companion_snapshot.max_point();
+        };
+        excerpt.patch.edit_for_old_position(point).new
+    }
+
     pub(crate) fn companion_excerpt_to_excerpt(
         &self,
         display_map_id: EntityId,
@@ -468,59 +472,41 @@ impl DisplayMap {
         }
     }
 
+    // TODO(split-diff) figure out how to free the LHS from having to build a block map before this is called
     pub(crate) fn set_companion(
         &mut self,
-        companion: Option<(WeakEntity<DisplayMap>, Entity<Companion>)>,
+        companion: Option<(Entity<DisplayMap>, Entity<Companion>)>,
         cx: &mut Context<Self>,
     ) {
+        let this = cx.weak_entity();
+        // Reverting to no companion, recompute the block map to clear spacers
+        // and balancing blocks.
         let Some((companion_display_map, companion)) = companion else {
-            self.companion = None;
+            let Some((_, companion)) = self.companion.take() else {
+                return;
+            };
             let (snapshot, edits) = self.sync_through_wrap(cx);
             let edits = edits.compose([text::Edit {
                 old: WrapRow(0)..snapshot.max_point().row(),
                 new: WrapRow(0)..snapshot.max_point().row(),
             }]);
-            self.block_map.read(snapshot, edits, None);
+            self.block_map.write(snapshot, edits, None).remove(
+                companion
+                    .read(cx)
+                    .lhs_custom_block_to_balancing_block
+                    .borrow()
+                    .values()
+                    .copied()
+                    .collect(),
+            );
             return;
         };
-
-        // Second call to set_companion doesn't need to do anything
-        if companion_display_map
-            .update(cx, |companion_dm, _| companion_dm.companion.is_none())
-            .unwrap_or(true)
-        {
-            self.companion = Some((companion_display_map, companion));
-            return;
-        }
-
-        let rhs_display_map_id = companion.read(cx).rhs_display_map_id;
-        if self.entity_id != rhs_display_map_id {
-            let buffer_mapping = companion
-                .read(cx)
-                .buffer_to_companion_buffer(rhs_display_map_id);
-            self.block_map.folded_buffers = companion
-                .read(cx)
-                .rhs_folded_buffers
-                .iter()
-                .filter_map(|id| buffer_mapping.get(id).copied())
-                .collect();
-        }
+        assert_eq!(self.entity_id, companion.read(cx).rhs_display_map_id);
 
         let snapshot = self.unfold_intersecting([Anchor::min()..Anchor::max()], true, cx);
 
-        self.companion = Some((companion_display_map.clone(), companion));
-
-        let companion_wrap_data = companion_display_map
-            .update(cx, |dm, cx| dm.sync_through_wrap(cx))
-            .ok();
-        let companion = self.companion.as_ref().map(|(_, c)| c.read(cx));
-        let companion_view =
-            companion_wrap_data
-                .as_ref()
-                .zip(companion)
-                .map(|((snapshot, edits), companion)| {
-                    CompanionView::new(self.entity_id, snapshot, edits, companion)
-                });
+        let (companion_wrap_snapshot, companion_wrap_edits) =
+            companion_display_map.update(cx, |dm, cx| dm.sync_through_wrap(cx));
 
         let edits = Patch::new(
             [text::Edit {
@@ -530,58 +516,64 @@ impl DisplayMap {
             .into_iter()
             .collect(),
         );
-        self.block_map
-            .read(snapshot.clone(), edits.clone(), companion_view);
-
-        if let Some((companion_dm, _)) = &self.companion {
-            let _ = companion_dm.update(cx, |dm, cx| {
-                if let Some((companion_snapshot, companion_edits)) = companion_wrap_data {
-                    let their_companion_ref = dm.companion.as_ref().map(|(_, c)| c);
 
-                    dm.block_map.read(
-                        companion_snapshot,
-                        companion_edits,
-                        their_companion_ref.map(|c| {
-                            CompanionView::new(dm.entity_id, &snapshot, &edits, c.read(cx))
-                        }),
-                    );
-                }
-            });
-        }
-    }
+        let reader = self.block_map.read(
+            snapshot.clone(),
+            edits.clone(),
+            Some(CompanionView::new(
+                self.entity_id,
+                &companion_wrap_snapshot,
+                &companion_wrap_edits,
+                companion.read(cx),
+            )),
+        );
 
-    pub(crate) fn sync_custom_blocks_into_companion(&mut self, cx: &mut Context<Self>) {
-        if self.companion.is_none() {
-            return;
-        }
+        companion_display_map.update(cx, |companion_display_map, cx| {
+            for my_buffer in self.folded_buffers() {
+                let their_buffer = companion
+                    .read(cx)
+                    .rhs_buffer_to_lhs_buffer
+                    .get(my_buffer)
+                    .unwrap();
+                companion_display_map
+                    .block_map
+                    .folded_buffers
+                    .insert(*their_buffer);
+            }
+            for block in reader.blocks {
+                let Some(their_block) = block_map::balancing_block(
+                    &block.properties(),
+                    snapshot.buffer(),
+                    companion_wrap_snapshot.buffer(),
+                    self.entity_id,
+                    companion.read(cx),
+                ) else {
+                    continue;
+                };
+                let their_id = companion_display_map
+                    .block_map
+                    .insert_block_raw(their_block, companion_wrap_snapshot.buffer());
+                companion.update(cx, |companion, _cx| {
+                    companion
+                        .custom_block_to_balancing_block(self.entity_id)
+                        .borrow_mut()
+                        .insert(block.id, their_id);
+                });
+            }
+            companion_display_map.block_map.read(
+                companion_wrap_snapshot,
+                companion_wrap_edits,
+                Some(CompanionView::new(
+                    companion_display_map.entity_id,
+                    &snapshot,
+                    &edits,
+                    companion.read(cx),
+                )),
+            );
+            companion_display_map.companion = Some((this, companion.clone()));
+        });
 
-        let (self_wrap_snapshot, _) = self.sync_through_wrap(cx);
-        let (companion_dm, companion) = self
-            .companion
-            .as_ref()
-            .expect("companion must exist at this point");
-
-        companion
-            .update(cx, |companion, cx| {
-                companion_dm.update(cx, |dm, cx| {
-                    let (companion_snapshot, _) = dm.sync_through_wrap(cx);
-                    // Sync existing custom blocks to the companion
-                    for block in self
-                        .block_map
-                        .read(self_wrap_snapshot.clone(), Patch::default(), None)
-                        .blocks
-                    {
-                        dm.block_map.insert_custom_block_into_companion(
-                            self.entity_id,
-                            &companion_snapshot,
-                            block,
-                            self_wrap_snapshot.buffer_snapshot(),
-                            companion,
-                        )
-                    }
-                })
-            })
-            .ok();
+        self.companion = Some((companion_display_map.downgrade(), companion));
     }
 
     pub(crate) fn companion(&self) -> Option<&Entity<Companion>> {
@@ -612,6 +604,40 @@ impl DisplayMap {
             .update(cx, |map, cx| map.sync(snapshot, edits, cx))
     }
 
+    fn with_synced_companion_mut<R>(
+        display_map_id: EntityId,
+        companion: &Option<(WeakEntity<DisplayMap>, Entity<Companion>)>,
+        cx: &mut App,
+        callback: impl FnOnce(Option<CompanionViewMut<'_>>, &mut App) -> R,
+    ) -> R {
+        let Some((companion_display_map, companion)) = companion else {
+            return callback(None, cx);
+        };
+        let Some(companion_display_map) = companion_display_map.upgrade() else {
+            return callback(None, cx);
+        };
+        companion_display_map.update(cx, |companion_display_map, cx| {
+            let (companion_wrap_snapshot, companion_wrap_edits) =
+                companion_display_map.sync_through_wrap(cx);
+            companion_display_map
+                .buffer
+                .update(cx, |companion_multibuffer, cx| {
+                    companion.update(cx, |companion, cx| {
+                        let companion_view = CompanionViewMut::new(
+                            display_map_id,
+                            companion_display_map.entity_id,
+                            &companion_wrap_snapshot,
+                            &companion_wrap_edits,
+                            companion_multibuffer,
+                            companion,
+                            &mut companion_display_map.block_map,
+                        );
+                        callback(Some(companion_view), cx)
+                    })
+                })
+        })
+    }
+
     #[instrument(skip_all)]
     pub fn snapshot(&mut self, cx: &mut Context<Self>) -> DisplaySnapshot {
         let (self_wrap_snapshot, self_wrap_edits) = self.sync_through_wrap(cx);
@@ -729,29 +755,13 @@ impl DisplayMap {
         let edits = self.buffer_subscription.consume().into_inner();
         let tab_size = Self::tab_size(&self.buffer, cx);
 
-        let companion_wrap_data = self.companion.as_ref().and_then(|(companion_dm, _)| {
-            companion_dm
-                .update(cx, |dm, cx| dm.sync_through_wrap(cx))
-                .ok()
-        });
-
         let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot.clone(), edits);
         let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
         let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
-        let (snapshot, edits) = self
+        let (_snapshot, _edits) = self
             .wrap_map
             .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 
-        {
-            let companion_ref = self.companion.as_ref().map(|(_, c)| c.read(cx));
-            let companion_view = companion_wrap_data.as_ref().zip(companion_ref).map(
-                |((snapshot, edits), companion)| {
-                    CompanionView::new(self.entity_id, snapshot, edits, companion)
-                },
-            );
-            self.block_map.read(snapshot, edits, companion_view);
-        }
-
         let inline = creases.iter().filter_map(|crease| {
             if let Crease::Inline {
                 range, placeholder, ..
@@ -765,13 +775,10 @@ impl DisplayMap {
         let (snapshot, edits) = fold_map.fold(inline);
 
         let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
-        let (self_new_wrap_snapshot, self_new_wrap_edits) = self
+        let (snapshot, edits) = self
             .wrap_map
             .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 
-        let (self_wrap_snapshot, self_wrap_edits) =
-            (self_new_wrap_snapshot.clone(), self_new_wrap_edits.clone());
-
         let blocks = creases
             .into_iter()
             .filter_map(|crease| {
@@ -807,53 +814,7 @@ impl DisplayMap {
                 }
             });
 
-        if let Some((companion_dm, companion)) = self.companion.as_ref()
-            && let Some((snapshot, edits)) = companion_wrap_data.as_ref()
-        {
-            companion_dm
-                .update(cx, |dm, cx| {
-                    companion.update(cx, |companion, _| {
-                        self.block_map
-                            .write(
-                                self_new_wrap_snapshot,
-                                self_new_wrap_edits,
-                                Some(CompanionViewMut::new(
-                                    self.entity_id,
-                                    snapshot,
-                                    edits,
-                                    companion,
-                                    &mut dm.block_map,
-                                )),
-                            )
-                            .insert(blocks);
-                    })
-                })
-                .ok();
-        } else {
-            self.block_map
-                .write(self_new_wrap_snapshot, self_new_wrap_edits, None)
-                .insert(blocks);
-        };
-
-        if let Some((companion_dm, _)) = &self.companion {
-            let _ = companion_dm.update(cx, |dm, cx| {
-                if let Some((companion_snapshot, companion_edits)) = companion_wrap_data {
-                    let their_companion_ref = dm.companion.as_ref().map(|(_, c)| c.read(cx));
-                    dm.block_map.read(
-                        companion_snapshot,
-                        companion_edits,
-                        their_companion_ref.map(|c| {
-                            CompanionView::new(
-                                dm.entity_id,
-                                &self_wrap_snapshot,
-                                &self_wrap_edits,
-                                c,
-                            )
-                        }),
-                    );
-                }
-            });
-        }
+        self.block_map.write(snapshot, edits, None).insert(blocks);
     }
 
     /// Removes any folds with the given ranges.
@@ -868,28 +829,13 @@ impl DisplayMap {
         let edits = self.buffer_subscription.consume().into_inner();
         let tab_size = Self::tab_size(&self.buffer, cx);
 
-        let companion_wrap_data = self.companion.as_ref().and_then(|(companion_dm, _)| {
-            companion_dm
-                .update(cx, |dm, cx| dm.sync_through_wrap(cx))
-                .ok()
-        });
-
         let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
         let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
         let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
         let (snapshot, edits) = self
             .wrap_map
             .update(cx, |map, cx| map.sync(snapshot, edits, cx));
-
-        {
-            let companion_ref = self.companion.as_ref().map(|(_, c)| c.read(cx));
-            let companion_view = companion_wrap_data.as_ref().zip(companion_ref).map(
-                |((snapshot, edits), companion)| {
-                    CompanionView::new(self.entity_id, snapshot, edits, companion)
-                },
-            );
-            self.block_map.read(snapshot, edits, companion_view);
-        }
+        self.block_map.read(snapshot, edits, None);
 
         let (snapshot, edits) = fold_map.remove_folds(ranges, type_id);
         let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
@@ -897,53 +843,8 @@ impl DisplayMap {
             .wrap_map
             .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 
-        let (self_wrap_snapshot, self_wrap_edits) =
-            (self_new_wrap_snapshot.clone(), self_new_wrap_edits.clone());
-
-        if let Some((companion_dm, companion)) = self.companion.as_ref()
-            && let Some((snapshot, edits)) = companion_wrap_data.as_ref()
-        {
-            companion_dm
-                .update(cx, |dm, cx| {
-                    companion.update(cx, |companion, _| {
-                        self.block_map.write(
-                            self_new_wrap_snapshot,
-                            self_new_wrap_edits,
-                            Some(CompanionViewMut::new(
-                                self.entity_id,
-                                snapshot,
-                                edits,
-                                companion,
-                                &mut dm.block_map,
-                            )),
-                        );
-                    })
-                })
-                .ok();
-        } else {
-            self.block_map
-                .write(self_new_wrap_snapshot, self_new_wrap_edits, None);
-        }
-
-        if let Some((companion_dm, _)) = &self.companion {
-            let _ = companion_dm.update(cx, |dm, cx| {
-                if let Some((companion_snapshot, companion_edits)) = companion_wrap_data {
-                    let their_companion_ref = dm.companion.as_ref().map(|(_, c)| c.read(cx));
-                    dm.block_map.read(
-                        companion_snapshot,
-                        companion_edits,
-                        their_companion_ref.map(|c| {
-                            CompanionView::new(
-                                dm.entity_id,
-                                &self_wrap_snapshot,
-                                &self_wrap_edits,
-                                c,
-                            )
-                        }),
-                    );
-                }
-            });
-        }
+        self.block_map
+            .write(self_new_wrap_snapshot, self_new_wrap_edits, None);
     }
 
     /// Removes any folds whose ranges intersect any of the given ranges.
@@ -962,28 +863,13 @@ impl DisplayMap {
         let edits = self.buffer_subscription.consume().into_inner();
         let tab_size = Self::tab_size(&self.buffer, cx);
 
-        let companion_wrap_data = self.companion.as_ref().and_then(|(companion_dm, _)| {
-            companion_dm
-                .update(cx, |dm, cx| dm.sync_through_wrap(cx))
-                .ok()
-        });
-
         let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
         let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
         let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
         let (snapshot, edits) = self
             .wrap_map
             .update(cx, |map, cx| map.sync(snapshot, edits, cx));
-
-        {
-            let companion_ref = self.companion.as_ref().map(|(_, c)| c.read(cx));
-            let companion_view = companion_wrap_data.as_ref().zip(companion_ref).map(
-                |((snapshot, edits), companion)| {
-                    CompanionView::new(self.entity_id, snapshot, edits, companion)
-                },
-            );
-            self.block_map.read(snapshot, edits, companion_view);
-        }
+        self.block_map.read(snapshot, edits, None);
 
         let (snapshot, edits) =
             fold_map.unfold_intersecting(offset_ranges.iter().cloned(), inclusive);
@@ -992,56 +878,9 @@ impl DisplayMap {
             .wrap_map
             .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 
-        let (self_wrap_snapshot, self_wrap_edits) =
-            (self_new_wrap_snapshot.clone(), self_new_wrap_edits.clone());
-
-        if let Some((companion_dm, companion)) = self.companion.as_ref()
-            && let Some((snapshot, edits)) = companion_wrap_data.as_ref()
-        {
-            companion_dm
-                .update(cx, |dm, cx| {
-                    companion.update(cx, |companion, _| {
-                        self.block_map
-                            .write(
-                                self_new_wrap_snapshot.clone(),
-                                self_new_wrap_edits,
-                                Some(CompanionViewMut::new(
-                                    self.entity_id,
-                                    snapshot,
-                                    edits,
-                                    companion,
-                                    &mut dm.block_map,
-                                )),
-                            )
-                            .remove_intersecting_replace_blocks(offset_ranges, inclusive);
-                    })
-                })
-                .ok();
-        } else {
-            self.block_map
-                .write(self_new_wrap_snapshot.clone(), self_new_wrap_edits, None)
-                .remove_intersecting_replace_blocks(offset_ranges, inclusive);
-        }
-
-        if let Some((companion_dm, _)) = &self.companion {
-            let _ = companion_dm.update(cx, |dm, cx| {
-                if let Some((companion_snapshot, companion_edits)) = companion_wrap_data {
-                    let their_companion_ref = dm.companion.as_ref().map(|(_, c)| c.read(cx));
-                    dm.block_map.read(
-                        companion_snapshot,
-                        companion_edits,
-                        their_companion_ref.map(|c| {
-                            CompanionView::new(
-                                dm.entity_id,
-                                &self_wrap_snapshot,
-                                &self_wrap_edits,
-                                c,
-                            )
-                        }),
-                    );
-                }
-            });
-        }
+        self.block_map
+            .write(self_new_wrap_snapshot.clone(), self_new_wrap_edits, None)
+            .remove_intersecting_replace_blocks(offset_ranges, inclusive);
 
         self_new_wrap_snapshot
     }
@@ -1049,160 +888,35 @@ impl DisplayMap {
     #[instrument(skip_all)]
     pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
         let (self_wrap_snapshot, self_wrap_edits) = self.sync_through_wrap(cx);
+        self.block_map
+            .write(self_wrap_snapshot, self_wrap_edits, None)
+            .disable_header_for_buffer(buffer_id);
+    }
 
-        let companion_wrap_data = self.companion.as_ref().and_then(|(companion_dm, _)| {
-            companion_dm
-                .update(cx, |dm, cx| dm.sync_through_wrap(cx))
-                .ok()
-        });
-
-        if let Some((companion_dm, companion)) = self.companion.as_ref()
-            && let Some((snapshot, edits)) = companion_wrap_data.as_ref()
-        {
-            companion_dm
-                .update(cx, |dm, cx| {
-                    companion.update(cx, |companion, _| {
-                        self.block_map
-                            .write(
-                                self_wrap_snapshot.clone(),
-                                self_wrap_edits.clone(),
-                                Some(CompanionViewMut::new(
-                                    self.entity_id,
-                                    snapshot,
-                                    edits,
-                                    companion,
-                                    &mut dm.block_map,
-                                )),
-                            )
-                            .disable_header_for_buffer(buffer_id);
-                    })
-                })
-                .ok();
-        } else {
-            self.block_map
-                .write(self_wrap_snapshot.clone(), self_wrap_edits.clone(), None)
-                .disable_header_for_buffer(buffer_id);
-        }
-
-        if let Some((companion_dm, _)) = &self.companion {
-            let _ = companion_dm.update(cx, |dm, cx| {
-                if let Some((companion_snapshot, companion_edits)) = companion_wrap_data {
-                    let their_companion_ref = dm.companion.as_ref().map(|(_, c)| c.read(cx));
-                    dm.block_map.read(
-                        companion_snapshot,
-                        companion_edits,
-                        their_companion_ref.map(|c| {
-                            CompanionView::new(
-                                dm.entity_id,
-                                &self_wrap_snapshot,
-                                &self_wrap_edits,
-                                c,
-                            )
-                        }),
-                    );
-                }
-            });
-        }
-    }
-
-    #[instrument(skip_all)]
-    pub fn fold_buffers(
-        &mut self,
-        buffer_ids: impl IntoIterator<Item = language::BufferId>,
-        cx: &mut App,
-    ) {
-        let buffer_ids: Vec<_> = buffer_ids.into_iter().collect();
-
-        if let Some((_, companion_entity)) = &self.companion {
-            companion_entity.update(cx, |companion, _| {
-                if self.entity_id == companion.rhs_display_map_id {
-                    companion
-                        .rhs_folded_buffers
-                        .extend(buffer_ids.iter().copied());
-                } else {
-                    let rhs_ids = buffer_ids
-                        .iter()
-                        .filter_map(|id| companion.lhs_buffer_to_rhs_buffer.get(id).copied());
-                    companion.rhs_folded_buffers.extend(rhs_ids);
-                }
-            });
-        }
+    #[instrument(skip_all)]
+    pub fn fold_buffers(
+        &mut self,
+        buffer_ids: impl IntoIterator<Item = language::BufferId>,
+        cx: &mut App,
+    ) {
+        let buffer_ids: Vec<_> = buffer_ids.into_iter().collect();
 
         let (self_wrap_snapshot, self_wrap_edits) = self.sync_through_wrap(cx);
 
-        let companion_wrap_data = self.companion.as_ref().and_then(|(companion_dm, _)| {
-            companion_dm
-                .update(cx, |dm, cx| dm.sync_through_wrap(cx))
-                .ok()
-        });
-
-        if let Some((companion_dm, companion)) = self.companion.as_ref()
-            && let Some((snapshot, edits)) = companion_wrap_data.as_ref()
-        {
-            companion_dm
-                .update(cx, |dm, cx| {
-                    companion.update(cx, |companion, cx| {
-                        self.block_map
-                            .write(
-                                self_wrap_snapshot.clone(),
-                                self_wrap_edits.clone(),
-                                Some(CompanionViewMut::new(
-                                    self.entity_id,
-                                    snapshot,
-                                    edits,
-                                    companion,
-                                    &mut dm.block_map,
-                                )),
-                            )
-                            .fold_buffers(buffer_ids.iter().copied(), self.buffer.read(cx), cx);
-                    })
-                })
-                .ok();
-        } else {
-            self.block_map
-                .write(self_wrap_snapshot.clone(), self_wrap_edits.clone(), None)
-                .fold_buffers(buffer_ids.iter().copied(), self.buffer.read(cx), cx);
-        }
-
-        if let Some((companion_dm, companion_entity)) = &self.companion {
-            let buffer_mapping = companion_entity
-                .read(cx)
-                .buffer_to_companion_buffer(self.entity_id);
-            let their_buffer_ids: Vec<_> = buffer_ids
-                .iter()
-                .filter_map(|id| buffer_mapping.get(id).copied())
-                .collect();
-
-            let _ = companion_dm.update(cx, |dm, cx| {
-                if let Some((companion_snapshot, companion_edits)) = companion_wrap_data {
-                    if let Some((_, their_companion)) = dm.companion.as_ref() {
-                        their_companion.update(cx, |their_companion, cx| {
-                            let mut block_map = dm.block_map.write(
-                                companion_snapshot,
-                                companion_edits,
-                                Some(CompanionViewMut::new(
-                                    dm.entity_id,
-                                    &self_wrap_snapshot,
-                                    &self_wrap_edits,
-                                    their_companion,
-                                    &mut self.block_map,
-                                )),
-                            );
-                            if !their_buffer_ids.is_empty() {
-                                block_map.fold_buffers(their_buffer_ids, dm.buffer.read(cx), cx);
-                            }
-                        })
-                    } else {
-                        let mut block_map =
-                            dm.block_map
-                                .write(companion_snapshot, companion_edits, None);
-                        if !their_buffer_ids.is_empty() {
-                            block_map.fold_buffers(their_buffer_ids, dm.buffer.read(cx), cx);
-                        }
-                    }
-                }
-            });
-        }
+        Self::with_synced_companion_mut(
+            self.entity_id,
+            &self.companion,
+            cx,
+            |companion_view, cx| {
+                self.block_map
+                    .write(
+                        self_wrap_snapshot.clone(),
+                        self_wrap_edits.clone(),
+                        companion_view,
+                    )
+                    .fold_buffers(buffer_ids.iter().copied(), self.buffer.read(cx), cx);
+            },
+        )
     }
 
     #[instrument(skip_all)]
@@ -1213,97 +927,22 @@ impl DisplayMap {
     ) {
         let buffer_ids: Vec<_> = buffer_ids.into_iter().collect();
 
-        if let Some((_, companion_entity)) = &self.companion {
-            companion_entity.update(cx, |companion, _| {
-                if self.entity_id == companion.rhs_display_map_id {
-                    for id in &buffer_ids {
-                        companion.rhs_folded_buffers.remove(id);
-                    }
-                } else {
-                    for id in &buffer_ids {
-                        if let Some(rhs_id) = companion.lhs_buffer_to_rhs_buffer.get(id) {
-                            companion.rhs_folded_buffers.remove(rhs_id);
-                        }
-                    }
-                }
-            });
-        }
-
         let (self_wrap_snapshot, self_wrap_edits) = self.sync_through_wrap(cx);
 
-        let companion_wrap_data = self.companion.as_ref().and_then(|(companion_dm, _)| {
-            companion_dm
-                .update(cx, |dm, cx| dm.sync_through_wrap(cx))
-                .ok()
-        });
-
-        if let Some((companion_dm, companion)) = self.companion.as_ref()
-            && let Some((snapshot, edits)) = companion_wrap_data.as_ref()
-        {
-            companion_dm
-                .update(cx, |dm, cx| {
-                    companion.update(cx, |companion, cx| {
-                        self.block_map
-                            .write(
-                                self_wrap_snapshot.clone(),
-                                self_wrap_edits.clone(),
-                                Some(CompanionViewMut::new(
-                                    self.entity_id,
-                                    snapshot,
-                                    edits,
-                                    companion,
-                                    &mut dm.block_map,
-                                )),
-                            )
-                            .unfold_buffers(buffer_ids.iter().copied(), self.buffer.read(cx), cx);
-                    })
-                })
-                .ok();
-        } else {
-            self.block_map
-                .write(self_wrap_snapshot.clone(), self_wrap_edits.clone(), None)
-                .unfold_buffers(buffer_ids.iter().copied(), self.buffer.read(cx), cx);
-        }
-
-        if let Some((companion_dm, companion_entity)) = &self.companion {
-            let buffer_mapping = companion_entity
-                .read(cx)
-                .buffer_to_companion_buffer(self.entity_id);
-            let their_buffer_ids: Vec<_> = buffer_ids
-                .iter()
-                .filter_map(|id| buffer_mapping.get(id).copied())
-                .collect();
-
-            let _ = companion_dm.update(cx, |dm, cx| {
-                if let Some((companion_snapshot, companion_edits)) = companion_wrap_data {
-                    if let Some((_, their_companion)) = dm.companion.as_ref() {
-                        their_companion.update(cx, |their_companion, cx| {
-                            let mut block_map = dm.block_map.write(
-                                companion_snapshot,
-                                companion_edits,
-                                Some(CompanionViewMut::new(
-                                    dm.entity_id,
-                                    &self_wrap_snapshot,
-                                    &self_wrap_edits,
-                                    their_companion,
-                                    &mut self.block_map,
-                                )),
-                            );
-                            if !their_buffer_ids.is_empty() {
-                                block_map.unfold_buffers(their_buffer_ids, dm.buffer.read(cx), cx);
-                            }
-                        })
-                    } else {
-                        let mut block_map =
-                            dm.block_map
-                                .write(companion_snapshot, companion_edits, None);
-                        if !their_buffer_ids.is_empty() {
-                            block_map.unfold_buffers(their_buffer_ids, dm.buffer.read(cx), cx);
-                        }
-                    }
-                }
-            });
-        }
+        Self::with_synced_companion_mut(
+            self.entity_id,
+            &self.companion,
+            cx,
+            |companion_view, cx| {
+                self.block_map
+                    .write(
+                        self_wrap_snapshot.clone(),
+                        self_wrap_edits.clone(),
+                        companion_view,
+                    )
+                    .unfold_buffers(buffer_ids.iter().copied(), self.buffer.read(cx), cx);
+            },
+        )
     }
 
     #[instrument(skip_all)]
@@ -1419,122 +1058,40 @@ impl DisplayMap {
         cx: &mut Context<Self>,
     ) -> Vec<CustomBlockId> {
         let (self_wrap_snapshot, self_wrap_edits) = self.sync_through_wrap(cx);
-
-        let companion_wrap_data = self.companion.as_ref().and_then(|(companion_dm, _)| {
-            companion_dm
-                .update(cx, |dm, cx| dm.sync_through_wrap(cx))
-                .ok()
-        });
-
-        let result = if let Some((companion_dm, companion)) = self.companion.as_ref()
-            && let Some((snapshot, edits)) = companion_wrap_data.as_ref()
-        {
-            companion_dm
-                .update(cx, |dm, cx| {
-                    companion.update(cx, |companion, _| {
-                        self.block_map
-                            .write(
-                                self_wrap_snapshot.clone(),
-                                self_wrap_edits.clone(),
-                                Some(CompanionViewMut::new(
-                                    self.entity_id,
-                                    snapshot,
-                                    edits,
-                                    companion,
-                                    &mut dm.block_map,
-                                )),
-                            )
-                            .insert(blocks)
-                    })
-                })
-                .ok()
-                .expect("success inserting blocks with companion")
-        } else {
-            self.block_map
-                .write(self_wrap_snapshot.clone(), self_wrap_edits.clone(), None)
-                .insert(blocks)
-        };
-
-        if let Some((companion_dm, _)) = &self.companion {
-            let _ = companion_dm.update(cx, |dm, cx| {
-                if let Some((companion_snapshot, companion_edits)) = companion_wrap_data {
-                    let their_companion_ref = dm.companion.as_ref().map(|(_, c)| c.read(cx));
-                    dm.block_map.read(
-                        companion_snapshot,
-                        companion_edits,
-                        their_companion_ref.map(|c| {
-                            CompanionView::new(
-                                dm.entity_id,
-                                &self_wrap_snapshot,
-                                &self_wrap_edits,
-                                c,
-                            )
-                        }),
-                    );
-                }
-            });
-        }
-
-        result
+        Self::with_synced_companion_mut(
+            self.entity_id,
+            &self.companion,
+            cx,
+            |companion_view, _cx| {
+                self.block_map
+                    .write(
+                        self_wrap_snapshot.clone(),
+                        self_wrap_edits.clone(),
+                        companion_view,
+                    )
+                    .insert(blocks)
+            },
+        )
     }
 
     #[instrument(skip_all)]
     pub fn resize_blocks(&mut self, heights: HashMap<CustomBlockId, u32>, cx: &mut Context<Self>) {
         let (self_wrap_snapshot, self_wrap_edits) = self.sync_through_wrap(cx);
 
-        let companion_wrap_data = self.companion.as_ref().and_then(|(companion_dm, _)| {
-            companion_dm
-                .update(cx, |dm, cx| dm.sync_through_wrap(cx))
-                .ok()
-        });
-
-        if let Some((companion_dm, companion)) = self.companion.as_ref()
-            && let Some((snapshot, edits)) = companion_wrap_data.as_ref()
-        {
-            companion_dm
-                .update(cx, |dm, cx| {
-                    companion.update(cx, |companion, _| {
-                        self.block_map
-                            .write(
-                                self_wrap_snapshot.clone(),
-                                self_wrap_edits.clone(),
-                                Some(CompanionViewMut::new(
-                                    self.entity_id,
-                                    snapshot,
-                                    edits,
-                                    companion,
-                                    &mut dm.block_map,
-                                )),
-                            )
-                            .resize(heights);
-                    })
-                })
-                .ok();
-        } else {
-            self.block_map
-                .write(self_wrap_snapshot.clone(), self_wrap_edits.clone(), None)
-                .resize(heights);
-        }
-
-        if let Some((companion_dm, _)) = &self.companion {
-            let _ = companion_dm.update(cx, |dm, cx| {
-                if let Some((companion_snapshot, companion_edits)) = companion_wrap_data {
-                    let their_companion_ref = dm.companion.as_ref().map(|(_, c)| c.read(cx));
-                    dm.block_map.read(
-                        companion_snapshot,
-                        companion_edits,
-                        their_companion_ref.map(|c| {
-                            CompanionView::new(
-                                dm.entity_id,
-                                &self_wrap_snapshot,
-                                &self_wrap_edits,
-                                c,
-                            )
-                        }),
-                    );
-                }
-            });
-        }
+        Self::with_synced_companion_mut(
+            self.entity_id,
+            &self.companion,
+            cx,
+            |companion_view, _cx| {
+                self.block_map
+                    .write(
+                        self_wrap_snapshot.clone(),
+                        self_wrap_edits.clone(),
+                        companion_view,
+                    )
+                    .resize(heights);
+            },
+        )
     }
 
     #[instrument(skip_all)]

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

@@ -27,7 +27,7 @@ use std::{
 };
 use sum_tree::{Bias, ContextLessSummary, Dimensions, SumTree, TreeMap};
 use text::{BufferId, Edit};
-use ui::ElementId;
+use ui::{ElementId, IntoElement};
 
 const NEWLINES: &[u8; rope::Chunk::MASK_BITS] = &[b'\n'; _];
 const BULLETS: &[u8; rope::Chunk::MASK_BITS] = &[b'*'; _];
@@ -57,20 +57,17 @@ pub struct BlockMapWriter<'a> {
     companion: Option<BlockMapWriterCompanion<'a>>,
 }
 
-struct BlockMapWriterCompanion<'a>(CompanionViewMut<'a>);
-
-impl<'a> Deref for BlockMapWriterCompanion<'a> {
-    type Target = CompanionViewMut<'a>;
-
-    fn deref(&self) -> &Self::Target {
-        &self.0
-    }
+/// Auxiliary data needed when modifying a BlockMap whose parent DisplayMap has a companion.
+struct BlockMapWriterCompanion<'a> {
+    display_map_id: EntityId,
+    companion_wrap_snapshot: WrapSnapshot,
+    companion: &'a Companion,
+    inverse: Option<BlockMapInverseWriter<'a>>,
 }
 
-impl<'a> DerefMut for BlockMapWriterCompanion<'a> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.0
-    }
+struct BlockMapInverseWriter<'a> {
+    companion_multibuffer: &'a MultiBuffer,
+    companion_writer: Box<BlockMapWriter<'a>>,
 }
 
 #[derive(Clone)]
@@ -527,23 +524,23 @@ pub struct BlockRows<'a> {
 
 #[derive(Clone, Copy)]
 pub struct CompanionView<'a> {
-    entity_id: EntityId,
-    wrap_snapshot: &'a WrapSnapshot,
-    wrap_edits: &'a WrapPatch,
+    display_map_id: EntityId,
+    companion_wrap_snapshot: &'a WrapSnapshot,
+    companion_wrap_edits: &'a WrapPatch,
     companion: &'a Companion,
 }
 
 impl<'a> CompanionView<'a> {
     pub(crate) fn new(
-        entity_id: EntityId,
-        wrap_snapshot: &'a WrapSnapshot,
-        wrap_edits: &'a WrapPatch,
+        display_map_id: EntityId,
+        companion_wrap_snapshot: &'a WrapSnapshot,
+        companion_wrap_edits: &'a WrapPatch,
         companion: &'a Companion,
     ) -> Self {
         Self {
-            entity_id,
-            wrap_snapshot,
-            wrap_edits,
+            display_map_id,
+            companion_wrap_snapshot,
+            companion_wrap_edits,
             companion,
         }
     }
@@ -552,9 +549,9 @@ impl<'a> CompanionView<'a> {
 impl<'a> From<CompanionViewMut<'a>> for CompanionView<'a> {
     fn from(view_mut: CompanionViewMut<'a>) -> Self {
         Self {
-            entity_id: view_mut.entity_id,
-            wrap_snapshot: view_mut.wrap_snapshot,
-            wrap_edits: view_mut.wrap_edits,
+            display_map_id: view_mut.display_map_id,
+            companion_wrap_snapshot: view_mut.companion_wrap_snapshot,
+            companion_wrap_edits: view_mut.companion_wrap_edits,
             companion: view_mut.companion,
         }
     }
@@ -563,36 +560,42 @@ impl<'a> From<CompanionViewMut<'a>> for CompanionView<'a> {
 impl<'a> From<&'a CompanionViewMut<'a>> for CompanionView<'a> {
     fn from(view_mut: &'a CompanionViewMut<'a>) -> Self {
         Self {
-            entity_id: view_mut.entity_id,
-            wrap_snapshot: view_mut.wrap_snapshot,
-            wrap_edits: view_mut.wrap_edits,
+            display_map_id: view_mut.display_map_id,
+            companion_wrap_snapshot: view_mut.companion_wrap_snapshot,
+            companion_wrap_edits: view_mut.companion_wrap_edits,
             companion: view_mut.companion,
         }
     }
 }
 
 pub struct CompanionViewMut<'a> {
-    entity_id: EntityId,
-    wrap_snapshot: &'a WrapSnapshot,
-    wrap_edits: &'a WrapPatch,
-    companion: &'a mut Companion,
-    block_map: &'a mut BlockMap,
+    display_map_id: EntityId,
+    companion_display_map_id: EntityId,
+    companion_wrap_snapshot: &'a WrapSnapshot,
+    companion_wrap_edits: &'a WrapPatch,
+    companion_multibuffer: &'a MultiBuffer,
+    companion_block_map: &'a mut BlockMap,
+    companion: &'a Companion,
 }
 
 impl<'a> CompanionViewMut<'a> {
     pub(crate) fn new(
-        entity_id: EntityId,
-        wrap_snapshot: &'a WrapSnapshot,
-        wrap_edits: &'a WrapPatch,
-        companion: &'a mut Companion,
-        block_map: &'a mut BlockMap,
+        display_map_id: EntityId,
+        companion_display_map_id: EntityId,
+        companion_wrap_snapshot: &'a WrapSnapshot,
+        companion_wrap_edits: &'a WrapPatch,
+        companion_multibuffer: &'a MultiBuffer,
+        companion: &'a Companion,
+        companion_block_map: &'a mut BlockMap,
     ) -> Self {
         Self {
-            entity_id,
-            wrap_snapshot,
-            wrap_edits,
+            display_map_id,
+            companion_display_map_id,
+            companion_wrap_snapshot,
+            companion_wrap_edits,
+            companion_multibuffer,
             companion,
-            block_map,
+            companion_block_map,
         }
     }
 }
@@ -659,16 +662,76 @@ impl BlockMap {
     ) -> BlockMapWriter<'a> {
         self.sync(
             &wrap_snapshot,
-            edits,
+            edits.clone(),
             companion_view.as_ref().map(CompanionView::from),
         );
-        *self.wrap_snapshot.borrow_mut() = wrap_snapshot;
+        *self.wrap_snapshot.borrow_mut() = wrap_snapshot.clone();
+        let companion = if let Some(companion_view) = companion_view {
+            companion_view.companion_block_map.sync(
+                companion_view.companion_wrap_snapshot,
+                companion_view.companion_wrap_edits.clone(),
+                Some(CompanionView::new(
+                    companion_view.companion_display_map_id,
+                    &wrap_snapshot,
+                    &edits,
+                    companion_view.companion,
+                )),
+            );
+            *companion_view
+                .companion_block_map
+                .wrap_snapshot
+                .borrow_mut() = companion_view.companion_wrap_snapshot.clone();
+            Some(BlockMapWriterCompanion {
+                display_map_id: companion_view.display_map_id,
+                companion_wrap_snapshot: companion_view.companion_wrap_snapshot.clone(),
+                companion: companion_view.companion,
+                inverse: Some(BlockMapInverseWriter {
+                    companion_multibuffer: companion_view.companion_multibuffer,
+                    companion_writer: Box::new(BlockMapWriter {
+                        block_map: companion_view.companion_block_map,
+                        companion: Some(BlockMapWriterCompanion {
+                            display_map_id: companion_view.companion_display_map_id,
+                            companion_wrap_snapshot: wrap_snapshot,
+                            companion: companion_view.companion,
+                            inverse: None,
+                        }),
+                    }),
+                }),
+            })
+        } else {
+            None
+        };
         BlockMapWriter {
             block_map: self,
-            companion: companion_view.map(BlockMapWriterCompanion),
+            companion,
         }
     }
 
+    pub(crate) fn insert_block_raw(
+        &mut self,
+        block: BlockProperties<Anchor>,
+        buffer: &MultiBufferSnapshot,
+    ) -> CustomBlockId {
+        let id = CustomBlockId(self.next_block_id.fetch_add(1, SeqCst));
+        let block_ix = match self
+            .custom_blocks
+            .binary_search_by(|probe| probe.placement.cmp(&block.placement, &buffer))
+        {
+            Ok(ix) | Err(ix) => ix,
+        };
+        let new_block = Arc::new(CustomBlock {
+            id,
+            placement: block.placement.clone(),
+            height: block.height,
+            style: block.style,
+            render: Arc::new(Mutex::new(block.render.clone())),
+            priority: block.priority,
+        });
+        self.custom_blocks.insert(block_ix, new_block.clone());
+        self.custom_blocks_by_id.insert(id, new_block);
+        id
+    }
+
     #[ztracing::instrument(skip_all, fields(edits = ?edits))]
     fn sync(
         &self,
@@ -697,10 +760,10 @@ impl BlockMap {
 
         // Pull in companion edits to ensure we recompute spacers in ranges that have changed in the companion.
         if let Some(CompanionView {
-            wrap_snapshot: companion_new_snapshot,
-            wrap_edits: companion_edits,
+            companion_wrap_snapshot: companion_new_snapshot,
+            companion_wrap_edits: companion_edits,
             companion,
-            entity_id: display_map_id,
+            display_map_id,
             ..
         }) = companion_view
         {
@@ -962,9 +1025,9 @@ impl BlockMap {
             ));
 
             if let Some(CompanionView {
-                wrap_snapshot: companion_snapshot,
+                companion_wrap_snapshot: companion_snapshot,
                 companion,
-                entity_id: display_map_id,
+                display_map_id,
                 ..
             }) = companion_view
             {
@@ -1469,55 +1532,6 @@ impl BlockMap {
             _ => false,
         });
     }
-
-    pub(crate) fn insert_custom_block_into_companion(
-        &mut self,
-        entity_id: EntityId,
-        snapshot: &WrapSnapshot,
-        block: &CustomBlock,
-        companion_snapshot: &MultiBufferSnapshot,
-        companion: &mut Companion,
-    ) {
-        let their_anchor = block.placement.start();
-        let their_point = their_anchor.to_point(companion_snapshot);
-        let my_patches = companion.convert_rows_to_companion(
-            entity_id,
-            snapshot.buffer_snapshot(),
-            companion_snapshot,
-            (Bound::Included(their_point), Bound::Included(their_point)),
-        );
-        let my_excerpt = my_patches
-            .first()
-            .expect("at least one companion excerpt exists");
-        let my_range = my_excerpt.patch.edit_for_old_position(their_point).new;
-        let my_point = my_range.start;
-        let anchor = snapshot.buffer_snapshot().anchor_before(my_point);
-        let height = block.height.unwrap_or(1);
-        let new_block = BlockProperties {
-            placement: BlockPlacement::Above(anchor),
-            height: Some(height),
-            style: BlockStyle::Sticky,
-            render: Arc::new(move |cx| {
-                crate::EditorElement::render_spacer_block(
-                    cx.block_id,
-                    cx.height,
-                    cx.line_height,
-                    cx.window,
-                    cx.app,
-                )
-            }),
-            priority: 0,
-        };
-        log::debug!("Inserting matching companion custom block: {block:#?} => {new_block:#?}");
-        let new_block_id = self
-            .write(snapshot.clone(), Patch::default(), None)
-            .insert([new_block])[0];
-        if companion.is_rhs(entity_id) {
-            companion.add_custom_block_mapping(block.id, new_block_id);
-        } else {
-            companion.add_custom_block_mapping(new_block_id, block.id);
-        }
-    }
 }
 
 #[ztracing::instrument(skip(tree, wrap_snapshot))]
@@ -1629,6 +1643,63 @@ impl BlockMapReader<'_> {
     }
 }
 
+pub(crate) fn balancing_block(
+    my_block: &BlockProperties<Anchor>,
+    my_snapshot: &MultiBufferSnapshot,
+    their_snapshot: &MultiBufferSnapshot,
+    my_display_map_id: EntityId,
+    companion: &Companion,
+) -> Option<BlockProperties<Anchor>> {
+    let my_anchor = my_block.placement.start();
+    let my_point = my_anchor.to_point(&my_snapshot);
+    let their_range = companion.convert_point_to_companion(
+        my_display_map_id,
+        my_snapshot,
+        their_snapshot,
+        my_point,
+    );
+    let their_anchor = their_snapshot.anchor_at(their_range.start, my_anchor.bias());
+    let their_placement = match my_block.placement {
+        BlockPlacement::Above(_) => BlockPlacement::Above(their_anchor),
+        BlockPlacement::Below(_) => {
+            if their_range.is_empty() {
+                BlockPlacement::Above(their_anchor)
+            } else {
+                BlockPlacement::Below(their_anchor)
+            }
+        }
+        // Not supported for balancing
+        BlockPlacement::Near(_) | BlockPlacement::Replace(_) => return None,
+    };
+    Some(BlockProperties {
+        placement: their_placement,
+        height: my_block.height,
+        style: BlockStyle::Sticky,
+        render: Arc::new(move |cx| {
+            crate::EditorElement::render_spacer_block(
+                cx.block_id,
+                cx.height,
+                cx.line_height,
+                cx.window,
+                cx.app,
+            )
+        }),
+        priority: my_block.priority,
+    })
+}
+
+impl BlockMapWriterCompanion<'_> {
+    fn companion_view(&self) -> CompanionView<'_> {
+        static EMPTY_PATCH: Patch<WrapRow> = Patch::empty();
+        CompanionView {
+            display_map_id: self.display_map_id,
+            companion_wrap_snapshot: &self.companion_wrap_snapshot,
+            companion_wrap_edits: &EMPTY_PATCH,
+            companion: self.companion,
+        }
+    }
+}
+
 impl BlockMapWriter<'_> {
     #[ztracing::instrument(skip_all)]
     pub fn insert(
@@ -1638,20 +1709,21 @@ impl BlockMapWriter<'_> {
         let blocks = blocks.into_iter();
         let mut ids = Vec::with_capacity(blocks.size_hint().1.unwrap_or(0));
         let mut edits = Patch::default();
-        let wrap_snapshot = &*self.block_map.wrap_snapshot.borrow();
+        let wrap_snapshot = self.block_map.wrap_snapshot.borrow().clone();
         let buffer = wrap_snapshot.buffer_snapshot();
 
         let mut previous_wrap_row_range: Option<Range<WrapRow>> = None;
+        let mut companion_blocks = Vec::new();
         for block in blocks {
             if let BlockPlacement::Replace(_) = &block.placement {
                 debug_assert!(block.height.unwrap() > 0);
             }
 
-            let id = CustomBlockId(self.block_map.next_block_id.fetch_add(1, SeqCst));
+            let id = self.block_map.insert_block_raw(block.clone(), &buffer);
             ids.push(id);
 
-            let start = block.placement.start().to_point(buffer);
-            let end = block.placement.end().to_point(buffer);
+            let start = block.placement.start().to_point(&buffer);
+            let end = block.placement.end().to_point(&buffer);
             let start_wrap_row = wrap_snapshot.make_wrap_point(start, Bias::Left).row();
             let end_wrap_row = wrap_snapshot.make_wrap_point(end, Bias::Left).row();
 
@@ -1669,44 +1741,18 @@ impl BlockMapWriter<'_> {
                 });
                 (range.start, range.end)
             };
-            let block_ix = match self
-                .block_map
-                .custom_blocks
-                .binary_search_by(|probe| probe.placement.cmp(&block.placement, buffer))
-            {
-                Ok(ix) | Err(ix) => ix,
-            };
-            let new_block = Arc::new(CustomBlock {
-                id,
-                placement: block.placement.clone(),
-                height: block.height,
-                render: Arc::new(Mutex::new(block.render)),
-                style: block.style,
-                priority: block.priority,
-            });
-            self.block_map
-                .custom_blocks
-                .insert(block_ix, new_block.clone());
-            self.block_map
-                .custom_blocks_by_id
-                .insert(id, new_block.clone());
 
             // Insert a matching custom block in the companion (if any)
-            if let Some(CompanionViewMut {
-                entity_id: their_entity_id,
-                wrap_snapshot: their_snapshot,
-                block_map: their_block_map,
-                companion,
-                ..
-            }) = self.companion.as_deref_mut()
+            if let Some(companion) = &mut self.companion
+                && companion.inverse.is_some()
             {
-                their_block_map.insert_custom_block_into_companion(
-                    *their_entity_id,
-                    their_snapshot,
-                    &new_block,
-                    buffer,
-                    companion,
-                );
+                companion_blocks.extend(balancing_block(
+                    &block,
+                    &buffer,
+                    companion.companion_wrap_snapshot.buffer(),
+                    companion.display_map_id,
+                    companion.companion,
+                ));
             }
 
             edits = edits.compose([Edit {
@@ -1715,31 +1761,36 @@ impl BlockMapWriter<'_> {
             }]);
         }
 
-        let default_patch = Patch::default();
         self.block_map.sync(
-            wrap_snapshot,
+            &wrap_snapshot,
             edits,
-            self.companion.as_deref().map(
-                |CompanionViewMut {
-                     entity_id,
-                     wrap_snapshot,
-                     companion,
-                     ..
-                 }| {
-                    CompanionView::new(*entity_id, wrap_snapshot, &default_patch, companion)
-                },
-            ),
+            self.companion
+                .as_ref()
+                .map(BlockMapWriterCompanion::companion_view),
         );
+
+        if let Some(companion) = &mut self.companion
+            && let Some(inverse) = &mut companion.inverse
+        {
+            let companion_ids = inverse.companion_writer.insert(companion_blocks);
+            companion
+                .companion
+                .custom_block_to_balancing_block(companion.display_map_id)
+                .borrow_mut()
+                .extend(ids.iter().copied().zip(companion_ids));
+        }
+
         ids
     }
 
     #[ztracing::instrument(skip_all)]
     pub fn resize(&mut self, mut heights: HashMap<CustomBlockId, u32>) {
-        let wrap_snapshot = &*self.block_map.wrap_snapshot.borrow();
+        let wrap_snapshot = self.block_map.wrap_snapshot.borrow().clone();
         let buffer = wrap_snapshot.buffer_snapshot();
         let mut edits = Patch::default();
         let mut last_block_buffer_row = None;
 
+        let mut companion_heights = HashMap::default();
         for block in &mut self.block_map.custom_blocks {
             if let Some(new_height) = heights.remove(&block.id) {
                 if let BlockPlacement::Replace(_) = &block.placement {
@@ -1761,6 +1812,18 @@ impl BlockMapWriter<'_> {
                         .custom_blocks_by_id
                         .insert(block.id, new_block);
 
+                    if let Some(companion) = &self.companion
+                        && companion.inverse.is_some()
+                        && let Some(companion_block_id) = companion
+                            .companion
+                            .custom_block_to_balancing_block(companion.display_map_id)
+                            .borrow()
+                            .get(&block.id)
+                            .copied()
+                    {
+                        companion_heights.insert(companion_block_id, new_height);
+                    }
+
                     let start_row = block.placement.start().to_point(buffer).row;
                     let end_row = block.placement.end().to_point(buffer).row;
                     if last_block_buffer_row != Some(end_row) {
@@ -1785,21 +1848,18 @@ impl BlockMapWriter<'_> {
             }
         }
 
-        let default_patch = Patch::default();
         self.block_map.sync(
-            wrap_snapshot,
+            &wrap_snapshot,
             edits,
-            self.companion.as_deref().map(
-                |CompanionViewMut {
-                     entity_id,
-                     wrap_snapshot,
-                     companion,
-                     ..
-                 }| {
-                    CompanionView::new(*entity_id, wrap_snapshot, &default_patch, companion)
-                },
-            ),
+            self.companion
+                .as_ref()
+                .map(BlockMapWriterCompanion::companion_view),
         );
+        if let Some(companion) = &mut self.companion
+            && let Some(inverse) = &mut companion.inverse
+        {
+            inverse.companion_writer.resize(companion_heights);
+        }
     }
 
     #[ztracing::instrument(skip_all)]
@@ -1809,6 +1869,7 @@ impl BlockMapWriter<'_> {
         let mut edits = Patch::default();
         let mut last_block_buffer_row = None;
         let mut previous_wrap_row_range: Option<Range<WrapRow>> = None;
+        let mut companion_block_ids: HashSet<CustomBlockId> = HashSet::default();
         self.block_map.custom_blocks.retain(|block| {
             if block_ids.contains(&block.id) {
                 let start = block.placement.start().to_point(buffer);
@@ -1837,6 +1898,18 @@ impl BlockMapWriter<'_> {
                         new: start_row..end_row,
                     })
                 }
+                if let Some(companion) = &self.companion
+                    && companion.inverse.is_some()
+                {
+                    companion_block_ids.extend(
+                        companion
+                            .companion
+                            .custom_block_to_balancing_block(companion.display_map_id)
+                            .borrow()
+                            .get(&block.id)
+                            .copied(),
+                    );
+                }
                 false
             } else {
                 true
@@ -1846,51 +1919,23 @@ impl BlockMapWriter<'_> {
             .custom_blocks_by_id
             .retain(|id, _| !block_ids.contains(id));
 
-        if let Some(CompanionViewMut {
-            entity_id: their_entity_id,
-            wrap_snapshot: their_snapshot,
-            companion,
-            block_map: their_block_map,
-            ..
-        }) = self.companion.as_deref_mut()
-        {
-            let their_block_ids: HashSet<_> = block_ids
-                .iter()
-                .filter_map(|my_block_id| {
-                    let mapping = companion.companion_custom_block_to_custom_block(*their_entity_id);
-                    let their_block_id =
-                        mapping.get(my_block_id)?;
-                    log::debug!("Removing custom block in the companion with id {their_block_id:?} for mine {my_block_id:?}");
-                    Some(*their_block_id)
-                })
-                .collect();
-            for (lhs_id, rhs_id) in block_ids.iter().zip(their_block_ids.iter()) {
-                if !companion.is_rhs(*their_entity_id) {
-                    companion.remove_custom_block_mapping(lhs_id, rhs_id);
-                } else {
-                    companion.remove_custom_block_mapping(rhs_id, lhs_id);
-                }
-            }
-            their_block_map
-                .write(their_snapshot.clone(), Patch::default(), None)
-                .remove(their_block_ids);
-        }
-
-        let default_patch = Patch::default();
         self.block_map.sync(
             wrap_snapshot,
             edits,
-            self.companion.as_deref().map(
-                |CompanionViewMut {
-                     entity_id,
-                     wrap_snapshot,
-                     companion,
-                     ..
-                 }| {
-                    CompanionView::new(*entity_id, wrap_snapshot, &default_patch, companion)
-                },
-            ),
+            self.companion
+                .as_ref()
+                .map(BlockMapWriterCompanion::companion_view),
         );
+        if let Some(companion) = &mut self.companion
+            && let Some(inverse) = &mut companion.inverse
+        {
+            companion
+                .companion
+                .custom_block_to_balancing_block(companion.display_map_id)
+                .borrow_mut()
+                .retain(|id, _| !block_ids.contains(&id));
+            inverse.companion_writer.remove(companion_block_ids);
+        }
     }
 
     #[ztracing::instrument(skip_all)]
@@ -1947,6 +1992,7 @@ impl BlockMapWriter<'_> {
         cx: &App,
     ) {
         let mut ranges = Vec::new();
+        let mut companion_buffer_ids = HashSet::default();
         for buffer_id in buffer_ids {
             if fold {
                 self.block_map.folded_buffers.insert(buffer_id);
@@ -1954,6 +2000,17 @@ impl BlockMapWriter<'_> {
                 self.block_map.folded_buffers.remove(&buffer_id);
             }
             ranges.extend(multi_buffer.excerpt_ranges_for_buffer(buffer_id, cx));
+            if let Some(companion) = &self.companion
+                && companion.inverse.is_some()
+            {
+                companion_buffer_ids.extend(
+                    companion
+                        .companion
+                        .buffer_to_companion_buffer(companion.display_map_id)
+                        .get(&buffer_id)
+                        .copied(),
+                )
+            }
         }
         ranges.sort_unstable_by_key(|range| range.start);
 
@@ -1971,21 +2028,23 @@ impl BlockMapWriter<'_> {
             });
         }
 
-        let default_patch = Patch::default();
         self.block_map.sync(
             &wrap_snapshot,
-            edits,
-            self.companion.as_deref().map(
-                |CompanionViewMut {
-                     entity_id,
-                     wrap_snapshot,
-                     companion,
-                     ..
-                 }| {
-                    CompanionView::new(*entity_id, wrap_snapshot, &default_patch, companion)
-                },
-            ),
+            edits.clone(),
+            self.companion
+                .as_ref()
+                .map(BlockMapWriterCompanion::companion_view),
         );
+        if let Some(companion) = &mut self.companion
+            && let Some(inverse) = &mut companion.inverse
+        {
+            inverse.companion_writer.fold_or_unfold_buffers(
+                fold,
+                companion_buffer_ids,
+                inverse.companion_multibuffer,
+                cx,
+            );
+        }
     }
 
     #[ztracing::instrument(skip_all)]
@@ -2696,6 +2755,19 @@ impl CustomBlock {
     pub fn style(&self) -> BlockStyle {
         self.style
     }
+
+    pub fn properties(&self) -> BlockProperties<Anchor> {
+        BlockProperties {
+            placement: self.placement.clone(),
+            height: self.height,
+            style: self.style,
+            render: Arc::new(|_| {
+                // Not used
+                gpui::Empty.into_any_element()
+            }),
+            priority: self.priority,
+        }
+    }
 }
 
 impl Debug for CustomBlock {
@@ -4534,7 +4606,6 @@ mod tests {
         let companion = cx.new(|_| {
             let mut c = Companion::new(
                 rhs_entity_id,
-                Default::default(),
                 convert_rhs_rows_to_lhs,
                 convert_lhs_rows_to_rhs,
             );

crates/editor/src/split.rs 🔗

@@ -634,11 +634,8 @@ impl SplittableEditor {
                 .collect()
         };
 
-        let rhs_folded_buffers = rhs_display_map.read(cx).folded_buffers().clone();
-
         let mut companion = Companion::new(
             rhs_display_map_id,
-            rhs_folded_buffers,
             convert_rhs_rows_to_lhs,
             convert_lhs_rows_to_rhs,
         );
@@ -659,13 +656,7 @@ impl SplittableEditor {
         let companion = cx.new(|_| companion);
 
         rhs_display_map.update(cx, |dm, cx| {
-            dm.set_companion(Some((lhs_display_map.downgrade(), companion.clone())), cx);
-        });
-        lhs_display_map.update(cx, |dm, cx| {
-            dm.set_companion(Some((rhs_display_map.downgrade(), companion)), cx);
-        });
-        rhs_display_map.update(cx, |dm, cx| {
-            dm.sync_custom_blocks_into_companion(cx);
+            dm.set_companion(Some((lhs_display_map, companion.clone())), cx);
         });
 
         let shared_scroll_anchor = self
@@ -2017,7 +2008,7 @@ mod tests {
     use std::sync::Arc;
 
     use buffer_diff::BufferDiff;
-    use collections::HashSet;
+    use collections::{HashMap, HashSet};
     use fs::FakeFs;
     use gpui::Element as _;
     use gpui::{AppContext as _, Entity, Pixels, VisualTestContext};
@@ -2039,6 +2030,7 @@ mod tests {
     async fn init_test(
         cx: &mut gpui::TestAppContext,
         soft_wrap: SoftWrap,
+        style: DiffViewStyle,
     ) -> (Entity<SplittableEditor>, &mut VisualTestContext) {
         cx.update(|cx| {
             let store = SettingsStore::test(cx);
@@ -2055,26 +2047,22 @@ mod tests {
             multibuffer
         });
         let editor = cx.new_window_entity(|window, cx| {
-            let mut editor = SplittableEditor::new(
-                DiffViewStyle::Stacked,
+            let editor = SplittableEditor::new(
+                style,
                 rhs_multibuffer.clone(),
                 project.clone(),
                 workspace,
                 window,
                 cx,
             );
-            editor.split(&Default::default(), window, cx);
             editor.rhs_editor.update(cx, |editor, cx| {
                 editor.set_soft_wrap_mode(soft_wrap, cx);
             });
-            editor
-                .lhs
-                .as_ref()
-                .unwrap()
-                .editor
-                .update(cx, |editor, cx| {
+            if let Some(lhs) = &editor.lhs {
+                lhs.editor.update(cx, |editor, cx| {
                     editor.set_soft_wrap_mode(soft_wrap, cx);
                 });
+            }
             editor
         });
         (editor, cx)
@@ -2144,7 +2132,7 @@ mod tests {
     async fn test_random_split_editor(mut rng: StdRng, cx: &mut gpui::TestAppContext) {
         use rand::prelude::*;
 
-        let (editor, cx) = init_test(cx, SoftWrap::EditorWidth).await;
+        let (editor, cx) = init_test(cx, SoftWrap::EditorWidth, DiffViewStyle::SideBySide).await;
         let operations = std::env::var("OPERATIONS")
             .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
             .unwrap_or(10);
@@ -2228,7 +2216,8 @@ mod tests {
         use rope::Point;
         use unindent::Unindent as _;
 
-        let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await;
+        let (editor, mut cx) =
+            init_test(cx, SoftWrap::EditorWidth, DiffViewStyle::SideBySide).await;
 
         let base_text = "
             aaa
@@ -2357,7 +2346,8 @@ mod tests {
         use rope::Point;
         use unindent::Unindent as _;
 
-        let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await;
+        let (editor, mut cx) =
+            init_test(cx, SoftWrap::EditorWidth, DiffViewStyle::SideBySide).await;
 
         let base_text1 = "
             aaa
@@ -2515,7 +2505,8 @@ mod tests {
         use rope::Point;
         use unindent::Unindent as _;
 
-        let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await;
+        let (editor, mut cx) =
+            init_test(cx, SoftWrap::EditorWidth, DiffViewStyle::SideBySide).await;
 
         let base_text = "
             aaa
@@ -2634,7 +2625,8 @@ mod tests {
         use rope::Point;
         use unindent::Unindent as _;
 
-        let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await;
+        let (editor, mut cx) =
+            init_test(cx, SoftWrap::EditorWidth, DiffViewStyle::SideBySide).await;
 
         let base_text = "
             aaa
@@ -2763,7 +2755,8 @@ mod tests {
         use rope::Point;
         use unindent::Unindent as _;
 
-        let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await;
+        let (editor, mut cx) =
+            init_test(cx, SoftWrap::EditorWidth, DiffViewStyle::SideBySide).await;
 
         let base_text = "
             aaa
@@ -2888,7 +2881,8 @@ mod tests {
         use rope::Point;
         use unindent::Unindent as _;
 
-        let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await;
+        let (editor, mut cx) =
+            init_test(cx, SoftWrap::EditorWidth, DiffViewStyle::SideBySide).await;
 
         let base_text = "
             aaa
@@ -3001,7 +2995,8 @@ mod tests {
         use rope::Point;
         use unindent::Unindent as _;
 
-        let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await;
+        let (editor, mut cx) =
+            init_test(cx, SoftWrap::EditorWidth, DiffViewStyle::SideBySide).await;
 
         let text = "aaaa bbbb cccc dddd eeee ffff";
 
@@ -3069,7 +3064,8 @@ mod tests {
         use rope::Point;
         use unindent::Unindent as _;
 
-        let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await;
+        let (editor, mut cx) =
+            init_test(cx, SoftWrap::EditorWidth, DiffViewStyle::SideBySide).await;
 
         let base_text = "
             aaaa bbbb cccc dddd eeee ffff
@@ -3131,7 +3127,8 @@ mod tests {
         use rope::Point;
         use unindent::Unindent as _;
 
-        let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await;
+        let (editor, mut cx) =
+            init_test(cx, SoftWrap::EditorWidth, DiffViewStyle::SideBySide).await;
 
         let base_text = "
             aaaa bbbb cccc dddd eeee ffff
@@ -3200,7 +3197,8 @@ mod tests {
         use rope::Point;
         use unindent::Unindent as _;
 
-        let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await;
+        let (editor, mut cx) =
+            init_test(cx, SoftWrap::EditorWidth, DiffViewStyle::SideBySide).await;
 
         let text = "
             aaaa bbbb cccc dddd eeee ffff
@@ -3312,7 +3310,8 @@ mod tests {
         use rope::Point;
         use unindent::Unindent as _;
 
-        let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await;
+        let (editor, mut cx) =
+            init_test(cx, SoftWrap::EditorWidth, DiffViewStyle::SideBySide).await;
 
         let (buffer1, diff1) = buffer_with_diff("xxx\nyyy", "xxx\nyyy", &mut cx);
 
@@ -3416,7 +3415,8 @@ mod tests {
         use rope::Point;
         use unindent::Unindent as _;
 
-        let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await;
+        let (editor, mut cx) =
+            init_test(cx, SoftWrap::EditorWidth, DiffViewStyle::SideBySide).await;
 
         let base_text = "
             aaa
@@ -3498,7 +3498,8 @@ mod tests {
         use rope::Point;
         use unindent::Unindent as _;
 
-        let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await;
+        let (editor, mut cx) =
+            init_test(cx, SoftWrap::EditorWidth, DiffViewStyle::SideBySide).await;
 
         let base_text = "aaaa bbbb cccc dddd eeee ffff\n";
 
@@ -3577,7 +3578,8 @@ mod tests {
         use rope::Point;
         use unindent::Unindent as _;
 
-        let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await;
+        let (editor, mut cx) =
+            init_test(cx, SoftWrap::EditorWidth, DiffViewStyle::SideBySide).await;
 
         let base_text = "
             aaa
@@ -3699,7 +3701,8 @@ mod tests {
         use rope::Point;
         use unindent::Unindent as _;
 
-        let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await;
+        let (editor, mut cx) =
+            init_test(cx, SoftWrap::EditorWidth, DiffViewStyle::SideBySide).await;
 
         let base_text = "";
         let current_text = "
@@ -3775,7 +3778,8 @@ mod tests {
         use rope::Point;
         use unindent::Unindent as _;
 
-        let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await;
+        let (editor, mut cx) =
+            init_test(cx, SoftWrap::EditorWidth, DiffViewStyle::SideBySide).await;
 
         let base_text = "
             aaa
@@ -3870,7 +3874,7 @@ mod tests {
         use gpui::size;
         use rope::Point;
 
-        let (editor, mut cx) = init_test(cx, SoftWrap::None).await;
+        let (editor, mut cx) = init_test(cx, SoftWrap::None, DiffViewStyle::SideBySide).await;
 
         let long_line = "x".repeat(200);
         let mut lines: Vec<String> = (0..50).map(|i| format!("line {i}")).collect();
@@ -3953,7 +3957,8 @@ mod tests {
         use rope::Point;
         use unindent::Unindent as _;
 
-        let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await;
+        let (editor, mut cx) =
+            init_test(cx, SoftWrap::EditorWidth, DiffViewStyle::SideBySide).await;
 
         let base_text = "
             first line
@@ -4083,7 +4088,7 @@ mod tests {
         use rope::Point;
         use unindent::Unindent as _;
 
-        let (editor, mut cx) = init_test(cx, SoftWrap::None).await;
+        let (editor, mut cx) = init_test(cx, SoftWrap::None, DiffViewStyle::SideBySide).await;
 
         let base_text = "
             bbb
@@ -4163,10 +4168,9 @@ mod tests {
         let lhs_block_id = lhs_editor.read_with(cx, |lhs_editor, cx| {
             let display_map = lhs_editor.display_map.read(cx);
             let companion = display_map.companion().unwrap().read(cx);
-            let mapping = companion.companion_custom_block_to_custom_block(
-                rhs_editor.read(cx).display_map.entity_id(),
-            );
-            *mapping.get(&block_ids[0]).unwrap()
+            let mapping = companion
+                .custom_block_to_balancing_block(rhs_editor.read(cx).display_map.entity_id());
+            *mapping.borrow().get(&block_ids[0]).unwrap()
         });
 
         cx.update(|_, cx| {
@@ -4231,7 +4235,7 @@ mod tests {
         use rope::Point;
         use unindent::Unindent as _;
 
-        let (editor, mut cx) = init_test(cx, SoftWrap::None).await;
+        let (editor, mut cx) = init_test(cx, SoftWrap::None, DiffViewStyle::SideBySide).await;
 
         let base_text = "
             bbb
@@ -4324,12 +4328,11 @@ mod tests {
         let (lhs_block_id_1, lhs_block_id_2) = lhs_editor.read_with(cx, |lhs_editor, cx| {
             let display_map = lhs_editor.display_map.read(cx);
             let companion = display_map.companion().unwrap().read(cx);
-            let mapping = companion.companion_custom_block_to_custom_block(
-                rhs_editor.read(cx).display_map.entity_id(),
-            );
+            let mapping = companion
+                .custom_block_to_balancing_block(rhs_editor.read(cx).display_map.entity_id());
             (
-                *mapping.get(&block_ids[0]).unwrap(),
-                *mapping.get(&block_ids[1]).unwrap(),
+                *mapping.borrow().get(&block_ids[0]).unwrap(),
+                *mapping.borrow().get(&block_ids[1]).unwrap(),
             )
         });
 
@@ -4414,10 +4417,9 @@ mod tests {
         let lhs_block_id_2 = lhs_editor.read_with(cx, |lhs_editor, cx| {
             let display_map = lhs_editor.display_map.read(cx);
             let companion = display_map.companion().unwrap().read(cx);
-            let mapping = companion.companion_custom_block_to_custom_block(
-                rhs_editor.read(cx).display_map.entity_id(),
-            );
-            *mapping.get(&block_ids[1]).unwrap()
+            let mapping = companion
+                .custom_block_to_balancing_block(rhs_editor.read(cx).display_map.entity_id());
+            *mapping.borrow().get(&block_ids[1]).unwrap()
         });
 
         cx.update(|_, cx| {
@@ -4455,7 +4457,7 @@ mod tests {
         use rope::Point;
         use unindent::Unindent as _;
 
-        let (editor, mut cx) = init_test(cx, SoftWrap::None).await;
+        let (editor, mut cx) = init_test(cx, SoftWrap::None, DiffViewStyle::SideBySide).await;
 
         let base_text = "
             bbb
@@ -4559,12 +4561,11 @@ mod tests {
         let (lhs_block_id_1, lhs_block_id_2) = lhs_editor.read_with(cx, |lhs_editor, cx| {
             let display_map = lhs_editor.display_map.read(cx);
             let companion = display_map.companion().unwrap().read(cx);
-            let mapping = companion.companion_custom_block_to_custom_block(
-                rhs_editor.read(cx).display_map.entity_id(),
-            );
+            let mapping = companion
+                .custom_block_to_balancing_block(rhs_editor.read(cx).display_map.entity_id());
             (
-                *mapping.get(&block_ids[0]).unwrap(),
-                *mapping.get(&block_ids[1]).unwrap(),
+                *mapping.borrow().get(&block_ids[0]).unwrap(),
+                *mapping.borrow().get(&block_ids[1]).unwrap(),
             )
         });
 
@@ -4649,10 +4650,9 @@ mod tests {
         let lhs_block_id_2 = lhs_editor.read_with(cx, |lhs_editor, cx| {
             let display_map = lhs_editor.display_map.read(cx);
             let companion = display_map.companion().unwrap().read(cx);
-            let mapping = companion.companion_custom_block_to_custom_block(
-                rhs_editor.read(cx).display_map.entity_id(),
-            );
-            *mapping.get(&block_ids[1]).unwrap()
+            let mapping = companion
+                .custom_block_to_balancing_block(rhs_editor.read(cx).display_map.entity_id());
+            *mapping.borrow().get(&block_ids[1]).unwrap()
         });
 
         cx.update(|_, cx| {
@@ -4711,10 +4711,9 @@ mod tests {
         let lhs_block_id_3 = lhs_editor.read_with(cx, |lhs_editor, cx| {
             let display_map = lhs_editor.display_map.read(cx);
             let companion = display_map.companion().unwrap().read(cx);
-            let mapping = companion.companion_custom_block_to_custom_block(
-                rhs_editor.read(cx).display_map.entity_id(),
-            );
-            *mapping.get(&new_block_ids[0]).unwrap()
+            let mapping = companion
+                .custom_block_to_balancing_block(rhs_editor.read(cx).display_map.entity_id());
+            *mapping.borrow().get(&new_block_ids[0]).unwrap()
         });
 
         cx.update(|_, cx| {
@@ -4777,4 +4776,629 @@ mod tests {
             &mut cx,
         );
     }
+
+    #[gpui::test]
+    async fn test_buffer_folding_sync(cx: &mut gpui::TestAppContext) {
+        use rope::Point;
+        use unindent::Unindent as _;
+
+        let (editor, mut cx) = init_test(cx, SoftWrap::None, DiffViewStyle::Stacked).await;
+
+        let base_text1 = "
+            aaa
+            bbb
+            ccc"
+        .unindent();
+        let current_text1 = "
+            aaa
+            bbb
+            ccc"
+        .unindent();
+
+        let base_text2 = "
+            ddd
+            eee
+            fff"
+        .unindent();
+        let current_text2 = "
+            ddd
+            eee
+            fff"
+        .unindent();
+
+        let (buffer1, diff1) = buffer_with_diff(&base_text1, &current_text1, &mut cx);
+        let (buffer2, diff2) = buffer_with_diff(&base_text2, &current_text2, &mut cx);
+
+        let buffer1_id = buffer1.read_with(cx, |buffer, _| buffer.remote_id());
+        let buffer2_id = buffer2.read_with(cx, |buffer, _| buffer.remote_id());
+
+        editor.update(cx, |editor, cx| {
+            let path1 = PathKey::for_buffer(&buffer1, cx);
+            editor.set_excerpts_for_path(
+                path1,
+                buffer1.clone(),
+                vec![Point::new(0, 0)..buffer1.read(cx).max_point()],
+                0,
+                diff1.clone(),
+                cx,
+            );
+            let path2 = PathKey::for_buffer(&buffer2, cx);
+            editor.set_excerpts_for_path(
+                path2,
+                buffer2.clone(),
+                vec![Point::new(0, 0)..buffer2.read(cx).max_point()],
+                1,
+                diff2.clone(),
+                cx,
+            );
+        });
+
+        cx.run_until_parked();
+
+        editor.update(cx, |editor, cx| {
+            editor.rhs_editor.update(cx, |rhs_editor, cx| {
+                rhs_editor.fold_buffer(buffer1_id, cx);
+            });
+        });
+
+        cx.run_until_parked();
+
+        let rhs_buffer1_folded = editor.read_with(cx, |editor, cx| {
+            editor.rhs_editor.read(cx).is_buffer_folded(buffer1_id, cx)
+        });
+        assert!(
+            rhs_buffer1_folded,
+            "buffer1 should be folded in rhs before split"
+        );
+
+        editor.update_in(cx, |editor, window, cx| {
+            editor.split(&Default::default(), window, cx);
+        });
+
+        cx.run_until_parked();
+
+        let (rhs_editor, lhs_editor) = editor.read_with(cx, |editor, _cx| {
+            (
+                editor.rhs_editor.clone(),
+                editor.lhs.as_ref().unwrap().editor.clone(),
+            )
+        });
+
+        let rhs_buffer1_folded =
+            rhs_editor.read_with(cx, |editor, cx| editor.is_buffer_folded(buffer1_id, cx));
+        assert!(
+            rhs_buffer1_folded,
+            "buffer1 should be folded in rhs after split"
+        );
+
+        let base_buffer1_id = diff1.read_with(cx, |diff, cx| diff.base_text(cx).remote_id());
+        let lhs_buffer1_folded = lhs_editor.read_with(cx, |editor, cx| {
+            editor.is_buffer_folded(base_buffer1_id, cx)
+        });
+        assert!(
+            lhs_buffer1_folded,
+            "buffer1 should be folded in lhs after split"
+        );
+
+        assert_split_content(
+            &editor,
+            "
+            § <no file>
+            § -----
+            § <no file>
+            § -----
+            ddd
+            eee
+            fff"
+            .unindent(),
+            "
+            § <no file>
+            § -----
+            § <no file>
+            § -----
+            ddd
+            eee
+            fff"
+            .unindent(),
+            &mut cx,
+        );
+
+        editor.update(cx, |editor, cx| {
+            editor.rhs_editor.update(cx, |rhs_editor, cx| {
+                rhs_editor.fold_buffer(buffer2_id, cx);
+            });
+        });
+
+        cx.run_until_parked();
+
+        let rhs_buffer2_folded =
+            rhs_editor.read_with(cx, |editor, cx| editor.is_buffer_folded(buffer2_id, cx));
+        assert!(rhs_buffer2_folded, "buffer2 should be folded in rhs");
+
+        let base_buffer2_id = diff2.read_with(cx, |diff, cx| diff.base_text(cx).remote_id());
+        let lhs_buffer2_folded = lhs_editor.read_with(cx, |editor, cx| {
+            editor.is_buffer_folded(base_buffer2_id, cx)
+        });
+        assert!(lhs_buffer2_folded, "buffer2 should be folded in lhs");
+
+        let rhs_buffer1_still_folded =
+            rhs_editor.read_with(cx, |editor, cx| editor.is_buffer_folded(buffer1_id, cx));
+        assert!(
+            rhs_buffer1_still_folded,
+            "buffer1 should still be folded in rhs"
+        );
+
+        let lhs_buffer1_still_folded = lhs_editor.read_with(cx, |editor, cx| {
+            editor.is_buffer_folded(base_buffer1_id, cx)
+        });
+        assert!(
+            lhs_buffer1_still_folded,
+            "buffer1 should still be folded in lhs"
+        );
+
+        assert_split_content(
+            &editor,
+            "
+            § <no file>
+            § -----
+            § <no file>
+            § -----"
+                .unindent(),
+            "
+            § <no file>
+            § -----
+            § <no file>
+            § -----"
+                .unindent(),
+            &mut cx,
+        );
+    }
+
+    #[gpui::test]
+    async fn test_custom_block_in_middle_of_added_hunk(cx: &mut gpui::TestAppContext) {
+        use rope::Point;
+        use unindent::Unindent as _;
+
+        let (editor, mut cx) = init_test(cx, SoftWrap::None, DiffViewStyle::SideBySide).await;
+
+        let base_text = "
+            ddd
+            eee
+        "
+        .unindent();
+        let current_text = "
+            aaa
+            bbb
+            ccc
+            ddd
+            eee
+        "
+        .unindent();
+
+        let (buffer, diff) = buffer_with_diff(&base_text, &current_text, &mut cx);
+
+        editor.update(cx, |editor, cx| {
+            let path = PathKey::for_buffer(&buffer, cx);
+            editor.set_excerpts_for_path(
+                path,
+                buffer.clone(),
+                vec![Point::new(0, 0)..buffer.read(cx).max_point()],
+                0,
+                diff.clone(),
+                cx,
+            );
+        });
+
+        cx.run_until_parked();
+
+        assert_split_content(
+            &editor,
+            "
+            § <no file>
+            § -----
+            aaa
+            bbb
+            ccc
+            ddd
+            eee"
+            .unindent(),
+            "
+            § <no file>
+            § -----
+            § spacer
+            § spacer
+            § spacer
+            ddd
+            eee"
+            .unindent(),
+            &mut cx,
+        );
+
+        let block_ids = editor.update(cx, |splittable_editor, cx| {
+            splittable_editor.rhs_editor.update(cx, |rhs_editor, cx| {
+                let snapshot = rhs_editor.buffer().read(cx).snapshot(cx);
+                let anchor = snapshot.anchor_before(Point::new(2, 0));
+                rhs_editor.insert_blocks(
+                    [BlockProperties {
+                        placement: BlockPlacement::Above(anchor),
+                        height: Some(1),
+                        style: BlockStyle::Fixed,
+                        render: Arc::new(|_| div().into_any()),
+                        priority: 0,
+                    }],
+                    None,
+                    cx,
+                )
+            })
+        });
+
+        let rhs_editor = editor.read_with(cx, |editor, _| editor.rhs_editor.clone());
+        let lhs_editor =
+            editor.read_with(cx, |editor, _| editor.lhs.as_ref().unwrap().editor.clone());
+
+        cx.update(|_, cx| {
+            set_block_content_for_tests(&rhs_editor, block_ids[0], cx, |_| {
+                "custom block".to_string()
+            });
+        });
+
+        let lhs_block_id = lhs_editor.read_with(cx, |lhs_editor, cx| {
+            let display_map = lhs_editor.display_map.read(cx);
+            let companion = display_map.companion().unwrap().read(cx);
+            let mapping = companion
+                .custom_block_to_balancing_block(rhs_editor.read(cx).display_map.entity_id());
+            *mapping.borrow().get(&block_ids[0]).unwrap()
+        });
+
+        cx.update(|_, cx| {
+            set_block_content_for_tests(&lhs_editor, lhs_block_id, cx, |_| {
+                "custom block".to_string()
+            });
+        });
+
+        cx.run_until_parked();
+
+        assert_split_content(
+            &editor,
+            "
+            § <no file>
+            § -----
+            aaa
+            bbb
+            § custom block
+            ccc
+            ddd
+            eee"
+            .unindent(),
+            "
+            § <no file>
+            § -----
+            § spacer
+            § spacer
+            § spacer
+            § custom block
+            ddd
+            eee"
+            .unindent(),
+            &mut cx,
+        );
+
+        editor.update(cx, |splittable_editor, cx| {
+            splittable_editor.rhs_editor.update(cx, |rhs_editor, cx| {
+                rhs_editor.remove_blocks(HashSet::from_iter(block_ids), None, cx);
+            });
+        });
+
+        cx.run_until_parked();
+
+        assert_split_content(
+            &editor,
+            "
+            § <no file>
+            § -----
+            aaa
+            bbb
+            ccc
+            ddd
+            eee"
+            .unindent(),
+            "
+            § <no file>
+            § -----
+            § spacer
+            § spacer
+            § spacer
+            ddd
+            eee"
+            .unindent(),
+            &mut cx,
+        );
+    }
+
+    #[gpui::test]
+    async fn test_custom_block_below_in_middle_of_added_hunk(cx: &mut gpui::TestAppContext) {
+        use rope::Point;
+        use unindent::Unindent as _;
+
+        let (editor, mut cx) = init_test(cx, SoftWrap::None, DiffViewStyle::SideBySide).await;
+
+        let base_text = "
+            ddd
+            eee
+        "
+        .unindent();
+        let current_text = "
+            aaa
+            bbb
+            ccc
+            ddd
+            eee
+        "
+        .unindent();
+
+        let (buffer, diff) = buffer_with_diff(&base_text, &current_text, &mut cx);
+
+        editor.update(cx, |editor, cx| {
+            let path = PathKey::for_buffer(&buffer, cx);
+            editor.set_excerpts_for_path(
+                path,
+                buffer.clone(),
+                vec![Point::new(0, 0)..buffer.read(cx).max_point()],
+                0,
+                diff.clone(),
+                cx,
+            );
+        });
+
+        cx.run_until_parked();
+
+        assert_split_content(
+            &editor,
+            "
+            § <no file>
+            § -----
+            aaa
+            bbb
+            ccc
+            ddd
+            eee"
+            .unindent(),
+            "
+            § <no file>
+            § -----
+            § spacer
+            § spacer
+            § spacer
+            ddd
+            eee"
+            .unindent(),
+            &mut cx,
+        );
+
+        let block_ids = editor.update(cx, |splittable_editor, cx| {
+            splittable_editor.rhs_editor.update(cx, |rhs_editor, cx| {
+                let snapshot = rhs_editor.buffer().read(cx).snapshot(cx);
+                let anchor = snapshot.anchor_after(Point::new(1, 3));
+                rhs_editor.insert_blocks(
+                    [BlockProperties {
+                        placement: BlockPlacement::Below(anchor),
+                        height: Some(1),
+                        style: BlockStyle::Fixed,
+                        render: Arc::new(|_| div().into_any()),
+                        priority: 0,
+                    }],
+                    None,
+                    cx,
+                )
+            })
+        });
+
+        let rhs_editor = editor.read_with(cx, |editor, _| editor.rhs_editor.clone());
+        let lhs_editor =
+            editor.read_with(cx, |editor, _| editor.lhs.as_ref().unwrap().editor.clone());
+
+        cx.update(|_, cx| {
+            set_block_content_for_tests(&rhs_editor, block_ids[0], cx, |_| {
+                "custom block".to_string()
+            });
+        });
+
+        let lhs_block_id = lhs_editor.read_with(cx, |lhs_editor, cx| {
+            let display_map = lhs_editor.display_map.read(cx);
+            let companion = display_map.companion().unwrap().read(cx);
+            let mapping = companion
+                .custom_block_to_balancing_block(rhs_editor.read(cx).display_map.entity_id());
+            *mapping.borrow().get(&block_ids[0]).unwrap()
+        });
+
+        cx.update(|_, cx| {
+            set_block_content_for_tests(&lhs_editor, lhs_block_id, cx, |_| {
+                "custom block".to_string()
+            });
+        });
+
+        cx.run_until_parked();
+
+        assert_split_content(
+            &editor,
+            "
+            § <no file>
+            § -----
+            aaa
+            bbb
+            § custom block
+            ccc
+            ddd
+            eee"
+            .unindent(),
+            "
+            § <no file>
+            § -----
+            § spacer
+            § spacer
+            § spacer
+            § custom block
+            ddd
+            eee"
+            .unindent(),
+            &mut cx,
+        );
+
+        editor.update(cx, |splittable_editor, cx| {
+            splittable_editor.rhs_editor.update(cx, |rhs_editor, cx| {
+                rhs_editor.remove_blocks(HashSet::from_iter(block_ids), None, cx);
+            });
+        });
+
+        cx.run_until_parked();
+
+        assert_split_content(
+            &editor,
+            "
+            § <no file>
+            § -----
+            aaa
+            bbb
+            ccc
+            ddd
+            eee"
+            .unindent(),
+            "
+            § <no file>
+            § -----
+            § spacer
+            § spacer
+            § spacer
+            ddd
+            eee"
+            .unindent(),
+            &mut cx,
+        );
+    }
+
+    #[gpui::test]
+    async fn test_custom_block_resize_syncs_balancing_block(cx: &mut gpui::TestAppContext) {
+        use rope::Point;
+        use unindent::Unindent as _;
+
+        let (editor, mut cx) = init_test(cx, SoftWrap::None, DiffViewStyle::SideBySide).await;
+
+        let base_text = "
+            bbb
+            ccc
+        "
+        .unindent();
+        let current_text = "
+            aaa
+            bbb
+            ccc
+        "
+        .unindent();
+
+        let (buffer, diff) = buffer_with_diff(&base_text, &current_text, &mut cx);
+
+        editor.update(cx, |editor, cx| {
+            let path = PathKey::for_buffer(&buffer, cx);
+            editor.set_excerpts_for_path(
+                path,
+                buffer.clone(),
+                vec![Point::new(0, 0)..buffer.read(cx).max_point()],
+                0,
+                diff.clone(),
+                cx,
+            );
+        });
+
+        cx.run_until_parked();
+
+        let block_ids = editor.update(cx, |splittable_editor, cx| {
+            splittable_editor.rhs_editor.update(cx, |rhs_editor, cx| {
+                let snapshot = rhs_editor.buffer().read(cx).snapshot(cx);
+                let anchor = snapshot.anchor_before(Point::new(2, 0));
+                rhs_editor.insert_blocks(
+                    [BlockProperties {
+                        placement: BlockPlacement::Above(anchor),
+                        height: Some(1),
+                        style: BlockStyle::Fixed,
+                        render: Arc::new(|_| div().into_any()),
+                        priority: 0,
+                    }],
+                    None,
+                    cx,
+                )
+            })
+        });
+
+        let rhs_editor = editor.read_with(cx, |editor, _| editor.rhs_editor.clone());
+        let lhs_editor =
+            editor.read_with(cx, |editor, _| editor.lhs.as_ref().unwrap().editor.clone());
+
+        let lhs_block_id = lhs_editor.read_with(cx, |lhs_editor, cx| {
+            let display_map = lhs_editor.display_map.read(cx);
+            let companion = display_map.companion().unwrap().read(cx);
+            let mapping = companion
+                .custom_block_to_balancing_block(rhs_editor.read(cx).display_map.entity_id());
+            *mapping.borrow().get(&block_ids[0]).unwrap()
+        });
+
+        cx.run_until_parked();
+
+        let get_block_height = |editor: &Entity<crate::Editor>,
+                                block_id: crate::CustomBlockId,
+                                cx: &mut VisualTestContext| {
+            editor.update_in(cx, |editor, window, cx| {
+                let snapshot = editor.snapshot(window, cx);
+                snapshot
+                    .block_for_id(crate::BlockId::Custom(block_id))
+                    .map(|block| block.height())
+            })
+        };
+
+        assert_eq!(
+            get_block_height(&rhs_editor, block_ids[0], &mut cx),
+            Some(1)
+        );
+        assert_eq!(
+            get_block_height(&lhs_editor, lhs_block_id, &mut cx),
+            Some(1)
+        );
+
+        editor.update(cx, |splittable_editor, cx| {
+            splittable_editor.rhs_editor.update(cx, |rhs_editor, cx| {
+                let mut heights = HashMap::default();
+                heights.insert(block_ids[0], 3);
+                rhs_editor.resize_blocks(heights, None, cx);
+            });
+        });
+
+        cx.run_until_parked();
+
+        assert_eq!(
+            get_block_height(&rhs_editor, block_ids[0], &mut cx),
+            Some(3)
+        );
+        assert_eq!(
+            get_block_height(&lhs_editor, lhs_block_id, &mut cx),
+            Some(3)
+        );
+
+        editor.update(cx, |splittable_editor, cx| {
+            splittable_editor.rhs_editor.update(cx, |rhs_editor, cx| {
+                let mut heights = HashMap::default();
+                heights.insert(block_ids[0], 5);
+                rhs_editor.resize_blocks(heights, None, cx);
+            });
+        });
+
+        cx.run_until_parked();
+
+        assert_eq!(
+            get_block_height(&rhs_editor, block_ids[0], &mut cx),
+            Some(5)
+        );
+        assert_eq!(
+            get_block_height(&lhs_editor, lhs_block_id, &mut cx),
+            Some(5)
+        );
+    }
 }

crates/editor/src/test.rs 🔗

@@ -240,6 +240,9 @@ pub fn editor_content_with_blocks_and_size(
                 first_excerpt,
                 height,
             } => {
+                while lines.len() <= row.0 as usize {
+                    lines.push(String::new());
+                }
                 lines[row.0 as usize].push_str(&cx.update(|_, cx| {
                     format!(
                         "§ {}",
@@ -251,15 +254,24 @@ pub fn editor_content_with_blocks_and_size(
                     )
                 }));
                 for row in row.0 + 1..row.0 + height {
+                    while lines.len() <= row as usize {
+                        lines.push(String::new());
+                    }
                     lines[row as usize].push_str("§ -----");
                 }
             }
             Block::ExcerptBoundary { height, .. } => {
                 for row in row.0..row.0 + height {
+                    while lines.len() <= row as usize {
+                        lines.push(String::new());
+                    }
                     lines[row as usize].push_str("§ -----");
                 }
             }
             Block::BufferHeader { excerpt, height } => {
+                while lines.len() <= row.0 as usize {
+                    lines.push(String::new());
+                }
                 lines[row.0 as usize].push_str(&cx.update(|_, cx| {
                     format!(
                         "§ {}",
@@ -271,6 +283,9 @@ pub fn editor_content_with_blocks_and_size(
                     )
                 }));
                 for row in row.0 + 1..row.0 + height {
+                    while lines.len() <= row as usize {
+                        lines.push(String::new());
+                    }
                     lines[row as usize].push_str("§ -----");
                 }
             }

crates/text/src/patch.rs 🔗

@@ -11,6 +11,10 @@ impl<T> Patch<T>
 where
     T: 'static + Clone + Copy + Ord + Default,
 {
+    pub const fn empty() -> Self {
+        Self(Vec::new())
+    }
+
     pub fn new(edits: Vec<Edit<T>>) -> Self {
         #[cfg(debug_assertions)]
         {