shell: Expose GPUI's list and uniform_list to scripts - #2952
Merged
Conversation
huacnlee
force-pushed
the
feat/shell-gpui-list
branch
from
September 4, 2026 13:55
7d75464 to
f86a759
Compare
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
force-pushed
the
feat/shell-gpui-list
branch
from
September 4, 2026 14:26
f86a759 to
e1e3221
Compare
huacnlee
enabled auto-merge (squash)
September 4, 2026 14:34
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Exposes GPUI's own lazy lists to gpui-shell scripts. They are GPUI elements, so they are exported from the
gpui-kitmodule next todivandsvg, not fromgpui-basewherev_virtual_list/h_virtual_listlive:list(id, item_count, get_key, render(index, cx))wrapsgpui::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))wrapsgpui::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_clickwork unchanged.The problem it solves: a
v_virtual_listplaces 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:
ScrollHandle. The shared slot aScrollbarpairs with is now aSharedScrollenum holding aScrollHandle, aUniformListScrollHandleor aListState, and it answers as the bar's handle itself, soScrollbar.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.listkeeps 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
listcosts is one host crossing per visible item per frame, becausegpui::list's renderer takes a single index; the range-based lists cost one crossing per frame however many rows are on screen. Thematerializemodule 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_keyuniqueness is checked within a call, so alistcannot 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
VirtualListScrollHandleyet;track_scrollon them logs a warning and is ignored, as doeswith_item_to_measure_indexon alist.Docs:
website/shell/api.mdand 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 warningsThe four new tests mount a script list in a 200px box:
uniform_listdescribes only the visible window (about ten of five hundred 20px rows) and moves it on wheel scroll;listmeasures 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 throughon_item_click.Checklist
cargo runfor story tests related to the changes (no story exercises the shell lists; the gpui-shell test suite passes).🤖 Generated with Claude Code
https://claude.ai/code/session_01KWyH4iQb8w6q62ZbMMDcJu