diff --git a/pkg/gui/arrangement.go b/pkg/gui/arrangement.go index b24c82af..6b0250e5 100644 --- a/pkg/gui/arrangement.go +++ b/pkg/gui/arrangement.go @@ -204,30 +204,15 @@ func (gui *Gui) sidePanelChildren(width int, height int) []*boxlayout.Box { } } -// TODO: reintroduce -// func (gui *Gui) currentSideWindowName() string { -// // there is always one and only one cyclable context in the context stack. We'll look from top to bottom -// gui.State.ContextManager.RLock() -// defer gui.State.ContextManager.RUnlock() - -// for idx := range gui.State.ContextManager.ContextStack { -// reversedIdx := len(gui.State.ContextManager.ContextStack) - 1 - idx -// context := gui.State.ContextManager.ContextStack[reversedIdx] - -// if context.GetKind() == types.SIDE_CONTEXT { -// return context.GetWindowName() -// } -// } - -// return "files" // default -// } - -// TODO: do this better. func (gui *Gui) currentSideWindowName() string { - windowName := gui.currentWindow() - if !lo.Contains(gui.sideViewNames(), windowName) { - return gui.peekPreviousView() + // we expect that there is a side window somewhere in the view stack, so we will search from top to bottom + for idx := range gui.State.ViewStack { + reversedIdx := len(gui.State.ViewStack) - 1 - idx + viewName := gui.State.ViewStack[reversedIdx] + if lo.Contains(gui.sideViewNames(), viewName) { + return viewName + } } - return windowName + return gui.initiallyFocusedViewName() } diff --git a/pkg/gui/confirmation_panel.go b/pkg/gui/confirmation_panel.go index 4c3b761a..85f3fe85 100644 --- a/pkg/gui/confirmation_panel.go +++ b/pkg/gui/confirmation_panel.go @@ -86,7 +86,7 @@ func (gui *Gui) prepareConfirmationPanel(currentView *gocui.View, title, prompt confirmationView.Title = title confirmationView.Visible = true gui.g.Update(func(g *gocui.Gui) error { - return gui.switchFocus(gui.g, currentView, confirmationView, false) + return gui.switchFocus(confirmationView) }) return nil } @@ -96,8 +96,8 @@ func (gui *Gui) onNewPopupPanel() { gui.Views.Confirmation.Visible = false } -// it is very important that within this function we never include the original prompt in any error messages, because it may contain e.g. a user password -// unparam complains that handleClose is alwans nil but one day it won't be nil. +// It is very important that within this function we never include the original prompt in any error messages, because it may contain e.g. a user password. +// The golangcilint unparam linter complains that handleClose is alwans nil but one day it won't be nil. // nolint:unparam func (gui *Gui) createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, title, prompt string, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error { return gui.createPopupPanel(g, currentView, title, prompt, false, handleConfirm, handleClose) diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index c664edb0..1b2199a7 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -78,6 +78,7 @@ type Gui struct { type Mutexes struct { SubprocessMutex sync.Mutex + ViewStackMutex sync.Mutex } type servicePanelState struct { diff --git a/pkg/gui/layout.go b/pkg/gui/layout.go index 22f5fd89..11e86758 100644 --- a/pkg/gui/layout.go +++ b/pkg/gui/layout.go @@ -117,16 +117,13 @@ func (gui *Gui) layout(g *gocui.Gui) error { } if gui.g.CurrentView() == nil { - v, err := gui.g.View(gui.peekPreviousView()) + viewName := gui.initiallyFocusedViewName() + view, err := gui.g.View(viewName) if err != nil { - viewName := gui.initiallyFocusedViewName() - v, err = gui.g.View(viewName) - if err != nil { - return err - } + return err } - if err := gui.switchFocus(gui.g, nil, v, false); err != nil { + if err := gui.switchFocus(view); err != nil { return err } } diff --git a/pkg/gui/main_panel.go b/pkg/gui/main_panel.go index 6b59ef73..c8f13721 100644 --- a/pkg/gui/main_panel.go +++ b/pkg/gui/main_panel.go @@ -101,7 +101,7 @@ func (gui *Gui) handleEnterMain(g *gocui.Gui, v *gocui.View) error { mainView := gui.getMainView() mainView.ParentView = v - return gui.switchFocus(gui.g, v, mainView, false) + return gui.switchFocus(mainView) } func (gui *Gui) handleExitMain(g *gocui.Gui, v *gocui.View) error { @@ -122,5 +122,5 @@ func (gui *Gui) handleMainClick(g *gocui.Gui, v *gocui.View) error { v.ParentView = currentView } - return gui.switchFocus(gui.g, currentView, v, false) + return gui.switchFocus(v) } diff --git a/pkg/gui/menu_panel.go b/pkg/gui/menu_panel.go index adb35818..f6e5e74b 100644 --- a/pkg/gui/menu_panel.go +++ b/pkg/gui/menu_panel.go @@ -100,8 +100,7 @@ func (gui *Gui) createMenu(title string, items interface{}, itemCount int, handl gui.g.Update(func(g *gocui.Gui) error { menuView.Visible = true - currentView := gui.g.CurrentView() - return gui.switchFocus(gui.g, currentView, menuView, false) + return gui.switchFocus(menuView) }) return nil } diff --git a/pkg/gui/subprocess.go b/pkg/gui/subprocess.go index 6d114bc8..54af269d 100644 --- a/pkg/gui/subprocess.go +++ b/pkg/gui/subprocess.go @@ -22,7 +22,7 @@ func (gui *Gui) runSubprocess(cmd *exec.Cmd) error { gui.PauseBackgroundThreads = true - cmdErr := gui.runSubprocess(cmd) + cmdErr := gui.runCommand(cmd) if err := gui.g.Resume(); err != nil { return gui.createErrorPanel(gui.g, err.Error()) diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index a585d807..2ab39f19 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -33,8 +33,7 @@ func (gui *Gui) nextView(g *gocui.Gui, v *gocui.View) error { panic(err) } gui.resetMainView() - gui.popView() - return gui.switchFocus(g, v, focusedView, false) + return gui.switchFocus(focusedView) } func (gui *Gui) previousView(g *gocui.Gui, v *gocui.View) error { @@ -59,8 +58,7 @@ func (gui *Gui) previousView(g *gocui.Gui, v *gocui.View) error { panic(err) } gui.resetMainView() - gui.popView() - return gui.switchFocus(g, v, focusedView, false) + return gui.switchFocus(focusedView) } func (gui *Gui) resetMainView() { @@ -96,24 +94,48 @@ func (gui *Gui) newLineFocused(v *gocui.View) error { } } -func (gui *Gui) popView() string { - if len(gui.State.ViewStack) > 0 { - value := gui.State.ViewStack[len(gui.State.ViewStack)-1] - gui.State.ViewStack = gui.State.ViewStack[:len(gui.State.ViewStack)-1] - return value - } +// TODO: move some of this logic into our onFocusLost and onFocus hooks +func (gui *Gui) switchFocus(newView *gocui.View) error { + gui.Mutexes.ViewStackMutex.Lock() + defer gui.Mutexes.ViewStackMutex.Unlock() - return "" + return gui.switchFocusAux(newView) } -func (gui *Gui) peekPreviousView() string { - if len(gui.State.ViewStack) > 0 { - return gui.State.ViewStack[len(gui.State.ViewStack)-1] +func (gui *Gui) switchFocusAux(newView *gocui.View) error { + gui.pushView(newView.Name()) + gui.Log.Info("setting highlight to true for view " + newView.Name()) + gui.Log.Info("new focused view is " + newView.Name()) + if _, err := gui.g.SetCurrentView(newView.Name()); err != nil { + return err } - return "" + gui.g.Cursor = newView.Editable + + if err := gui.renderPanelOptions(); err != nil { + return err + } + + return gui.newLineFocused(newView) } +func (gui *Gui) returnFocus(g *gocui.Gui, v *gocui.View) error { + gui.Mutexes.ViewStackMutex.Lock() + defer gui.Mutexes.ViewStackMutex.Unlock() + + if len(gui.State.ViewStack) <= 1 { + return nil + } + + previousViewName := gui.State.ViewStack[len(gui.State.ViewStack)-2] + previousView, err := g.View(previousViewName) + if err != nil { + return err + } + return gui.switchFocusAux(previousView) +} + +// Not to be called directly. Use `switchFocus` instead func (gui *Gui) pushView(name string) { // No matter what view we're pushing, we first remove all popup panels from the stack gui.State.ViewStack = lo.Filter(gui.State.ViewStack, func(viewName string, _ int) bool { @@ -122,51 +144,17 @@ func (gui *Gui) pushView(name string) { // If we're pushing a side panel, we remove all other panels if lo.Contains(gui.sideViewNames(), name) { - gui.State.ViewStack = lo.Filter(gui.State.ViewStack, func(viewName string, _ int) bool { - return viewName != "main" - }) + gui.State.ViewStack = []string{} } + // If we're pushing a panel that's already in the stack, we remove it + gui.State.ViewStack = lo.Filter(gui.State.ViewStack, func(viewName string, _ int) bool { + return viewName != name + }) + gui.State.ViewStack = append(gui.State.ViewStack, name) } -func (gui *Gui) returnFocus(g *gocui.Gui, v *gocui.View) error { - previousViewName := gui.popView() - previousView, err := g.View(previousViewName) - if err != nil { - // always fall back to services view if there's no 'previous' view stored - previousView, err = g.View(gui.initiallyFocusedViewName()) - if err != nil { - gui.Log.Error(err) - } - } - return gui.switchFocus(g, v, previousView, true) -} - -// pass in oldView = nil if you don't want to be able to return to your old view -// TODO: move some of this logic into our onFocusLost and onFocus hooks -func (gui *Gui) switchFocus(g *gocui.Gui, oldView, newView *gocui.View, returning bool) error { - // we assume we'll never want to return focus to a popup panel i.e. - // we should never stack popup panels - if oldView != nil && !gui.isPopupPanel(oldView.Name()) && !returning { - gui.pushView(oldView.Name()) - } - - gui.Log.Info("setting highlight to true for view " + newView.Name()) - gui.Log.Info("new focused view is " + newView.Name()) - if _, err := g.SetCurrentView(newView.Name()); err != nil { - return err - } - - g.Cursor = newView.Editable - - if err := gui.renderPanelOptions(); err != nil { - return err - } - - return gui.newLineFocused(newView) -} - // if the cursor down past the last item, move it to the last line // nolint:unparam func (gui *Gui) focusPoint(selectedX int, selectedY int, lineCount int, v *gocui.View) {