mirror of
https://github.com/chenasraf/watchr.git
synced 2026-05-17 17:28:06 +00:00
feat: add refresh countdown timer
This commit is contained in:
@@ -39,30 +39,31 @@ type Config struct {
|
||||
|
||||
// model represents the application state
|
||||
type model struct {
|
||||
config Config
|
||||
lines []runner.Line
|
||||
filtered []int // indices into lines that match filter
|
||||
cursor int // cursor position in filtered list
|
||||
offset int // scroll offset for visible window
|
||||
filter string
|
||||
filterMode bool
|
||||
showPreview bool
|
||||
showHelp bool // help overlay visible
|
||||
width int
|
||||
height int
|
||||
runner *runner.Runner
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
loading bool
|
||||
streaming bool // true while command is running (streaming output)
|
||||
streamResult *runner.StreamingResult // current streaming result
|
||||
lastLineCount int // track line count for updates
|
||||
userScrolled bool // true if user manually scrolled during streaming
|
||||
refreshGeneration int // incremented on manual refresh to reset timer
|
||||
spinnerFrame int // current spinner animation frame
|
||||
errorMsg string
|
||||
statusMsg string // temporary status message (e.g., "Yanked!")
|
||||
exitCode int // last command exit code
|
||||
config Config
|
||||
lines []runner.Line
|
||||
filtered []int // indices into lines that match filter
|
||||
cursor int // cursor position in filtered list
|
||||
offset int // scroll offset for visible window
|
||||
filter string
|
||||
filterMode bool
|
||||
showPreview bool
|
||||
showHelp bool // help overlay visible
|
||||
width int
|
||||
height int
|
||||
runner *runner.Runner
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
loading bool
|
||||
streaming bool // true while command is running (streaming output)
|
||||
streamResult *runner.StreamingResult // current streaming result
|
||||
lastLineCount int // track line count for updates
|
||||
userScrolled bool // true if user manually scrolled during streaming
|
||||
refreshGeneration int // incremented on manual refresh to reset timer
|
||||
refreshStartTime time.Time // when the refresh timer was started
|
||||
spinnerFrame int // current spinner animation frame
|
||||
errorMsg string
|
||||
statusMsg string // temporary status message (e.g., "Yanked!")
|
||||
exitCode int // last command exit code
|
||||
}
|
||||
|
||||
// messages
|
||||
@@ -76,8 +77,11 @@ type tickMsg struct {
|
||||
}
|
||||
type clearStatusMsg struct{}
|
||||
type spinnerTickMsg time.Time
|
||||
type streamTickMsg time.Time // periodic check for streaming updates
|
||||
type startStreamMsg struct{} // trigger to start streaming
|
||||
type streamTickMsg time.Time // periodic check for streaming updates
|
||||
type startStreamMsg struct{} // trigger to start streaming
|
||||
type countdownTickMsg struct { // periodic update for refresh countdown display
|
||||
generation int
|
||||
}
|
||||
|
||||
// Spinner frames for the loading animation
|
||||
var spinnerFrames = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
|
||||
@@ -153,6 +157,13 @@ func (m model) streamTickCmd() tea.Cmd {
|
||||
})
|
||||
}
|
||||
|
||||
func (m model) countdownTickCmd() tea.Cmd {
|
||||
gen := m.refreshGeneration
|
||||
return tea.Tick(time.Second, func(t time.Time) tea.Msg {
|
||||
return countdownTickMsg{generation: gen}
|
||||
})
|
||||
}
|
||||
|
||||
func (m *model) startStreaming() tea.Cmd {
|
||||
// Cancel any existing context and create a new one
|
||||
if m.cancel != nil {
|
||||
@@ -236,7 +247,13 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
// If auto-refresh is enabled, schedule the next run
|
||||
if m.config.RefreshInterval > 0 {
|
||||
return m, m.tickCmd()
|
||||
m.refreshStartTime = time.Now()
|
||||
cmds := []tea.Cmd{m.tickCmd()}
|
||||
// Start countdown display updates if interval > 1s
|
||||
if m.config.RefreshInterval > time.Second {
|
||||
cmds = append(cmds, m.countdownTickCmd())
|
||||
}
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
@@ -272,6 +289,20 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, m.spinnerTickCmd()
|
||||
}
|
||||
return m, nil
|
||||
|
||||
case countdownTickMsg:
|
||||
// Ignore ticks from before a manual refresh
|
||||
if msg.generation != m.refreshGeneration {
|
||||
return m, nil
|
||||
}
|
||||
// Continue ticking if waiting for auto-refresh
|
||||
if m.config.RefreshInterval > time.Second && !m.streaming && !m.refreshStartTime.IsZero() {
|
||||
elapsed := time.Since(m.refreshStartTime)
|
||||
if elapsed < m.config.RefreshInterval {
|
||||
return m, m.countdownTickCmd()
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
return m, nil
|
||||
@@ -891,6 +922,22 @@ func (m model) renderMainView() string {
|
||||
commandLine = prefix + failStyle.Render(fmt.Sprintf("✗ [%d] %s", m.exitCode, m.config.Command))
|
||||
}
|
||||
|
||||
// Add refresh countdown on the right if auto-refresh is enabled and > 1s
|
||||
if m.config.RefreshInterval > time.Second && !m.streaming && !m.refreshStartTime.IsZero() {
|
||||
elapsed := time.Since(m.refreshStartTime)
|
||||
remaining := m.config.RefreshInterval - elapsed
|
||||
if remaining > 0 {
|
||||
countdownStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("241")) // dim gray
|
||||
countdown := countdownStyle.Render(fmt.Sprintf("(%ds)", int(remaining.Seconds())+1))
|
||||
cmdWidth := lipgloss.Width(commandLine)
|
||||
countdownWidth := lipgloss.Width(countdown)
|
||||
gap := innerWidth - cmdWidth - countdownWidth
|
||||
if gap > 0 {
|
||||
commandLine += strings.Repeat(" ", gap) + countdown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build prompt line (will go at bottom)
|
||||
var promptLine string
|
||||
switch {
|
||||
|
||||
Reference in New Issue
Block a user