Skip to content

shell: Expose GPUI's list and uniform_list to scripts - #2952

Merged
huacnlee merged 1 commit into
mainfrom
feat/shell-gpui-list
Sep 4, 2026
Merged

shell: Expose GPUI's list and uniform_list to scripts#2952
huacnlee merged 1 commit into
mainfrom
feat/shell-gpui-list

Conversation

@huacnlee

@huacnlee huacnlee commented Sep 4, 2026

Copy link
Copy Markdown
Member

Description

Exposes GPUI's own lazy lists to gpui-shell scripts. They are GPUI elements, so they are exported from the gpui-kit module next to div and svg, not from gpui-base where v_virtual_list / h_virtual_list live:

  • list(id, item_count, get_key, render(index, cx)) wraps gpui::list. Every item it draws is measured, so rows (or panels) of unequal, unstated height scroll as one collection without the script supplying a size table.
  • uniform_list(id, item_count, get_key, render(range, cx)) wraps gpui::uniform_list. One row is measured and every row is placed by it.

Both reach the script through the same confined item-renderer path the virtual list uses (Layout phase, own arena, no handler registration), and on_item_click / on_item_secondary_click work unchanged.

The problem it solves: a v_virtual_list places rows by the sizes the script states, which a column of content-sized panels cannot supply. In longbridge-lite the Watchlist page's detail column (three panels of different, content-determined heights) needed a list that measures instead.

Two supporting changes:

  • The scroll position a name carries is no longer only a ScrollHandle. The shared slot a Scrollbar pairs with is now a SharedScroll enum holding a ScrollHandle, a UniformListScrollHandle or a ListState, and it answers as the bar's handle itself, so Scrollbar.vertical(id) drives either new list by name. A scroll area and a list sharing one name now logs a warning and the later claimant wins.
  • A list keeps a 160px overdraw band: GPUI's list can only scroll into measured ground, and with nothing drawn past the viewport a list whose last row ends exactly at the bottom edge could never scroll. The band is deliberately a few rows rather than the screenful GPUI's own callers use, so an item a screen away is not paid for every frame.

What list costs is one host crossing per visible item per frame, because gpui::list's renderer takes a single index; the range-based lists cost one crossing per frame however many rows are on screen. The materialize module doc and the typings now say so, rather than leaving the old "once per list" claim to cover both shapes. A consequence worth knowing: get_key uniqueness is checked within a call, so a list cannot detect two items sharing a key where the other two throw — the typings say that too.

A scroll area now keeps its own handle under a key of its own rather than reading it back out of the shared slot: a lazy list of the same name rewrites that slot every frame, and an area that read from it would be handed a fresh, unscrolled handle each frame — it would stop scrolling rather than merely lose the bar. A name claimed by both reports itself once instead of once a frame.

Neither list takes a VirtualListScrollHandle yet; track_scroll on them logs a warning and is ignored, as does with_item_to_measure_index on a list.

Docs: website/shell/api.md and the zh-CN page, plus the typings.

AI assistance: the whole change (Rust, JS wrapper, typings, tests, docs) was written with Claude Code under my direction, and reviewed and tested by me before submitting.

Screenshot

No visual change of its own; the lists draw whatever the item renderer returns.

How to Test

cargo test -p gpui-shell --lib -- tests::render::a_uniform_list tests::render::a_list_ typings
cargo clippy -p gpui-shell --all-targets -- --deny warnings

The four new tests mount a script list in a 200px box: uniform_list describes only the visible window (about ten of five hundred 20px rows) and moves it on wheel scroll; list measures alternating 20px and 40px rows (seven fit, not the ten a uniform guess would place) and moves on scroll; both report the clicked row's stable key through on_item_click.

Checklist

  • I have read the CONTRIBUTING document and followed the guidelines.
  • Reviewed the changes in this PR and confirmed AI generated code (If any) is accurate.
  • Passed cargo run for story tests related to the changes (no story exercises the shell lists; the gpui-shell test suite passes).
  • Tested macOS, Windows and Linux platforms performance (verified on Linux only; the change is platform-independent).

🤖 Generated with Claude Code

https://claude.ai/code/session_01KWyH4iQb8w6q62ZbMMDcJu

@huacnlee
huacnlee force-pushed the feat/shell-gpui-list branch from 7d75464 to f86a759 Compare September 4, 2026 13:55
A v_virtual_list places rows by a size table the script states, which a
column of panels that size to their own content cannot supply. GPUI's own
lazy lists measure instead: `uniform_list` measures one row and places the
rest by it, `list` measures every item it draws and keeps the sizes. Both
now reach script from the `gpui-kit` module, where `div` and `svg` are, as
`list(id, item_count, get_key, render)` and
`uniform_list(id, item_count, get_key, render)`, through the same confined
item-renderer path the virtual list uses, with `on_item_click` and
`on_item_secondary_click` unchanged. The phase guard, the shared item
budget and the two callback registrations are now one pair of helpers both
constructors call, as are the three argument checks on the JavaScript side.

The scroll position a name carries is no longer only a ScrollHandle: the
shared slot a Scrollbar pairs with holds a ScrollHandle, a
UniformListScrollHandle or a ListState and answers as the bar's handle
itself, so `Scrollbar.vertical(id)` drives either list by name. A scroll
area keeps its own handle under a key of its own rather than reading it
back out of that slot, because a lazy list of the same name rewrites the
slot every frame and the area would otherwise be handed a fresh, unscrolled
handle each time; a name claimed by both now reports itself once instead of
once a frame. A `list` keeps a 160px overdraw band so it always has
measured ground to scroll into without paying for items a screen away.

What `list` costs is one host crossing per visible item per frame, where
the range-based lists cost one per frame however many rows are on screen.
The materialize module doc and the typings say so rather than leaving the
old "once per list" claim to cover both shapes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KWyH4iQb8w6q62ZbMMDcJu
@huacnlee
huacnlee force-pushed the feat/shell-gpui-list branch from f86a759 to e1e3221 Compare September 4, 2026 14:26
@huacnlee
huacnlee enabled auto-merge (squash) September 4, 2026 14:34
@huacnlee
huacnlee merged commit a1275a9 into main Sep 4, 2026
9 checks passed
@huacnlee
huacnlee deleted the feat/shell-gpui-list branch September 4, 2026 14:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant