diff --git a/pkg/gui/arrangement.go b/pkg/gui/arrangement.go index 25988bbd2..49311b6d4 100644 --- a/pkg/gui/arrangement.go +++ b/pkg/gui/arrangement.go @@ -244,27 +244,22 @@ func (gui *Gui) sidePanelChildren(width int, height int) []*boxlayout.Box { } func (gui *Gui) currentCyclableViewName() string { - currView := gui.g.CurrentView() - currentCyclebleView := gui.State.PreviousView - if currView != nil { - viewName := currView.Name() - usePreviousView := true - for _, view := range gui.getCyclableViews() { - if view == viewName { - currentCyclebleView = viewName - usePreviousView = false - break + // there is always a cyclable context in the context stack. We'll look from top to bottom + for idx := range gui.State.ContextStack { + reversedIdx := len(gui.State.ContextStack) - 1 - idx + context := gui.State.ContextStack[reversedIdx] + + if context.GetKind() == SIDE_CONTEXT { + viewName := context.GetViewName() + + // unfortunate result of the fact that these are separate views, have to map explicitly + if viewName == "commitFiles" { + return "commits" } - } - if usePreviousView { - currentCyclebleView = gui.State.PreviousView + + return viewName } } - // unfortunate result of the fact that these are separate views, have to map explicitly - if currentCyclebleView == "commitFiles" { - return "commits" - } - - return currentCyclebleView + return "files" // default } diff --git a/pkg/gui/commit_files_panel.go b/pkg/gui/commit_files_panel.go index e2018480b..d37f3fd5e 100644 --- a/pkg/gui/commit_files_panel.go +++ b/pkg/gui/commit_files_panel.go @@ -44,8 +44,6 @@ func (gui *Gui) handleCommitFileSelect() error { return err } - gui.getCommitFilesView().FocusPoint(0, gui.State.Panels.CommitFiles.SelectedLine) - cmd := gui.OSCommand.ExecutableFromString( gui.GitCommand.ShowCommitFileCmdStr(commitFile.Sha, commitFile.Name, false), ) @@ -57,7 +55,7 @@ func (gui *Gui) handleCommitFileSelect() error { } func (gui *Gui) handleSwitchToCommitsPanel(g *gocui.Gui, v *gocui.View) error { - return gui.switchFocus(v, gui.getCommitsView()) + return gui.switchContext(gui.Contexts.BranchCommits.Context) } func (gui *Gui) handleCheckoutCommitFile(g *gocui.Gui, v *gocui.View) error { @@ -223,8 +221,8 @@ func (gui *Gui) enterCommitFile(selectedLineIdx int) error { } } - gui.changeMainViewsContext("patch-building") - if err := gui.switchFocus(gui.getCommitFilesView(), gui.getMainView()); err != nil { + gui.changeMainViewsContext("patch-building") // TODO: bring into context code + if err := gui.switchContext(gui.Contexts.PatchBuilding.Context); err != nil { return err } return gui.refreshPatchBuildingPanel(selectedLineIdx) @@ -241,7 +239,7 @@ func (gui *Gui) enterCommitFile(selectedLineIdx int) error { return enterTheFile(selectedLineIdx) }, handleClose: func() error { - return gui.switchFocus(nil, gui.getCommitFilesView()) + return gui.switchContext(gui.Contexts.BranchCommits.Files.Context) }, }) } diff --git a/pkg/gui/commit_message_panel.go b/pkg/gui/commit_message_panel.go index 9c1a3ff52..f84abcdeb 100644 --- a/pkg/gui/commit_message_panel.go +++ b/pkg/gui/commit_message_panel.go @@ -44,19 +44,19 @@ func (gui *Gui) handleCommitConfirm(g *gocui.Gui, v *gocui.View) error { } v.Clear() + _, _ = g.SetViewOnBottom("commitMessage") // TODO: bring into context code _ = v.SetCursor(0, 0) _ = v.SetOrigin(0, 0) - _, _ = g.SetViewOnBottom("commitMessage") - _ = gui.switchFocus(v, gui.getFilesView()) + _ = gui.returnFromContext() return gui.refreshSidePanels(refreshOptions{mode: ASYNC}) } func (gui *Gui) handleCommitClose(g *gocui.Gui, v *gocui.View) error { - _, _ = g.SetViewOnBottom("commitMessage") - return gui.switchFocus(v, gui.getFilesView()) + _, _ = g.SetViewOnBottom("commitMessage") // TODO: bring into context code + return gui.returnFromContext() } -func (gui *Gui) handleCommitFocused() error { +func (gui *Gui) handleCommitMessageFocused() error { if _, err := gui.g.SetViewOnTop("commitMessage"); err != nil { return err } diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index 018782a34..6d88d30f9 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -54,8 +54,6 @@ func (gui *Gui) handleCommitSelect() error { return gui.newStringTask("main", gui.Tr.SLocalize("NoCommitsThisBranch")) } - gui.getCommitsView().FocusPoint(0, gui.State.Panels.Commits.SelectedLine) - if gui.inDiffMode() { return gui.renderDiff() } @@ -104,7 +102,7 @@ func (gui *Gui) refreshCommits() error { go func() { _ = gui.refreshCommitsWithLimit() - if gui.g.CurrentView() == gui.getCommitFilesView() || (gui.g.CurrentView() == gui.getMainView() && gui.State.MainContext == "patch-building") { + if gui.g.CurrentView() == gui.getCommitFilesView() || (gui.currentContext().GetKey() == gui.Contexts.PatchBuilding.Context.GetKey()) { _ = gui.refreshCommitFilesView() } wg.Done() @@ -528,7 +526,7 @@ func (gui *Gui) handleSwitchToCommitFilesPanel() error { return err } - return gui.switchFocus(gui.getCommitsView(), gui.getCommitFilesView()) + return gui.switchContext(gui.Contexts.BranchCommits.Files.Context) } func (gui *Gui) hasCommit(commits []*commands.Commit, target string) (int, bool) { diff --git a/pkg/gui/confirmation_panel.go b/pkg/gui/confirmation_panel.go index a7e41b246..3ead76fc1 100644 --- a/pkg/gui/confirmation_panel.go +++ b/pkg/gui/confirmation_panel.go @@ -94,15 +94,13 @@ func (gui *Gui) wrappedPromptConfirmationFunction(function func(string) error, r } func (gui *Gui) closeConfirmationPrompt(returnFocusOnClose bool) error { - view, err := gui.g.View("confirmation") - if err != nil { + view := gui.getConfirmationView() + if view == nil { return nil // if it's already been closed we can just return } view.Editable = false - if returnFocusOnClose { - if err := gui.returnFocus(view); err != nil { - panic(err) - } + if err := gui.returnFromContext(); err != nil { + return err } gui.g.DeleteKeybinding("confirmation", gui.getKey("universal.confirm"), gocui.ModNone) @@ -164,7 +162,7 @@ func (gui *Gui) prepareConfirmationPanel(currentView *gocui.View, title, prompt confirmationView.FgColor = theme.GocuiDefaultTextColor } gui.g.Update(func(g *gocui.Gui) error { - return gui.switchFocus(currentView, confirmationView) + return gui.switchContext(gui.Contexts.Confirmation.Context) }) return confirmationView, nil } diff --git a/pkg/gui/context.go b/pkg/gui/context.go index 06664571f..b209156da 100644 --- a/pkg/gui/context.go +++ b/pkg/gui/context.go @@ -1,7 +1,8 @@ package gui import ( - "github.com/jesseduffield/lazygit/pkg/gui/stack" + "github.com/golang-collections/collections/stack" + "github.com/jesseduffield/gocui" ) // changeContext is a helper function for when we want to change a 'main' context @@ -22,6 +23,27 @@ func (gui *Gui) changeMainViewsContext(context string) { gui.State.MainContext = context } +type Stack struct { + stack []Context +} + +func (s *Stack) Push(contextKey Context) { + s.stack = append(s.stack, contextKey) +} + +func (s *Stack) Pop() (Context, bool) { + if len(s.stack) == 0 { + return nil, false + } + + n := len(s.stack) - 1 + value := s.stack[n] + s.stack = s.stack[:n] + + return value, true +} + +// the context manager maintains a stack of contexts so that we can easily switch focus back and forth type contextManager struct { gui *Gui stack stack.Stack @@ -33,54 +55,345 @@ func (c *contextManager) push(contextKey string) { // push focus, pop focus. +const ( + SIDE_CONTEXT int = iota + MAIN_CONTEXT + TEMPORARY_POPUP + PERSISTENT_POPUP +) + +func GetKindWrapper(k int) func() int { return func() int { return k } } + type Context interface { - OnFocus() error + HandleFocus() error + HandleFocusLost() error + GetKind() int + GetViewName() string + GetKey() string } -type SimpleContext struct { - Self Context +type BasicContext struct { + OnFocus func() error + OnFocusLost func() error + Kind int + Key string + ViewName string } -type RemotesContext struct { - Self Context - Branches Context +func (c BasicContext) GetViewName() string { + return c.ViewName } -type CommitsContext struct { - Self Context - Files Context +func (c BasicContext) HandleFocus() error { + return c.OnFocus() +} + +func (c BasicContext) HandleFocusLost() error { + if c.OnFocusLost != nil { + return c.OnFocusLost() + } + return nil +} + +func (c BasicContext) GetKind() int { + return c.Kind +} + +func (c BasicContext) GetKey() string { + return c.Key +} + +type SimpleContextNode struct { + Context Context +} + +type RemotesContextNode struct { + Context Context + Branches SimpleContextNode +} + +type CommitsContextNode struct { + Context Context + Files SimpleContextNode } type ContextTree struct { - Status SimpleContext - Files SimpleContext - Branches SimpleContext - Remotes RemotesContext - Tags SimpleContext - Commits CommitsContext - Stash SimpleContext - Staging SimpleContext - PatchBuilding SimpleContext - Merging SimpleContext - Menu SimpleContext - Credentials SimpleContext - Confirmation SimpleContext - CommitMessage SimpleContext + Status SimpleContextNode + Files SimpleContextNode + Menu SimpleContextNode + Branches SimpleContextNode + Remotes RemotesContextNode + Tags SimpleContextNode + BranchCommits CommitsContextNode + ReflogCommits SimpleContextNode + Stash SimpleContextNode + Staging SimpleContextNode + PatchBuilding SimpleContextNode + Merging SimpleContextNode + Credentials SimpleContextNode + Confirmation SimpleContextNode + CommitMessage SimpleContextNode + Search SimpleContextNode +} + +func (gui *Gui) switchContext(c Context) error { + // push onto stack + // if we are switching to a side context, remove all other contexts in the stack + if c.GetKind() == SIDE_CONTEXT { + gui.State.ContextStack = []Context{c} + } else { + // TODO: think about other exceptional cases + gui.State.ContextStack = append(gui.State.ContextStack, c) + } + + return gui.activateContext(c) +} + +// 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) renderContextStack() string { + result := "" + for _, context := range gui.State.ContextStack { + result += context.GetViewName() + "\n" + } + return result +} + +func (gui *Gui) activateContext(c Context) error { + gui.Log.Warn(gui.renderContextStack()) + + if _, err := gui.g.SetCurrentView(c.GetViewName()); err != nil { + return err + } + + if _, err := gui.g.SetViewOnTop(c.GetViewName()); err != nil { + return err + } + + newView := gui.g.CurrentView() + + gui.g.Cursor = newView.Editable + + // TODO: move this logic to the context + if err := gui.renderPanelOptions(); err != nil { + return err + } + + // return gui.newLineFocused(newView) + + if err := c.HandleFocus(); err != nil { + return err + } + + gui.State.ViewContextMap[c.GetViewName()] = c + + return nil +} + +func (gui *Gui) returnFromContext() 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 := currentContext.HandleFocusLost(); err != nil { + return err + } + + return gui.activateContext(newContext) +} + +func (gui *Gui) currentContext() Context { + return gui.State.ContextStack[len(gui.State.ContextStack)-1] } func (gui *Gui) createContextTree() { - gui.State.Contexts = ContextTree{ - Files: SimpleContext{ - Self: gui.filesListView(), + gui.Contexts = ContextTree{ + Status: SimpleContextNode{ + Context: BasicContext{ + OnFocus: gui.handleStatusSelect, + Kind: SIDE_CONTEXT, + ViewName: "status", + }, }, + Files: SimpleContextNode{ + Context: gui.filesListView(), + }, + Menu: SimpleContextNode{ + Context: gui.menuListView(), + }, + Remotes: RemotesContextNode{ + Context: gui.remotesListView(), + Branches: SimpleContextNode{ + Context: gui.remoteBranchesListView(), + }, + }, + BranchCommits: CommitsContextNode{ + Context: gui.branchCommitsListView(), + Files: SimpleContextNode{ + Context: gui.commitFilesListView(), + }, + }, + ReflogCommits: SimpleContextNode{ + Context: gui.reflogCommitsListView(), + }, + Branches: SimpleContextNode{ + Context: gui.branchesListView(), + }, + Tags: SimpleContextNode{ + Context: gui.tagsListView(), + }, + Stash: SimpleContextNode{ + Context: gui.stashListView(), + }, + Staging: SimpleContextNode{ + Context: BasicContext{ + // TODO: think about different situations where this arises + OnFocus: func() error { + return gui.refreshStagingPanel(false, -1) + }, + Kind: MAIN_CONTEXT, + ViewName: "main", + Key: "staging", + }, + }, + PatchBuilding: SimpleContextNode{ + Context: BasicContext{ + // TODO: think about different situations where this arises + OnFocus: func() error { + return gui.refreshPatchBuildingPanel(-1) + }, + Kind: MAIN_CONTEXT, + ViewName: "main", + Key: "patch-building", + }, + }, + Merging: SimpleContextNode{ + Context: BasicContext{ + // TODO: think about different situations where this arises + OnFocus: func() error { + return gui.refreshMergePanel() + }, + Kind: MAIN_CONTEXT, + ViewName: "main", + Key: "merging", + }, + }, + Credentials: SimpleContextNode{ + Context: BasicContext{ + OnFocus: func() error { return gui.handleCredentialsViewFocused() }, + Kind: PERSISTENT_POPUP, + ViewName: "credentials", + Key: "credentials", + }, + }, + Confirmation: SimpleContextNode{ + Context: BasicContext{ + OnFocus: func() error { return nil }, + Kind: TEMPORARY_POPUP, + ViewName: "confirmation", + Key: "confirmation", + }, + }, + CommitMessage: SimpleContextNode{ + Context: BasicContext{ + OnFocus: func() error { return gui.handleCommitMessageFocused() }, + Kind: PERSISTENT_POPUP, + ViewName: "commitMessage", + Key: "commit-message", + }, + }, + Search: SimpleContextNode{ + Context: BasicContext{ + OnFocus: func() error { return nil }, + Kind: PERSISTENT_POPUP, + ViewName: "search", + Key: "search", + }, + }, + } + + gui.State.ViewContextMap = map[string]Context{ + "status": gui.Contexts.Status.Context, + "files": gui.Contexts.Files.Context, + "branches": gui.Contexts.Branches.Context, + "commits": gui.Contexts.BranchCommits.Context, + "stash": gui.Contexts.Stash.Context, + "menu": gui.Contexts.Menu.Context, + "confirmation": gui.Contexts.Confirmation.Context, + "credentials": gui.Contexts.Credentials.Context, + "commitMessage": gui.Contexts.CommitMessage.Context, + "main": gui.Contexts.Staging.Context, } } -// func (c *contextManager) pop() (string, bool) { -// value, ok := c.stack.Pop() +// getFocusLayout returns a manager function for when view gain and lose focus +func (gui *Gui) getFocusLayout() func(g *gocui.Gui) error { + var previousView *gocui.View + return func(g *gocui.Gui) error { + newView := gui.g.CurrentView() + if err := gui.onFocusChange(); err != nil { + return err + } + // for now we don't consider losing focus to a popup panel as actually losing focus + if newView != previousView && !gui.isPopupPanel(newView.Name()) { + if err := gui.onViewFocusLost(previousView, newView); err != nil { + return err + } -// if !ok { -// // bottom of the stack, let's go to the default context: the files context -// c.gui.switchFocus(nil, newView) -// } -// } + if err := gui.onViewFocus(newView); err != nil { + return err + } + previousView = newView + } + return nil + } +} + +func (gui *Gui) onFocusChange() error { + currentView := gui.g.CurrentView() + for _, view := range gui.g.Views() { + view.Highlight = view.Name() != "main" && view == currentView + } + return nil +} + +func (gui *Gui) onViewFocusLost(v *gocui.View, newView *gocui.View) error { + if v == nil { + return nil + } + + if v.IsSearching() && newView.Name() != "search" { + if err := gui.onSearchEscape(); err != nil { + return err + } + } + + if v.Name() == "main" { + // if we have lost focus to a first-class panel, we need to do some cleanup + gui.changeMainViewsContext("normal") + } + + gui.Log.Info(v.Name() + " focus lost") + return nil +} + +func (gui *Gui) onViewFocus(newView *gocui.View) error { + gui.setViewAsActiveForWindow(newView.Name()) + + return nil +} diff --git a/pkg/gui/credentials_panel.go b/pkg/gui/credentials_panel.go index ede5c865c..d1eed5b25 100644 --- a/pkg/gui/credentials_panel.go +++ b/pkg/gui/credentials_panel.go @@ -20,10 +20,11 @@ func (gui *Gui) promptUserForCredential(passOrUname string) string { credentialsView.Title = gui.Tr.SLocalize("CredentialsPassword") credentialsView.Mask = '*' } - err := gui.switchFocus(gui.g.CurrentView(), credentialsView) - if err != nil { + + if err := gui.switchContext(gui.Contexts.Credentials.Context); err != nil { return err } + gui.RenderCommitLength() return nil }) @@ -38,15 +39,11 @@ func (gui *Gui) handleSubmitCredential(g *gocui.Gui, v *gocui.View) error { gui.credentials <- message v.Clear() _ = v.SetCursor(0, 0) - _, _ = g.SetViewOnBottom("credentials") - nextView, err := gui.g.View("confirmation") - if err != nil { - nextView = gui.getFilesView() - } - err = gui.switchFocus(nil, nextView) - if err != nil { + _, _ = g.SetViewOnBottom("credentials") // TODO: move to context code + if err := gui.returnFromContext(); err != nil { return err } + return gui.refreshSidePanels(refreshOptions{}) } @@ -57,7 +54,7 @@ func (gui *Gui) handleCloseCredentialsView(g *gocui.Gui, v *gocui.View) error { } gui.credentials <- "" - return gui.switchFocus(nil, gui.getFilesView()) + return gui.returnFromContext() } func (gui *Gui) handleCredentialsViewFocused() error { diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 37918067d..a2e51d7c8 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -171,11 +171,10 @@ func (gui *Gui) enterFile(forceSecondaryFocused bool, selectedLineIdx int) error if file.HasMergeConflicts { return gui.createErrorPanel(gui.Tr.SLocalize("FileStagingRequirements")) } - gui.changeMainViewsContext("staging") - if err := gui.switchFocus(gui.getFilesView(), gui.getMainView()); err != nil { - return err - } - return gui.refreshStagingPanel(forceSecondaryFocused, selectedLineIdx) + gui.changeMainViewsContext("staging") // TODO: move into context code + gui.switchContext(gui.Contexts.Staging.Context) + + return gui.refreshStagingPanel(forceSecondaryFocused, selectedLineIdx) // TODO: check if this is broken, try moving into context code } func (gui *Gui) handleFilePress() error { @@ -310,11 +309,7 @@ func (gui *Gui) handleCommitPress() error { } gui.g.Update(func(g *gocui.Gui) error { - if _, err := g.SetViewOnTop("commitMessage"); err != nil { - return err - } - - if err := gui.switchFocus(gui.getFilesView(), commitMessageView); err != nil { + if err := gui.switchContext(gui.Contexts.CommitMessage.Context); err != nil { return err } @@ -596,11 +591,8 @@ func (gui *Gui) handleSwitchToMerge() error { if !file.HasInlineMergeConflicts { return gui.createErrorPanel(gui.Tr.SLocalize("FileNoMergeCons")) } - gui.changeMainViewsContext("merging") - if err := gui.switchFocus(gui.g.CurrentView(), gui.getMainView()); err != nil { - return err - } - return gui.refreshMergePanel() + gui.changeMainViewsContext("merging") // TODO: move into context code + return gui.switchContext(gui.Contexts.Merging.Context) } func (gui *Gui) openFile(filename string) error { diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 5530032d9..0ddf4b2ce 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -97,6 +97,7 @@ type Gui struct { // when lazygit is opened outside a git directory we want to open to the most // recent repo with the recent repos popup showing showRecentRepos bool + Contexts ContextTree } // for now the staging panel state, unlike the other panel states, is going to be @@ -237,7 +238,15 @@ type guiState struct { FilterPath string // the filename that gets passed to git log Diff DiffState - Contexts ContextTree + ContextStack []Context + ViewContextMap map[string]Context + + // WindowViewNameMap is a mapping of windows to the current view of that window. + // Currently the only case where the distinction between a window and a view + // matters is with the commits view and the commitFiles view which both appear + // in the same place (and thus constitute the 'commits' window). + // If a window contains only one view, it shares the same name as the view. + WindowViewNameMap map[string]string } func (gui *Gui) resetState() { diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index 102cc62a6..a1d51d356 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -1393,17 +1393,17 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { for _, viewName := range []string{"status", "branches", "files", "commits", "commitFiles", "stash", "menu"} { bindings = append(bindings, []*Binding{ - {ViewName: viewName, Key: gui.getKey("universal.togglePanel"), Modifier: gocui.ModNone, Handler: gui.nextView}, - {ViewName: viewName, Key: gui.getKey("universal.prevBlock"), Modifier: gocui.ModNone, Handler: gui.previousView}, - {ViewName: viewName, Key: gui.getKey("universal.nextBlock"), Modifier: gocui.ModNone, Handler: gui.nextView}, - {ViewName: viewName, Key: gui.getKey("universal.prevBlock-alt"), Modifier: gocui.ModNone, Handler: gui.previousView}, - {ViewName: viewName, Key: gui.getKey("universal.nextBlock-alt"), Modifier: gocui.ModNone, Handler: gui.nextView}, + {ViewName: viewName, Key: gui.getKey("universal.togglePanel"), Modifier: gocui.ModNone, Handler: gui.wrappedHandler(gui.nextSideWindow)}, + {ViewName: viewName, Key: gui.getKey("universal.prevBlock"), Modifier: gocui.ModNone, Handler: gui.wrappedHandler(gui.previousSideWindow)}, + {ViewName: viewName, Key: gui.getKey("universal.nextBlock"), Modifier: gocui.ModNone, Handler: gui.wrappedHandler(gui.nextSideWindow)}, + {ViewName: viewName, Key: gui.getKey("universal.prevBlock-alt"), Modifier: gocui.ModNone, Handler: gui.wrappedHandler(gui.previousSideWindow)}, + {ViewName: viewName, Key: gui.getKey("universal.nextBlock-alt"), Modifier: gocui.ModNone, Handler: gui.wrappedHandler(gui.nextSideWindow)}, }...) } // Appends keybindings to jump to a particular sideView using numbers - for i, viewName := range []string{"status", "files", "branches", "commits", "stash"} { - bindings = append(bindings, &Binding{ViewName: "", Key: rune(i+1) + '0', Modifier: gocui.ModNone, Handler: gui.goToSideView(viewName)}) + for i, window := range []string{"status", "files", "branches", "commits", "stash"} { + bindings = append(bindings, &Binding{ViewName: "", Key: rune(i+1) + '0', Modifier: gocui.ModNone, Handler: gui.goToSideWindow(window)}) } for _, listView := range gui.getListViews() { diff --git a/pkg/gui/layout.go b/pkg/gui/layout.go index 7e7772d88..66f2eca5e 100644 --- a/pkg/gui/layout.go +++ b/pkg/gui/layout.go @@ -12,68 +12,6 @@ import ( const SEARCH_PREFIX = "search: " const INFO_SECTION_PADDING = " " -// getFocusLayout returns a manager function for when view gain and lose focus -func (gui *Gui) getFocusLayout() func(g *gocui.Gui) error { - var previousView *gocui.View - return func(g *gocui.Gui) error { - newView := gui.g.CurrentView() - if err := gui.onFocusChange(); err != nil { - return err - } - // for now we don't consider losing focus to a popup panel as actually losing focus - if newView != previousView && !gui.isPopupPanel(newView.Name()) { - if err := gui.onFocusLost(previousView, newView); err != nil { - return err - } - if err := gui.onFocus(newView); err != nil { - return err - } - previousView = newView - } - return nil - } -} - -func (gui *Gui) onFocusChange() error { - currentView := gui.g.CurrentView() - for _, view := range gui.g.Views() { - view.Highlight = view == currentView - } - return nil -} - -func (gui *Gui) onFocusLost(v *gocui.View, newView *gocui.View) error { - if v == nil { - return nil - } - if v.IsSearching() && newView.Name() != "search" { - if err := gui.onSearchEscape(); err != nil { - return err - } - } - switch v.Name() { - case "main": - // if we have lost focus to a first-class panel, we need to do some cleanup - gui.changeMainViewsContext("normal") - case "commitFiles": - if gui.State.MainContext != "patch-building" { - if _, err := gui.g.SetViewOnBottom(v.Name()); err != nil { - return err - } - } - } - gui.Log.Info(v.Name() + " focus lost") - return nil -} - -func (gui *Gui) onFocus(v *gocui.View) error { - if v == nil { - return nil - } - gui.Log.Info(v.Name() + " focus gained") - return nil -} - func (gui *Gui) informationStr() string { if gui.inDiffMode() { return utils.ColoredString(fmt.Sprintf("%s %s %s", gui.Tr.SLocalize("showingGitDiff"), "git diff "+gui.diffStr(), utils.ColoredString(gui.Tr.SLocalize("(reset)"), color.Underline)), color.FgMagenta) @@ -329,15 +267,12 @@ func (gui *Gui) layout(g *gocui.Gui) error { } if gui.g.CurrentView() == nil { - initialView := gui.getFilesView() + initialContext := gui.Contexts.Files.Context if gui.inFilterMode() { - initialView = gui.getCommitsView() - } - if _, err := gui.g.SetCurrentView(initialView.Name()); err != nil { - return err + initialContext = gui.Contexts.BranchCommits.Context } - if err := gui.switchFocus(nil, initialView); err != nil { + if err := gui.switchContext(initialContext); err != nil { return err } } @@ -347,7 +282,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { lineCount int view *gocui.View context string - listView *listView + listView *ListView } listViewStates := []listViewState{ @@ -397,6 +332,10 @@ func (gui *Gui) layout(g *gocui.Gui) error { } func (gui *Gui) onInitialViewsCreation() error { + gui.createContextTree() + + gui.switchContext(gui.Contexts.Files.Context) + gui.changeMainViewsContext("normal") gui.getBranchesView().Context = "local-branches" diff --git a/pkg/gui/line_by_line_panel.go b/pkg/gui/line_by_line_panel.go index 0c4c5651c..b090103cb 100644 --- a/pkg/gui/line_by_line_panel.go +++ b/pkg/gui/line_by_line_panel.go @@ -234,7 +234,7 @@ func (gui *Gui) refreshMainView() error { var includedLineIndices []int // I'd prefer not to have knowledge of contexts using this file but I'm not sure // how to get around this - if gui.State.MainContext == "patch-building" { + if gui.currentContext().GetKey() == gui.Contexts.PatchBuilding.Context.GetKey() { filename := gui.getSelectedCommitFileName() includedLineIndices = gui.GitCommand.PatchManager.GetFileIncLineIndices(filename) } diff --git a/pkg/gui/list_view.go b/pkg/gui/list_view.go index b11c5bf02..715676ca6 100644 --- a/pkg/gui/list_view.go +++ b/pkg/gui/list_view.go @@ -2,27 +2,54 @@ package gui import "github.com/jesseduffield/gocui" -type listView struct { +type ListView struct { ViewName string Context string GetItemsLength func() int GetSelectedLineIdxPtr func() *int OnFocus func() error + OnFocusLost func() error OnItemSelect func() error OnClickSelectedItem func() error Gui *Gui RendersToMainView bool + Kind int + Key string } -func (lv *listView) handlePrevLine(g *gocui.Gui, v *gocui.View) error { +func (lv *ListView) GetKey() string { + return lv.Key +} + +func (lv *ListView) GetKind() int { + return lv.Kind +} + +func (lv *ListView) GetViewName() string { + return lv.ViewName +} + +func (lv *ListView) HandleFocusLost() error { + if lv.OnFocusLost != nil { + return lv.OnFocusLost() + } + + return nil +} + +func (lv *ListView) HandleFocus() error { + return lv.OnFocus() +} + +func (lv *ListView) handlePrevLine(g *gocui.Gui, v *gocui.View) error { return lv.handleLineChange(-1) } -func (lv *listView) handleNextLine(g *gocui.Gui, v *gocui.View) error { +func (lv *ListView) handleNextLine(g *gocui.Gui, v *gocui.View) error { return lv.handleLineChange(1) } -func (lv *listView) handleLineChange(change int) error { +func (lv *ListView) handleLineChange(change int) error { if !lv.Gui.isPopupPanel(lv.ViewName) && lv.Gui.popupPanelFocused() { return nil } @@ -50,7 +77,7 @@ func (lv *listView) handleLineChange(change int) error { return nil } -func (lv *listView) handleNextPage(g *gocui.Gui, v *gocui.View) error { +func (lv *ListView) handleNextPage(g *gocui.Gui, v *gocui.View) error { view, err := lv.Gui.g.View(lv.ViewName) if err != nil { return nil @@ -63,15 +90,15 @@ func (lv *listView) handleNextPage(g *gocui.Gui, v *gocui.View) error { return lv.handleLineChange(delta) } -func (lv *listView) handleGotoTop(g *gocui.Gui, v *gocui.View) error { +func (lv *ListView) handleGotoTop(g *gocui.Gui, v *gocui.View) error { return lv.handleLineChange(-lv.GetItemsLength()) } -func (lv *listView) handleGotoBottom(g *gocui.Gui, v *gocui.View) error { +func (lv *ListView) handleGotoBottom(g *gocui.Gui, v *gocui.View) error { return lv.handleLineChange(lv.GetItemsLength()) } -func (lv *listView) handlePrevPage(g *gocui.Gui, v *gocui.View) error { +func (lv *ListView) handlePrevPage(g *gocui.Gui, v *gocui.View) error { view, err := lv.Gui.g.View(lv.ViewName) if err != nil { return nil @@ -84,7 +111,7 @@ func (lv *listView) handlePrevPage(g *gocui.Gui, v *gocui.View) error { return lv.handleLineChange(-delta) } -func (lv *listView) handleClick(g *gocui.Gui, v *gocui.View) error { +func (lv *ListView) handleClick(g *gocui.Gui, v *gocui.View) error { if !lv.Gui.isPopupPanel(lv.ViewName) && lv.Gui.popupPanelFocused() { return nil } @@ -94,12 +121,12 @@ func (lv *listView) handleClick(g *gocui.Gui, v *gocui.View) error { newSelectedLineIdx := v.SelectedLineIdx() // we need to focus the view - if err := lv.Gui.switchFocus(nil, v); err != nil { + if err := lv.Gui.switchContext(lv); err != nil { return err } if newSelectedLineIdx > lv.GetItemsLength()-1 { - return lv.OnFocus() + return nil } *selectedLineIdxPtr = newSelectedLineIdx @@ -114,7 +141,7 @@ func (lv *listView) handleClick(g *gocui.Gui, v *gocui.View) error { return nil } -func (lv *listView) onSearchSelect(selectedLineIdx int) error { +func (lv *ListView) onSearchSelect(selectedLineIdx int) error { *lv.GetSelectedLineIdxPtr() = selectedLineIdx if lv.OnItemSelect != nil { return lv.OnItemSelect() @@ -122,8 +149,8 @@ func (lv *listView) onSearchSelect(selectedLineIdx int) error { return nil } -func (gui *Gui) menuListView() *listView { - return &listView{ +func (gui *Gui) menuListView() *ListView { + return &ListView{ ViewName: "menu", GetItemsLength: func() int { return gui.getMenuView().LinesHeight() }, GetSelectedLineIdxPtr: func() *int { return &gui.State.Panels.Menu.SelectedLine }, @@ -133,11 +160,13 @@ func (gui *Gui) menuListView() *listView { OnClickSelectedItem: func() error { return gui.State.Panels.Menu.OnPress(gui.g, nil) }, Gui: gui, RendersToMainView: false, + Kind: PERSISTENT_POPUP, + Key: "menu", } } -func (gui *Gui) filesListView() *listView { - return &listView{ +func (gui *Gui) filesListView() *ListView { + return &ListView{ ViewName: "files", GetItemsLength: func() int { return len(gui.State.Files) }, GetSelectedLineIdxPtr: func() *int { return &gui.State.Panels.Files.SelectedLine }, @@ -146,11 +175,13 @@ func (gui *Gui) filesListView() *listView { OnClickSelectedItem: gui.handleFilePress, Gui: gui, RendersToMainView: false, + Kind: SIDE_CONTEXT, + Key: "files", } } -func (gui *Gui) branchesListView() *listView { - return &listView{ +func (gui *Gui) branchesListView() *ListView { + return &ListView{ ViewName: "branches", Context: "local-branches", GetItemsLength: func() int { return len(gui.State.Branches) }, @@ -159,11 +190,13 @@ func (gui *Gui) branchesListView() *listView { OnItemSelect: gui.handleBranchSelect, Gui: gui, RendersToMainView: true, + Kind: SIDE_CONTEXT, + Key: "menu", } } -func (gui *Gui) remotesListView() *listView { - return &listView{ +func (gui *Gui) remotesListView() *ListView { + return &ListView{ ViewName: "branches", Context: "remotes", GetItemsLength: func() int { return len(gui.State.Remotes) }, @@ -173,11 +206,12 @@ func (gui *Gui) remotesListView() *listView { OnClickSelectedItem: gui.handleRemoteEnter, Gui: gui, RendersToMainView: true, + Kind: SIDE_CONTEXT, } } -func (gui *Gui) remoteBranchesListView() *listView { - return &listView{ +func (gui *Gui) remoteBranchesListView() *ListView { + return &ListView{ ViewName: "branches", Context: "remote-branches", GetItemsLength: func() int { return len(gui.State.RemoteBranches) }, @@ -186,11 +220,12 @@ func (gui *Gui) remoteBranchesListView() *listView { OnItemSelect: gui.handleRemoteBranchSelect, Gui: gui, RendersToMainView: true, + Kind: SIDE_CONTEXT, } } -func (gui *Gui) tagsListView() *listView { - return &listView{ +func (gui *Gui) tagsListView() *ListView { + return &ListView{ ViewName: "branches", Context: "tags", GetItemsLength: func() int { return len(gui.State.Tags) }, @@ -199,11 +234,12 @@ func (gui *Gui) tagsListView() *listView { OnItemSelect: gui.handleTagSelect, Gui: gui, RendersToMainView: true, + Kind: SIDE_CONTEXT, } } -func (gui *Gui) branchCommitsListView() *listView { - return &listView{ +func (gui *Gui) branchCommitsListView() *ListView { + return &ListView{ ViewName: "commits", Context: "branch-commits", GetItemsLength: func() int { return len(gui.State.Commits) }, @@ -213,11 +249,12 @@ func (gui *Gui) branchCommitsListView() *listView { OnClickSelectedItem: gui.handleSwitchToCommitFilesPanel, Gui: gui, RendersToMainView: true, + Kind: SIDE_CONTEXT, } } -func (gui *Gui) reflogCommitsListView() *listView { - return &listView{ +func (gui *Gui) reflogCommitsListView() *ListView { + return &ListView{ ViewName: "commits", Context: "reflog-commits", GetItemsLength: func() int { return len(gui.State.FilteredReflogCommits) }, @@ -226,11 +263,12 @@ func (gui *Gui) reflogCommitsListView() *listView { OnItemSelect: gui.handleReflogCommitSelect, Gui: gui, RendersToMainView: true, + Kind: SIDE_CONTEXT, } } -func (gui *Gui) stashListView() *listView { - return &listView{ +func (gui *Gui) stashListView() *ListView { + return &ListView{ ViewName: "stash", GetItemsLength: func() int { return len(gui.State.StashEntries) }, GetSelectedLineIdxPtr: func() *int { return &gui.State.Panels.Stash.SelectedLine }, @@ -238,11 +276,12 @@ func (gui *Gui) stashListView() *listView { OnItemSelect: gui.handleStashEntrySelect, Gui: gui, RendersToMainView: true, + Kind: SIDE_CONTEXT, } } -func (gui *Gui) commitFilesListView() *listView { - return &listView{ +func (gui *Gui) commitFilesListView() *ListView { + return &ListView{ ViewName: "commitFiles", GetItemsLength: func() int { return len(gui.State.CommitFiles) }, GetSelectedLineIdxPtr: func() *int { return &gui.State.Panels.CommitFiles.SelectedLine }, @@ -250,11 +289,12 @@ func (gui *Gui) commitFilesListView() *listView { OnItemSelect: gui.handleCommitFileSelect, Gui: gui, RendersToMainView: true, + Kind: SIDE_CONTEXT, } } -func (gui *Gui) getListViews() []*listView { - return []*listView{ +func (gui *Gui) getListViews() []*ListView { + return []*ListView{ gui.menuListView(), gui.filesListView(), gui.branchesListView(), diff --git a/pkg/gui/menu_panel.go b/pkg/gui/menu_panel.go index 07c786ec3..4ca136215 100644 --- a/pkg/gui/menu_panel.go +++ b/pkg/gui/menu_panel.go @@ -17,7 +17,6 @@ type menuItem struct { // list panel functions func (gui *Gui) handleMenuSelect() error { - gui.getMenuView().FocusPoint(0, gui.State.Panels.Menu.SelectedLine) return nil } @@ -46,7 +45,7 @@ func (gui *Gui) handleMenuClose(g *gocui.Gui, v *gocui.View) error { if err != nil { return err } - return gui.returnFocus(v) + return gui.returnFromContext() } type createMenuOptions struct { @@ -84,8 +83,6 @@ func (gui *Gui) createMenu(title string, items []*menuItem, createMenuOptions cr menuView.ContainsList = true menuView.Clear() menuView.SetOnSelectItem(gui.onSelectItemWrapper(func(selectedLine int) error { - gui.State.Panels.Menu.SelectedLine = selectedLine - menuView.FocusPoint(0, selectedLine) return nil })) fmt.Fprint(menuView, list) @@ -103,7 +100,7 @@ func (gui *Gui) createMenu(title string, items []*menuItem, createMenuOptions cr } } - return gui.returnFocus(menuView) + return gui.returnFromContext() } gui.State.Panels.Menu.OnPress = wrappedHandlePress @@ -117,13 +114,7 @@ func (gui *Gui) createMenu(title string, items []*menuItem, createMenuOptions cr } gui.g.Update(func(g *gocui.Gui) error { - if _, err := gui.g.View("menu"); err == nil { - if _, err := g.SetViewOnTop("menu"); err != nil { - return err - } - } - currentView := gui.g.CurrentView() - return gui.switchFocus(currentView, menuView) + return gui.switchContext(gui.Contexts.Menu.Context) }) return nil } diff --git a/pkg/gui/merge_panel.go b/pkg/gui/merge_panel.go index f7c1984b2..bbb58c5ff 100644 --- a/pkg/gui/merge_panel.go +++ b/pkg/gui/merge_panel.go @@ -308,7 +308,7 @@ func (gui *Gui) handleEscapeMerge() error { // it's possible this method won't be called from the merging view so we need to // ensure we only 'return' focus if we already have it if gui.g.CurrentView() == gui.getMainView() { - return gui.switchFocus(gui.getMainView(), gui.getFilesView()) + return gui.switchContext(gui.Contexts.Files.Context) } return nil } diff --git a/pkg/gui/patch_building_panel.go b/pkg/gui/patch_building_panel.go index 4d7e5de69..6bd8949d2 100644 --- a/pkg/gui/patch_building_panel.go +++ b/pkg/gui/patch_building_panel.go @@ -83,7 +83,7 @@ func (gui *Gui) handleEscapePatchBuildingPanel() error { gui.State.SplitMainPanel = false } - return gui.switchFocus(nil, gui.getCommitFilesView()) + return gui.switchContext(gui.Contexts.BranchCommits.Files.Context) } func (gui *Gui) refreshSecondaryPatchPanel() error { diff --git a/pkg/gui/reflog_panel.go b/pkg/gui/reflog_panel.go index 66be53e55..4362aec98 100644 --- a/pkg/gui/reflog_panel.go +++ b/pkg/gui/reflog_panel.go @@ -35,8 +35,6 @@ func (gui *Gui) handleReflogCommitSelect() error { if commit == nil { return gui.newStringTask("main", "No reflog history") } - gui.getCommitsView().FocusPoint(0, gui.State.Panels.ReflogCommits.SelectedLine) - if gui.inDiffMode() { return gui.renderDiff() } diff --git a/pkg/gui/remote_branches_panel.go b/pkg/gui/remote_branches_panel.go index 2c9ae6b9b..bf0287e90 100644 --- a/pkg/gui/remote_branches_panel.go +++ b/pkg/gui/remote_branches_panel.go @@ -37,8 +37,6 @@ func (gui *Gui) handleRemoteBranchSelect() error { return gui.newStringTask("main", "No branches for this remote") } - gui.getBranchesView().FocusPoint(0, gui.State.Panels.RemoteBranches.SelectedLine) - if gui.inDiffMode() { return gui.renderDiff() } diff --git a/pkg/gui/remotes_panel.go b/pkg/gui/remotes_panel.go index 7c5274c02..d7fa250bd 100644 --- a/pkg/gui/remotes_panel.go +++ b/pkg/gui/remotes_panel.go @@ -39,8 +39,6 @@ func (gui *Gui) handleRemoteSelect() error { if remote == nil { return gui.newStringTask("main", "No remotes") } - gui.getBranchesView().FocusPoint(0, gui.State.Panels.Remotes.SelectedLine) - if gui.inDiffMode() { return gui.renderDiff() } diff --git a/pkg/gui/searching.go b/pkg/gui/searching.go index 7ced4292f..261815824 100644 --- a/pkg/gui/searching.go +++ b/pkg/gui/searching.go @@ -12,7 +12,7 @@ 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.switchFocus(v, gui.getSearchView()); err != nil { + if err := gui.switchContext(gui.Contexts.Search.Context); err != nil { return err } @@ -21,7 +21,7 @@ func (gui *Gui) handleOpenSearch(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleSearch(g *gocui.Gui, v *gocui.View) error { gui.State.Searching.searchString = gui.getSearchView().Buffer() - if err := gui.switchFocus(nil, gui.State.Searching.view); err != nil { + if err := gui.switchContextToView(gui.State.Searching.view.Name()); err != nil { return err } @@ -84,7 +84,7 @@ func (gui *Gui) onSearchEscape() error { } func (gui *Gui) handleSearchEscape(g *gocui.Gui, v *gocui.View) error { - if err := gui.switchFocus(nil, gui.State.Searching.view); err != nil { + if err := gui.switchContextToView(gui.State.Searching.view.Name()); err != nil { return err } diff --git a/pkg/gui/side_window.go b/pkg/gui/side_window.go new file mode 100644 index 000000000..5d50984bb --- /dev/null +++ b/pkg/gui/side_window.go @@ -0,0 +1,66 @@ +package gui + +import "github.com/jesseduffield/gocui" + +func (gui *Gui) nextSideWindow() error { + windows := gui.getCyclableWindows() + currentWindow := gui.currentWindow() + var newWindow string + if currentWindow == "" || currentWindow == windows[len(windows)-1] { + newWindow = windows[0] + } else { + for i := range windows { + if currentWindow == windows[i] { + newWindow = windows[i+1] + break + } + if i == len(windows)-1 { + return nil + } + } + } + if err := gui.resetOrigin(gui.getMainView()); err != nil { + return err + } + + viewName := gui.getViewNameForWindow(newWindow) + + return gui.switchContext(gui.State.ViewContextMap[viewName]) +} + +func (gui *Gui) previousSideWindow() error { + windows := gui.getCyclableWindows() + currentWindow := gui.currentWindow() + var newWindow string + if currentWindow == "" || currentWindow == windows[0] { + newWindow = windows[len(windows)-1] + } else { + for i := range windows { + if currentWindow == windows[i] { + newWindow = windows[i-1] + break + } + if i == len(windows)-1 { + return nil + } + } + } + if err := gui.resetOrigin(gui.getMainView()); err != nil { + return err + } + + viewName := gui.getViewNameForWindow(newWindow) + + return gui.switchContext(gui.State.ViewContextMap[viewName]) +} + +func (gui *Gui) goToSideWindow(sideViewName string) func(g *gocui.Gui, v *gocui.View) error { + return func(g *gocui.Gui, v *gocui.View) error { + err := gui.closePopupPanels() + if err != nil { + gui.Log.Error(err) + return nil + } + return gui.switchContext(gui.State.ViewContextMap[sideViewName]) + } +} diff --git a/pkg/gui/stack/stack.go b/pkg/gui/stack/stack.go deleted file mode 100644 index f4805b968..000000000 --- a/pkg/gui/stack/stack.go +++ /dev/null @@ -1,21 +0,0 @@ -package stack - -type Stack struct { - stack []string -} - -func (s *Stack) Push(contextKey string) { - s.stack = append(s.stack, contextKey) -} - -func (s *Stack) Pop() (string, bool) { - if len(s.stack) == 0 { - return "", false - } - - n := len(s.stack) - 1 - value := s.stack[n] - s.stack = s.stack[:n] - - return value, true -} diff --git a/pkg/gui/staging_panel.go b/pkg/gui/staging_panel.go index 0496fed96..5fc8fe9dc 100644 --- a/pkg/gui/staging_panel.go +++ b/pkg/gui/staging_panel.go @@ -12,12 +12,12 @@ func (gui *Gui) refreshStagingPanel(forceSecondaryFocused bool, selectedLineIdx state := gui.State.Panels.LineByLine - // We need to force focus here because the confirmation panel for safely staging lines does not return focus automatically. - // This is because if we tell it to return focus it will unconditionally return it to the main panel which may not be what we want - // e.g. in the event that there's nothing left to stage. - if err := gui.switchFocus(nil, gui.getMainView()); err != nil { - return err - } + // // We need to force focus here because the confirmation panel for safely staging lines does not return focus automatically. + // // This is because if we tell it to return focus it will unconditionally return it to the main panel which may not be what we want + // // e.g. in the event that there's nothing left to stage. + // if err := gui.switchContext(nil, gui.getMainView()); err != nil { + // return err + // } file, err := gui.getSelectedFile() if err != nil { @@ -96,7 +96,7 @@ func (gui *Gui) handleTogglePanel(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleStagingEscape() error { gui.handleEscapeLineByLinePanel() - return gui.switchFocus(nil, gui.getFilesView()) + return gui.switchContext(gui.Contexts.Files.Context) } func (gui *Gui) handleToggleStagedSelection(g *gocui.Gui, v *gocui.View) error { diff --git a/pkg/gui/stash_panel.go b/pkg/gui/stash_panel.go index 7bbed65c2..771ea61de 100644 --- a/pkg/gui/stash_panel.go +++ b/pkg/gui/stash_panel.go @@ -34,8 +34,6 @@ func (gui *Gui) handleStashEntrySelect() error { if stashEntry == nil { return gui.newStringTask("main", gui.Tr.SLocalize("NoStashEntries")) } - gui.getStashView().FocusPoint(0, gui.State.Panels.Stash.SelectedLine) - if gui.inDiffMode() { return gui.renderDiff() } diff --git a/pkg/gui/tags_panel.go b/pkg/gui/tags_panel.go index a3d778a3d..e31a8f8c3 100644 --- a/pkg/gui/tags_panel.go +++ b/pkg/gui/tags_panel.go @@ -30,7 +30,6 @@ func (gui *Gui) handleTagSelect() error { if tag == nil { return gui.newStringTask("main", "No tags") } - gui.getBranchesView().FocusPoint(0, gui.State.Panels.Tags.SelectedLine) if gui.inDiffMode() { return gui.renderDiff() diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index 423cab90c..ea2ef3263 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -6,13 +6,12 @@ import ( "strings" "sync" - "github.com/go-errors/errors" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/utils" "github.com/spkg/bom" ) -func (gui *Gui) getCyclableViews() []string { +func (gui *Gui) getCyclableWindows() []string { return []string{"status", "files", "branches", "commits", "stash"} } @@ -140,157 +139,6 @@ func (gui *Gui) refreshSidePanels(options refreshOptions) error { return nil } -func (gui *Gui) nextView(g *gocui.Gui, v *gocui.View) error { - var focusedViewName string - cyclableViews := gui.getCyclableViews() - if v == nil || v.Name() == cyclableViews[len(cyclableViews)-1] { - focusedViewName = cyclableViews[0] - } else { - // if we're in the commitFiles view we'll act like we're in the commits view - viewName := v.Name() - if viewName == "commitFiles" { - viewName = "commits" - } - for i := range cyclableViews { - if viewName == cyclableViews[i] { - focusedViewName = cyclableViews[i+1] - break - } - if i == len(cyclableViews)-1 { - message := gui.Tr.TemplateLocalize( - "IssntListOfViews", - Teml{ - "name": viewName, - }, - ) - gui.Log.Info(message) - return nil - } - } - } - focusedView, err := g.View(focusedViewName) - if err != nil { - panic(err) - } - if err := gui.resetOrigin(gui.getMainView()); err != nil { - return err - } - return gui.switchFocus(v, focusedView) -} - -func (gui *Gui) previousView(g *gocui.Gui, v *gocui.View) error { - cyclableViews := gui.getCyclableViews() - var focusedViewName string - if v == nil || v.Name() == cyclableViews[0] { - focusedViewName = cyclableViews[len(cyclableViews)-1] - } else { - // if we're in the commitFiles view we'll act like we're in the commits view - viewName := v.Name() - if viewName == "commitFiles" { - viewName = "commits" - } - for i := range cyclableViews { - if viewName == cyclableViews[i] { - focusedViewName = cyclableViews[i-1] // TODO: make this work properly - break - } - if i == len(cyclableViews)-1 { - message := gui.Tr.TemplateLocalize( - "IssntListOfViews", - Teml{ - "name": viewName, - }, - ) - gui.Log.Info(message) - return nil - } - } - } - focusedView, err := g.View(focusedViewName) - if err != nil { - panic(err) - } - if err := gui.resetOrigin(gui.getMainView()); err != nil { - return err - } - return gui.switchFocus(v, focusedView) -} - -func (gui *Gui) newLineFocused(v *gocui.View) error { - switch v.Name() { - case "menu": - return gui.handleMenuSelect() - case "status": - return gui.handleStatusSelect() - case "files": - return gui.focusAndSelectFile() - case "branches": - branchesView := gui.getBranchesView() - switch branchesView.Context { - case "local-branches": - return gui.handleBranchSelect() - case "remotes": - return gui.handleRemoteSelect() - case "remote-branches": - return gui.handleRemoteBranchSelect() - case "tags": - return gui.handleTagSelect() - default: - return errors.New("unknown branches panel context: " + branchesView.Context) - } - case "commits": - return gui.handleCommitSelect() - case "commitFiles": - return gui.handleCommitFileSelect() - case "stash": - return gui.handleStashEntrySelect() - case "confirmation": - return nil - case "commitMessage": - return gui.handleCommitFocused() - case "credentials": - return gui.handleCredentialsViewFocused() - case "main": - if gui.State.MainContext == "merging" { - return gui.refreshMergePanel() - } - v.Highlight = false - return nil - case "search": - return nil - default: - panic(gui.Tr.SLocalize("NoViewMachingNewLineFocusedSwitchStatement")) - } -} - -func (gui *Gui) returnFocus(v *gocui.View) error { - previousView, err := gui.g.View(gui.State.PreviousView) - if err != nil { - // always fall back to files view if there's no 'previous' view stored - previousView, err = gui.g.View("files") - if err != nil { - gui.Log.Error(err) - } - } - return gui.switchFocus(v, previousView) -} - -func (gui *Gui) goToSideView(sideViewName string) func(g *gocui.Gui, v *gocui.View) error { - return func(g *gocui.Gui, v *gocui.View) error { - view, err := g.View(sideViewName) - if err != nil { - gui.Log.Error(err) - return nil - } - err = gui.closePopupPanels() - if err != nil { - gui.Log.Error(err) - return nil - } - return gui.switchFocus(nil, view) - } -} - func (gui *Gui) closePopupPanels() error { gui.onNewPopupPanel() err := gui.closeConfirmationPrompt(true) @@ -301,39 +149,6 @@ func (gui *Gui) closePopupPanels() error { return nil } -// 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(oldView, newView *gocui.View) 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()) { - gui.State.PreviousView = oldView.Name() - } - - gui.Log.Info("setting highlight to true for view" + newView.Name()) - message := gui.Tr.TemplateLocalize( - "newFocusedViewIs", - Teml{ - "newFocusedView": newView.Name(), - }, - ) - gui.Log.Info(message) - if _, err := gui.g.SetCurrentView(newView.Name()); err != nil { - return err - } - if _, err := gui.g.SetViewOnTop(newView.Name()); err != nil { - return err - } - - gui.g.Cursor = newView.Editable - - if err := gui.renderPanelOptions(); err != nil { - return err - } - - return gui.newLineFocused(newView) -} - func (gui *Gui) resetOrigin(v *gocui.View) error { _ = v.SetCursor(0, 0) return v.SetOrigin(0, 0) @@ -442,6 +257,11 @@ func (gui *Gui) getStatusView() *gocui.View { return v } +func (gui *Gui) getConfirmationView() *gocui.View { + v, _ := gui.g.View("confirmation") + return v +} + func (gui *Gui) trimmedContent(v *gocui.View) string { return strings.TrimSpace(v.Buffer()) } diff --git a/pkg/gui/window.go b/pkg/gui/window.go new file mode 100644 index 000000000..bec14d863 --- /dev/null +++ b/pkg/gui/window.go @@ -0,0 +1,37 @@ +package gui + +// A window refers to a place on the screen which can hold one or more views. +// A view is a box that renders content, and within a window only one view will +// appear at a time. When a view appears within a window, it occupies the whole +// space. Right now most windows are 1:1 with views, except for commitFiles which +// is a view belonging to the 'commits' window, alongside the 'commits' view. + +func (gui *Gui) getViewNameForWindow(window string) string { + viewName, ok := gui.State.WindowViewNameMap[window] + if !ok { + return window + } + + return viewName +} + +func (gui *Gui) getWindowForViewName(viewName string) string { + // should soft-code this + if viewName == "commitFiles" { + return "commits" + } + + return viewName +} + +func (gui *Gui) setViewAsActiveForWindow(viewName string) { + if gui.State.WindowViewNameMap == nil { + gui.State.WindowViewNameMap = map[string]string{} + } + + gui.State.WindowViewNameMap[gui.getWindowForViewName(viewName)] = viewName +} + +func (gui *Gui) currentWindow() string { + return gui.getWindowForViewName(gui.currentViewName()) +} diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index 446a6435b..28552f433 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -397,9 +397,6 @@ func addDutch(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "IssntListOfViews", Other: "{{.name}} is niet in de lijst van weergaves", - }, &i18n.Message{ - ID: "NoViewMachingNewLineFocusedSwitchStatement", - Other: "Er machen geen weergave met de newLineFocused switch declaratie", }, &i18n.Message{ ID: "newFocusedViewIs", Other: "nieuw gefocussed weergave is {{.newFocusedView}}", diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 71d0923c8..08fda9e7d 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -405,9 +405,6 @@ func addEnglish(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "IssntListOfViews", Other: "{{.name}} is not in the list of views", - }, &i18n.Message{ - ID: "NoViewMachingNewLineFocusedSwitchStatement", - Other: "No view matching newLineFocused switch statement", }, &i18n.Message{ ID: "newFocusedViewIs", Other: "new focused view is {{.newFocusedView}}", diff --git a/pkg/i18n/polish.go b/pkg/i18n/polish.go index 6dce69481..17ec4b2f2 100644 --- a/pkg/i18n/polish.go +++ b/pkg/i18n/polish.go @@ -320,9 +320,6 @@ func addPolish(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "IssntListOfViews", Other: "{{.name}} nie jest na liście widoków", - }, &i18n.Message{ - ID: "NoViewMachingNewLineFocusedSwitchStatement", - Other: "Brak widoku pasującego do instrukcji przełączania newLineFocused", }, &i18n.Message{ ID: "newFocusedViewIs", Other: "nowy skupiony widok to {{.newFocusedView}}",