Add Bubble Tea TUI with interactive treemap
Cell canvas renderer with frame cache and a perceptual 256-colour quantizer; responsive geometry (large/medium/small/too-small); squarified treemap with nested previews, LOD grouping, category colouring and adaptive labels; spatial arrow navigation with stable NodeID selection; zoom and breadcrumbs; modal system (info, help, confirm, save); list, file-type, top-N, scan-info, duplicate and comparison views; live search/filter; mouse support; move-to-trash.
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
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 }()
|
||||
|
||||
_, 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
|
||||
}
|
||||
Reference in New Issue
Block a user