item.go

  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}