diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index a783f49fe..b82301aaa 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -76,7 +76,7 @@ func (gui *Gui) refreshBranches() { } gui.State.Branches = builder.Build() - if err := gui.rerenderIfVisible(gui.Contexts.Branches.Context); err != nil { + if err := gui.postRefreshUpdate(gui.Contexts.Branches.Context); err != nil { gui.Log.Error(err) } diff --git a/pkg/gui/context.go b/pkg/gui/context.go index a54625306..559cfa108 100644 --- a/pkg/gui/context.go +++ b/pkg/gui/context.go @@ -98,159 +98,26 @@ type ContextTree struct { Search SimpleContextNode } -func (gui *Gui) switchContext(c Context) error { - gui.g.Update(func(*gocui.Gui) error { - // push onto stack - // if we are switching to a side context, remove all other contexts in the stack - if c.GetKind() == SIDE_CONTEXT { - for _, stackContext := range gui.State.ContextStack { - if stackContext.GetKey() != c.GetKey() { - if err := gui.deactivateContext(stackContext); err != nil { - return err - } - } - } - gui.State.ContextStack = []Context{c} - } else { - // TODO: think about other exceptional cases - gui.State.ContextStack = append(gui.State.ContextStack, c) - } - - return gui.activateContext(c) - }) - - return nil -} - -// switchContextToView is to be used when you don't know which context you -// want to switch to: you only know the view that you want to switch to. It will -// look up the context currently active for that view and switch to that context -func (gui *Gui) switchContextToView(viewName string) error { - return gui.switchContext(gui.State.ViewContextMap[viewName]) -} - -func (gui *Gui) returnFromContext() error { - gui.g.Update(func(*gocui.Gui) error { - // TODO: add mutexes - - if len(gui.State.ContextStack) == 1 { - // cannot escape from bottommost context - return nil - } - - n := len(gui.State.ContextStack) - 1 - - currentContext := gui.State.ContextStack[n] - newContext := gui.State.ContextStack[n-1] - - gui.State.ContextStack = gui.State.ContextStack[:n] - - if err := gui.deactivateContext(currentContext); err != nil { - return err - } - - return gui.activateContext(newContext) - }) - - return nil -} - -func (gui *Gui) deactivateContext(c Context) error { - // if we are the kind of context that is sent to back upon deactivation, we should do that - if c.GetKind() == TEMPORARY_POPUP || c.GetKind() == PERSISTENT_POPUP { - _, _ = gui.g.SetViewOnBottom(c.GetViewName()) +func (gui *Gui) allContexts() []Context { + return []Context{ + gui.Contexts.Status.Context, + gui.Contexts.Files.Context, + gui.Contexts.Branches.Context, + gui.Contexts.Remotes.Context, + gui.Contexts.Remotes.Branches.Context, + gui.Contexts.BranchCommits.Context, + gui.Contexts.BranchCommits.Files.Context, + gui.Contexts.ReflogCommits.Context, + gui.Contexts.Stash.Context, + gui.Contexts.Menu.Context, + gui.Contexts.Confirmation.Context, + gui.Contexts.Credentials.Context, + gui.Contexts.CommitMessage.Context, + gui.Contexts.Normal.Context, + gui.Contexts.Staging.Context, + gui.Contexts.Merging.Context, + gui.Contexts.PatchBuilding.Context, } - - if err := c.HandleFocusLost(); err != nil { - return err - } - - return nil -} - -func (gui *Gui) rerenderIfVisible(c Context) error { - v, err := gui.g.View(c.GetViewName()) - if err != nil { - return nil - } - - if v.Context == c.GetKey() { - if err := c.HandleRender(); err != nil { - return err - } - } - - return nil -} - -func (gui *Gui) activateContext(c Context) error { - gui.Log.Warn(spew.Sdump(gui.renderContextStack())) - - viewName := c.GetViewName() - v, err := gui.g.View(viewName) - // if view no longer exists, pop again - if err != nil { - return gui.returnFromContext() - } - - // if the new context's view was previously displaying another context, render the new context - if v.Context != c.GetKey() { - if err := c.HandleRender(); err != nil { - return err - } - } - - if viewName == "main" { - gui.changeMainViewsContext(c.GetKey()) - } else { - gui.changeMainViewsContext("normal") - } - - gui.setViewTabForContext(c) - - if _, err := gui.g.SetCurrentView(viewName); err != nil { - return err - } - - if _, err := gui.g.SetViewOnTop(viewName); err != nil { - return err - } - - newView := gui.g.CurrentView() - newView.Context = c.GetKey() - - gui.g.Cursor = newView.Editable - - // TODO: move this logic to the context - if err := gui.renderPanelOptions(); err != nil { - return err - } - - if err := c.HandleFocus(); err != nil { - return err - } - - // TODO: consider removing this and instead depending on the .Context field of views - gui.State.ViewContextMap[c.GetViewName()] = c - - return nil -} - -func (gui *Gui) renderContextStack() string { - result := "" - for _, context := range gui.State.ContextStack { - result += context.GetKey() + "\n" - } - return result -} - -func (gui *Gui) currentContextKey() string { - // on startup the stack can be empty so we'll return an empty string in that case - if len(gui.State.ContextStack) == 0 { - return "" - } - - return gui.State.ContextStack[len(gui.State.ContextStack)-1].GetKey() } func (gui *Gui) contextTree() ContextTree { @@ -389,6 +256,174 @@ func (gui *Gui) initialViewContextMap() map[string]Context { } } +func (gui *Gui) switchContext(c Context) error { + gui.g.Update(func(*gocui.Gui) error { + // push onto stack + // if we are switching to a side context, remove all other contexts in the stack + if c.GetKind() == SIDE_CONTEXT { + for _, stackContext := range gui.State.ContextStack { + if stackContext.GetKey() != c.GetKey() { + if err := gui.deactivateContext(stackContext); err != nil { + return err + } + } + } + gui.State.ContextStack = []Context{c} + } else { + // TODO: think about other exceptional cases + gui.State.ContextStack = append(gui.State.ContextStack, c) + } + + return gui.activateContext(c) + }) + + return nil +} + +// switchContextToView is to be used when you don't know which context you +// want to switch to: you only know the view that you want to switch to. It will +// look up the context currently active for that view and switch to that context +func (gui *Gui) switchContextToView(viewName string) error { + return gui.switchContext(gui.State.ViewContextMap[viewName]) +} + +func (gui *Gui) returnFromContext() error { + gui.g.Update(func(*gocui.Gui) error { + // TODO: add mutexes + + if len(gui.State.ContextStack) == 1 { + // cannot escape from bottommost context + return nil + } + + n := len(gui.State.ContextStack) - 1 + + currentContext := gui.State.ContextStack[n] + newContext := gui.State.ContextStack[n-1] + + gui.State.ContextStack = gui.State.ContextStack[:n] + + if err := gui.deactivateContext(currentContext); err != nil { + return err + } + + return gui.activateContext(newContext) + }) + + return nil +} + +func (gui *Gui) deactivateContext(c Context) error { + // if we are the kind of context that is sent to back upon deactivation, we should do that + if c.GetKind() == TEMPORARY_POPUP || c.GetKind() == PERSISTENT_POPUP { + _, _ = gui.g.SetViewOnBottom(c.GetViewName()) + } + + if err := c.HandleFocusLost(); err != nil { + return err + } + + return nil +} + +// postRefreshUpdate is to be called on a context after the state that it depends on has been refreshed +// if the context's view is set to another context we do nothing. +// if the context's view is the current view we trigger a focus; re-selecting the current item. +func (gui *Gui) postRefreshUpdate(c Context) error { + v, err := gui.g.View(c.GetViewName()) + if err != nil { + return nil + } + + if v.Context != c.GetKey() { + return nil + } + + if err := c.HandleRender(); err != nil { + return err + } + + if gui.currentViewName() == c.GetViewName() { + if err := c.HandleFocus(); err != nil { + return err + } + } + + return nil +} + +func (gui *Gui) activateContext(c Context) error { + gui.Log.Warn(spew.Sdump(gui.renderContextStack())) + + viewName := c.GetViewName() + v, err := gui.g.View(viewName) + // if view no longer exists, pop again + if err != nil { + return gui.returnFromContext() + } + + // if the new context's view was previously displaying another context, render the new context + if v.Context != c.GetKey() { + if err := c.HandleRender(); err != nil { + return err + } + } + + gui.setViewAsActiveForWindow(viewName) + + if viewName == "main" { + gui.changeMainViewsContext(c.GetKey()) + } else { + gui.changeMainViewsContext("normal") + } + + gui.setViewTabForContext(c) + + if _, err := gui.g.SetCurrentView(viewName); err != nil { + return err + } + + if _, err := gui.g.SetViewOnTop(viewName); err != nil { + return err + } + + newView := gui.g.CurrentView() + newView.Context = c.GetKey() + + gui.g.Cursor = newView.Editable + + // TODO: move this logic to the context + if err := gui.renderPanelOptions(); err != nil { + return err + } + + if err := c.HandleFocus(); err != nil { + return err + } + + // TODO: consider removing this and instead depending on the .Context field of views + gui.State.ViewContextMap[c.GetViewName()] = c + + return nil +} + +func (gui *Gui) renderContextStack() string { + result := "" + for _, context := range gui.State.ContextStack { + result += context.GetKey() + "\n" + } + return result +} + +func (gui *Gui) currentContextKey() string { + // on startup the stack can be empty so we'll return an empty string in that case + if len(gui.State.ContextStack) == 0 { + return "" + } + + return gui.State.ContextStack[len(gui.State.ContextStack)-1].GetKey() +} + func (gui *Gui) setInitialViewContexts() { // arguably we should only have our ViewContextMap and we should do away with // contexts on views, or vice versa @@ -417,9 +452,6 @@ func (gui *Gui) getFocusLayout() func(g *gocui.Gui) error { return err } - if err := gui.onViewFocus(newView); err != nil { - return err - } previousView = newView } return nil @@ -449,12 +481,6 @@ func (gui *Gui) onViewFocusLost(v *gocui.View, newView *gocui.View) error { return nil } -func (gui *Gui) onViewFocus(newView *gocui.View) error { - gui.setViewAsActiveForWindow(newView.Name()) - - return nil -} - // changeContext is a helper function for when we want to change a 'main' context // which currently just means a context that affects both the main and secondary views // other views can have their context changed directly but this function helps @@ -553,7 +579,24 @@ type tabContext struct { contexts []Context } -func (gui *Gui) handleContextRefresh(c Context) { - // if context is not the current context of it's view, return +func (gui *Gui) contextForContextKey(contextKey string) Context { + for _, context := range gui.allContexts() { + if context.GetKey() == contextKey { + return context + } + } + panic(fmt.Sprintf("context now found for key %s", contextKey)) +} + +func (gui *Gui) rerenderView(viewName string) error { + v, err := gui.g.View(viewName) + if err != nil { + return nil + } + + contextKey := v.Context + context := gui.contextForContextKey(contextKey) + + return context.HandleRender() } diff --git a/pkg/gui/global_handlers.go b/pkg/gui/global_handlers.go index ef98b98e6..d37fb8dea 100644 --- a/pkg/gui/global_handlers.go +++ b/pkg/gui/global_handlers.go @@ -9,15 +9,23 @@ import ( "github.com/jesseduffield/lazygit/pkg/utils" ) +// these views need to be re-rendered when the screen mode changes. The commits view, +// for example, will show authorship information in half and full screen mode. +func (gui *Gui) viewsWithScreenModeDependentContent() []string { + return []string{"branches", "commits"} +} + func (gui *Gui) nextScreenMode(g *gocui.Gui, v *gocui.View) error { gui.State.ScreenMode = utils.NextIntInCycle([]int{SCREEN_NORMAL, SCREEN_HALF, SCREEN_FULL}, gui.State.ScreenMode) // commits render differently depending on whether we're in fullscreen more or not if err := gui.refreshCommitsViewWithSelection(); err != nil { return err } - // same with branches - if err := gui.refreshBranchesViewWithSelection(); err != nil { - return err + + for _, viewName := range gui.viewsWithScreenModeDependentContent() { + if err := gui.rerenderView(viewName); err != nil { + return err + } } return nil @@ -25,13 +33,11 @@ func (gui *Gui) nextScreenMode(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) prevScreenMode(g *gocui.Gui, v *gocui.View) error { gui.State.ScreenMode = utils.PrevIntInCycle([]int{SCREEN_NORMAL, SCREEN_HALF, SCREEN_FULL}, gui.State.ScreenMode) - // commits render differently depending on whether we're in fullscreen more or not - if err := gui.refreshCommitsViewWithSelection(); err != nil { - return err - } - // same with branches - if err := gui.refreshBranchesViewWithSelection(); err != nil { - return err + + for _, viewName := range gui.viewsWithScreenModeDependentContent() { + if err := gui.rerenderView(viewName); err != nil { + return err + } } return nil diff --git a/pkg/gui/searching.go b/pkg/gui/searching.go index 261815824..ddf595a5f 100644 --- a/pkg/gui/searching.go +++ b/pkg/gui/searching.go @@ -11,7 +11,9 @@ import ( func (gui *Gui) handleOpenSearch(g *gocui.Gui, v *gocui.View) error { gui.State.Searching.isSearching = true gui.State.Searching.view = v + gui.renderString("search", "") + if err := gui.switchContext(gui.Contexts.Search.Context); err != nil { return err }