on main help sign in
feat(ui): optimize model ui rendering
commit81cb9d990aa272dc1fdf17ae086b2b23ee3cae08 authorKieran Klukas <kieran@dunkirk.sh> dateMon 22 Jun 2026, 15:59 (4 months ago) parent21a457d
3 files changed+29−5
internal/ui/dialog/models.go+1−2 internal/ui/dialog/models_list.go−1 internal/ui/list/list.go+28−2
internal/ui/dialog/models.go+1−2
@@ -269,10 +269,9 @@ func (m *Models) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor {
269269 m.help.SetWidth(innerWidth)
270270
271271 listHeight := height - heightOffset
272− m.list.SetSize(innerWidth, listHeight)
273− listTotalHeight := m.list.TotalHeight()
274272 listWidth := max(0, innerWidth-3) // Reserve space for scrollbar.
275273 m.list.SetSize(listWidth, listHeight)
274+ listTotalHeight := m.list.TotalHeight()
276275
277276 rc := NewRenderContext(t, width)
278277 rc.Title = "Switch Model"
internal/ui/dialog/models_list.go−1
@@ -254,7 +254,6 @@ func (f *ModelsList) VisibleItems() []list.Item {
254254
255255// Render renders the filterable list.
256256func (f *ModelsList) Render() string {
257− f.SetItems(f.VisibleItems()...)
258257 return f.List.Render()
259258}
260259
internal/ui/list/list.go+28−2
@@ -34,6 +34,12 @@ type List struct {
3434 // renderCallbacks is a list of callbacks to apply when rendering items.
3535 renderCallbacks []func(idx, selectedIdx int, item Item) Item
3636
37+ // totalHeightCache is a cached value of the total rendered height of
38+ // all items. It is invalidated whenever the item set changes or the
39+ // viewport width changes (which can alter per-item line counts).
40+ totalHeightCache int
41+ totalHeightValid bool
42+
3743 // cache is the F6 list-level render memo, keyed by item pointer.
3844 // Each entry stores the rendered content, a pre-split slice of
3945 // lines (so AtBottom / Render / VisibleItemIndices /
@@ -158,15 +164,25 @@ func (l *List) Len() int {
158164}
159165
160166// TotalHeight returns the total height of all items in the list.
167+// The result is cached and only recomputed when the item set or
168+// viewport width changes.
161169func (l *List) TotalHeight() int {
170+ if l.totalHeightValid {
171+ return l.totalHeightCache
172+ }
162173 total := 0
163174 for idx := range l.items {
164− item := l.getItem(idx)
165− total += item.height
175+ entry := l.renderItemEntry(idx)
176+ if entry == nil {
177+ continue
178+ }
179+ total += entry.height
166180 if l.gap > 0 && idx < len(l.items)-1 {
167181 total += l.gap
168182 }
169183 }
184+ l.totalHeightCache = total
185+ l.totalHeightValid = true
170186 return total
171187}
172188
@@ -289,6 +305,11 @@ func (l *List) renderItemEntry(idx int) *listCacheEntry {
289305 entry = &listCacheEntry{}
290306 l.cache[rawItem] = entry
291307 }
308+ // If the item's rendered height changed, the cached total height is
309+ // no longer valid and must be recomputed on the next TotalHeight call.
310+ if entry.height != height {
311+ l.totalHeightValid = false
312+ }
292313 entry.width = l.width
293314 entry.version = finalVersion
294315 entry.frozen = frozen
@@ -303,6 +324,7 @@ func (l *List) invalidateAll() {
303324 for k := range l.cache {
304325 delete(l.cache, k)
305326 }
327+ l.totalHeightValid = false
306328}
307329
308330// Invalidate drops the cache entry for the given item, forcing a
@@ -575,6 +597,7 @@ func (l *List) PrependItems(items ...Item) {
575597 if l.selectedIdx != -1 {
576598 l.selectedIdx += len(items)
577599 }
600+ l.totalHeightValid = false
578601}
579602
580603// SetItems sets the items in the list. Cache entries for items that
@@ -586,11 +609,13 @@ func (l *List) SetItems(items ...Item) {
586609 l.offsetIdx = min(l.offsetIdx, len(l.items)-1)
587610 l.offsetLine = 0
588611 l.retainCacheFor(items)
612+ l.totalHeightValid = false
589613}
590614
591615// AppendItems appends items to the list.
592616func (l *List) AppendItems(items ...Item) {
593617 l.items = append(l.items, items...)
618+ l.totalHeightValid = false
594619}
595620
596621// RemoveItem removes the item at the given index from the list.
@@ -623,6 +648,7 @@ func (l *List) RemoveItem(idx int) {
623648 l.offsetIdx = max(0, len(l.items)-1)
624649 l.offsetLine = 0
625650 }
651+ l.totalHeightValid = false
626652}
627653
628654// Focused returns whether the list is focused.