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}