From e7996331d13337d72459a3ea26ac3d68926772e4 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 18 Feb 2026 13:54:36 +0100 Subject: [PATCH] editor: Batch calls to fold_buffer in ProjectDiff::refresh (#49278) (#49451) Cherry-pick of #49278 to preview. Release Notes: - Improved project diff performance when opening very large diffs/repositories. --- crates/editor/src/editor.rs | 41 +++++++++++--- .../editor/src/highlight_matching_bracket.rs | 55 ++++++++++--------- crates/git_ui/src/commit_view.rs | 4 +- crates/git_ui/src/project_diff.rs | 25 +++++++-- 4 files changed, 82 insertions(+), 43 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 05b4c1d2095fd867ec67f26f5f5c7f5f028e4966..7a67abfeb6427181610d3382ff06062932bb1680 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3600,7 +3600,7 @@ impl Editor { refresh_linked_ranges(self, window, cx); self.refresh_selected_text_highlights(false, window, cx); - self.refresh_matching_bracket_highlights(window, cx); + self.refresh_matching_bracket_highlights(&display_map, cx); self.refresh_outline_symbols(cx); self.update_visible_edit_prediction(window, cx); self.edit_prediction_requires_modifier_in_indent_conflict = true; @@ -20265,22 +20265,46 @@ impl Editor { } pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context) { - if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) { + self.fold_buffers([buffer_id], cx); + } + + pub fn fold_buffers( + &mut self, + buffer_ids: impl IntoIterator, + cx: &mut Context, + ) { + if self.buffer().read(cx).is_singleton() { return; } - let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx); + let ids_to_fold: Vec = buffer_ids + .into_iter() + .filter(|id| !self.is_buffer_folded(*id, cx)) + .collect(); + + if ids_to_fold.is_empty() { + return; + } + + let mut all_folded_excerpt_ids = Vec::new(); + for buffer_id in &ids_to_fold { + let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(*buffer_id, cx); + all_folded_excerpt_ids.extend(folded_excerpts.into_iter().map(|(id, _)| id)); + } + self.display_map.update(cx, |display_map, cx| { - display_map.fold_buffers([buffer_id], cx) + display_map.fold_buffers(ids_to_fold.clone(), cx) }); let snapshot = self.display_snapshot(cx); self.selections.change_with(&snapshot, |selections| { - selections.remove_selections_from_buffer(buffer_id); + for buffer_id in ids_to_fold { + selections.remove_selections_from_buffer(buffer_id); + } }); cx.emit(EditorEvent::BufferFoldToggled { - ids: folded_excerpts.iter().map(|&(id, _)| id).collect(), + ids: all_folded_excerpt_ids, folded: true, }); cx.notify(); @@ -23856,9 +23880,10 @@ impl Editor { self.refresh_active_diagnostics(cx); self.refresh_code_actions(window, cx); self.refresh_single_line_folds(window, cx); - self.refresh_matching_bracket_highlights(window, cx); + let snapshot = self.snapshot(window, cx); + self.refresh_matching_bracket_highlights(&snapshot, cx); self.refresh_outline_symbols(cx); - self.refresh_sticky_headers(&self.snapshot(window, cx), cx); + self.refresh_sticky_headers(&snapshot, cx); if self.has_active_edit_prediction() { self.update_visible_edit_prediction(window, cx); } diff --git a/crates/editor/src/highlight_matching_bracket.rs b/crates/editor/src/highlight_matching_bracket.rs index 020eea6f9677ea9a04cda798d4d1dff7b2f85b85..4401a87195eb13413cfc2fa9c8e7784f737ac3fc 100644 --- a/crates/editor/src/highlight_matching_bracket.rs +++ b/crates/editor/src/highlight_matching_bracket.rs @@ -1,5 +1,5 @@ -use crate::{Editor, HighlightKey, RangeToAnchorExt}; -use gpui::{AppContext, Context, HighlightStyle, Window}; +use crate::{Editor, HighlightKey, RangeToAnchorExt, display_map::DisplaySnapshot}; +use gpui::{AppContext, Context, HighlightStyle}; use language::CursorShape; use multi_buffer::MultiBufferOffset; use theme::ActiveTheme; @@ -8,12 +8,11 @@ impl Editor { #[ztracing::instrument(skip_all)] pub fn refresh_matching_bracket_highlights( &mut self, - window: &Window, + snapshot: &DisplaySnapshot, cx: &mut Context, ) { self.clear_highlights(HighlightKey::MatchingBracket, cx); - let snapshot = self.snapshot(window, cx); let newest_selection = self.selections.newest::(&snapshot); // Don't highlight brackets if the selection isn't empty if !newest_selection.is_empty() { @@ -39,29 +38,31 @@ impl Editor { let buffer_snapshot = buffer_snapshot.clone(); async move { buffer_snapshot.innermost_enclosing_bracket_ranges(head..tail, None) } }); - self.refresh_matching_bracket_highlights_task = cx.spawn(async move |editor, cx| { - if let Some((opening_range, closing_range)) = task.await { - let buffer_snapshot = snapshot.buffer_snapshot(); - editor - .update(cx, |editor, cx| { - editor.highlight_text( - HighlightKey::MatchingBracket, - vec![ - opening_range.to_anchors(&buffer_snapshot), - closing_range.to_anchors(&buffer_snapshot), - ], - HighlightStyle { - background_color: Some( - cx.theme() - .colors() - .editor_document_highlight_bracket_background, - ), - ..Default::default() - }, - cx, - ) - }) - .ok(); + self.refresh_matching_bracket_highlights_task = cx.spawn({ + let buffer_snapshot = buffer_snapshot.clone(); + async move |editor, cx| { + if let Some((opening_range, closing_range)) = task.await { + editor + .update(cx, |editor, cx| { + editor.highlight_text( + HighlightKey::MatchingBracket, + vec![ + opening_range.to_anchors(&buffer_snapshot), + closing_range.to_anchors(&buffer_snapshot), + ], + HighlightStyle { + background_color: Some( + cx.theme() + .colors() + .editor_document_highlight_bracket_background, + ), + ..Default::default() + }, + cx, + ) + }) + .ok(); + } } }); } diff --git a/crates/git_ui/src/commit_view.rs b/crates/git_ui/src/commit_view.rs index 79f581777485b08952b95f2097f2e7083de35c98..a9025456f760a24701ab064165b285ff388c9635 100644 --- a/crates/git_ui/src/commit_view.rs +++ b/crates/git_ui/src/commit_view.rs @@ -362,9 +362,7 @@ impl CommitView { }); if !binary_buffer_ids.is_empty() { this.editor.update(cx, |editor, cx| { - for buffer_id in binary_buffer_ids { - editor.fold_buffer(buffer_id, cx); - } + editor.fold_buffers(binary_buffer_ids, cx); }); } })?; diff --git a/crates/git_ui/src/project_diff.rs b/crates/git_ui/src/project_diff.rs index 21a0d41fe099d60436bd80b7f4ee06982735c847..e5ac232dc036082f102ece4822aa7ea695a44eb7 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/crates/git_ui/src/project_diff.rs @@ -24,7 +24,7 @@ use gpui::{ Action, AnyElement, App, AppContext as _, AsyncWindowContext, Entity, EventEmitter, FocusHandle, Focusable, Render, Subscription, Task, WeakEntity, actions, }; -use language::{Anchor, Buffer, Capability, OffsetRangeExt}; +use language::{Anchor, Buffer, BufferId, Capability, OffsetRangeExt}; use multi_buffer::{MultiBuffer, PathKey}; use project::{ Project, ProjectPath, @@ -608,7 +608,7 @@ impl ProjectDiff { diff: Entity, window: &mut Window, cx: &mut Context, - ) { + ) -> Option { let subscription = cx.subscribe_in(&diff, window, move |this, _, _, window, cx| { this._task = window.spawn(cx, { let this = cx.weak_entity(); @@ -653,6 +653,8 @@ impl ProjectDiff { } }; + let mut needs_fold = None; + let (was_empty, is_excerpt_newly_added) = self.editor.update(cx, |editor, cx| { let was_empty = editor.rhs_editor().read(cx).buffer().read(cx).is_empty(); let (_, is_newly_added) = editor.set_excerpts_for_path( @@ -685,7 +687,7 @@ impl ProjectDiff { || (file_status.is_untracked() && GitPanelSettings::get_global(cx).collapse_untracked_diff)) { - editor.fold_buffer(snapshot.text.remote_id(), cx) + needs_fold = Some(snapshot.text.remote_id()); } }) }); @@ -706,6 +708,8 @@ impl ProjectDiff { if self.pending_scroll.as_ref() == Some(&path_key) { self.move_to_path(path_key, window, cx); } + + needs_fold } #[instrument(skip_all)] @@ -761,6 +765,8 @@ impl ProjectDiff { buffers_to_load })?; + let mut buffers_to_fold = Vec::new(); + for (entry, path_key) in buffers_to_load.into_iter().zip(path_keys.into_iter()) { if let Some((buffer, diff)) = entry.load.await.log_err() { // We might be lagging behind enough that all future entry.load futures are no longer pending. @@ -780,14 +786,16 @@ impl ProjectDiff { RefreshReason::StatusesChanged => false, }; if !skip { - this.register_buffer( + if let Some(buffer_id) = this.register_buffer( path_key, entry.file_status, buffer, diff, window, cx, - ) + ) { + buffers_to_fold.push(buffer_id); + } } }) .ok(); @@ -795,6 +803,13 @@ impl ProjectDiff { } } this.update(cx, |this, cx| { + if !buffers_to_fold.is_empty() { + this.editor.update(cx, |editor, cx| { + editor + .rhs_editor() + .update(cx, |editor, cx| editor.fold_buffers(buffers_to_fold, cx)); + }); + } this.pending_scroll.take(); cx.notify(); })?;