mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-06-15 00:15:32 +02:00
WIP
This commit is contained in:
@ -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
|
||||
}
|
||||
}
|
||||
if usePreviousView {
|
||||
currentCyclebleView = gui.State.PreviousView
|
||||
}
|
||||
}
|
||||
// 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 currentCyclebleView == "commitFiles" {
|
||||
if viewName == "commitFiles" {
|
||||
return "commits"
|
||||
}
|
||||
|
||||
return currentCyclebleView
|
||||
return viewName
|
||||
}
|
||||
}
|
||||
|
||||
return "files" // default
|
||||
}
|
||||
|
@ -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)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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() {
|
||||
|
@ -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() {
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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(),
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
66
pkg/gui/side_window.go
Normal file
66
pkg/gui/side_window.go
Normal file
@ -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])
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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 {
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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())
|
||||
}
|
||||
|
37
pkg/gui/window.go
Normal file
37
pkg/gui/window.go
Normal file
@ -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())
|
||||
}
|
@ -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}}",
|
||||
|
@ -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}}",
|
||||
|
@ -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}}",
|
||||
|
Reference in New Issue
Block a user