fix(tui): quit on signal cancellation

This commit is contained in:
Vincent Koc 2026-05-03 10:08:00 -07:00
parent 5da91640e6
commit 37017c6763
No known key found for this signature in database
2 changed files with 31 additions and 2 deletions

View File

@ -81,6 +81,8 @@ type refreshResultMsg struct {
manual bool
}
type contextDoneMsg struct{}
type Item struct {
Title string `json:"title"`
Subtitle string `json:"subtitle,omitempty"`
@ -342,7 +344,6 @@ func Run(ctx context.Context, opts Options) error {
}
defer restoreTerminalOutput(output)
model := newModel(opts)
model.ctx = ctx
if width, height, ok := terminalSize(input, output); ok {
model.width = width
model.height = height
@ -352,6 +353,7 @@ func Run(ctx context.Context, opts Options) error {
}
runCtx, stopSignals := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
defer stopSignals()
model.ctx = runCtx
program := tea.NewProgram(
model,
tea.WithContext(runCtx),
@ -736,7 +738,7 @@ type itemGroup struct {
}
func (m model) Init() tea.Cmd {
return m.refreshTickCmd()
return tea.Batch(m.refreshTickCmd(), m.contextDoneCmd())
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
@ -756,6 +758,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case refreshResultMsg:
m.finishRefresh(typed)
return m, nil
case contextDoneMsg:
return m, tea.Quit
case tea.MouseMsg:
if typed.Action == tea.MouseActionMotion && typed.Button == tea.MouseButtonNone {
if m.menuOpen {
@ -1527,6 +1531,16 @@ func (m model) refreshTickCmd() tea.Cmd {
})
}
func (m model) contextDoneCmd() tea.Cmd {
if m.ctx == nil {
return nil
}
return func() tea.Msg {
<-m.ctx.Done()
return contextDoneMsg{}
}
}
func (m *model) startRefresh(manual bool) tea.Cmd {
if m.refresh == nil {
if manual {

View File

@ -1322,6 +1322,21 @@ func TestRefreshCurrentStatusUsesGitcrawlSourceLanguage(t *testing.T) {
}
}
func TestContextDoneQuitsModelForSignalCleanup(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
m := newModel(Options{Title: "archive", Items: []Item{{Title: "alpha"}}})
m.ctx = ctx
cmd := m.contextDoneCmd()
if cmd == nil {
t.Fatal("context done command missing")
}
cancel()
updated, quit := m.Update(cmd())
if _, ok := updated.(model); !ok || quit == nil {
t.Fatalf("context cancellation should return quit command, updated=%T quit=%v", updated, quit)
}
}
func TestHelpPaneRendersUniversalControls(t *testing.T) {
m := newModel(Options{
Title: "archive",