From 930f484fe4d20b36efd7d3e4e3b8872729c18981 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Sun, 8 Feb 2026 20:10:52 -0500 Subject: [PATCH] git: Follow-up fixes for custom blocks in the side-by-side diff (#48747) - 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 --- crates/editor/src/display_map.rs | 1008 +++++--------------- 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, 1239 insertions(+), 1053 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index b785469bff20f405c181355a84d7d067e7d6f0fa..604d5f97ca2342ffc18ebd54e90920cc6813a510 100644 --- a/crates/editor/src/display_map.rs +++ b/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>, } -// test change - pub(crate) struct Companion { rhs_display_map_id: EntityId, - rhs_folded_buffers: HashSet, rhs_buffer_to_lhs_buffer: HashMap, lhs_buffer_to_rhs_buffer: HashMap, rhs_excerpt_to_lhs_excerpt: HashMap, 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, + rhs_custom_block_to_balancing_block: RefCell>, + lhs_custom_block_to_balancing_block: RefCell>, } impl Companion { pub(crate) fn new( rhs_display_map_id: EntityId, - rhs_folded_buffers: HashSet, 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 { + ) -> &RefCell> { 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 { + 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, Entity)>, + companion: Option<(Entity, Entity)>, cx: &mut Context, ) { + 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) { - 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> { @@ -612,6 +604,40 @@ impl DisplayMap { .update(cx, |map, cx| map.sync(snapshot, edits, cx)) } + fn with_synced_companion_mut( + display_map_id: EntityId, + companion: &Option<(WeakEntity, Entity)>, + cx: &mut App, + callback: impl FnOnce(Option>, &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) -> 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) { 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, - 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, + 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, ) -> Vec { 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, cx: &mut Context) { 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)] @@ -1546,59 +1103,20 @@ impl DisplayMap { pub fn remove_blocks(&mut self, ids: HashSet, cx: &mut Context) { 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, - )), - ) - .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| { - 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, + ) + .remove(ids); + }, + ) } #[instrument(skip_all)] @@ -1764,28 +1282,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.update_fold_widths(widths); let widths_changed = !edits.is_empty(); @@ -1794,39 +1297,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()); - - { - 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(self_new_wrap_snapshot, self_new_wrap_edits, 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.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 + .read(self_new_wrap_snapshot, self_new_wrap_edits, None); widths_changed } diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 3489c8774811a2201b0e0ad526b867fb68df7b44..93eb76c511d4500620df1b833e4cffd2878a3f48 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/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>, } -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>, } -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>, } #[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> 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> 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, + 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, + my_snapshot: &MultiBufferSnapshot, + their_snapshot: &MultiBufferSnapshot, + my_display_map_id: EntityId, + companion: &Companion, +) -> Option> { + 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 = 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> = 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) { - 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> = None; + let mut companion_block_ids: HashSet = 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 { + 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, ); diff --git a/crates/editor/src/split.rs b/crates/editor/src/split.rs index d14cc34d4ed646dbc68fb35d64ad27515abd53b8..927770fb478c17a6d2eb06f50516108a092ea92b 100644 --- a/crates/editor/src/split.rs +++ b/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, &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 = (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, ¤t_text1, &mut cx); + let (buffer2, diff2) = buffer_with_diff(&base_text2, ¤t_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, + " + § + § ----- + § + § ----- + ddd + eee + fff" + .unindent(), + " + § + § ----- + § + § ----- + 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, + " + § + § ----- + § + § -----" + .unindent(), + " + § + § ----- + § + § -----" + .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, ¤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 + ddd + eee" + .unindent(), + " + § + § ----- + § 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, + " + § + § ----- + aaa + bbb + § custom block + ccc + ddd + eee" + .unindent(), + " + § + § ----- + § 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, + " + § + § ----- + aaa + bbb + ccc + ddd + eee" + .unindent(), + " + § + § ----- + § 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, ¤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 + ddd + eee" + .unindent(), + " + § + § ----- + § 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, + " + § + § ----- + aaa + bbb + § custom block + ccc + ddd + eee" + .unindent(), + " + § + § ----- + § 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, + " + § + § ----- + aaa + bbb + ccc + ddd + eee" + .unindent(), + " + § + § ----- + § 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, ¤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(); + + 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, + 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) + ); + } } diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index 1c8e117369ec65b7a784d4145891548f1fda8756..8052cf215e7ec879dba939a2f66699827bb58aeb 100644 --- a/crates/editor/src/test.rs +++ b/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("§ -----"); } } diff --git a/crates/text/src/patch.rs b/crates/text/src/patch.rs index c9f353a9cce22b54ecf7e2fa872035cd486438e6..eff3d0af110763074d7ca9fdc7842d45eece03c1 100644 --- a/crates/text/src/patch.rs +++ b/crates/text/src/patch.rs @@ -11,6 +11,10 @@ impl Patch where T: 'static + Clone + Copy + Ord + Default, { + pub const fn empty() -> Self { + Self(Vec::new()) + } + pub fn new(edits: Vec>) -> Self { #[cfg(debug_assertions)] {