1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-08-09 09:22:48 +03:00
This commit is contained in:
Jesse Duffield
2020-08-16 13:58:29 +10:00
parent 0ea0c48631
commit 7f89113245
30 changed files with 615 additions and 461 deletions

View File

@@ -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
}