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			return m, m.clearMessageCmd(m.info.TTL)
 92		}
 93	}
 94	return m, nil
 95}
 96
 97func (m *statusCmp) View() tea.View {
 98	t := styles.CurrentTheme()
 99	status := t.S().Base.Padding(0, 1, 1, 1).Render(m.help.View(m.keyMap))
100	if m.info.Msg != "" {
101		status = m.infoMsg()
102	}
103	return tea.NewView(status)
104}
105
106func (m *statusCmp) infoMsg() string {
107	t := styles.CurrentTheme()
108	message := ""
109	infoType := ""
110	switch m.info.Type {
111	case util.InfoTypeError:
112		infoType = t.S().Base.Background(t.Red).Padding(0, 1).Render("ERROR")
113		width := m.width - lipgloss.Width(infoType)
114		message = t.S().Base.Background(t.Error).Foreground(t.White).Padding(0, 1).Width(width).Render(ansi.Truncate(m.info.Msg, width, "…"))
115	case util.InfoTypeWarn:
116		infoType = t.S().Base.Foreground(t.BgOverlay).Background(t.Yellow).Padding(0, 1).Render("WARNING")
117		width := m.width - lipgloss.Width(infoType)
118		message = t.S().Base.Foreground(t.BgOverlay).Background(t.Warning).Padding(0, 1).Width(width).Render(ansi.Truncate(m.info.Msg, width, "…"))
119	default:
120		infoType = t.S().Base.Foreground(t.BgOverlay).Background(t.Green).Padding(0, 1).Render("OKAY!")
121		width := m.width - lipgloss.Width(infoType)
122		message = t.S().Base.Background(t.Success).Foreground(t.White).Padding(0, 1).Width(width).Render(ansi.Truncate(m.info.Msg, width, "…"))
123	}
124	return strings.Join([]string{infoType, message}, "")
125}
126
127func (m *statusCmp) ToggleFullHelp() {
128	m.help.ShowAll = !m.help.ShowAll
129}
130
131func (m *statusCmp) SetKeyMap(keyMap help.KeyMap) {
132	m.keyMap = keyMap
133}
134
135func NewStatusCmp(keyMap help.KeyMap) StatusCmp {
136	t := styles.CurrentTheme()
137	help := help.New()
138	help.Styles = t.S().Help
139	return &statusCmp{
140		messageTTL: 5 * time.Second,
141		help:       help,
142		keyMap:     keyMap,
143	}
144}