diff options
author | Jesse Duffield <jessedduffield@gmail.com> | 2020-08-16 13:58:29 +1000 |
---|---|---|
committer | Jesse Duffield <jessedduffield@gmail.com> | 2020-08-23 14:29:18 +1000 |
commit | 7f89113245307be8a1642105014e9ce51a47210f (patch) | |
tree | 0b1237c4bdd4a465bedb7cc49c8372d0bfc46ea5 /pkg/gui/context.go | |
parent | 0ea0c486310558e26af7ad6e4fcf17f57c2b62e3 (diff) |
WIP
Diffstat (limited to 'pkg/gui/context.go')
-rw-r--r-- | pkg/gui/context.go | 381 |
1 files changed, 347 insertions, 34 deletions
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 err := gui.onViewFocus(newView); err != nil { + return err + } + previousView = newView + } + return nil + } +} -// if !ok { -// // bottom of the stack, let's go to the default context: the files context -// c.gui.switchFocus(nil, newView) -// } -// } +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 +} |