fixup! wip: refactor: API

This commit is contained in:
Rob Watson 2025-05-10 07:36:39 +02:00
parent c6f21b194a
commit 888ac7d67d
2 changed files with 25 additions and 29 deletions

View File

@ -2,7 +2,6 @@ package main
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"log/slog" "log/slog"
"os" "os"
@ -90,7 +89,7 @@ func run() error {
} }
}) })
ui, err := terminal.StartUI(ctx, terminal.StartParams{ ui, err := terminal.NewUI(ctx, terminal.Params{
EventBus: bus, EventBus: bus,
Dispatcher: func(cmd event.Command) { Dispatcher: func(cmd event.Command) {
logger.Info("Command dispatched", "cmd", cmd.Name()) logger.Info("Command dispatched", "cmd", cmd.Name())
@ -112,18 +111,11 @@ func run() error {
} }
defer ui.Close() defer ui.Close()
errUIClosed := errors.New("UI closed") g.Go(func() error { return ui.Run(ctx) })
g.Go(func() error {
ui.Wait()
logger.Info("UI closed!")
return errUIClosed
})
if err := g.Wait(); err == errUIClosed { if err := g.Wait(); err == terminal.ErrUserClosed {
logger.Info("UI closed, exiting")
return nil return nil
} else { } else {
logger.Error("UI closed with error", "err", err)
return fmt.Errorf("errgroup.Wait: %w", err) return fmt.Errorf("errgroup.Wait: %w", err)
} }
} }

View File

@ -3,6 +3,7 @@ package terminal
import ( import (
"cmp" "cmp"
"context" "context"
"errors"
"fmt" "fmt"
"log/slog" "log/slog"
"maps" "maps"
@ -46,7 +47,7 @@ type UI struct {
clipboardAvailable bool clipboardAvailable bool
rtmpURL, rtmpsURL string rtmpURL, rtmpsURL string
buildInfo domain.BuildInfo buildInfo domain.BuildInfo
doneC chan struct{} appExitC chan error
logger *slog.Logger logger *slog.Logger
// tview state // tview state
@ -92,9 +93,9 @@ type ScreenCapture struct {
Width, Height int Width, Height int
} }
// StartParams contains the parameters for starting a new terminal user // Params contains the parameters for starting a new terminal user
// interface. // interface.
type StartParams struct { type Params struct {
EventBus *event.Bus EventBus *event.Bus
Dispatcher func(event.Command) Dispatcher func(event.Command)
Logger *slog.Logger Logger *slog.Logger
@ -103,8 +104,9 @@ type StartParams struct {
Screen *Screen // Screen may be nil. Screen *Screen // Screen may be nil.
} }
// StartUI starts the terminal user interface. // NewUI creates the user interface. Call [Run] on the *UI instance to block
func StartUI(ctx context.Context, params StartParams) (*UI, error) { // until it is completed.
func NewUI(ctx context.Context, params Params) (*UI, error) {
app := tview.NewApplication() app := tview.NewApplication()
var screen tcell.Screen var screen tcell.Screen
@ -210,7 +212,7 @@ func StartUI(ctx context.Context, params StartParams) (*UI, error) {
eventBus: params.EventBus, eventBus: params.EventBus,
dispatch: params.Dispatcher, dispatch: params.Dispatcher,
clipboardAvailable: params.ClipboardAvailable, clipboardAvailable: params.ClipboardAvailable,
doneC: make(chan struct{}, 1), appExitC: make(chan error, 1),
buildInfo: params.BuildInfo, buildInfo: params.BuildInfo,
logger: params.Logger, logger: params.Logger,
app: app, app: app,
@ -236,8 +238,6 @@ func StartUI(ctx context.Context, params StartParams) (*UI, error) {
app.SetInputCapture(ui.inputCaptureHandler) app.SetInputCapture(ui.inputCaptureHandler)
app.SetAfterDrawFunc(ui.afterDrawHandler) app.SetAfterDrawFunc(ui.afterDrawHandler)
go ui.run(ctx)
return ui, nil return ui, nil
} }
@ -264,23 +264,28 @@ func (ui *UI) renderAboutView() {
ui.aboutView.AddItem(tview.NewTextView().SetDynamicColors(true).SetText("[grey]?[-] About"), 1, 0, false) ui.aboutView.AddItem(tview.NewTextView().SetDynamicColors(true).SetText("[grey]?[-] About"), 1, 0, false)
} }
func (ui *UI) run(ctx context.Context) { var ErrUserClosed = errors.New("user closed UI")
// Run runs the user interface. It always returns a non-nil error, which will
// be [ErrUserClosed] if the user voluntarily closed the UI.
func (ui *UI) Run(ctx context.Context) error {
eventC := ui.eventBus.Register() eventC := ui.eventBus.Register()
defer ui.eventBus.Deregister(eventC) defer ui.eventBus.Deregister(eventC)
go func() { go func() {
defer close(ui.doneC) err := ui.app.Run()
if err != nil {
if err := ui.app.Run(); err != nil {
ui.logger.Error("Error in UI run loop, exiting", "err", err) ui.logger.Error("Error in UI run loop, exiting", "err", err)
} }
ui.appExitC <- err
}() }()
for { for {
select { select {
case evt, ok := <-eventC: case evt, ok := <-eventC:
if !ok { if !ok {
return // should never happen
return errors.New("event channel closed")
} }
ui.app.QueueUpdateDraw(func() { ui.app.QueueUpdateDraw(func() {
switch evt := evt.(type) { switch evt := evt.(type) {
@ -307,12 +312,11 @@ func (ui *UI) run(ctx context.Context) {
default: default:
ui.logger.Warn("unhandled event", "event", evt) ui.logger.Warn("unhandled event", "event", evt)
} }
}) })
case <-ctx.Done(): case <-ctx.Done():
return return ctx.Err()
case <-ui.doneC: case err := <-ui.appExitC:
return return cmp.Or(err, ErrUserClosed)
} }
} }
} }
@ -819,7 +823,7 @@ func (ui *UI) Close() {
// Wait waits for the terminal user interface to finish. // Wait waits for the terminal user interface to finish.
func (ui *UI) Wait() { func (ui *UI) Wait() {
<-ui.doneC <-ui.appExitC
} }
func (ui *UI) addDestination() { func (ui *UI) addDestination() {