on main help sign in
feat(ui): optimize model ui rendering
81cb9d9Kieran Klukas
internal/ui/dialog/models.go+1−2
@@ -269,10 +269,9 @@ func (m *Models) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor {
269 m.help.SetWidth(innerWidth)
269 m.help.SetWidth(innerWidth)
270
270
271 listHeight := height - heightOffset
271 listHeight := height - heightOffset
272− m.list.SetSize(innerWidth, listHeight)
273− listTotalHeight := m.list.TotalHeight()
274 listWidth := max(0, innerWidth-3) // Reserve space for scrollbar.
272 listWidth := max(0, innerWidth-3) // Reserve space for scrollbar.
275 m.list.SetSize(listWidth, listHeight)
273 m.list.SetSize(listWidth, listHeight)
274+ listTotalHeight := m.list.TotalHeight()
276
275
277 rc := NewRenderContext(t, width)
276 rc := NewRenderContext(t, width)
278 rc.Title = "Switch Model"
277 rc.Title = "Switch Model"
internal/ui/dialog/models_list.go−1
@@ -254,7 +254,6 @@ func (f *ModelsList) VisibleItems() []list.Item {
254
254
255// Render renders the filterable list.
255// Render renders the filterable list.
256func (f *ModelsList) Render() string {
256func (f *ModelsList) Render() string {
257− f.SetItems(f.VisibleItems()...)
258 return f.List.Render()
257 return f.List.Render()
259}
258}
260
259
internal/ui/list/list.go+28−2
@@ -34,6 +34,12 @@ type List struct {
34 // renderCallbacks is a list of callbacks to apply when rendering items.
34 // renderCallbacks is a list of callbacks to apply when rendering items.
35 renderCallbacks []func(idx, selectedIdx int, item Item) Item
35 renderCallbacks []func(idx, selectedIdx int, item Item) Item
36
36
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+
37 // cache is the F6 list-level render memo, keyed by item pointer.
43 // cache is the F6 list-level render memo, keyed by item pointer.
38 // Each entry stores the rendered content, a pre-split slice of
44 // Each entry stores the rendered content, a pre-split slice of
39 // lines (so AtBottom / Render / VisibleItemIndices /
45 // lines (so AtBottom / Render / VisibleItemIndices /
@@ -158,15 +164,25 @@ func (l *List) Len() int {
158}
164}
159
165
160// TotalHeight returns the total height of all items in the list.
166// 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.
161func (l *List) TotalHeight() int {
169func (l *List) TotalHeight() int {
170+ if l.totalHeightValid {
171+ return l.totalHeightCache
172+ }
162 total := 0
173 total := 0
163 for idx := range l.items {
174 for idx := range l.items {
164− item := l.getItem(idx)
175+ entry := l.renderItemEntry(idx)
176+ if entry == nil {
177+ continue
178+ }
165− total += item.height
179+ total += entry.height
166 if l.gap > 0 && idx < len(l.items)-1 {
180 if l.gap > 0 && idx < len(l.items)-1 {
167 total += l.gap
181 total += l.gap
168 }
182 }
169 }
183 }
184+ l.totalHeightCache = total
185+ l.totalHeightValid = true
170 return total
186 return total
171}
187}
172
188
@@ -289,6 +305,11 @@ func (l *List) renderItemEntry(idx int) *listCacheEntry {
289 entry = &listCacheEntry{}
305 entry = &listCacheEntry{}
290 l.cache[rawItem] = entry
306 l.cache[rawItem] = entry
291 }
307 }
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+ }
292 entry.width = l.width
313 entry.width = l.width
293 entry.version = finalVersion
314 entry.version = finalVersion
294 entry.frozen = frozen
315 entry.frozen = frozen
@@ -303,6 +324,7 @@ func (l *List) invalidateAll() {
303 for k := range l.cache {
324 for k := range l.cache {
304 delete(l.cache, k)
325 delete(l.cache, k)
305 }
326 }
327+ l.totalHeightValid = false
306}
328}
307
329
308// Invalidate drops the cache entry for the given item, forcing a
330// Invalidate drops the cache entry for the given item, forcing a
@@ -575,6 +597,7 @@ func (l *List) PrependItems(items ...Item) {
575 if l.selectedIdx != -1 {
597 if l.selectedIdx != -1 {
576 l.selectedIdx += len(items)
598 l.selectedIdx += len(items)
577 }
599 }
600+ l.totalHeightValid = false
578}
601}
579
602
580// SetItems sets the items in the list. Cache entries for items that
603// SetItems sets the items in the list. Cache entries for items that
@@ -586,11 +609,13 @@ func (l *List) SetItems(items ...Item) {
586 l.offsetIdx = min(l.offsetIdx, len(l.items)-1)
609 l.offsetIdx = min(l.offsetIdx, len(l.items)-1)
587 l.offsetLine = 0
610 l.offsetLine = 0
588 l.retainCacheFor(items)
611 l.retainCacheFor(items)
612+ l.totalHeightValid = false
589}
613}
590
614
591// AppendItems appends items to the list.
615// AppendItems appends items to the list.
592func (l *List) AppendItems(items ...Item) {
616func (l *List) AppendItems(items ...Item) {
593 l.items = append(l.items, items...)
617 l.items = append(l.items, items...)
618+ l.totalHeightValid = false
594}
619}
595
620
596// RemoveItem removes the item at the given index from the list.
621// RemoveItem removes the item at the given index from the list.
@@ -623,6 +648,7 @@ func (l *List) RemoveItem(idx int) {
623 l.offsetIdx = max(0, len(l.items)-1)
648 l.offsetIdx = max(0, len(l.items)-1)
624 l.offsetLine = 0
649 l.offsetLine = 0
625 }
650 }
651+ l.totalHeightValid = false
626}
652}
627
653
628// Focused returns whether the list is focused.
654// Focused returns whether the list is focused.