package main import ( tea "charm.land/bubbletea/v2" "context" "example.com/charm-tui-worked/internal/display" "fmt" "strings" ) type lineMsg string type closedMsg struct{} func waitForLine(ctx context.Context, ch <-chan string) tea.Cmd { return func() tea.Msg { line, ok := receive(ctx, ch) if !ok { return closedMsg{} } return lineMsg(line) } } type model struct { ctx context.Context cancel context.CancelFunc events <-chan string history history width, height int ended bool } func (m *model) rows() int { return display.BodyRows(m.height, 1, 2) } func (m *model) Init() tea.Cmd { return waitForLine(m.ctx, m.events) } func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: m.width = max(0, msg.Width) m.height = max(0, msg.Height) m.history.resize(m.rows()) case tea.KeyPressMsg: switch msg.String() { case "q", "ctrl+c": m.cancel() return m, tea.Quit case "up", "k": m.history.scroll(-1, m.rows()) case "down", "j": m.history.scroll(1, m.rows()) case "end": m.history.latest(m.rows()) } case lineMsg: if m.ended { return m, nil } m.history.append(string(msg), m.rows()) return m, waitForLine(m.ctx, m.events) // Exactly one successor listener. case closedMsg: m.ended = true return m, nil // Never re-arm a closed stream. } return m, nil } func (m *model) View() tea.View { v := tea.NewView("") v.AltScreen = true if m.width <= 0 || m.height <= 0 { return v } if m.width < 20 || m.height < 5 { v.Content = display.Line("resize · q quit", m.width) return v } state := "following" if !m.history.follow { state = fmt.Sprintf("reading · %d new · end follows", m.history.unseen) } if m.history.expired { state = "old anchor evicted · end follows" } if m.ended { state += " · stream ended" } state += fmt.Sprintf(" · %d evicted", m.history.first) lines := m.history.visible(m.rows()) for i := range lines { lines[i] = display.SingleLine(lines[i]) } v.Content = strings.Join([]string{ display.Line("worker events · retain last 32 records", m.width), display.FillRows(lines, m.width, m.rows()), display.Line(state, m.width), display.Line("↑/↓ scroll · end latest · q quit", m.width), }, "\n") return v }