1package list
2
3import (
4 "strings"
5
6 "github.com/charmbracelet/x/ansi"
7)
8
9// Item represents a single item in the lazy-loaded list.
10//
11// Items participate in the list-level render memo (F6). The cache key
12// for each item is (pointer, width, version). Items must:
13//
14// - Bump their version (via the embedded *Versioned helper) on every
15// mutation that changes the rendered output.
16// - Return Finished() == true once their rendered output will not
17// change again unless an explicit mutator is invoked. Frozen
18// entries are emitted verbatim — no Render call — until either
19// Version() bumps, the viewport width changes, or the list
20// explicitly invalidates the entry.
21type Item interface {
22 // Render returns the string representation of the item for the given
23 // width.
24 Render(width int) string
25
26 // Version returns a monotonic counter that the list-level cache
27 // uses to detect mutations. Items must increment the version
28 // (via Versioned.Bump) on every state change that would alter
29 // the rendered output.
30 Version() uint64
31
32 // Finished reports whether the item's rendered output has reached
33 // a terminal state and may be frozen by the list cache. Items
34 // that animate, stream, or otherwise still mutate must return
35 // false. A finished item that later mutates must bump its
36 // version on the mutation; the cache treats version bumps as
37 // implicit unfreeze + invalidate.
38 Finished() bool
39}
40
41// Versioned is a tiny embeddable helper that satisfies Item.Version()
42// and provides a Bump() method to call from every state-mutating
43// method. Items typically embed *Versioned alongside their other
44// helpers; see chat.AssistantMessageItem for the canonical wiring.
45//
46// Bump() is not safe for concurrent use; callers must hold whatever
47// synchronization their item type already requires for state
48// mutations. The list itself never reads Version() from a goroutine
49// other than the UI thread.
50type Versioned struct {
51 v uint64
52}
53
54// NewVersioned returns a fresh *Versioned at version zero.
55func NewVersioned() *Versioned {
56 return &Versioned{}
57}
58
59// Version returns the current version counter.
60func (vc *Versioned) Version() uint64 {
61 return vc.v
62}
63
64// Bump advances the version counter by one. Mutators on items that
65// affect the rendered output must call Bump exactly once per
66// observable state change. Bumping more than once per change is
67// harmless other than a single extra cache miss.
68func (vc *Versioned) Bump() {
69 vc.v++
70}
71
72// RawRenderable represents an item that can provide a raw rendering
73// without additional styling.
74type RawRenderable interface {
75 // RawRender returns the raw rendered string without any additional
76 // styling.
77 RawRender(width int) string
78}
79
80// Focusable represents an item that can be aware of focus state changes.
81type Focusable interface {
82 // SetFocused sets the focus state of the item.
83 SetFocused(focused bool)
84}
85
86// Highlightable represents an item that can highlight a portion of its content.
87type Highlightable interface {
88 // SetHighlight highlights the content from the given start to end
89 // positions. Use -1 for no highlight.
90 SetHighlight(startLine, startCol, endLine, endCol int)
91 // Highlight returns the current highlight positions within the item.
92 Highlight() (startLine, startCol, endLine, endCol int)
93}
94
95// MouseClickable represents an item that can handle mouse click events.
96type MouseClickable interface {
97 // HandleMouseClick processes a mouse click event at the given coordinates.
98 // It returns true if the event was handled, false otherwise.
99 HandleMouseClick(btn ansi.MouseButton, x, y int) bool
100}
101
102// SpacerItem is a spacer item that adds vertical space in the list.
103type SpacerItem struct {
104 *Versioned
105 Height int
106}
107
108// NewSpacerItem creates a new [SpacerItem] with the specified height.
109func NewSpacerItem(height int) *SpacerItem {
110 return &SpacerItem{
111 Versioned: NewVersioned(),
112 Height: max(0, height-1),
113 }
114}
115
116// Render implements the Item interface for [SpacerItem].
117func (s *SpacerItem) Render(width int) string {
118 return strings.Repeat("\n", s.Height)
119}
120
121// Finished implements Item. SpacerItems are immutable in practice and
122// safe to freeze; any mutation goes through Versioned.Bump which
123// invalidates the frozen entry.
124func (s *SpacerItem) Finished() bool {
125 return true
126}