@@ -9,7 +9,6 @@ import (
99 "github.com/charmbracelet/bubbles/v2/key"
1010 tea "github.com/charmbracelet/bubbletea/v2"
1111 "github.com/charmbracelet/lipgloss/v2"
12− "github.com/opencode-ai/opencode/internal/logging"
1312 "github.com/opencode-ai/opencode/internal/tui/components/anim"
1413 "github.com/opencode-ai/opencode/internal/tui/layout"
1514 "github.com/opencode-ai/opencode/internal/tui/util"
@@ -39,7 +38,7 @@ type renderedItem struct {
3938}
4039type model struct {
4140 width, height, offset int
42− finalHight int // this gets set when the last item is rendered to mark the max offset
41+ finalHeight int // this gets set when the last item is rendered to mark the max offset
4342 reverse bool
4443 help help.Model
4544 keymap KeyMap
@@ -95,7 +94,7 @@ func New(opts ...listOptions) ListModel {
9594 gapSize: 0,
9695 padding: []int{},
9796 selectedItemInx: -1,
98− finalHight: -1,
97+ finalHeight: -1,
9998 lastRenderedInx: -1,
10099 renderedItems: new(sync.Map),
101100 }
@@ -160,20 +159,7 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
160159 m.goToBottom()
161160 return m, nil
162161 }
163− case anim.ColorCycleMsg:
164− logging.Info("ColorCycleMsg", "msg", msg)
165− for inx, item := range m.items {
166− if i, ok := item.(HasAnim); ok {
167− if i.Spinning() {
168− updated, cmd := i.Update(msg)
169− cmds = append(cmds, cmd)
170− m.UpdateItem(inx, updated.(util.Model))
171− }
172− }
173− }
174− return m, tea.Batch(cmds...)
175− case anim.StepCharsMsg:
176− logging.Info("ColorCycleMsg", "msg", msg)
162+ case anim.ColorCycleMsg, anim.StepCharsMsg:
177163 for inx, item := range m.items {
178164 if i, ok := item.(HasAnim); ok {
179165 if i.Spinning() {
@@ -189,7 +175,6 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
189175 u, cmd := m.items[m.selectedItemInx].Update(msg)
190176 cmds = append(cmds, cmd)
191177 m.UpdateItem(m.selectedItemInx, u.(util.Model))
192− cmds = append(cmds, cmd)
193178 return m, tea.Batch(cmds...)
194179 }
195180
@@ -232,7 +217,7 @@ func (m *model) renderVisibleReverse() {
232217 itemLines = cachedContent.(renderedItem).lines
233218 } else {
234219 itemLines = strings.Split(items[i].View(), "\n")
235− if m.gapSize > 0 && realIndex != len(m.items)-1 {
220+ if m.gapSize > 0 {
236221 for range m.gapSize {
237222 itemLines = append(itemLines, "")
238223 }
@@ -245,7 +230,7 @@ func (m *model) renderVisibleReverse() {
245230 }
246231
247232 if realIndex == 0 {
248− m.finalHight = max(0, start+len(itemLines)-m.listHeight())
233+ m.finalHeight = max(0, start+len(itemLines)-m.listHeight())
249234 }
250235 m.renderedLines = append(itemLines, m.renderedLines...)
251236 m.lastRenderedInx = realIndex
@@ -256,9 +241,9 @@ func (m *model) renderVisibleReverse() {
256241 start += len(itemLines)
257242 }
258243 m.needsRerender = false
259− if m.finalHight > -1 {
244+ if m.finalHeight > -1 {
260245 // make sure we don't go over the final height, this can happen if we did not render the last item and we overshot the offset
261− m.offset = min(m.offset, m.finalHight)
246+ m.offset = min(m.offset, m.finalHeight)
262247 }
263248 maxHeight := min(m.listHeight(), len(m.renderedLines))
264249 if m.offset < len(m.renderedLines) {
@@ -293,7 +278,7 @@ func (m *model) renderVisible() {
293278 itemLines = cachedContent.(renderedItem).lines
294279 } else {
295280 itemLines = strings.Split(item.View(), "\n")
296− if m.gapSize > 0 && realIndex != len(m.items)-1 {
281+ if m.gapSize > 0 {
297282 for range m.gapSize {
298283 itemLines = append(itemLines, "")
299284 }
@@ -310,7 +295,7 @@ func (m *model) renderVisible() {
310295 }
311296
312297 if realIndex == len(m.items)-1 {
313− m.finalHight = max(0, start+len(itemLines)-m.listHeight())
298+ m.finalHeight = max(0, start+len(itemLines)-m.listHeight())
314299 }
315300
316301 m.renderedLines = append(m.renderedLines, itemLines...)
@@ -319,9 +304,9 @@ func (m *model) renderVisible() {
319304 }
320305 m.needsRerender = false
321306 maxHeight := min(m.listHeight(), len(m.renderedLines))
322− if m.finalHight > -1 {
307+ if m.finalHeight > -1 {
323308 // make sure we don't go over the final height, this can happen if we did not render the last item and we overshot the offset
324− m.offset = min(m.offset, m.finalHight)
309+ m.offset = min(m.offset, m.finalHeight)
325310 }
326311 if m.offset < len(m.renderedLines) {
327312 m.content = strings.Join(m.renderedLines[m.offset:maxHeight+m.offset], "\n")
@@ -412,6 +397,9 @@ func (m *model) downOneItem() tea.Cmd {
412397}
413398
414399func (m *model) goToBottom() tea.Cmd {
400+ if len(m.items) == 0 {
401+ return nil
402+ }
415403 var cmds []tea.Cmd
416404 m.reverse = true
417405 cmd := m.blurSelected()
@@ -428,11 +416,14 @@ func (m *model) ResetView() {
428416 m.renderedLines = []string{}
429417 m.offset = 0
430418 m.lastRenderedInx = -1
431− m.finalHight = -1
419+ m.finalHeight = -1
432420 m.needsRerender = true
433421}
434422
435423func (m *model) goToTop() tea.Cmd {
424+ if len(m.items) == 0 {
425+ return nil
426+ }
436427 var cmds []tea.Cmd
437428 m.reverse = false
438429 cmd := m.blurSelected()
@@ -480,7 +471,7 @@ func (m *model) rerenderItem(inx int) {
480471 }
481472 rerenderedItem := m.items[inx].View()
482473 rerenderedLines := strings.Split(rerenderedItem, "\n")
483− if m.gapSize > 0 && inx != len(m.items)-1 {
474+ if m.gapSize > 0 {
484475 for range m.gapSize {
485476 rerenderedLines = append(rerenderedLines, "")
486477 }
@@ -492,7 +483,6 @@ func (m *model) rerenderItem(inx int) {
492483 }
493484 // check if the item is in the content
494485 start := cachedItem.start
495− logging.Info("rerenderItem", "inx", inx, "start", start, "cachedItem.start", cachedItem.start, "cachedItem.height", cachedItem.height)
496486 end := start + cachedItem.height
497487 totalLines := len(m.renderedLines)
498488 if m.reverse {
@@ -504,9 +494,35 @@ func (m *model) rerenderItem(inx int) {
504494 m.renderedLines = slices.Insert(m.renderedLines, start, rerenderedLines...)
505495 }
506496 // TODO: if hight changed do something
507− if cachedItem.height != len(rerenderedLines) && inx != len(m.items)-1 {
497+ if cachedItem.height != len(rerenderedLines) {
508498 if inx == len(m.items)-1 {
509− m.finalHight = max(0, start+len(rerenderedLines)-m.listHeight())
499+ m.finalHeight = max(0, start+len(rerenderedLines)-m.listHeight())
500+ }
501+
502+ // update the start of the other cached items
503+ currentStart := cachedItem.start + len(rerenderedLines)
504+ if m.reverse {
505+ for i := inx - 1; i < len(m.items); i-- {
506+ if existing, ok := m.renderedItems.Load(i); ok {
507+ cached := existing.(renderedItem)
508+ cached.start = currentStart
509+ currentStart += cached.height
510+ m.renderedItems.Store(i, cached)
511+ } else {
512+ break
513+ }
514+ }
515+ } else {
516+ for i := inx + 1; i < len(m.items); i++ {
517+ if existing, ok := m.renderedItems.Load(i); ok {
518+ cached := existing.(renderedItem)
519+ cached.start = currentStart
520+ currentStart += cached.height
521+ m.renderedItems.Store(i, cached)
522+ } else {
523+ break
524+ }
525+ }
510526 }
511527 }
512528 m.renderedItems.Store(inx, renderedItem{
@@ -518,11 +534,11 @@ func (m *model) rerenderItem(inx int) {
518534}
519535
520536func (m *model) increaseOffset(n int) {
521− if m.finalHight > -1 {
522− if m.offset < m.finalHight {
537+ if m.finalHeight > -1 {
538+ if m.offset < m.finalHeight {
523539 m.offset += n
524− if m.offset > m.finalHight {
525− m.offset = m.finalHight
540+ if m.offset > m.finalHeight {
541+ m.offset = m.finalHeight
526542 }
527543 m.needsRerender = true
528544 }
@@ -550,7 +566,8 @@ func (m *model) UpdateItem(inx int, item util.Model) {
550566 i.Focus()
551567 }
552568 }
553− m.ResetView()
569+ m.setItemSize(inx)
570+ m.rerenderItem(inx)
554571 m.needsRerender = true
555572}
556573
@@ -565,16 +582,15 @@ func (m *model) SetSize(width int, height int) tea.Cmd {
565582 return nil
566583 }
567584 if m.height != height {
568− m.finalHight = -1
585+ m.finalHeight = -1
569586 m.height = height
570587 }
571588 m.width = width
572589 m.ResetView()
573− return m.setItemsSize()
590+ return m.setAllItemsSize()
574591}
575592
576−func (m *model) setItemsSize() tea.Cmd {
577− var cmds []tea.Cmd
593+func (m *model) getItemSize() int {
578594 width := m.width
579595 if m.padding != nil {
580596 if len(m.padding) == 1 {
@@ -585,11 +601,22 @@ func (m *model) setItemsSize() tea.Cmd {
585601 width -= m.padding[1] + m.padding[3]
586602 }
587603 }
588− for _, item := range m.items {
589− if i, ok := item.(layout.Sizeable); ok {
590− cmd := i.SetSize(width, 0) // height is not limited
591− cmds = append(cmds, cmd)
592− }
604+ return width
605+}
606+
607+func (m *model) setItemSize(inx int) tea.Cmd {
608+ if i, ok := m.items[inx].(layout.Sizeable); ok {
609+ cmd := i.SetSize(m.getItemSize(), 0) // height is not limited
610+ return cmd
611+ }
612+ return nil
613+}
614+
615+func (m *model) setAllItemsSize() tea.Cmd {
616+ var cmds []tea.Cmd
617+ for i := range m.items {
618+ cmd := m.setItemSize(i)
619+ cmds = append(cmds, cmd)
593620 }
594621 return tea.Batch(cmds...)
595622}
@@ -612,11 +639,16 @@ func (m *model) listHeight() int {
612639
613640// AppendItem implements List.
614641func (m *model) AppendItem(item util.Model) tea.Cmd {
642+ var cmds []tea.Cmd
615643 cmd := item.Init()
644+ cmds = append(cmds, cmd)
616645 m.items = append(m.items, item)
617− m.goToBottom()
646+ cmd = m.setItemSize(len(m.items) - 1)
647+ cmds = append(cmds, cmd)
648+ cmd = m.goToBottom()
649+ cmds = append(cmds, cmd)
618650 m.needsRerender = true
619− return cmd
651+ return tea.Batch(cmds...)
620652}
621653
622654// DeleteItem implements List.
@@ -632,7 +664,9 @@ func (m *model) DeleteItem(i int) {
632664
633665// PrependItem implements List.
634666func (m *model) PrependItem(item util.Model) tea.Cmd {
667+ var cmds []tea.Cmd
635668 cmd := item.Init()
669+ cmds = append(cmds, cmd)
636670 m.items = append([]util.Model{item}, m.items...)
637671 // update the indices of the rendered items
638672 newRenderedItems := make(map[int]renderedItem)
@@ -647,9 +681,12 @@ func (m *model) PrependItem(item util.Model) tea.Cmd {
647681 for k, v := range newRenderedItems {
648682 m.renderedItems.Store(k, v)
649683 }
650− m.goToTop()
684+ cmd = m.goToTop()
685+ cmds = append(cmds, cmd)
686+ cmd = m.setItemSize(0)
687+ cmds = append(cmds, cmd)
651688 m.needsRerender = true
652− return cmd
689+ return tea.Batch(cmds...)
653690}
654691
655692func (m *model) setReverse(reverse bool) {
@@ -664,7 +701,7 @@ func (m *model) setReverse(reverse bool) {
664701func (m *model) SetItems(items []util.Model) tea.Cmd {
665702 m.items = items
666703 var cmds []tea.Cmd
667− cmd := m.setItemsSize()
704+ cmd := m.setAllItemsSize()
668705 cmds = append(cmds, cmd)
669706 for _, item := range m.items {
670707 cmds = append(cmds, item.Init())
@@ -678,7 +715,6 @@ func (m *model) SetItems(items []util.Model) tea.Cmd {
678715 cmd := m.focusSelected()
679716 cmds = append(cmds, cmd)
680717 }
681− m.needsRerender = true
682718 m.ResetView()
683719 return tea.Batch(cmds...)
684720}