diff --git a/app/app_manager.go b/app/app_manager.go index 00f209c3..9ee2eebd 100644 --- a/app/app_manager.go +++ b/app/app_manager.go @@ -1,8 +1,9 @@ package app import ( - "errors" + "fmt" + "github.com/gdamore/tcell" "github.com/olebedev/config" "github.com/rivo/tview" "github.com/wtfutil/wtf/support" @@ -10,21 +11,29 @@ import ( // WtfAppManager handles the instances of WtfApp, ensuring that they're displayed as requested type WtfAppManager struct { - WtfApps []*WtfApp + WtfApps []WtfApp - config *config.Config - ghUser *support.GitHubUser - selected int + config *config.Config + ghUser *support.GitHubUser + selectedIdx int + tviewApp *tview.Application } // NewAppManager creates and returns an instance of AppManager -func NewAppManager(config *config.Config) WtfAppManager { +func NewAppManager(config *config.Config, tviewApp *tview.Application) WtfAppManager { appMan := WtfAppManager{ - WtfApps: []*WtfApp{}, + WtfApps: []WtfApp{}, config: config, + + tviewApp: tviewApp, } + appMan.tviewApp.SetBeforeDrawFunc(func(s tcell.Screen) bool { + s.Clear() + return false + }) + githubAPIKey := readGitHubAPIKey(config) appMan.ghUser = support.NewGitHubUser(githubAPIKey) @@ -35,47 +44,96 @@ func NewAppManager(config *config.Config) WtfAppManager { // MakeNewWtfApp creates and starts a new instance of WtfApp from a set of configuration params func (appMan *WtfAppManager) MakeNewWtfApp(configFilePath string) { - wtfApp := NewWtfApp(tview.NewApplication(), appMan.config, configFilePath) + wtfApp := NewWtfApp(appMan.config, appMan.tviewApp, configFilePath) appMan.Add(wtfApp) wtfApp.Start() } +/* -------------------- Exported Functions -------------------- */ + // Add adds a WtfApp to the collection of apps that the AppManager manages. // This app is then available for display onscreen. -func (appMan *WtfAppManager) Add(wtfApp *WtfApp) { +func (appMan *WtfAppManager) Add(wtfApp WtfApp) { appMan.WtfApps = append(appMan.WtfApps, wtfApp) } -// Current returns the currently-displaying instance of WtfApp -func (appMan *WtfAppManager) Current() (*WtfApp, error) { - if appMan.selected < 0 || appMan.selected >= len(appMan.WtfApps) { - return nil, errors.New("invalid app index selected") +// CurrentWtfApp returns the currently-displaying instance of WtfApp +func (appMan *WtfAppManager) CurrentWtfApp() (WtfApp, error) { + appCount := len(appMan.WtfApps) + + if appCount < 1 { + return WtfApp{}, fmt.Errorf("no wtf apps defined, cannot select current app: %d", appCount) } - return appMan.WtfApps[appMan.selected], nil + if appMan.selectedIdx < 0 || appMan.selectedIdx >= appCount { + return WtfApp{}, fmt.Errorf("invalid app index selected: %d", appMan.selectedIdx) + } + + return appMan.WtfApps[appMan.selectedIdx], nil } -// Next cycles the WtfApps forward by one, making the next one in the list +// NextWtfApp cycles the WtfApps forward by one, making the next one in the list // the current one. If there are none after the current one, it wraps around. -func (appMan *WtfAppManager) Next() (*WtfApp, error) { - appMan.selected++ +func (appMan *WtfAppManager) NextWtfApp() { + appMan.selectedIdx++ - if appMan.selected >= len(appMan.WtfApps) { - appMan.selected = 0 + if appMan.selectedIdx >= len(appMan.WtfApps) { + appMan.selectedIdx = 0 } - - return appMan.Current() } -// Prev cycles the WtfApps backwards by one, making the previous one in the +// PrevWtfApp cycles the WtfApps backwards by one, making the previous one in the // list the current one. If there are none before the current one, it wraps around. -func (appMan *WtfAppManager) Prev() (*WtfApp, error) { - appMan.selected-- +func (appMan *WtfAppManager) PrevWtfApp() { + appMan.selectedIdx-- - if appMan.selected < 0 { - appMan.selected = len(appMan.WtfApps) - 1 + if appMan.selectedIdx < 0 { + appMan.selectedIdx = len(appMan.WtfApps) - 1 + } +} + +// KeyboardIntercept controls all the top-level keyboard input handling. +func (appMan *WtfAppManager) KeyboardIntercept(event *tcell.EventKey) *tcell.EventKey { + currentWtfApp, err := appMan.CurrentWtfApp() + if err != nil { + return nil } - return appMan.Current() + // These keys are global keys used by the app. Widgets should not implement these keys + switch event.Key() { + case tcell.KeyCtrlC: + currentWtfApp.Stop() + appMan.DisplayExitMessage() + case tcell.KeyCtrlR: + currentWtfApp.refreshAllWidgets() + return nil + case tcell.KeyCtrlSpace: + fmt.Println(">> Next app") + appMan.NextWtfApp() + return nil + case tcell.KeyTab: + currentWtfApp.focusTracker.Next() + case tcell.KeyBacktab: + currentWtfApp.focusTracker.Prev() + return nil + case tcell.KeyEsc: + currentWtfApp.focusTracker.None() + } + + // Checks to see if any widget has been assigned the pressed key as its focus key + if currentWtfApp.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 !currentWtfApp.focusTracker.IsFocused { + switch string(event.Rune()) { + case "/": + return nil + default: + } + } + + return event } diff --git a/app/exit_message_test.go b/app/exit_message_test.go index 5022afa6..d5ea4543 100644 --- a/app/exit_message_test.go +++ b/app/exit_message_test.go @@ -4,6 +4,8 @@ import ( "strings" "testing" + "github.com/olebedev/config" + "github.com/rivo/tview" "github.com/wtfutil/wtf/support" "gotest.tools/assert" ) @@ -51,7 +53,7 @@ func Test_displayExitMessage(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - appMan := NewAppManager() + appMan := NewAppManager(&config.Config{}, tview.NewApplication()) appMan.ghUser = &support.GitHubUser{ IsContributor: tt.isContributor, IsSponsor: tt.isSponsor, diff --git a/app/wtf_app.go b/app/wtf_app.go index 500b7b55..51ce97b3 100644 --- a/app/wtf_app.go +++ b/app/wtf_app.go @@ -36,8 +36,8 @@ type WtfApp struct { } // NewWtfApp creates and returns an instance of WtfApp -func NewWtfApp(tviewApp *tview.Application, config *config.Config, configFilePath string) *WtfApp { - wtfApp := &WtfApp{ +func NewWtfApp(config *config.Config, tviewApp *tview.Application, configFilePath string) WtfApp { + wtfApp := WtfApp{ TViewApp: tviewApp, config: config, @@ -45,11 +45,6 @@ func NewWtfApp(tviewApp *tview.Application, config *config.Config, configFilePat pages: tview.NewPages(), } - wtfApp.TViewApp.SetBeforeDrawFunc(func(s tcell.Screen) bool { - s.Clear() - return false - }) - wtfApp.widgets = MakeWidgets(wtfApp.TViewApp, wtfApp.pages, wtfApp.config) wtfApp.display = NewDisplay(wtfApp.widgets, wtfApp.config) wtfApp.focusTracker = NewFocusTracker(wtfApp.TViewApp, wtfApp.widgets, wtfApp.config) @@ -61,7 +56,6 @@ func NewWtfApp(tviewApp *tview.Application, config *config.Config, configFilePat wtfApp.setBackgroundColor() - wtfApp.TViewApp.SetInputCapture(wtfApp.keyboardIntercept) wtfApp.TViewApp.SetRoot(wtfApp.pages, true) return wtfApp @@ -96,6 +90,7 @@ func (wtfApp *WtfApp) Start() { // Stop kills all the currently-running widgets in this app func (wtfApp *WtfApp) Stop() { wtfApp.stopAllWidgets() + wtfApp.TViewApp.Stop() } /* -------------------- Unexported Functions -------------------- */ @@ -119,49 +114,6 @@ func (wtfApp *WtfApp) stopAllWidgets() { } } -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.KeyCtrlC: - // FIXME: This can't reside in the app, the app shouldn't know - // about termination. The AppManager needs to catch this - wtfApp.Stop() - wtfApp.TViewApp.Stop() - wtfApp.DisplayExitMessage() - case tcell.KeyCtrlR: - wtfApp.refreshAllWidgets() - return nil - case tcell.KeyCtrlSpace: - // FIXME: This can't reside in the app, the app doesn't know about - // the AppManager. The AppManager needs to catch this one - fmt.Println("Next app") - return nil - case tcell.KeyTab: - wtfApp.focusTracker.Next() - case tcell.KeyBacktab: - wtfApp.focusTracker.Prev() - return nil - case tcell.KeyEsc: - wtfApp.focusTracker.None() - } - - // 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 - default: - } - } - - return event -} - func (wtfApp *WtfApp) refreshAllWidgets() { for _, widget := range wtfApp.widgets { go widget.Refresh() @@ -187,7 +139,7 @@ func (wtfApp *WtfApp) watchForConfigChanges() { wtfApp.Stop() config := cfg.LoadWtfConfigFile(wtfApp.configFilePath) - newApp := NewWtfApp(wtfApp.TViewApp, config, wtfApp.configFilePath) + newApp := NewWtfApp(config, wtfApp.TViewApp, wtfApp.configFilePath) openURLUtil := utils.ToStrs(config.UList("wtf.openUrlUtil", []interface{}{})) utils.Init(config.UString("wtf.openFileUtil", "open"), openURLUtil) diff --git a/main.go b/main.go index 2027b579..7db3a08d 100644 --- a/main.go +++ b/main.go @@ -13,6 +13,7 @@ import ( "github.com/logrusorgru/aurora" "github.com/pkg/profile" + "github.com/rivo/tview" "github.com/wtfutil/wtf/app" "github.com/wtfutil/wtf/cfg" @@ -26,6 +27,9 @@ var ( version = "dev" ) +var appMan app.WtfAppManager +var tviewApp *tview.Application + /* -------------------- Main -------------------- */ func main() { @@ -52,10 +56,12 @@ func main() { utils.Init(openFileUtil, openURLUtil) /* Initialize the App Manager */ - appMan := app.NewAppManager(config) + tviewApp = tview.NewApplication() + appMan = app.NewAppManager(config, tviewApp) appMan.MakeNewWtfApp(flags.Config) + tviewApp.SetInputCapture(appMan.KeyboardIntercept) - currentApp, err := appMan.Current() + currentApp, err := appMan.CurrentWtfApp() if err != nil { fmt.Printf("\n%s %v\n", aurora.Red("ERROR"), err) os.Exit(1)