Optimize editor rendering when clipped by parent containers (#44995)
Antonio Scandurra
created
Fixes #44997
## Summary
Optimizes editor rendering when an editor is partially clipped by a
parent container (e.g., a `List`). The editor now only lays out and
renders lines that are actually visible within the viewport, rather than
all lines in the document.
## Problem
When an `AutoHeight` editor with thousands of lines is placed inside a
scrollable `List` (such as in the Agent Panel thread view), the editor
would lay out **all** lines during prepaint, even though only a small
portion was visible. Profiling showed that ~50% of frame time was spent
in `EditorElement::prepaint` → `LineWithInvisibles::from_chunks`,
processing thousands of invisible lines.
## Solution
Calculate the intersection of the editor's bounds with the current
content mask (which represents the visible viewport after all parent
clipping). Use this to determine:
1. `clipped_top_in_lines` - how many lines are clipped above the
viewport
2. `visible_height_in_lines` - how many lines are actually visible
Then adjust `start_row` and `end_row` to only include visible lines. The
parent container handles positioning, so `scroll_position` remains
unchanged for paint calculations.
## Example
For a 3000-line editor where only 50 lines are visible:
- **Before**: Lay out and render 3000 lines
- **After**: Lay out and render ~50 lines
## Testing
Verified the following scenarios work correctly:
- Editor fully visible (no clipping)
- Editor clipped from top
- Editor clipped from bottom
- Editor completely outside viewport (renders nothing)
- Fractional line clipping at boundaries
- Scrollable editors with internal scroll state inside a clipped
container
Release Notes:
- Improved agent panel performance when rendering large diffs.
@@ -9164,6 +9164,15 @@ 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,
@@ -9220,10 +9229,14 @@ 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.
- let start_row = DisplayRow(scroll_position.y as u32);
+ // 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 max_row = snapshot.max_point().row();
let end_row = cmp::min(
- (scroll_position.y + height_in_lines).ceil() as u32,
+ (scroll_position.y + clipped_top_in_lines + visible_height_in_lines).ceil()
+ as u32,
max_row.next_row().0,
);
let end_row = DisplayRow(end_row);