From ff829524f26b689015e76ab9c80b6d2526b34842 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Fri, 13 Feb 2026 15:03:44 -0500 Subject: [PATCH] Cherry-pick "git: Remove unnecessary block map recomputation when splitting `SplittableEditor` (#49075)" to v0.224.x (#49127) Release Notes: - Improved performance when toggling from the unified diff to the split diff. Co-authored-by: Jakub Co-authored-by: Cameron McLoughlin --- crates/editor/src/display_map.rs | 27 ++++++- crates/editor/src/display_map/block_map.rs | 15 ++++ crates/editor/src/split.rs | 94 +++++++++++++++++++++- 3 files changed, 133 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 21c8a15ef194ddabfa9145f182a87fdd1f1f75e1..15f7a37ad61afca40c3a1443cb5784d52e6ebd9e 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -503,14 +503,37 @@ impl DisplayMap { }; assert_eq!(self.entity_id, companion.read(cx).rhs_display_map_id); - let snapshot = self.unfold_intersecting([Anchor::min()..Anchor::max()], true, cx); + // Note, throwing away the wrap edits because we're going to recompute the maximal range for the block map regardless + let old_max_row = self.block_map.wrap_snapshot.borrow().max_point().row(); + let snapshot = { + let edits = self.buffer_subscription.consume(); + let snapshot = self.buffer.read(cx).snapshot(cx); + let tab_size = Self::tab_size(&self.buffer, cx); + let (snapshot, edits) = self.inlay_map.sync(snapshot, edits.into_inner()); + let (mut writer, 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, |wrap_map, cx| wrap_map.sync(snapshot, edits, cx)); + + let (snapshot, edits) = + writer.unfold_intersecting([Anchor::min()..Anchor::max()], true); + let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); + let (snapshot, _edits) = self + .wrap_map + .update(cx, |wrap_map, cx| wrap_map.sync(snapshot, edits, cx)); + + self.block_map + .retain_blocks_raw(|block| !matches!(block.placement, BlockPlacement::Replace(_))); + snapshot + }; 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 { - old: WrapRow(0)..snapshot.max_point().row(), + old: WrapRow(0)..old_max_row, new: WrapRow(0)..snapshot.max_point().row(), }] .into_iter() diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index b1abe6b39b2a4701bdbd99d51ad208b184b57ca3..f0547332d00d60d3c5ccc4407add977a659c6ab6 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -707,6 +707,7 @@ impl BlockMap { } } + // Warning: doesn't sync the block map, use advisedly pub(crate) fn insert_block_raw( &mut self, block: BlockProperties, @@ -732,6 +733,20 @@ impl BlockMap { id } + // Warning: doesn't sync the block map, use advisedly + pub(crate) fn retain_blocks_raw(&mut self, mut pred: impl FnMut(&Arc) -> bool) { + let mut ids_to_remove = HashSet::default(); + self.custom_blocks.retain(|block| { + let keep = pred(block); + if !keep { + ids_to_remove.insert(block.id); + } + keep + }); + self.custom_blocks_by_id + .retain(|id, _| !ids_to_remove.contains(id)); + } + #[ztracing::instrument(skip_all, fields(edits = ?edits))] fn sync( &self, diff --git a/crates/editor/src/split.rs b/crates/editor/src/split.rs index f44fb4ae9a31ebac882b6dc1d818a03a92f3d8b5..bc6ab567d36336a97fd39f31e5123edaeebf189d 100644 --- a/crates/editor/src/split.rs +++ b/crates/editor/src/split.rs @@ -2090,9 +2090,12 @@ mod tests { use workspace::Workspace; use crate::SplittableEditor; - use crate::display_map::{BlockPlacement, BlockProperties, BlockStyle}; + use crate::display_map::{ + BlockPlacement, BlockProperties, BlockStyle, Crease, FoldPlaceholder, + }; use crate::inlays::Inlay; use crate::test::{editor_content_with_blocks_and_width, set_block_content_for_tests}; + use multi_buffer::MultiBufferOffset; async fn init_test( cx: &mut gpui::TestAppContext, @@ -5452,6 +5455,95 @@ mod tests { ); } + #[gpui::test] + async fn test_range_folds_removed_on_split(cx: &mut gpui::TestAppContext) { + use rope::Point; + use unindent::Unindent as _; + + let (editor, mut cx) = init_test(cx, SoftWrap::None, DiffViewStyle::Unified).await; + + let base_text = " + aaa + bbb + ccc + 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(); + + editor.update_in(cx, |editor, window, cx| { + editor.rhs_editor.update(cx, |rhs_editor, cx| { + rhs_editor.fold_creases( + vec![Crease::simple( + Point::new(1, 0)..Point::new(3, 0), + FoldPlaceholder::test(), + )], + false, + window, + cx, + ); + }); + }); + + cx.run_until_parked(); + + editor.update_in(cx, |editor, window, cx| { + editor.split(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_has_folds_after_split = rhs_editor.update(cx, |editor, cx| { + let snapshot = editor.display_snapshot(cx); + snapshot + .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len()) + .next() + .is_some() + }); + assert!( + !rhs_has_folds_after_split, + "rhs should not have range folds after split" + ); + + let lhs_has_folds = lhs_editor.update(cx, |editor, cx| { + let snapshot = editor.display_snapshot(cx); + snapshot + .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len()) + .next() + .is_some() + }); + assert!(!lhs_has_folds, "lhs should not have any range folds"); + } + #[gpui::test] async fn test_multiline_inlays_create_spacers(cx: &mut gpui::TestAppContext) { use rope::Point;