status.go

  1package status
  2
  3import (
  4	"strings"
  5	"time"
  6
  7	"github.com/charmbracelet/bubbles/v2/help"
  8	tea "github.com/charmbracelet/bubbletea/v2"
  9	"github.com/charmbracelet/crush/internal/logging"
 10	"github.com/charmbracelet/crush/internal/pubsub"
 11	"github.com/charmbracelet/crush/internal/session"
 12	"github.com/charmbracelet/crush/internal/tui/styles"
 13	"github.com/charmbracelet/crush/internal/tui/util"
 14	"github.com/charmbracelet/lipgloss/v2"
 15	"github.com/charmbracelet/x/ansi"
 16)
 17
 18type StatusCmp interface {
 19	util.Model
 20	ToggleFullHelp()
 21	SetKeyMap(keyMap help.KeyMap)
 22}
 23
 24type statusCmp struct {
 25	info       util.InfoMsg
 26	width      int
 27	messageTTL time.Duration
 28	session    session.Session
 29	help       help.Model
 30	keyMap     help.KeyMap
 31}
 32
 33// clearMessageCmd is a command that clears status messages after a timeout
 34func (m *statusCmp) clearMessageCmd(ttl time.Duration) tea.Cmd {
 35	return tea.Tick(ttl, func(time.Time) tea.Msg {
 36		return util.ClearStatusMsg{}
 37	})
 38}
 39
 40func (m *statusCmp) Init() tea.Cmd {
 41	return nil
 42}
 43
 44func (m *statusCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 45	switch msg := msg.(type) {
 46	case tea.WindowSizeMsg:
 47		m.width = msg.Width
 48		m.help.Width = msg.Width - 2
 49		return m, nil
 50
 51	// Handle status info
 52	case util.InfoMsg:
 53		m.info = msg
 54		ttl := msg.TTL
 55		if ttl == 0 {
 56			ttl = m.messageTTL
 57		}
 58		return m, m.clearMessageCmd(ttl)
 59	case util.ClearStatusMsg:
 60		m.info = util.InfoMsg{}
 61
 62	// Handle persistent logs
 63	case pubsub.Event[logging.LogMessage]:
 64		if msg.Payload.Persist {
 65			switch msg.Payload.Level {
 66			case "error":
 67				m.info = util.InfoMsg{
 68					Type: util.InfoTypeError,
 69					Msg:  msg.Payload.Message,
 70					TTL:  msg.Payload.PersistTime,
 71				}
 72			case "info":
 73				m.info = util.InfoMsg{
 74					Type: util.InfoTypeInfo,
 75					Msg:  msg.Payload.Message,
 76					TTL:  msg.Payload.PersistTime,
 77				}
 78			case "warn":
 79				m.info = util.InfoMsg{
 80					Type: util.InfoTypeWarn,
 81					Msg:  msg.Payload.Message,
 82					TTL:  msg.Payload.PersistTime,
 83				}
 84			default:
 85				m.info = util.InfoMsg{
 86					Type: util.InfoTypeInfo,
 87					Msg:  msg.Payload.Message,
 88					TTL:  msg.Payload.PersistTime,
 89				}
 90			}
 91		}
 92	}
 93	return m, nil
 94}
 95
 96func (m *statusCmp) View() tea.View {
 97	t := styles.CurrentTheme()
 98	status := t.S().Base.Padding(0, 1, 1, 1).Render(m.help.View(m.keyMap))
 99	if m.info.Msg != "" {
100		status = m.infoMsg()
101	}
102	return tea.NewView(status)
103}
104
105func (m *statusCmp) infoMsg() string {
106	t := styles.CurrentTheme()
107	message := ""
108	infoType := ""
109	switch m.info.Type {
110	case util.InfoTypeError:
111		infoType = t.S().Base.Background(t.Red).Padding(0, 1).Render("ERROR")
112		width := m.width - lipgloss.Width(infoType)
113		message = t.S().Base.Background(t.Error).Foreground(t.White).Padding(0, 1).Width(width).Render(ansi.Truncate(m.info.Msg, width, "…"))
114	case util.InfoTypeWarn:
115		infoType = t.S().Base.Foreground(t.BgOverlay).Background(t.Yellow).Padding(0, 1).Render("WARNING")
116		width := m.width - lipgloss.Width(infoType)
117		message = t.S().Base.Foreground(t.BgOverlay).Background(t.Warning).Padding(0, 1).Width(width).Render(ansi.Truncate(m.info.Msg, width, "…"))
118	default:
119		infoType = t.S().Base.Foreground(t.BgOverlay).Background(t.Green).Padding(0, 1).Render("OKAY!")
120		width := m.width - lipgloss.Width(infoType)
121		message = t.S().Base.Background(t.Success).Foreground(t.White).Padding(0, 1).Width(width).Render(ansi.Truncate(m.info.Msg, width, "…"))
122	}
123	return strings.Join([]string{infoType, message}, "")
124}
125
126func (m *statusCmp) ToggleFullHelp() {
127	m.help.ShowAll = !m.help.ShowAll
128}
129
130func (m *statusCmp) SetKeyMap(keyMap help.KeyMap) {
131	m.keyMap = keyMap
132}
133
134func NewStatusCmp(keyMap help.KeyMap) StatusCmp {
135	t := styles.CurrentTheme()
136	help := help.New()
137	help.Styles = t.S().Help
138	return &statusCmp{
139		messageTTL: 10 * time.Second,
140		help:       help,
141		keyMap:     keyMap,
142	}
143}