mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-08-09 09:22:48 +03:00
WIP
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
Reference in New Issue
Block a user