summaryrefslogtreecommitdiffstats
path: root/pkg/gui/context.go
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2020-08-16 13:58:29 +1000
committerJesse Duffield <jessedduffield@gmail.com>2020-08-23 14:29:18 +1000
commit7f89113245307be8a1642105014e9ce51a47210f (patch)
tree0b1237c4bdd4a465bedb7cc49c8372d0bfc46ea5 /pkg/gui/context.go
parent0ea0c486310558e26af7ad6e4fcf17f57c2b62e3 (diff)
WIP
Diffstat (limited to 'pkg/gui/context.go')
-rw-r--r--pkg/gui/context.go381
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
+}