From a6d18e286a1382fea27ecb2468dacd40c651bdac Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Fri, 26 Jul 2019 23:58:26 -0700 Subject: [PATCH] Extract most app setup out of main and into wtf_app --- {wtf => app}/display.go | 21 +++--- app/wtf_app.go | 156 ++++++++++++++++++++++++++++++++++++++++ main.go | 132 ++++------------------------------ 3 files changed, 183 insertions(+), 126 deletions(-) rename {wtf => app}/display.go (51%) create mode 100644 app/wtf_app.go diff --git a/wtf/display.go b/app/display.go similarity index 51% rename from wtf/display.go rename to app/display.go index 27931950..0801c879 100644 --- a/wtf/display.go +++ b/app/display.go @@ -1,30 +1,33 @@ -package wtf +package app import ( "github.com/olebedev/config" "github.com/rivo/tview" + "github.com/wtfutil/wtf/wtf" ) +// Display is the container for the onscreen representation of a WtfApp type Display struct { Grid *tview.Grid config *config.Config } -func NewDisplay(widgets []Wtfable, config *config.Config) *Display { +// NewDisplay creates and returns a Display +func NewDisplay(widgets []wtf.Wtfable, config *config.Config) *Display { display := Display{ Grid: tview.NewGrid(), config: config, } display.build(widgets) - display.Grid.SetBackgroundColor(ColorFor(config.UString("wtf.colors.background", "black"))) + display.Grid.SetBackgroundColor(wtf.ColorFor(config.UString("wtf.colors.background", "black"))) return &display } /* -------------------- Unexported Functions -------------------- */ -func (display *Display) add(widget Wtfable) { +func (display *Display) add(widget wtf.Wtfable) { if widget.Disabled() { return } @@ -41,14 +44,16 @@ func (display *Display) add(widget Wtfable) { ) } -func (display *Display) build(widgets []Wtfable) *tview.Grid { - display.Grid.SetColumns(ToInts(display.config.UList("wtf.grid.columns"))...) - display.Grid.SetRows(ToInts(display.config.UList("wtf.grid.rows"))...) +func (display *Display) build(widgets []wtf.Wtfable) *tview.Grid { + cols := wtf.ToInts(display.config.UList("wtf.grid.columns")) + rows := wtf.ToInts(display.config.UList("wtf.grid.rows")) + + display.Grid.SetColumns(cols...) + display.Grid.SetRows(rows...) display.Grid.SetBorder(false) for _, widget := range widgets { display.add(widget) - go Schedule(widget) } return display.Grid diff --git a/app/wtf_app.go b/app/wtf_app.go new file mode 100644 index 00000000..289ad7e3 --- /dev/null +++ b/app/wtf_app.go @@ -0,0 +1,156 @@ +package app + +import ( + "log" + "time" + + "github.com/gdamore/tcell" + "github.com/olebedev/config" + "github.com/radovskyb/watcher" + "github.com/rivo/tview" + "github.com/wtfutil/wtf/cfg" + "github.com/wtfutil/wtf/maker" + "github.com/wtfutil/wtf/utils" + "github.com/wtfutil/wtf/wtf" +) + +// WtfApp is the container for a collection of widgets that are all constructed from a single +// configuration file and displayed together +type WtfApp struct { + App *tview.Application + Config *config.Config + ConfigFilePath string + Display *Display + FocusTracker wtf.FocusTracker + IsCustomConfig bool + Pages *tview.Pages + Widgets []wtf.Wtfable +} + +// NewWtfApp creates and returns an instance of WtfApp +func NewWtfApp(app *tview.Application, config *config.Config, configFilePath string, isCustom bool) *WtfApp { + wtfApp := WtfApp{ + App: app, + Config: config, + ConfigFilePath: configFilePath, + IsCustomConfig: isCustom, + Pages: tview.NewPages(), + } + + wtfApp.Widgets = maker.MakeWidgets(wtfApp.App, wtfApp.Pages, wtfApp.Config) + wtfApp.Display = NewDisplay(wtfApp.Widgets, wtfApp.Config) + wtfApp.FocusTracker = wtf.NewFocusTracker(wtfApp.App, wtfApp.Widgets, wtfApp.Config) + + wtfApp.App.SetInputCapture(wtfApp.keyboardIntercept) + wtfApp.Pages.AddPage("grid", wtfApp.Display.Grid, true, true) + wtfApp.App.SetRoot(wtfApp.Pages, true) + + wtf.ValidateWidgets(wtfApp.Widgets) + + wtfApp.scheduleWidgets() + + return &wtfApp +} + +/* -------------------- Exported Functions -------------------- */ + +// Start initializes the app +func (wtfApp *WtfApp) Start() { + go wtfApp.watchForConfigChanges() +} + +// Stop kills all the currently-running widgets in this app +func (wtfApp *WtfApp) Stop() { + // TODO: Pretty sure we should kill their go routines that run them as well + // otherwise....? + wtfApp.disableAllWidgets() +} + +/* -------------------- Unexported Functions -------------------- */ + +func (wtfApp *WtfApp) disableAllWidgets() { + for _, widget := range wtfApp.Widgets { + widget.Disable() + } +} + +func (wtfApp *WtfApp) keyboardIntercept(event *tcell.EventKey) *tcell.EventKey { + // These keys are global keys used by the app. Widgets should not implement these keys + switch event.Key() { + case tcell.KeyCtrlR: + wtfApp.refreshAllWidgets() + return nil + case tcell.KeyTab: + wtfApp.FocusTracker.Next() + return nil + case tcell.KeyBacktab: + wtfApp.FocusTracker.Prev() + return nil + case tcell.KeyEsc: + wtfApp.FocusTracker.None() + return nil + } + + // Checks to see if any widget has been assigned the pressed key as its focus key + if wtfApp.FocusTracker.FocusOn(string(event.Rune())) { + return nil + } + + // If no specific widget has focus, then allow the key presses to fall through to the app + if !wtfApp.FocusTracker.IsFocused { + switch string(event.Rune()) { + case "/": + return nil + } + } + + return event +} + +func (wtfApp *WtfApp) refreshAllWidgets() { + for _, widget := range wtfApp.Widgets { + go widget.Refresh() + } +} + +func (wtfApp *WtfApp) scheduleWidgets() { + for _, widget := range wtfApp.Widgets { + go wtf.Schedule(widget) + } +} + +func (wtfApp *WtfApp) watchForConfigChanges() { + watch := watcher.New() + + // Notify write events + watch.FilterOps(watcher.Write) + + go func() { + for { + select { + case <-watch.Event: + wtfApp.Stop() + + config := cfg.LoadWtfConfigFile(wtfApp.ConfigFilePath, wtfApp.IsCustomConfig) + + newApp := NewWtfApp(wtfApp.App, config, wtfApp.ConfigFilePath, wtfApp.IsCustomConfig) + newApp.Start() + case err := <-watch.Error: + log.Fatalln(err) + case <-watch.Closed: + return + } + } + }() + + // Watch config file for changes. + absPath, _ := utils.ExpandHomeDir(wtfApp.ConfigFilePath) + if err := watch.Add(absPath); err != nil { + log.Fatalln(err) + } + + // Start the watching process - it'll check for changes every 100ms. + if err := watch.Start(time.Millisecond * 100); err != nil { + log.Fatalln(err) + } +} diff --git a/main.go b/main.go index 31288936..de8a37a1 100644 --- a/main.go +++ b/main.go @@ -9,23 +9,19 @@ import ( "fmt" "log" "os" - "time" - "github.com/gdamore/tcell" "github.com/logrusorgru/aurora" "github.com/olebedev/config" "github.com/pkg/profile" - "github.com/radovskyb/watcher" + "github.com/rivo/tview" + "github.com/wtfutil/wtf/app" "github.com/wtfutil/wtf/cfg" "github.com/wtfutil/wtf/flags" - "github.com/wtfutil/wtf/maker" - "github.com/wtfutil/wtf/utils" "github.com/wtfutil/wtf/wtf" ) -var focusTracker wtf.FocusTracker -var runningWidgets []wtf.Wtfable +var tviewApp *tview.Application var ( commit = "dev" @@ -35,52 +31,6 @@ var ( /* -------------------- Functions -------------------- */ -func disableAllWidgets(widgets []wtf.Wtfable) { - for _, widget := range widgets { - widget.Disable() - } -} - -func keyboardIntercept(event *tcell.EventKey) *tcell.EventKey { - // These keys are global keys used by the app. Widgets should not implement these keys - switch event.Key() { - case tcell.KeyCtrlR: - refreshAllWidgets(runningWidgets) - return nil - case tcell.KeyTab: - focusTracker.Next() - return nil - case tcell.KeyBacktab: - focusTracker.Prev() - return nil - case tcell.KeyEsc: - focusTracker.None() - return nil - } - - // This function checks to see if any widget has been assigned the pressed key as its - // focus key - if focusTracker.FocusOn(string(event.Rune())) { - return nil - } - - // If no specific widget has focus, then allow the key presses to fall through to the app - if !focusTracker.IsFocused { - switch string(event.Rune()) { - case "/": - return nil - } - } - - return event -} - -func refreshAllWidgets(widgets []wtf.Wtfable) { - for _, widget := range widgets { - go widget.Refresh() - } -} - func setTerm(config *config.Config) { term := config.UString("wtf.term", os.Getenv("TERM")) err := os.Setenv("TERM", term) @@ -90,61 +40,19 @@ func setTerm(config *config.Config) { } } -func watchForConfigChanges(app *tview.Application, configFilePath string, isCustomConfig bool, grid *tview.Grid, pages *tview.Pages) { - watch := watcher.New() - absPath, _ := utils.ExpandHomeDir(configFilePath) - - // Notify write events - watch.FilterOps(watcher.Write) - - go func() { - for { - select { - case <-watch.Event: - // Disable all widgets to stop scheduler goroutines and remove widgets from memory - disableAllWidgets(runningWidgets) - - config := cfg.LoadWtfConfigFile(absPath, false) - - widgets := maker.MakeWidgets(app, pages, config) - runningWidgets = widgets - - wtf.ValidateWidgets(widgets) - - focusTracker = wtf.NewFocusTracker(app, widgets, config) - - display := wtf.NewDisplay(widgets, config) - pages.AddPage("grid", display.Grid, true, true) - case err := <-watch.Error: - log.Fatalln(err) - case <-watch.Closed: - return - } - } - }() - - // Watch config file for changes. - if err := watch.Add(absPath); err != nil { - log.Fatalln(err) - } - - // Start the watching process - it'll check for changes every 100ms. - if err := watch.Start(time.Millisecond * 100); err != nil { - log.Fatalln(err) - } -} - /* -------------------- Main -------------------- */ func main() { log.SetFlags(log.LstdFlags | log.Lshortfile) - // Manage the configuration directories and file + // Manage the configuration directories and config file cfg.Initialize() // Parse and handle flags flags := flags.NewFlags() flags.Parse() + + // Load the configuration file config := cfg.LoadWtfConfigFile(flags.ConfigFilePath(), flags.HasCustomConfig()) flags.RenderIf(version, config) @@ -152,28 +60,16 @@ func main() { defer profile.Start(profile.MemProfile).Stop() } - setTerm(config) - - app := tview.NewApplication() - pages := tview.NewPages() - - widgets := maker.MakeWidgets(app, pages, config) - runningWidgets = widgets - - wtf.ValidateWidgets(widgets) - - focusTracker = wtf.NewFocusTracker(app, widgets, config) - - display := wtf.NewDisplay(widgets, config) - pages.AddPage("grid", display.Grid, true, true) - - app.SetInputCapture(keyboardIntercept) - - go watchForConfigChanges(app, flags.Config, flags.HasCustomConfig(), display.Grid, pages) - wtf.Init(config.UString("wtf.openFileUtil", "open")) - if err := app.SetRoot(pages, true).Run(); err != nil { + setTerm(config) + + // Build the application + tviewApp = tview.NewApplication() + wtfApp := app.NewWtfApp(tviewApp, config, flags.Config, flags.HasCustomConfig()) + wtfApp.Start() + + if err := wtfApp.App.Run(); err != nil { fmt.Printf("\n%s %v\n", aurora.Red("ERROR"), err) os.Exit(1) }