Files
mapsize/internal/tui/run.go
T
Andy Dixon 0e5b9f7cc3 Add interactive snapshot comparison, CLI tests and treegen
--compare opens the TUI with growth/shrink colouring unless a report is
requested. treegen builds deterministic synthetic trees (Unicode,
hostile names, sparse files). MAPSIZE_DEBUG_PANIC verifies terminal
restoration on UI and scanner panics.
2026-10-02 08:28:52 +01:00

73 lines
2.1 KiB
Go

package tui
import (
"context"
"fmt"
"os"
"runtime/debug"
"time"
tea "charm.land/bubbletea/v2"
"github.com/charmbracelet/colorprofile"
"github.com/andydixon/mapsize/internal/inventory"
"github.com/andydixon/mapsize/internal/scan"
"github.com/andydixon/mapsize/internal/snapshot"
"github.com/andydixon/mapsize/internal/textutil"
)
var profiles = map[string]colorprofile.Profile{
"truecolor": colorprofile.TrueColor, "24bit": colorprofile.TrueColor,
"256": colorprofile.ANSI256, "16": colorprofile.ANSI, "none": colorprofile.ASCII,
}
func saveFile(path string, t *inventory.Tree) error { return snapshot.SaveFile(path, t) }
// Run starts the interactive UI. The terminal is restored on every exit
// path: normal quit, Ctrl+C, errors and panics (Bubble Tea recovers panics
// in Update/View; scanner goroutine panics are routed here via
// scan.OnPanic and reported after the terminal is restored).
func Run(opts Options) error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
s, t, err := opts.Start(ctx)
if err != nil {
return err
}
m := newModel(opts, s, t, cancel)
var popts []tea.ProgramOption
if p, ok := profiles[opts.Color]; ok {
m.profile, m.forced = p, true
popts = append(popts, tea.WithColorProfile(p))
} else if opts.Color != "" && opts.Color != "auto" {
return fmt.Errorf("invalid --color %q (auto, truecolor, 256, 16, none)", opts.Color)
}
p := tea.NewProgram(m, popts...)
prev := scan.OnPanic
scan.OnPanic = func(v any, stack []byte) {
p.Send(fatalMsg{v, stack})
select {} // park the broken goroutine; the program is quitting
}
defer func() { scan.OnPanic = prev }()
if debugPanic == "scanner" {
go func() {
time.Sleep(500 * time.Millisecond)
defer func() {
if v := recover(); v != nil {
scan.OnPanic(v, debug.Stack())
}
}()
panic("MAPSIZE_DEBUG_PANIC=scanner: deliberate panic to test terminal restoration")
}()
}
_, err = p.Run()
cancel()
if m.fatal != nil {
fmt.Fprintf(os.Stderr, "mapsize: internal error in scanner: %s\n\n%s\n",
textutil.Sanitize(fmt.Sprint(m.fatal.v)), m.fatal.stack)
return fmt.Errorf("scanner crashed")
}
return err
}