diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 28e23b263d0f78d8cbe216d90b63802d21e4aa81..388c7649919e836a60510c1ca3500430a2767a7f 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -81,8 +81,8 @@ mod wrap_map; pub use crate::display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap}; pub use block_map::{ Block, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap, BlockPlacement, - BlockPoint, BlockProperties, BlockRows, BlockStyle, CompanionView, CustomBlockId, - EditorMargins, RenderBlock, StickyHeaderExcerpt, + BlockPoint, BlockProperties, BlockRows, BlockStyle, CompanionView, CompanionViewMut, + CustomBlockId, EditorMargins, RenderBlock, StickyHeaderExcerpt, }; pub use crease_map::*; pub use fold_map::{ @@ -238,6 +238,8 @@ pub(crate) struct Companion { lhs_excerpt_to_rhs_excerpt: HashMap, rhs_rows_to_lhs_rows: ConvertMultiBufferRows, lhs_rows_to_rhs_rows: ConvertMultiBufferRows, + rhs_custom_blocks_to_lhs_custom_blocks: HashMap, + lhs_custom_blocks_to_rhs_custom_blocks: HashMap, } impl Companion { @@ -256,9 +258,46 @@ impl Companion { 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(), } } + pub(crate) fn is_rhs(&self, display_map_id: EntityId) -> bool { + self.rhs_display_map_id == display_map_id + } + + pub(crate) fn companion_custom_block_to_custom_block( + &self, + display_map_id: EntityId, + ) -> &HashMap { + if self.is_rhs(display_map_id) { + &self.lhs_custom_blocks_to_rhs_custom_blocks + } else { + &self.rhs_custom_blocks_to_lhs_custom_blocks + } + } + + 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, @@ -266,7 +305,7 @@ impl Companion { our_snapshot: &MultiBufferSnapshot, bounds: (Bound, Bound), ) -> Vec { - let (excerpt_map, convert_fn) = if display_map_id == self.rhs_display_map_id { + 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) @@ -281,7 +320,7 @@ impl Companion { companion_snapshot: &MultiBufferSnapshot, point: MultiBufferPoint, ) -> Range { - let (excerpt_map, convert_fn) = if display_map_id == self.rhs_display_map_id { + let (excerpt_map, convert_fn) = if self.is_rhs(display_map_id) { (&self.lhs_excerpt_to_rhs_excerpt, self.lhs_rows_to_rhs_rows) } else { (&self.rhs_excerpt_to_lhs_excerpt, self.rhs_rows_to_lhs_rows) @@ -306,7 +345,7 @@ impl Companion { &self, display_map_id: EntityId, ) -> &HashMap { - if display_map_id == self.rhs_display_map_id { + if self.is_rhs(display_map_id) { &self.lhs_excerpt_to_rhs_excerpt } else { &self.rhs_excerpt_to_lhs_excerpt @@ -314,7 +353,7 @@ impl Companion { } fn buffer_to_companion_buffer(&self, display_map_id: EntityId) -> &HashMap { - if display_map_id == self.rhs_display_map_id { + if self.is_rhs(display_map_id) { &self.rhs_buffer_to_lhs_buffer } else { &self.lhs_buffer_to_rhs_buffer @@ -493,20 +532,56 @@ impl DisplayMap { .read(snapshot.clone(), edits.clone(), companion_view); if let Some((companion_dm, _)) = &self.companion { - let _ = companion_dm.update(cx, |dm, _cx| { + 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)); + 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)), + their_companion_ref.map(|c| { + CompanionView::new(dm.entity_id, &snapshot, &edits, c.read(cx)) + }), ); } }); } } + pub(crate) fn sync_custom_blocks_into_companion(&mut self, cx: &mut Context) { + if self.companion.is_none() { + return; + } + + 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(); + } + pub(crate) fn companion(&self) -> Option<&Entity> { self.companion.as_ref().map(|(_, c)| c) } @@ -693,52 +768,68 @@ impl DisplayMap { let (self_wrap_snapshot, self_wrap_edits) = (self_new_wrap_snapshot.clone(), self_new_wrap_edits.clone()); - let mut block_map = { - 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 - .write(self_new_wrap_snapshot, self_new_wrap_edits, companion_view) - }; - let blocks = creases.into_iter().filter_map(|crease| { - if let Crease::Block { - range, - block_height, - render_block, - block_style, - block_priority, - .. - } = crease - { - Some(( + let blocks = creases + .into_iter() + .filter_map(|crease| { + if let Crease::Block { range, - render_block, block_height, + render_block, block_style, block_priority, - )) - } else { - None - } - }); - block_map.insert( - blocks - .into_iter() - .map(|(range, render, height, style, priority)| { - let start = buffer_snapshot.anchor_before(range.start); - let end = buffer_snapshot.anchor_after(range.end); - BlockProperties { - placement: BlockPlacement::Replace(start..=end), - render, - height: Some(height), - style, - priority, - } - }), - ); + .. + } = crease + { + Some(( + range, + render_block, + block_height, + block_style, + block_priority, + )) + } else { + None + } + }) + .map(|(range, render, height, style, priority)| { + let start = buffer_snapshot.anchor_before(range.start); + let end = buffer_snapshot.anchor_after(range.end); + BlockProperties { + placement: BlockPlacement::Replace(start..=end), + render, + height: Some(height), + style, + priority, + } + }); + + 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| { @@ -805,15 +896,29 @@ impl DisplayMap { 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() { - 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) - }, - ); + 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, companion_view); + .write(self_new_wrap_snapshot, self_new_wrap_edits, None); } if let Some((companion_dm, _)) = &self.companion { @@ -886,20 +991,33 @@ impl DisplayMap { let (self_wrap_snapshot, self_wrap_edits) = (self_new_wrap_snapshot.clone(), self_new_wrap_edits.clone()); - let mut block_map = { - 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.write( - self_new_wrap_snapshot.clone(), - self_new_wrap_edits, - companion_view, - ) - }; - block_map.remove_intersecting_replace_blocks(offset_ranges, inclusive); + 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| { @@ -934,19 +1052,33 @@ impl DisplayMap { .ok() }); - 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) - }, - ); - - let mut block_map = self.block_map.write( - self_wrap_snapshot.clone(), - self_wrap_edits.clone(), - companion_view, - ); - block_map.disable_header_for_buffer(buffer_id); + 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| { @@ -1000,19 +1132,33 @@ impl DisplayMap { .ok() }); - 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) - }, - ); - - let mut block_map = self.block_map.write( - self_wrap_snapshot.clone(), - self_wrap_edits.clone(), - companion_view, - ); - block_map.fold_buffers(buffer_ids.iter().copied(), self.buffer.read(cx), cx); + 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 @@ -1025,21 +1171,30 @@ impl DisplayMap { 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)); - let mut block_map = dm.block_map.write( - companion_snapshot, - companion_edits, - their_companion_ref.map(|c| { - CompanionView::new( - dm.entity_id, - &self_wrap_snapshot, - &self_wrap_edits, - c, - ) - }), - ); - if !their_buffer_ids.is_empty() { - block_map.fold_buffers(their_buffer_ids, dm.buffer.read(cx), cx); + 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); + } } } }); @@ -1078,19 +1233,33 @@ impl DisplayMap { .ok() }); - 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) - }, - ); - - let mut block_map = self.block_map.write( - self_wrap_snapshot.clone(), - self_wrap_edits.clone(), - companion_view, - ); - block_map.unfold_buffers(buffer_ids.iter().copied(), self.buffer.read(cx), cx); + 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 @@ -1103,21 +1272,30 @@ impl DisplayMap { 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)); - let mut block_map = dm.block_map.write( - companion_snapshot, - companion_edits, - their_companion_ref.map(|c| { - CompanionView::new( - dm.entity_id, - &self_wrap_snapshot, - &self_wrap_edits, - c, - ) - }), - ); - if !their_buffer_ids.is_empty() { - block_map.unfold_buffers(their_buffer_ids, dm.buffer.read(cx), cx); + 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); + } } } }); @@ -1168,19 +1346,34 @@ impl DisplayMap { .ok() }); - 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) - }, - ); - - let mut block_map = self.block_map.write( - self_wrap_snapshot.clone(), - self_wrap_edits.clone(), - companion_view, - ); - let result = block_map.insert(blocks); + 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| { @@ -1215,19 +1408,33 @@ impl DisplayMap { .ok() }); - 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) - }, - ); - - let mut block_map = self.block_map.write( - self_wrap_snapshot.clone(), - self_wrap_edits.clone(), - companion_view, - ); - block_map.resize(heights); + 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| { @@ -1265,19 +1472,33 @@ impl DisplayMap { .ok() }); - 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) - }, - ); - - let mut block_map = self.block_map.write( - self_wrap_snapshot.clone(), - self_wrap_edits.clone(), - companion_view, - ); - block_map.remove(ids); + 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, + )), + ) + .remove(ids); + }) + }) + .ok(); + } else { + self.block_map + .write(self_wrap_snapshot.clone(), self_wrap_edits.clone(), None) + .remove(ids); + } if let Some((companion_dm, _)) = &self.companion { let _ = companion_dm.update(cx, |dm, cx| { diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index ed7b2d87469ce38754c95f2d6e4173c3a83e8dc2..e9411f653398dab97e3264fd15039064f660f566 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -48,7 +48,7 @@ pub struct BlockMap { } pub struct BlockMapReader<'a> { - blocks: &'a Vec>, + pub blocks: &'a Vec>, pub snapshot: BlockSnapshot, } @@ -57,16 +57,22 @@ pub struct BlockMapWriter<'a> { companion: Option>, } -struct BlockMapWriterCompanion<'a>(CompanionView<'a>); +struct BlockMapWriterCompanion<'a>(CompanionViewMut<'a>); impl<'a> Deref for BlockMapWriterCompanion<'a> { - type Target = CompanionView<'a>; + type Target = CompanionViewMut<'a>; fn deref(&self) -> &Self::Target { &self.0 } } +impl<'a> DerefMut for BlockMapWriterCompanion<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + #[derive(Clone)] pub struct BlockSnapshot { pub(super) wrap_snapshot: WrapSnapshot, @@ -282,6 +288,7 @@ pub struct BlockContext<'a, 'b> { pub em_width: Pixels, pub line_height: Pixels, pub block_id: BlockId, + pub height: u32, pub selected: bool, pub editor_style: &'b EditorStyle, } @@ -542,6 +549,54 @@ impl<'a> CompanionView<'a> { } } +impl<'a> From> 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, + companion: view_mut.companion, + } + } +} + +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, + 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, +} + +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, + ) -> Self { + Self { + entity_id, + wrap_snapshot, + wrap_edits, + companion, + block_map, + } + } +} + impl BlockMap { #[ztracing::instrument(skip_all)] pub fn new( @@ -600,9 +655,13 @@ impl BlockMap { &'a mut self, wrap_snapshot: WrapSnapshot, edits: WrapPatch, - companion_view: Option>, + companion_view: Option>, ) -> BlockMapWriter<'a> { - self.sync(&wrap_snapshot, edits, companion_view); + self.sync( + &wrap_snapshot, + edits, + companion_view.as_ref().map(CompanionView::from), + ); *self.wrap_snapshot.borrow_mut() = wrap_snapshot; BlockMapWriter { block_map: self, @@ -1402,6 +1461,55 @@ 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))] @@ -1562,7 +1670,7 @@ impl BlockMapWriter<'_> { }; let new_block = Arc::new(CustomBlock { id, - placement: block.placement, + placement: block.placement.clone(), height: block.height, render: Arc::new(Mutex::new(block.render)), style: block.style, @@ -1571,7 +1679,27 @@ impl BlockMapWriter<'_> { self.block_map .custom_blocks .insert(block_ix, new_block.clone()); - self.block_map.custom_blocks_by_id.insert(id, new_block); + 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() + { + their_block_map.insert_custom_block_into_companion( + *their_entity_id, + their_snapshot, + &new_block, + buffer, + companion, + ); + } edits = edits.compose([Edit { old: start_row..end_row, @@ -1584,13 +1712,13 @@ impl BlockMapWriter<'_> { wrap_snapshot, edits, self.companion.as_deref().map( - |&CompanionView { + |CompanionViewMut { entity_id, wrap_snapshot, companion, .. }| { - CompanionView::new(entity_id, wrap_snapshot, &default_patch, companion) + CompanionView::new(*entity_id, wrap_snapshot, &default_patch, companion) }, ), ); @@ -1654,13 +1782,13 @@ impl BlockMapWriter<'_> { wrap_snapshot, edits, self.companion.as_deref().map( - |&CompanionView { + |CompanionViewMut { entity_id, wrap_snapshot, companion, .. }| { - CompanionView::new(entity_id, wrap_snapshot, &default_patch, companion) + CompanionView::new(*entity_id, wrap_snapshot, &default_patch, companion) }, ), ); @@ -1709,18 +1837,49 @@ impl BlockMapWriter<'_> { self.block_map .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( - |&CompanionView { + |CompanionViewMut { entity_id, wrap_snapshot, companion, .. }| { - CompanionView::new(entity_id, wrap_snapshot, &default_patch, companion) + CompanionView::new(*entity_id, wrap_snapshot, &default_patch, companion) }, ), ); @@ -1809,13 +1968,13 @@ impl BlockMapWriter<'_> { &wrap_snapshot, edits, self.companion.as_deref().map( - |&CompanionView { + |CompanionViewMut { entity_id, wrap_snapshot, companion, .. }| { - CompanionView::new(entity_id, wrap_snapshot, &default_patch, companion) + CompanionView::new(*entity_id, wrap_snapshot, &default_patch, companion) }, ), ); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 53b3c51b9f666083a196b87442ce34c65e7612c7..d1c5f3c8be8500d1e89308aaf54d407632d8f21a 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -3914,6 +3914,7 @@ impl EditorElement { line_height, em_width, block_id, + height: custom.height.unwrap_or(1), selected, max_width: text_hitbox.size.width.max(*scroll_width), editor_style: &self.style, @@ -4004,26 +4005,9 @@ impl EditorElement { result.into_any() } - Block::Spacer { height, .. } => div() - .id(block_id) - .w_full() - .h((*height as f32) * line_height) - // the checkerboard pattern is semi-transparent, so we render a - // solid background to prevent indent guides peeking through - .bg(cx.theme().colors().editor_background) - .child( - div() - .size_full() - .bg(checkerboard(cx.theme().colors().panel_background, { - let target_size = 16.0; - let scale = window.scale_factor(); - Self::checkerboard_size( - f32::from(line_height) * scale, - target_size * scale, - ) - })), - ) - .into_any(), + Block::Spacer { height, .. } => { + Self::render_spacer_block(block_id, *height, line_height, window, cx) + } }; // Discover the element's content height, then round up to the nearest multiple of line height. @@ -4102,6 +4086,32 @@ impl EditorElement { } } + pub fn render_spacer_block( + block_id: BlockId, + block_height: u32, + line_height: Pixels, + window: &mut Window, + cx: &App, + ) -> AnyElement { + div() + .id(block_id) + .w_full() + .h((block_height as f32) * line_height) + // the checkerboard pattern is semi-transparent, so we render a + // solid background to prevent indent guides peeking through + .bg(cx.theme().colors().editor_background) + .child( + div() + .size_full() + .bg(checkerboard(cx.theme().colors().panel_background, { + let target_size = 16.0; + let scale = window.scale_factor(); + Self::checkerboard_size(f32::from(line_height) * scale, target_size * scale) + })), + ) + .into_any() + } + fn render_buffer_header( &self, for_excerpt: &ExcerptInfo, diff --git a/crates/editor/src/split.rs b/crates/editor/src/split.rs index 6b4a6bf46f08c1fe25c52d5d38d2ced80aa9c40d..c1ac7814164224c28435066d4dc01aa445de225e 100644 --- a/crates/editor/src/split.rs +++ b/crates/editor/src/split.rs @@ -574,6 +574,9 @@ impl SplittableEditor { 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); + }); let shared_scroll_anchor = self .rhs_editor @@ -1922,7 +1925,9 @@ impl LhsEditor { #[cfg(test)] mod tests { use buffer_diff::BufferDiff; + use collections::HashSet; use fs::FakeFs; + use gpui::Element as _; use gpui::{AppContext as _, Entity, Pixels, VisualTestContext}; use language::language_settings::SoftWrap; use language::{Buffer, Capability}; @@ -1931,11 +1936,14 @@ mod tests { use project::Project; use rand::rngs::StdRng; use settings::SettingsStore; - use ui::{VisualContext as _, px}; + use std::sync::Arc; + use ui::{VisualContext as _, div, px}; use workspace::Workspace; use crate::SplittableEditor; - use crate::test::editor_content_with_blocks_and_width; + use crate::display_map::{BlockPlacement, BlockProperties, BlockStyle}; + use crate::split::{SplitDiff, UnsplitDiff}; + use crate::test::{editor_content_with_blocks_and_width, set_block_content_for_tests}; async fn init_test( cx: &mut gpui::TestAppContext, @@ -3847,4 +3855,704 @@ mod tests { "LHS should have same horizontal scroll position as RHS after autoscroll" ); } + + #[gpui::test] + async fn test_custom_block_sync_between_split_views(cx: &mut gpui::TestAppContext) { + use rope::Point; + use unindent::Unindent as _; + + let (editor, mut cx) = init_test(cx, SoftWrap::None).await; + + let base_text = " + bbb + ccc + " + .unindent(); + let current_text = " + aaa + bbb + ccc + " + .unindent(); + + let (buffer, diff) = buffer_with_diff(&base_text, ¤t_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, + " + § + § ----- + aaa + bbb + ccc" + .unindent(), + " + § + § ----- + § spacer + bbb + ccc" + .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.companion_custom_block_to_custom_block( + rhs_editor.read(cx).display_map.entity_id(), + ); + *mapping.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, + " + § + § ----- + aaa + bbb + § custom block + ccc" + .unindent(), + " + § + § ----- + § spacer + bbb + § custom block + ccc" + .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, + " + § + § ----- + aaa + bbb + ccc" + .unindent(), + " + § + § ----- + § spacer + bbb + ccc" + .unindent(), + &mut cx, + ); + } + + #[gpui::test] + async fn test_custom_block_deletion_and_resplit_sync(cx: &mut gpui::TestAppContext) { + use rope::Point; + use unindent::Unindent as _; + + let (editor, mut cx) = init_test(cx, SoftWrap::None).await; + + let base_text = " + bbb + ccc + " + .unindent(); + let current_text = " + aaa + bbb + ccc + " + .unindent(); + + let (buffer, diff) = buffer_with_diff(&base_text, ¤t_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, + " + § + § ----- + aaa + bbb + ccc" + .unindent(), + " + § + § ----- + § spacer + bbb + ccc" + .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 anchor1 = snapshot.anchor_before(Point::new(2, 0)); + let anchor2 = snapshot.anchor_before(Point::new(3, 0)); + rhs_editor.insert_blocks( + [ + BlockProperties { + placement: BlockPlacement::Above(anchor1), + height: Some(1), + style: BlockStyle::Fixed, + render: Arc::new(|_| div().into_any()), + priority: 0, + }, + BlockProperties { + placement: BlockPlacement::Above(anchor2), + 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 1".to_string() + }); + set_block_content_for_tests(&rhs_editor, block_ids[1], cx, |_| { + "custom block 2".to_string() + }); + }); + + 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(), + ); + ( + *mapping.get(&block_ids[0]).unwrap(), + *mapping.get(&block_ids[1]).unwrap(), + ) + }); + + cx.update(|_, cx| { + set_block_content_for_tests(&lhs_editor, lhs_block_id_1, cx, |_| { + "custom block 1".to_string() + }); + set_block_content_for_tests(&lhs_editor, lhs_block_id_2, cx, |_| { + "custom block 2".to_string() + }); + }); + + cx.run_until_parked(); + + assert_split_content( + &editor, + " + § + § ----- + aaa + bbb + § custom block 1 + ccc + § custom block 2" + .unindent(), + " + § + § ----- + § spacer + bbb + § custom block 1 + ccc + § custom block 2" + .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[0]]), None, cx); + }); + }); + + cx.run_until_parked(); + + assert_split_content( + &editor, + " + § + § ----- + aaa + bbb + ccc + § custom block 2" + .unindent(), + " + § + § ----- + § spacer + bbb + ccc + § custom block 2" + .unindent(), + &mut cx, + ); + + editor.update_in(cx, |splittable_editor, window, cx| { + splittable_editor.unsplit(&UnsplitDiff, window, cx); + }); + + cx.run_until_parked(); + + editor.update_in(cx, |splittable_editor, window, cx| { + splittable_editor.split(&SplitDiff, window, cx); + }); + + cx.run_until_parked(); + + let lhs_editor = + editor.read_with(cx, |editor, _| editor.lhs.as_ref().unwrap().editor.clone()); + + 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() + }); + + cx.update(|_, cx| { + set_block_content_for_tests(&lhs_editor, lhs_block_id_2, cx, |_| { + "custom block 2".to_string() + }); + }); + + cx.run_until_parked(); + + assert_split_content( + &editor, + " + § + § ----- + aaa + bbb + ccc + § custom block 2" + .unindent(), + " + § + § ----- + § spacer + bbb + ccc + § custom block 2" + .unindent(), + &mut cx, + ); + } + + #[gpui::test] + async fn test_custom_block_sync_with_unsplit_start(cx: &mut gpui::TestAppContext) { + use rope::Point; + use unindent::Unindent as _; + + let (editor, mut cx) = init_test(cx, SoftWrap::None).await; + + let base_text = " + bbb + ccc + " + .unindent(); + let current_text = " + aaa + bbb + ccc + " + .unindent(); + + let (buffer, diff) = buffer_with_diff(&base_text, ¤t_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(); + + editor.update_in(cx, |splittable_editor, window, cx| { + splittable_editor.unsplit(&UnsplitDiff, window, cx); + }); + + cx.run_until_parked(); + + let rhs_editor = editor.read_with(cx, |editor, _| editor.rhs_editor.clone()); + + 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 anchor1 = snapshot.anchor_before(Point::new(2, 0)); + let anchor2 = snapshot.anchor_before(Point::new(3, 0)); + rhs_editor.insert_blocks( + [ + BlockProperties { + placement: BlockPlacement::Above(anchor1), + height: Some(1), + style: BlockStyle::Fixed, + render: Arc::new(|_| div().into_any()), + priority: 0, + }, + BlockProperties { + placement: BlockPlacement::Above(anchor2), + height: Some(1), + style: BlockStyle::Fixed, + render: Arc::new(|_| div().into_any()), + priority: 0, + }, + ], + None, + cx, + ) + }) + }); + + cx.update(|_, cx| { + set_block_content_for_tests(&rhs_editor, block_ids[0], cx, |_| { + "custom block 1".to_string() + }); + set_block_content_for_tests(&rhs_editor, block_ids[1], cx, |_| { + "custom block 2".to_string() + }); + }); + + cx.run_until_parked(); + + let rhs_content = editor_content_with_blocks_and_width(&rhs_editor, px(3000.0), &mut cx); + assert_eq!( + rhs_content, + " + § + § ----- + aaa + bbb + § custom block 1 + ccc + § custom block 2" + .unindent(), + "rhs content before split" + ); + + editor.update_in(cx, |splittable_editor, window, cx| { + splittable_editor.split(&SplitDiff, window, cx); + }); + + cx.run_until_parked(); + + let lhs_editor = + editor.read_with(cx, |editor, _| editor.lhs.as_ref().unwrap().editor.clone()); + + 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(), + ); + ( + *mapping.get(&block_ids[0]).unwrap(), + *mapping.get(&block_ids[1]).unwrap(), + ) + }); + + cx.update(|_, cx| { + set_block_content_for_tests(&lhs_editor, lhs_block_id_1, cx, |_| { + "custom block 1".to_string() + }); + set_block_content_for_tests(&lhs_editor, lhs_block_id_2, cx, |_| { + "custom block 2".to_string() + }); + }); + + cx.run_until_parked(); + + assert_split_content( + &editor, + " + § + § ----- + aaa + bbb + § custom block 1 + ccc + § custom block 2" + .unindent(), + " + § + § ----- + § spacer + bbb + § custom block 1 + ccc + § custom block 2" + .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[0]]), None, cx); + }); + }); + + cx.run_until_parked(); + + assert_split_content( + &editor, + " + § + § ----- + aaa + bbb + ccc + § custom block 2" + .unindent(), + " + § + § ----- + § spacer + bbb + ccc + § custom block 2" + .unindent(), + &mut cx, + ); + + editor.update_in(cx, |splittable_editor, window, cx| { + splittable_editor.unsplit(&UnsplitDiff, window, cx); + }); + + cx.run_until_parked(); + + editor.update_in(cx, |splittable_editor, window, cx| { + splittable_editor.split(&SplitDiff, window, cx); + }); + + cx.run_until_parked(); + + let lhs_editor = + editor.read_with(cx, |editor, _| editor.lhs.as_ref().unwrap().editor.clone()); + + 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() + }); + + cx.update(|_, cx| { + set_block_content_for_tests(&lhs_editor, lhs_block_id_2, cx, |_| { + "custom block 2".to_string() + }); + }); + + cx.run_until_parked(); + + assert_split_content( + &editor, + " + § + § ----- + aaa + bbb + ccc + § custom block 2" + .unindent(), + " + § + § ----- + § spacer + bbb + ccc + § custom block 2" + .unindent(), + &mut cx, + ); + + let new_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, + ) + }) + }); + + cx.update(|_, cx| { + set_block_content_for_tests(&rhs_editor, new_block_ids[0], cx, |_| { + "custom block 3".to_string() + }); + }); + + 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() + }); + + cx.update(|_, cx| { + set_block_content_for_tests(&lhs_editor, lhs_block_id_3, cx, |_| { + "custom block 3".to_string() + }); + }); + + cx.run_until_parked(); + + assert_split_content( + &editor, + " + § + § ----- + aaa + bbb + § custom block 3 + ccc + § custom block 2" + .unindent(), + " + § + § ----- + § spacer + bbb + § custom block 3 + ccc + § custom block 2" + .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([new_block_ids[0]]), None, cx); + }); + }); + + cx.run_until_parked(); + + assert_split_content( + &editor, + " + § + § ----- + aaa + bbb + ccc + § custom block 2" + .unindent(), + " + § + § ----- + § spacer + bbb + ccc + § custom block 2" + .unindent(), + &mut cx, + ); + } }