Skip to content

Revert "Optimize editor rendering when clipped by parent containers"#45011

Merged
JosephTLyons merged 1 commit intomainfrom
revert-editor-clipping-optimization
Dec 16, 2025
Merged

Revert "Optimize editor rendering when clipped by parent containers"#45011
JosephTLyons merged 1 commit intomainfrom
revert-editor-clipping-optimization

Conversation

@nathansobo
Copy link
Contributor

This reverts commit 914b011 (#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

…44995)"

This reverts commit 914b011.

The optimization introduced a regression that causes the main thread to
hang for 100+ seconds in certain scenarios.

## 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.

## Symptoms

- Main thread unresponsive for 33-113 seconds
- UI completely frozen
- High CPU usage on main thread (10+ seconds of CPU time)
- Force quit required to recover

The original optimization goal (reducing line layout work for clipped
editors) is valid, but the implementation needs to correctly calculate
the intersection of editor bounds with the visible viewport, and ensure
row calculations stay within valid ranges.
@cla-bot cla-bot bot added the cla-signed The user has signed the Contributor License Agreement label Dec 16, 2025
@JosephTLyons JosephTLyons merged commit c1317ba into main Dec 16, 2025
24 checks passed
@JosephTLyons JosephTLyons deleted the revert-editor-clipping-optimization branch December 16, 2025 17:58
JosephTLyons pushed a commit that referenced this pull request Dec 16, 2025
…45011)

This reverts commit 914b011 (#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
as-cii added a commit that referenced this pull request Dec 17, 2025
The editor optimization from #45077 uses window.content_mask().bounds to
determine visible rows. In tests, the content mask defaults to the window's
viewport size (1920x1043 from TestDisplay), not the draw size passed to
cx.draw().

Fix by calling cx.simulate_resize() before drawing to ensure the window's
viewport matches the intended draw size (3000x3000).

This fix was originally part of #44995 but was lost when that PR was
reverted in #45011.
as-cii added a commit that referenced this pull request Dec 17, 2025
Fixes the hang introduced in #44995 (which was reverted in #45011) and
re-enables the optimization.

## Background

PR #44995 introduced an optimization to skip rendering lines that are
clipped by parent containers (e.g., when a large AutoHeight editor is
inside a scrollable List). This significantly improved performance for
large diffs in the Agent Panel.
However, #45011 reverted this change because it caused the main thread
to hang for 100+ seconds in certain scenarios, requiring a force quit to
recover.

## Root Cause
The original analysis in #45011 suggested that visible_bounds wasn’t
being intersected properly, but that was incorrect—the intersection via
with_content_mask works correctly. The actual bug: when an editor is
positioned above the visible viewport (e.g., scrolled past in a List),
the clipping calculation produces a start_row that exceeds max_row:

1. Editor’s bounds.origin.y becomes very negative (e.g., -10000px)
2. After intersection, visible_bounds.origin.y is at the viewport top
(e.g., 0)
3. clipped_top_in_lines = (0 - (-10000)) / line_height = huge number
4. start_row = huge number, but end_row is clamped to max_row
5. This creates an invalid range where start_row > end_row

This caused two different failures depending on build mode:
- Debug mode: Panic from subtraction overflow in
Range<DisplayRow>::len()
- Release mode: Integer wraparound causing blocks_in_range to enter an
infinite loop (the 100+ second hang)

## Fix

Simply clamp start_row to max_row, ensuring the row range is always
valid:

```rs
let start_row = cmp::min(
    DisplayRow((scroll_position.y + clipped_top_in_lines).floor() as u32),
    max_row,
);
```

## Testing
Added a regression test that draws an editor at y=-10000 to simulate an
editor that’s been scrolled past in a List. This would panic in debug
mode (and hang in release mode) before the fix.

Release Notes:
- Improved agent panel performance when rendering large diffs.
HactarCE pushed a commit that referenced this pull request Dec 17, 2025
Fixes the hang introduced in #44995 (which was reverted in #45011) and
re-enables the optimization.

## Background

PR #44995 introduced an optimization to skip rendering lines that are
clipped by parent containers (e.g., when a large AutoHeight editor is
inside a scrollable List). This significantly improved performance for
large diffs in the Agent Panel.
However, #45011 reverted this change because it caused the main thread
to hang for 100+ seconds in certain scenarios, requiring a force quit to
recover.

## Root Cause
The original analysis in #45011 suggested that visible_bounds wasn’t
being intersected properly, but that was incorrect—the intersection via
with_content_mask works correctly. The actual bug: when an editor is
positioned above the visible viewport (e.g., scrolled past in a List),
the clipping calculation produces a start_row that exceeds max_row:

1. Editor’s bounds.origin.y becomes very negative (e.g., -10000px)
2. After intersection, visible_bounds.origin.y is at the viewport top
(e.g., 0)
3. clipped_top_in_lines = (0 - (-10000)) / line_height = huge number
4. start_row = huge number, but end_row is clamped to max_row
5. This creates an invalid range where start_row > end_row

This caused two different failures depending on build mode:
- Debug mode: Panic from subtraction overflow in
Range<DisplayRow>::len()
- Release mode: Integer wraparound causing blocks_in_range to enter an
infinite loop (the 100+ second hang)

## Fix

Simply clamp start_row to max_row, ensuring the row range is always
valid:

```rs
let start_row = cmp::min(
    DisplayRow((scroll_position.y + clipped_top_in_lines).floor() as u32),
    max_row,
);
```

## Testing
Added a regression test that draws an editor at y=-10000 to simulate an
editor that’s been scrolled past in a List. This would panic in debug
mode (and hang in release mode) before the fix.

Release Notes:
- Improved agent panel performance when rendering large diffs.
LivioGama pushed a commit to LivioGama/zed that referenced this pull request Jan 20, 2026
…ed-industries#45011)

This reverts commit 914b011 (zed-industries#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
LivioGama pushed a commit to LivioGama/zed that referenced this pull request Jan 20, 2026
Fixes the hang introduced in zed-industries#44995 (which was reverted in zed-industries#45011) and
re-enables the optimization.

## Background

PR zed-industries#44995 introduced an optimization to skip rendering lines that are
clipped by parent containers (e.g., when a large AutoHeight editor is
inside a scrollable List). This significantly improved performance for
large diffs in the Agent Panel.
However, zed-industries#45011 reverted this change because it caused the main thread
to hang for 100+ seconds in certain scenarios, requiring a force quit to
recover.

## Root Cause
The original analysis in zed-industries#45011 suggested that visible_bounds wasn’t
being intersected properly, but that was incorrect—the intersection via
with_content_mask works correctly. The actual bug: when an editor is
positioned above the visible viewport (e.g., scrolled past in a List),
the clipping calculation produces a start_row that exceeds max_row:

1. Editor’s bounds.origin.y becomes very negative (e.g., -10000px)
2. After intersection, visible_bounds.origin.y is at the viewport top
(e.g., 0)
3. clipped_top_in_lines = (0 - (-10000)) / line_height = huge number
4. start_row = huge number, but end_row is clamped to max_row
5. This creates an invalid range where start_row > end_row

This caused two different failures depending on build mode:
- Debug mode: Panic from subtraction overflow in
Range<DisplayRow>::len()
- Release mode: Integer wraparound causing blocks_in_range to enter an
infinite loop (the 100+ second hang)

## Fix

Simply clamp start_row to max_row, ensuring the row range is always
valid:

```rs
let start_row = cmp::min(
    DisplayRow((scroll_position.y + clipped_top_in_lines).floor() as u32),
    max_row,
);
```

## Testing
Added a regression test that draws an editor at y=-10000 to simulate an
editor that’s been scrolled past in a List. This would panic in debug
mode (and hang in release mode) before the fix.

Release Notes:
- Improved agent panel performance when rendering large diffs.
LivioGama pushed a commit to LivioGama/zed that referenced this pull request Jan 20, 2026
…ed-industries#45011)

This reverts commit 914b011 (zed-industries#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
LivioGama pushed a commit to LivioGama/zed that referenced this pull request Jan 20, 2026
Fixes the hang introduced in zed-industries#44995 (which was reverted in zed-industries#45011) and
re-enables the optimization.

## Background

PR zed-industries#44995 introduced an optimization to skip rendering lines that are
clipped by parent containers (e.g., when a large AutoHeight editor is
inside a scrollable List). This significantly improved performance for
large diffs in the Agent Panel.
However, zed-industries#45011 reverted this change because it caused the main thread
to hang for 100+ seconds in certain scenarios, requiring a force quit to
recover.

## Root Cause
The original analysis in zed-industries#45011 suggested that visible_bounds wasn’t
being intersected properly, but that was incorrect—the intersection via
with_content_mask works correctly. The actual bug: when an editor is
positioned above the visible viewport (e.g., scrolled past in a List),
the clipping calculation produces a start_row that exceeds max_row:

1. Editor’s bounds.origin.y becomes very negative (e.g., -10000px)
2. After intersection, visible_bounds.origin.y is at the viewport top
(e.g., 0)
3. clipped_top_in_lines = (0 - (-10000)) / line_height = huge number
4. start_row = huge number, but end_row is clamped to max_row
5. This creates an invalid range where start_row > end_row

This caused two different failures depending on build mode:
- Debug mode: Panic from subtraction overflow in
Range<DisplayRow>::len()
- Release mode: Integer wraparound causing blocks_in_range to enter an
infinite loop (the 100+ second hang)

## Fix

Simply clamp start_row to max_row, ensuring the row range is always
valid:

```rs
let start_row = cmp::min(
    DisplayRow((scroll_position.y + clipped_top_in_lines).floor() as u32),
    max_row,
);
```

## Testing
Added a regression test that draws an editor at y=-10000 to simulate an
editor that’s been scrolled past in a List. This would panic in debug
mode (and hang in release mode) before the fix.

Release Notes:
- Improved agent panel performance when rendering large diffs.
LivioGama pushed a commit to LivioGama/zed that referenced this pull request Feb 15, 2026
…ed-industries#45011)

This reverts commit 914b011 (zed-industries#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
LivioGama pushed a commit to LivioGama/zed that referenced this pull request Feb 15, 2026
Fixes the hang introduced in zed-industries#44995 (which was reverted in zed-industries#45011) and
re-enables the optimization.

## Background

PR zed-industries#44995 introduced an optimization to skip rendering lines that are
clipped by parent containers (e.g., when a large AutoHeight editor is
inside a scrollable List). This significantly improved performance for
large diffs in the Agent Panel.
However, zed-industries#45011 reverted this change because it caused the main thread
to hang for 100+ seconds in certain scenarios, requiring a force quit to
recover.

## Root Cause
The original analysis in zed-industries#45011 suggested that visible_bounds wasn’t
being intersected properly, but that was incorrect—the intersection via
with_content_mask works correctly. The actual bug: when an editor is
positioned above the visible viewport (e.g., scrolled past in a List),
the clipping calculation produces a start_row that exceeds max_row:

1. Editor’s bounds.origin.y becomes very negative (e.g., -10000px)
2. After intersection, visible_bounds.origin.y is at the viewport top
(e.g., 0)
3. clipped_top_in_lines = (0 - (-10000)) / line_height = huge number
4. start_row = huge number, but end_row is clamped to max_row
5. This creates an invalid range where start_row > end_row

This caused two different failures depending on build mode:
- Debug mode: Panic from subtraction overflow in
Range<DisplayRow>::len()
- Release mode: Integer wraparound causing blocks_in_range to enter an
infinite loop (the 100+ second hang)

## Fix

Simply clamp start_row to max_row, ensuring the row range is always
valid:

```rs
let start_row = cmp::min(
    DisplayRow((scroll_position.y + clipped_top_in_lines).floor() as u32),
    max_row,
);
```

## Testing
Added a regression test that draws an editor at y=-10000 to simulate an
editor that’s been scrolled past in a List. This would panic in debug
mode (and hang in release mode) before the fix.

Release Notes:
- Improved agent panel performance when rendering large diffs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed The user has signed the Contributor License Agreement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants