Fix GPUI crash when using cached view with Input (#50665)

The `draw()` method was calling `.pop()` on `next_frame.input_handlers`
to extract the active input handler for the platform window. This
reduced the Vec length, making cached `paint_range` indices stale. On
the next frame, when a cached view called `reuse_paint()`, it would
index into `rendered_frame.input_handlers` with out-of-bounds indices,
causing a panic: `range start index 1 out of range for slice of length
0`.

**Fix:** Use `.last_mut().and_then(|h| h.take())` instead of `.pop()` to
extract the handler without changing the Vec length. The slot becomes
`None`, which is already handled by `reuse_paint`'s `.take()` logic.

Closes #50456

### Testing

- All 83 existing GPUI unit tests pass
- Manually verified with
[longbridge/gpui-component](https://github.com/longbridge/gpui-component)
`cargo run --example dock`:
  - Switch to Input panel → type text → no crash ✅
  - Before fix: crash immediately on first keystroke

---

Before you mark this PR as ready for review, make sure that you have:
- [x] Added a solid test coverage and/or screenshots from doing manual
testing
- [x] Done a self-review taking into account security and performance
aspects
- [ ] Aligned any UI changes with the [UI
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
*(N/A — no UI changes)*

Release Notes:

- Fixed a crash in GPUI when typing into an Input widget inside a cached
view ([#50456](https://github.com/zed-industries/zed/issues/50456))

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
This commit is contained in:
Nguyen Anh Tu (Elior)andMikayla Maki authored and GitHub committed 2026-05-14 15:31:46 +00:00
1 parent af6f7d1004
commit b3b7459e3e
1 file changed
+26 -4
+26 -4
View File
@@ -2494,8 +2494,21 @@ impl Window {
self.requested_autoscroll = None;
// Restore the previously-used input handler.
// Place it back into a None slot (left by a previous .take()) so that
// cached paint_range indices in reuse_paint find the handler at the
// expected position.
if let Some(input_handler) = self.platform_window.take_input_handler() {
self.rendered_frame.input_handlers.push(Some(input_handler));
if let Some(slot) = self
.rendered_frame
.input_handlers
.iter_mut()
.rev()
.find(|h| h.is_none())
{
*slot = Some(input_handler);
} else {
self.rendered_frame.input_handlers.push(Some(input_handler));
}
}
if !cx.mode.skip_drawing() {
self.draw_roots(cx);
@@ -2504,9 +2517,18 @@ impl Window {
self.next_frame.window_active = self.active.get();
// Register requested input handler with the platform window.
if let Some(input_handler) = self.next_frame.input_handlers.pop() {
self.platform_window
.set_input_handler(input_handler.unwrap());
// Use .take() instead of .pop() to preserve Vec length, so that cached
// paint_range indices remain valid for reuse_paint on the next frame.
// Search backwards to find the last Some entry, since reuse_paint may
// have copied None slots from the previous frame. (Fixes #50456)
if let Some(input_handler) = self
.next_frame
.input_handlers
.iter_mut()
.rev()
.find_map(|h| h.take())
{
self.platform_window.set_input_handler(input_handler);
}
self.layout_engine.as_mut().unwrap().clear();