Skip to content

Optimize terminal rendering when clipped by parent containers#45537

Merged
nathansobo merged 1 commit intomainfrom
fix-terminal-viewport-culling-rebased
Dec 26, 2025
Merged

Optimize terminal rendering when clipped by parent containers#45537
nathansobo merged 1 commit intomainfrom
fix-terminal-viewport-culling-rebased

Conversation

@nathansobo
Copy link
Contributor

This brings the terminal element's viewport culling in line with the editor optimization in PR #44995 and the fix in PR #45077.

Problem

When a terminal is inside a scrollable container (e.g., the Agent Panel thread view), it would render ALL cells during prepaint, even when the terminal was entirely outside the viewport. This caused unnecessary CPU usage when multiple terminal tool outputs existed in the Agent Panel.

Solution

Calculate the intersection of the terminal's bounds with the current content_mask (the visible viewport after all parent clipping). If the intersection has zero area, skip all cell processing entirely.

Three code paths

  1. Offscreen (intersection.size <= 0): Early exit, process 0 cells
  2. Fully visible (intersection == bounds): Fast path, stream cells directly (no allocation)
  3. Partially clipped: Group cells by line, skip/take visible rows only

Key insight: filter by screen position, not buffer coordinates

The previous approach tried to filter cells by cell.point.line (terminal buffer coordinates), which breaks in Scrollable mode where cells can have negative line numbers for scrollback history.

The new approach filters by screen position using chunk_by(line).skip(N).take(M), which works regardless of the actual line numbers because we're filtering on enumerated line group index.

Testing

Added comprehensive unit tests for:

  • Screen-position filtering with positive lines (Inline mode)
  • Screen-position filtering with negative lines (Scrollable mode with scrollback)
  • Edge cases (skip all, positioning math)
  • Unified filtering works for both modes

Manually verified:

  • Terminal fully visible (no clipping) ✓
  • Terminal clipped from top/bottom ✓
  • Terminal completely outside viewport ✓
  • Scrollable terminals with scrollback history ✓
  • Selection/interaction still works ✓

Release Notes:

  • Improved Agent Panel performance when terminals are scrolled offscreen.

/cc @as-cii

This brings the terminal element's viewport culling in line with the editor
optimization in PR #44995 and the fix in PR #45077.

## Problem

When a terminal is inside a scrollable container (e.g., the Agent Panel
thread view), it would render ALL cells during prepaint, even when the
terminal was entirely outside the viewport. This caused unnecessary CPU
usage when multiple terminal tool outputs existed in the Agent Panel.

## Solution

Calculate the intersection of the terminal's bounds with the current
content_mask (the visible viewport after all parent clipping). If the
intersection has zero area, skip all cell processing entirely.

### Important distinction between content modes:

- **ContentMode::Scrollable**: Cells use the terminal's internal coordinate
  system with negative line numbers for scrollback history. We cannot filter
  cells by screen-space row numbers, so we render all cells when visible.
  The early-exit for zero intersection handles the offscreen case.

- **ContentMode::Inline**: Cells use 0-based line numbers (no scrollback).
  We can filter cells to only those in the visible row range, using the
  same logic that existed before but now extracted into a helper function
  with proper bounds clamping to prevent the hang bug from PR #45077.

## Testing

Added comprehensive unit tests for:
- compute_visible_row_range edge cases
- Cell filtering logic for Inline mode
- Line/i32 comparison behavior

Manually verified:
- Terminal fully visible (no clipping)
- Terminal clipped from top/bottom
- Terminal completely outside viewport (renders nothing)
- Scrollable terminals with scrollback history work correctly

Release Notes:

- Improved Agent Panel performance when terminals are scrolled offscreen.
@cla-bot cla-bot bot added the cla-signed The user has signed the Contributor License Agreement label Dec 22, 2025
@nathansobo nathansobo merged commit a7ce677 into main Dec 26, 2025
24 checks passed
@nathansobo nathansobo deleted the fix-terminal-viewport-culling-rebased branch December 26, 2025 18:20
CherryWorm pushed a commit to CherryWorm/zed that referenced this pull request Dec 30, 2025
…dustries#45537)

This brings the terminal element's viewport culling in line with the
editor optimization in PR zed-industries#44995 and the fix in PR zed-industries#45077.

## Problem

When a terminal is inside a scrollable container (e.g., the Agent Panel
thread view), it would render ALL cells during prepaint, even when the
terminal was entirely outside the viewport. This caused unnecessary CPU
usage when multiple terminal tool outputs existed in the Agent Panel.

## Solution

Calculate the intersection of the terminal's bounds with the current
content_mask (the visible viewport after all parent clipping). If the
intersection has zero area, skip all cell processing entirely.

### Three code paths

1. **Offscreen** (`intersection.size <= 0`): Early exit, process 0 cells
2. **Fully visible** (`intersection == bounds`): Fast path, stream cells
directly (no allocation)
3. **Partially clipped**: Group cells by line, skip/take visible rows
only

### Key insight: filter by screen position, not buffer coordinates

The previous approach tried to filter cells by `cell.point.line`
(terminal buffer coordinates), which breaks in Scrollable mode where
cells can have negative line numbers for scrollback history.

The new approach filters by **screen position** using
`chunk_by(line).skip(N).take(M)`, which works regardless of the actual
line numbers because we're filtering on enumerated line group index.

## Testing

Added comprehensive unit tests for:
- Screen-position filtering with positive lines (Inline mode)
- Screen-position filtering with negative lines (Scrollable mode with
scrollback)
- Edge cases (skip all, positioning math)
- Unified filtering works for both modes

Manually verified:
- Terminal fully visible (no clipping) ✓
- Terminal clipped from top/bottom ✓
- Terminal completely outside viewport ✓
- Scrollable terminals with scrollback history ✓
- Selection/interaction still works ✓

Release Notes:

- Improved Agent Panel performance when terminals are scrolled
offscreen.

/cc @as-cii
rtfeldman pushed a commit that referenced this pull request Jan 5, 2026
This brings the terminal element's viewport culling in line with the
editor optimization in PR #44995 and the fix in PR #45077.

## Problem

When a terminal is inside a scrollable container (e.g., the Agent Panel
thread view), it would render ALL cells during prepaint, even when the
terminal was entirely outside the viewport. This caused unnecessary CPU
usage when multiple terminal tool outputs existed in the Agent Panel.

## Solution

Calculate the intersection of the terminal's bounds with the current
content_mask (the visible viewport after all parent clipping). If the
intersection has zero area, skip all cell processing entirely.

### Three code paths

1. **Offscreen** (`intersection.size <= 0`): Early exit, process 0 cells
2. **Fully visible** (`intersection == bounds`): Fast path, stream cells
directly (no allocation)
3. **Partially clipped**: Group cells by line, skip/take visible rows
only

### Key insight: filter by screen position, not buffer coordinates

The previous approach tried to filter cells by `cell.point.line`
(terminal buffer coordinates), which breaks in Scrollable mode where
cells can have negative line numbers for scrollback history.

The new approach filters by **screen position** using
`chunk_by(line).skip(N).take(M)`, which works regardless of the actual
line numbers because we're filtering on enumerated line group index.

## Testing

Added comprehensive unit tests for:
- Screen-position filtering with positive lines (Inline mode)
- Screen-position filtering with negative lines (Scrollable mode with
scrollback)
- Edge cases (skip all, positioning math)
- Unified filtering works for both modes

Manually verified:
- Terminal fully visible (no clipping) ✓
- Terminal clipped from top/bottom ✓
- Terminal completely outside viewport ✓
- Scrollable terminals with scrollback history ✓
- Selection/interaction still works ✓

Release Notes:

- Improved Agent Panel performance when terminals are scrolled
offscreen.

/cc @as-cii
LivioGama pushed a commit to LivioGama/zed that referenced this pull request Jan 20, 2026
…dustries#45537)

This brings the terminal element's viewport culling in line with the
editor optimization in PR zed-industries#44995 and the fix in PR zed-industries#45077.

## Problem

When a terminal is inside a scrollable container (e.g., the Agent Panel
thread view), it would render ALL cells during prepaint, even when the
terminal was entirely outside the viewport. This caused unnecessary CPU
usage when multiple terminal tool outputs existed in the Agent Panel.

## Solution

Calculate the intersection of the terminal's bounds with the current
content_mask (the visible viewport after all parent clipping). If the
intersection has zero area, skip all cell processing entirely.

### Three code paths

1. **Offscreen** (`intersection.size <= 0`): Early exit, process 0 cells
2. **Fully visible** (`intersection == bounds`): Fast path, stream cells
directly (no allocation)
3. **Partially clipped**: Group cells by line, skip/take visible rows
only

### Key insight: filter by screen position, not buffer coordinates

The previous approach tried to filter cells by `cell.point.line`
(terminal buffer coordinates), which breaks in Scrollable mode where
cells can have negative line numbers for scrollback history.

The new approach filters by **screen position** using
`chunk_by(line).skip(N).take(M)`, which works regardless of the actual
line numbers because we're filtering on enumerated line group index.

## Testing

Added comprehensive unit tests for:
- Screen-position filtering with positive lines (Inline mode)
- Screen-position filtering with negative lines (Scrollable mode with
scrollback)
- Edge cases (skip all, positioning math)
- Unified filtering works for both modes

Manually verified:
- Terminal fully visible (no clipping) ✓
- Terminal clipped from top/bottom ✓
- Terminal completely outside viewport ✓
- Scrollable terminals with scrollback history ✓
- Selection/interaction still works ✓

Release Notes:

- Improved Agent Panel performance when terminals are scrolled
offscreen.

/cc @as-cii
LivioGama pushed a commit to LivioGama/zed that referenced this pull request Jan 20, 2026
…dustries#45537)

This brings the terminal element's viewport culling in line with the
editor optimization in PR zed-industries#44995 and the fix in PR zed-industries#45077.

## Problem

When a terminal is inside a scrollable container (e.g., the Agent Panel
thread view), it would render ALL cells during prepaint, even when the
terminal was entirely outside the viewport. This caused unnecessary CPU
usage when multiple terminal tool outputs existed in the Agent Panel.

## Solution

Calculate the intersection of the terminal's bounds with the current
content_mask (the visible viewport after all parent clipping). If the
intersection has zero area, skip all cell processing entirely.

### Three code paths

1. **Offscreen** (`intersection.size <= 0`): Early exit, process 0 cells
2. **Fully visible** (`intersection == bounds`): Fast path, stream cells
directly (no allocation)
3. **Partially clipped**: Group cells by line, skip/take visible rows
only

### Key insight: filter by screen position, not buffer coordinates

The previous approach tried to filter cells by `cell.point.line`
(terminal buffer coordinates), which breaks in Scrollable mode where
cells can have negative line numbers for scrollback history.

The new approach filters by **screen position** using
`chunk_by(line).skip(N).take(M)`, which works regardless of the actual
line numbers because we're filtering on enumerated line group index.

## Testing

Added comprehensive unit tests for:
- Screen-position filtering with positive lines (Inline mode)
- Screen-position filtering with negative lines (Scrollable mode with
scrollback)
- Edge cases (skip all, positioning math)
- Unified filtering works for both modes

Manually verified:
- Terminal fully visible (no clipping) ✓
- Terminal clipped from top/bottom ✓
- Terminal completely outside viewport ✓
- Scrollable terminals with scrollback history ✓
- Selection/interaction still works ✓

Release Notes:

- Improved Agent Panel performance when terminals are scrolled
offscreen.

/cc @as-cii
LivioGama pushed a commit to LivioGama/zed that referenced this pull request Feb 15, 2026
…dustries#45537)

This brings the terminal element's viewport culling in line with the
editor optimization in PR zed-industries#44995 and the fix in PR zed-industries#45077.

## Problem

When a terminal is inside a scrollable container (e.g., the Agent Panel
thread view), it would render ALL cells during prepaint, even when the
terminal was entirely outside the viewport. This caused unnecessary CPU
usage when multiple terminal tool outputs existed in the Agent Panel.

## Solution

Calculate the intersection of the terminal's bounds with the current
content_mask (the visible viewport after all parent clipping). If the
intersection has zero area, skip all cell processing entirely.

### Three code paths

1. **Offscreen** (`intersection.size <= 0`): Early exit, process 0 cells
2. **Fully visible** (`intersection == bounds`): Fast path, stream cells
directly (no allocation)
3. **Partially clipped**: Group cells by line, skip/take visible rows
only

### Key insight: filter by screen position, not buffer coordinates

The previous approach tried to filter cells by `cell.point.line`
(terminal buffer coordinates), which breaks in Scrollable mode where
cells can have negative line numbers for scrollback history.

The new approach filters by **screen position** using
`chunk_by(line).skip(N).take(M)`, which works regardless of the actual
line numbers because we're filtering on enumerated line group index.

## Testing

Added comprehensive unit tests for:
- Screen-position filtering with positive lines (Inline mode)
- Screen-position filtering with negative lines (Scrollable mode with
scrollback)
- Edge cases (skip all, positioning math)
- Unified filtering works for both modes

Manually verified:
- Terminal fully visible (no clipping) ✓
- Terminal clipped from top/bottom ✓
- Terminal completely outside viewport ✓
- Scrollable terminals with scrollback history ✓
- Selection/interaction still works ✓

Release Notes:

- Improved Agent Panel performance when terminals are scrolled
offscreen.

/cc @as-cii
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.

1 participant