From 6d4915df16990bd04e3eee47e3b1dd18a0b35bd3 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 16 Dec 2025 10:58:10 -0700 Subject: [PATCH] Revert "Optimize editor rendering when clipped by parent containers" (#45011) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 914b0117fb5a23469af85e567d5723eca6b53635 (#44995). The optimization introduced a regression that causes the main thread to hang for **100+ seconds** in certain scenarios, requiring a force quit to recover. ## Analysis from spindump When a large `AutoHeight` editor is displayed inside a `List` (e.g., Agent Panel thread view), the clipping calculation can produce invalid row ranges: 1. `visible_bounds` from `window.content_mask().bounds` represents the window's content mask, not the intersection with the editor 2. When the editor is partially scrolled out of view, `clipped_top_in_lines` becomes extremely large 3. This causes `start_row` to be computed as an astronomically high value 4. `blocks_in_range(start_row..end_row)` then spends excessive time in `Cursor::search_forward` iterating through the block tree The spindump showed **~46% of samples** (459/1001 over 10+ seconds) stuck in `BlockSnapshot::blocks_in_range()`, specifically in cursor iteration. ### Heaviest stack trace ``` EditorElement::prepaint └─ blocks_in_range + 236 └─ Cursor::search_forward (459 samples) ``` ## Symptoms - Main thread unresponsive for 33-113 seconds before sampling even began - UI completely frozen - High CPU usage on main thread (10+ seconds of CPU time in the sample) - Force quit required to recover ## Path forward The original optimization goal (reducing line layout work for clipped editors) is valid, but the implementation needs to: 1. Correctly calculate the **intersection** of editor bounds with the visible viewport 2. Ensure row calculations stay within valid ranges (clamped to `max_row`) 3. Handle edge cases where the editor is completely outside the visible bounds Release Notes: - Fixed a hang that could occur when viewing large diffs in the Agent Panel --- crates/editor/src/element.rs | 17 ++--------------- crates/editor/src/test.rs | 8 +++++--- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 95047569c31c2e306b0f984832b568f052e4179a..8de660275ba9b455aec610568c41347888654495 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -9164,15 +9164,6 @@ impl Element for EditorElement { let height_in_lines = f64::from(bounds.size.height / line_height); let max_row = snapshot.max_point().row().as_f64(); - // Calculate how much of the editor is clipped by parent containers (e.g., List). - // This allows us to only render lines that are actually visible, which is - // critical for performance when large AutoHeight editors are inside Lists. - let visible_bounds = window.content_mask().bounds; - let clipped_top = (visible_bounds.origin.y - bounds.origin.y).max(px(0.)); - let clipped_top_in_lines = f64::from(clipped_top / line_height); - let visible_height_in_lines = - f64::from(visible_bounds.size.height / line_height); - // The max scroll position for the top of the window let max_scroll_top = if matches!( snapshot.mode, @@ -9229,14 +9220,10 @@ impl Element for EditorElement { let mut scroll_position = snapshot.scroll_position(); // The scroll position is a fractional point, the whole number of which represents // the top of the window in terms of display rows. - // We add clipped_top_in_lines to skip rows that are clipped by parent containers, - // but we don't modify scroll_position itself since the parent handles positioning. - let start_row = - DisplayRow((scroll_position.y + clipped_top_in_lines).floor() as u32); + let start_row = DisplayRow(scroll_position.y as u32); let max_row = snapshot.max_point().row(); let end_row = cmp::min( - (scroll_position.y + clipped_top_in_lines + visible_height_in_lines).ceil() - as u32, + (scroll_position.y + height_in_lines).ceil() as u32, max_row.next_row().0, ); let end_row = DisplayRow(end_row); diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index 1cc619385446502db6a3a0dceb6e70fa4b4e8416..5a0652bdd199a638f92234b1d50232071db18e07 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -176,9 +176,11 @@ pub fn block_content_for_tests( } pub fn editor_content_with_blocks(editor: &Entity, cx: &mut VisualTestContext) -> String { - let draw_size = size(px(3000.0), px(3000.0)); - cx.simulate_resize(draw_size); - cx.draw(gpui::Point::default(), draw_size, |_, _| editor.clone()); + cx.draw( + gpui::Point::default(), + size(px(3000.0), px(3000.0)), + |_, _| editor.clone(), + ); let (snapshot, mut lines, blocks) = editor.update_in(cx, |editor, window, cx| { let snapshot = editor.snapshot(window, cx); let text = editor.display_text(cx);