55 "fmt"
66 "log"
77 "os"
8+ "strings"
89
910 tea "github.com/charmbracelet/bubbletea"
1011 "github.com/charmbracelet/lipgloss"
@@ -13,24 +14,25 @@ import (
1314)
1415
1516type model struct {
16- tabs []string
17- activeTab int
18- runnersView * ui.RunnersView
19- logsView * ui.LogsView
20- configView * ui.ConfigView
21- systemView * ui.SystemView
22- historyView * ui.HistoryView
23- width int
24- height int
25- quitting bool
26- debugMode bool
17+ tabs []string
18+ activeTab int
19+ runnersView * ui.RunnersView
20+ logsView * ui.LogsView
21+ configView * ui.ConfigView
22+ systemView * ui.SystemView
23+ historyView * ui.HistoryView
24+ width int
25+ height int
26+ quitting bool
27+ debugMode bool
28+ initialized map [int ]bool
2729}
2830
2931func initialModel (configPath string , debugMode bool ) model {
3032 service := runner .NewService (configPath )
3133 service .SetDebugMode (debugMode )
3234
33- return model {
35+ m := model {
3436 tabs : []string {"Runners" , "Logs" , "Config" , "System" , "History" },
3537 activeTab : 0 ,
3638 runnersView : ui .NewRunnersView (service ),
@@ -39,17 +41,15 @@ func initialModel(configPath string, debugMode bool) model {
3941 systemView : ui .NewSystemView (service ),
4042 historyView : ui .NewHistoryView (service ),
4143 debugMode : debugMode ,
44+ initialized : make (map [int ]bool ),
4245 }
46+ m .initialized [0 ] = true // Mark first tab as initialized
47+ return m
4348}
4449
4550func (m model ) Init () tea.Cmd {
46- return tea .Batch (
47- m .runnersView .Init (),
48- m .logsView .Init (),
49- m .configView .Init (),
50- m .systemView .Init (),
51- m .historyView .Init (),
52- )
51+ // Only initialize the first view
52+ return m .runnersView .Init ()
5353}
5454
5555func (m model ) Update (msg tea.Msg ) (tea.Model , tea.Cmd ) {
@@ -80,15 +80,16 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
8080
8181 case "tab" :
8282 m .activeTab = (m .activeTab + 1 ) % len (m .tabs )
83- return m , nil
83+ return m . switchTab ()
8484
8585 case "shift+tab" :
8686 m .activeTab = (m .activeTab - 1 + len (m .tabs )) % len (m .tabs )
87- return m , nil
87+ return m . switchTab ()
8888
8989 case "1" , "2" , "3" , "4" , "5" :
9090 if idx := int (msg .String ()[0 ] - '1' ); idx < len (m .tabs ) {
9191 m .activeTab = idx
92+ return m .switchTab ()
9293 }
9394 return m , nil
9495
@@ -97,6 +98,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
9798 if runner := m .runnersView .GetSelectedRunner (); runner != nil {
9899 m .logsView .SetRunner (runner .Name )
99100 m .activeTab = 1
101+ return m .switchTab ()
100102 }
101103 }
102104 }
@@ -128,6 +130,23 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
128130 return m , tea .Batch (cmds ... )
129131}
130132
133+ func (m model ) switchTab () (model , tea.Cmd ) {
134+ if ! m .initialized [m .activeTab ] {
135+ m .initialized [m .activeTab ] = true
136+ switch m .activeTab {
137+ case 1 :
138+ return m , m .logsView .Init ()
139+ case 2 :
140+ return m , m .configView .Init ()
141+ case 3 :
142+ return m , m .systemView .Init ()
143+ case 4 :
144+ return m , m .historyView .Init ()
145+ }
146+ }
147+ return m , nil
148+ }
149+
131150func (m model ) View () string {
132151 if m .quitting {
133152 return ""
@@ -149,10 +168,23 @@ func (m model) View() string {
149168 content = m .historyView .View ()
150169 }
151170
171+ statusBar := m .renderStatusBar ()
172+
173+ // Calculate available height for content
174+ availableHeight := m .height - lipgloss .Height (tabBar ) - lipgloss .Height (statusBar ) - 1
175+
176+ // Ensure content doesn't overflow
177+ contentLines := strings .Split (content , "\n " )
178+ if len (contentLines ) > availableHeight {
179+ content = strings .Join (contentLines [:availableHeight ], "\n " )
180+ }
181+
152182 return lipgloss .JoinVertical (
153183 lipgloss .Left ,
154184 tabBar ,
155185 content ,
186+ strings .Repeat ("\n " , m .height - lipgloss .Height (tabBar )- lipgloss .Height (content )- lipgloss .Height (statusBar )),
187+ statusBar ,
156188 )
157189}
158190
@@ -170,6 +202,61 @@ func (m model) renderTabBar() string {
170202 return lipgloss .JoinHorizontal (lipgloss .Top , tabs ... )
171203}
172204
205+ func (m model ) renderStatusBar () string {
206+ // Create styles for the status bar
207+ statusStyle := lipgloss .NewStyle ().
208+ Background (ui .ColorPrimary ).
209+ Foreground (ui .ColorBg ).
210+ Padding (0 , 1 )
211+
212+ helpStyle := lipgloss .NewStyle ().
213+ Background (ui .ColorSecondary ).
214+ Foreground (ui .ColorBg ).
215+ Padding (0 , 1 )
216+
217+ // Build contextual help based on active tab
218+ var commands []string
219+
220+ // Global commands
221+ commands = append (commands , "Tab/Shift+Tab: Switch tabs" , "1-5: Jump to tab" , "q: Quit" )
222+
223+ // Tab-specific commands
224+ switch m .activeTab {
225+ case 0 : // Runners
226+ commands = append (commands , "↑/↓: Navigate" , "Enter: View logs" , "r: Refresh" )
227+ case 1 : // Logs
228+ commands = append (commands , "↑/↓: Scroll" , "g/G: Top/Bottom" , "a: Auto-scroll" , "c: Clear" , "r: Refresh" )
229+ case 2 : // Config
230+ commands = append (commands , "Tab: Next field" , "Ctrl+S: Save" , "r: Edit runners" )
231+ case 3 : // System
232+ commands = append (commands , "r: Refresh" , "s: Restart service" )
233+ case 4 : // History
234+ commands = append (commands , "↑/↓: Navigate" , "r: Refresh" )
235+ }
236+
237+ // Add debug mode indicator if enabled
238+ statusText := ""
239+ if m .debugMode {
240+ statusText = " [DEBUG] "
241+ }
242+
243+ // Combine status and help
244+ status := statusStyle .Render (statusText + m .tabs [m .activeTab ])
245+ help := helpStyle .Render (strings .Join (commands , " • " ))
246+
247+ // Fill the remaining width
248+ statusBarContent := lipgloss .JoinHorizontal (lipgloss .Top , status , " " , help )
249+ remainingWidth := m .width - lipgloss .Width (statusBarContent )
250+ if remainingWidth > 0 {
251+ filler := lipgloss .NewStyle ().
252+ Background (ui .ColorSecondary ).
253+ Render (strings .Repeat (" " , remainingWidth ))
254+ statusBarContent = lipgloss .JoinHorizontal (lipgloss .Top , statusBarContent , filler )
255+ }
256+
257+ return statusBarContent
258+ }
259+
173260func main () {
174261 var configPath string
175262 var debugMode bool
0 commit comments