1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2024-12-12 11:15:00 +02:00
lazygit/pkg/gui/context.go

818 lines
21 KiB
Go
Raw Normal View History

2019-02-16 12:01:17 +02:00
package gui
2020-08-16 02:05:45 +02:00
import (
"fmt"
2020-08-16 05:58:29 +02:00
"github.com/jesseduffield/gocui"
2020-08-16 02:05:45 +02:00
)
type ContextKind int
2020-08-16 05:58:29 +02:00
const (
SIDE_CONTEXT ContextKind = iota
2020-08-16 05:58:29 +02:00
MAIN_CONTEXT
TEMPORARY_POPUP
PERSISTENT_POPUP
)
2021-04-04 15:51:59 +02:00
type ContextKey string
2020-08-20 01:00:55 +02:00
const (
2021-04-04 15:51:59 +02:00
STATUS_CONTEXT_KEY ContextKey = "status"
2021-04-04 17:10:23 +02:00
FILES_CONTEXT_KEY ContextKey = "files"
LOCAL_BRANCHES_CONTEXT_KEY ContextKey = "localBranches"
REMOTES_CONTEXT_KEY ContextKey = "remotes"
REMOTE_BRANCHES_CONTEXT_KEY ContextKey = "remoteBranches"
TAGS_CONTEXT_KEY ContextKey = "tags"
BRANCH_COMMITS_CONTEXT_KEY ContextKey = "commits"
REFLOG_COMMITS_CONTEXT_KEY ContextKey = "reflogCommits"
SUB_COMMITS_CONTEXT_KEY ContextKey = "subCommits"
COMMIT_FILES_CONTEXT_KEY ContextKey = "commitFiles"
STASH_CONTEXT_KEY ContextKey = "stash"
MAIN_NORMAL_CONTEXT_KEY ContextKey = "normal"
MAIN_MERGING_CONTEXT_KEY ContextKey = "merging"
MAIN_PATCH_BUILDING_CONTEXT_KEY ContextKey = "patchBuilding"
MAIN_STAGING_CONTEXT_KEY ContextKey = "staging"
MENU_CONTEXT_KEY ContextKey = "menu"
CREDENTIALS_CONTEXT_KEY ContextKey = "credentials"
CONFIRMATION_CONTEXT_KEY ContextKey = "confirmation"
SEARCH_CONTEXT_KEY ContextKey = "search"
COMMIT_MESSAGE_CONTEXT_KEY ContextKey = "commitMessage"
SUBMODULES_CONTEXT_KEY ContextKey = "submodules"
SUGGESTIONS_CONTEXT_KEY ContextKey = "suggestions"
2020-08-20 01:00:55 +02:00
)
2021-04-04 15:51:59 +02:00
var allContextKeys = []ContextKey{
2020-09-27 01:37:22 +02:00
STATUS_CONTEXT_KEY,
FILES_CONTEXT_KEY,
LOCAL_BRANCHES_CONTEXT_KEY,
REMOTES_CONTEXT_KEY,
REMOTE_BRANCHES_CONTEXT_KEY,
TAGS_CONTEXT_KEY,
BRANCH_COMMITS_CONTEXT_KEY,
REFLOG_COMMITS_CONTEXT_KEY,
SUB_COMMITS_CONTEXT_KEY,
COMMIT_FILES_CONTEXT_KEY,
STASH_CONTEXT_KEY,
MAIN_NORMAL_CONTEXT_KEY,
MAIN_MERGING_CONTEXT_KEY,
MAIN_PATCH_BUILDING_CONTEXT_KEY,
MAIN_STAGING_CONTEXT_KEY,
MENU_CONTEXT_KEY,
CREDENTIALS_CONTEXT_KEY,
CONFIRMATION_CONTEXT_KEY,
SEARCH_CONTEXT_KEY,
COMMIT_MESSAGE_CONTEXT_KEY,
2020-09-30 00:27:23 +02:00
SUBMODULES_CONTEXT_KEY,
SUGGESTIONS_CONTEXT_KEY,
2020-09-30 00:27:23 +02:00
}
type ContextTree struct {
2021-04-03 02:32:14 +02:00
Status Context
Files *ListContext
Submodules *ListContext
Menu *ListContext
Branches *ListContext
Remotes *ListContext
RemoteBranches *ListContext
Tags *ListContext
BranchCommits *ListContext
CommitFiles *ListContext
ReflogCommits *ListContext
SubCommits *ListContext
Stash *ListContext
Suggestions *ListContext
Normal Context
Staging Context
PatchBuilding Context
Merging Context
Credentials Context
Confirmation Context
CommitMessage Context
Search Context
2020-09-30 00:27:23 +02:00
}
func (gui *Gui) allContexts() []Context {
return []Context{
2021-04-03 06:56:11 +02:00
gui.State.Contexts.Status,
gui.State.Contexts.Files,
gui.State.Contexts.Submodules,
gui.State.Contexts.Branches,
gui.State.Contexts.Remotes,
gui.State.Contexts.RemoteBranches,
gui.State.Contexts.Tags,
gui.State.Contexts.BranchCommits,
gui.State.Contexts.CommitFiles,
gui.State.Contexts.ReflogCommits,
gui.State.Contexts.Stash,
gui.State.Contexts.Menu,
gui.State.Contexts.Confirmation,
gui.State.Contexts.Credentials,
gui.State.Contexts.CommitMessage,
gui.State.Contexts.Normal,
gui.State.Contexts.Staging,
gui.State.Contexts.Merging,
gui.State.Contexts.PatchBuilding,
gui.State.Contexts.SubCommits,
gui.State.Contexts.Suggestions,
2020-09-30 00:27:23 +02:00
}
2020-09-27 01:37:22 +02:00
}
2020-08-16 02:05:45 +02:00
type Context interface {
2020-08-16 05:58:29 +02:00
HandleFocus() error
HandleFocusLost() error
2020-08-19 10:06:51 +02:00
HandleRender() error
GetKind() ContextKind
2020-08-16 05:58:29 +02:00
GetViewName() string
2020-08-21 11:53:45 +02:00
GetWindowName() string
SetWindowName(string)
2021-04-04 15:51:59 +02:00
GetKey() ContextKey
SetParentContext(Context)
// we return a bool here to tell us whether or not the returned value just wraps a nil
GetParentContext() (Context, bool)
GetOptionsMap() map[string]string
2020-08-16 02:05:45 +02:00
}
2020-08-16 05:58:29 +02:00
type BasicContext struct {
OnFocus func() error
OnFocusLost func() error
OnRender func() error
OnGetOptionsMap func() map[string]string
Kind ContextKind
2021-04-04 15:51:59 +02:00
Key ContextKey
ViewName string
}
func (c BasicContext) GetOptionsMap() map[string]string {
if c.OnGetOptionsMap != nil {
return c.OnGetOptionsMap()
}
return nil
2020-08-16 02:05:45 +02:00
}
2020-08-21 11:53:45 +02:00
func (c BasicContext) SetWindowName(windowName string) {
panic("can't set window name on basic context")
}
func (c BasicContext) GetWindowName() string {
// TODO: fix this up
return c.GetViewName()
}
func (c BasicContext) SetParentContext(Context) {
panic("can't set parent context on basic context")
}
func (c BasicContext) GetParentContext() (Context, bool) {
return nil, false
}
2020-08-19 10:06:51 +02:00
func (c BasicContext) HandleRender() error {
if c.OnRender != nil {
return c.OnRender()
}
return nil
}
2020-08-16 05:58:29 +02:00
func (c BasicContext) GetViewName() string {
return c.ViewName
2020-08-16 02:05:45 +02:00
}
2020-08-16 05:58:29 +02:00
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() ContextKind {
2020-08-16 05:58:29 +02:00
return c.Kind
}
2021-04-04 15:51:59 +02:00
func (c BasicContext) GetKey() ContextKey {
2020-08-16 05:58:29 +02:00
return c.Key
}
2020-08-19 11:07:14 +02:00
func (gui *Gui) contextTree() ContextTree {
return ContextTree{
2021-04-03 02:32:14 +02:00
Status: BasicContext{
OnFocus: gui.handleStatusSelect,
Kind: SIDE_CONTEXT,
ViewName: "status",
Key: STATUS_CONTEXT_KEY,
2020-08-22 00:49:02 +02:00
},
2021-04-03 02:32:14 +02:00
Files: gui.filesListContext(),
Submodules: gui.submodulesListContext(),
Menu: gui.menuListContext(),
Remotes: gui.remotesListContext(),
RemoteBranches: gui.remoteBranchesListContext(),
BranchCommits: gui.branchCommitsListContext(),
CommitFiles: gui.commitFilesListContext(),
ReflogCommits: gui.reflogCommitsListContext(),
SubCommits: gui.subCommitsListContext(),
Branches: gui.branchesListContext(),
Tags: gui.tagsListContext(),
Stash: gui.stashListContext(),
Normal: BasicContext{
OnFocus: func() error {
return nil // TODO: should we do something here? We should allow for scrolling the panel
2020-08-19 11:07:14 +02:00
},
2021-04-03 02:32:14 +02:00
Kind: MAIN_CONTEXT,
ViewName: "main",
Key: MAIN_NORMAL_CONTEXT_KEY,
2020-08-19 11:07:14 +02:00
},
2021-04-03 02:32:14 +02:00
Staging: BasicContext{
OnFocus: func() error {
return nil
// TODO: centralise the code here
// return gui.refreshStagingPanel(false, -1)
2020-08-19 11:07:14 +02:00
},
2021-04-03 02:32:14 +02:00
Kind: MAIN_CONTEXT,
ViewName: "main",
Key: MAIN_STAGING_CONTEXT_KEY,
2020-08-19 11:07:14 +02:00
},
2021-04-03 02:32:14 +02:00
PatchBuilding: BasicContext{
OnFocus: func() error {
return nil
// TODO: centralise the code here
// return gui.refreshPatchBuildingPanel(-1)
2020-08-19 11:07:14 +02:00
},
2021-04-03 02:32:14 +02:00
Kind: MAIN_CONTEXT,
ViewName: "main",
Key: MAIN_PATCH_BUILDING_CONTEXT_KEY,
2020-08-19 11:07:14 +02:00
},
2021-04-03 02:32:14 +02:00
Merging: BasicContext{
OnFocus: gui.refreshMergePanelWithLock,
Kind: MAIN_CONTEXT,
ViewName: "main",
Key: MAIN_MERGING_CONTEXT_KEY,
OnGetOptionsMap: gui.getMergingOptions,
2020-08-19 11:07:14 +02:00
},
2021-04-03 02:32:14 +02:00
Credentials: BasicContext{
OnFocus: gui.handleCredentialsViewFocused,
Kind: PERSISTENT_POPUP,
ViewName: "credentials",
Key: CREDENTIALS_CONTEXT_KEY,
2020-08-19 11:07:14 +02:00
},
2021-04-03 02:32:14 +02:00
Confirmation: BasicContext{
OnFocus: func() error { return nil },
Kind: TEMPORARY_POPUP,
ViewName: "confirmation",
Key: CONFIRMATION_CONTEXT_KEY,
},
2021-04-03 02:32:14 +02:00
Suggestions: gui.suggestionsListContext(),
CommitMessage: BasicContext{
OnFocus: gui.handleCommitMessageFocused,
Kind: PERSISTENT_POPUP,
ViewName: "commitMessage",
Key: COMMIT_MESSAGE_CONTEXT_KEY,
2020-08-19 11:07:14 +02:00
},
2021-04-03 02:32:14 +02:00
Search: BasicContext{
OnFocus: func() error { return nil },
Kind: PERSISTENT_POPUP,
ViewName: "search",
Key: SEARCH_CONTEXT_KEY,
2020-08-19 11:07:14 +02:00
},
}
}
2021-04-03 06:56:11 +02:00
func (tree ContextTree) initialViewContextMap() map[string]Context {
2020-08-19 11:07:14 +02:00
return map[string]Context{
2021-04-03 06:56:11 +02:00
"status": tree.Status,
"files": tree.Files,
"branches": tree.Branches,
"commits": tree.BranchCommits,
"commitFiles": tree.CommitFiles,
"stash": tree.Stash,
"menu": tree.Menu,
"confirmation": tree.Confirmation,
"credentials": tree.Credentials,
"commitMessage": tree.CommitMessage,
"main": tree.Normal,
"secondary": tree.Normal,
2020-08-19 11:07:14 +02:00
}
}
func (gui *Gui) popupViewNames() []string {
result := []string{}
for _, context := range gui.allContexts() {
if context.GetKind() == PERSISTENT_POPUP || context.GetKind() == TEMPORARY_POPUP {
result = append(result, context.GetViewName())
}
}
return result
}
2021-04-03 06:56:11 +02:00
func (tree ContextTree) initialViewTabContextMap() map[string][]tabContext {
2020-08-19 13:13:43 +02:00
return map[string][]tabContext{
"branches": {
{
tab: "Local Branches",
2021-04-03 06:56:11 +02:00
contexts: []Context{tree.Branches},
2020-08-19 13:13:43 +02:00
},
{
tab: "Remotes",
contexts: []Context{
2021-04-03 06:56:11 +02:00
tree.Remotes,
tree.RemoteBranches,
2020-08-19 13:13:43 +02:00
},
},
{
tab: "Tags",
2021-04-03 06:56:11 +02:00
contexts: []Context{tree.Tags},
2020-08-19 13:13:43 +02:00
},
},
"commits": {
{
tab: "Commits",
2021-04-03 06:56:11 +02:00
contexts: []Context{tree.BranchCommits},
2020-08-19 13:13:43 +02:00
},
{
tab: "Reflog",
contexts: []Context{
2021-04-03 06:56:11 +02:00
tree.ReflogCommits,
2020-08-19 13:13:43 +02:00
},
},
},
2020-09-30 00:27:23 +02:00
"files": {
{
tab: "Files",
2021-04-03 06:56:11 +02:00
contexts: []Context{tree.Files},
2020-09-30 00:27:23 +02:00
},
{
tab: "Submodules",
contexts: []Context{
2021-04-03 06:56:11 +02:00
tree.Submodules,
2020-09-30 00:27:23 +02:00
},
},
},
2020-08-19 13:13:43 +02:00
}
}
2021-04-04 15:51:59 +02:00
func (gui *Gui) currentContextKeyIgnoringPopups() ContextKey {
gui.State.ContextManager.Lock()
defer gui.State.ContextManager.Unlock()
stack := gui.State.ContextManager.ContextStack
2020-08-23 03:23:32 +02:00
for i := range stack {
reversedIndex := len(stack) - 1 - i
context := stack[reversedIndex]
kind := stack[reversedIndex].GetKind()
if kind != TEMPORARY_POPUP && kind != PERSISTENT_POPUP {
return context.GetKey()
}
}
return ""
}
// use replaceContext when you don't want to return to the original context upon
// hitting escape: you want to go that context's parent instead.
func (gui *Gui) replaceContext(c Context) error {
gui.g.Update(func(*gocui.Gui) error {
gui.State.ContextManager.Lock()
defer gui.State.ContextManager.Unlock()
if len(gui.State.ContextManager.ContextStack) == 0 {
gui.State.ContextManager.ContextStack = []Context{c}
} else {
// replace the last item with the given item
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack[0:len(gui.State.ContextManager.ContextStack)-1], c)
}
return gui.activateContext(c)
})
return nil
}
func (gui *Gui) pushContext(c Context) error {
gui.g.Update(func(*gocui.Gui) error {
return gui.pushContextDirect(c)
})
return nil
2020-08-16 05:58:29 +02:00
}
func (gui *Gui) pushContextDirect(c Context) error {
gui.State.ContextManager.Lock()
// push onto stack
// if we are switching to a side context, remove all other contexts in the stack
if c.GetKind() == SIDE_CONTEXT {
for _, stackContext := range gui.State.ContextManager.ContextStack {
if stackContext.GetKey() != c.GetKey() {
if err := gui.deactivateContext(stackContext); err != nil {
gui.State.ContextManager.Unlock()
return err
}
}
}
gui.State.ContextManager.ContextStack = []Context{c}
} else if len(gui.State.ContextManager.ContextStack) == 0 || gui.State.ContextManager.ContextStack[len(gui.State.ContextManager.ContextStack)-1].GetKey() != c.GetKey() {
// Do not append if the one at the end is the same context (e.g. opening a menu from a menu)
// In that case we'll just close the menu entirely when the user hits escape.
// TODO: think about other exceptional cases
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack, c)
}
gui.State.ContextManager.Unlock()
return gui.activateContext(c)
}
// asynchronous code idea: functions return an error via a channel, when done
// pushContextWithView is to be used when you don't know which context you
2020-08-16 05:58:29 +02:00
// 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) pushContextWithView(viewName string) error {
return gui.pushContext(gui.State.ViewContextMap[viewName])
2020-08-16 05:58:29 +02:00
}
2020-08-16 10:17:16 +02:00
func (gui *Gui) returnFromContext() error {
gui.g.Update(func(*gocui.Gui) error {
gui.State.ContextManager.Lock()
2020-08-16 10:17:16 +02:00
if len(gui.State.ContextManager.ContextStack) == 1 {
// cannot escape from bottommost context
gui.State.ContextManager.Unlock()
return nil
}
2020-08-16 10:17:16 +02:00
n := len(gui.State.ContextManager.ContextStack) - 1
2020-08-16 10:17:16 +02:00
currentContext := gui.State.ContextManager.ContextStack[n]
newContext := gui.State.ContextManager.ContextStack[n-1]
2020-08-16 10:17:16 +02:00
gui.State.ContextManager.ContextStack = gui.State.ContextManager.ContextStack[:n]
2020-08-16 10:17:16 +02:00
gui.State.ContextManager.Unlock()
if err := gui.deactivateContext(currentContext); err != nil {
return err
}
2020-08-16 10:17:16 +02:00
return gui.activateContext(newContext)
})
return nil
2020-08-16 05:58:29 +02:00
}
2020-08-17 13:58:30 +02:00
func (gui *Gui) deactivateContext(c Context) error {
2021-04-06 02:26:18 +02:00
view, _ := gui.g.View(c.GetViewName())
if view != nil && view.IsSearching() {
if err := gui.onSearchEscape(); err != nil {
return err
2021-04-04 15:51:59 +02:00
}
2020-08-17 13:58:30 +02:00
}
2021-04-06 02:26:18 +02:00
// if we are the kind of context that is sent to back upon deactivation, we should do that
if view != nil && c.GetKind() == TEMPORARY_POPUP || c.GetKind() == PERSISTENT_POPUP || c.GetKey() == COMMIT_FILES_CONTEXT_KEY {
view.Visible = false
}
2020-08-17 13:58:30 +02:00
if err := c.HandleFocusLost(); err != nil {
return err
}
return nil
}
2020-08-19 11:07:14 +02:00
// postRefreshUpdate is to be called on a context after the state that it depends on has been refreshed
// if the context's view is set to another context we do nothing.
// if the context's view is the current view we trigger a focus; re-selecting the current item.
func (gui *Gui) postRefreshUpdate(c Context) error {
2020-08-19 10:41:57 +02:00
v, err := gui.g.View(c.GetViewName())
if err != nil {
return nil
}
2021-04-04 15:51:59 +02:00
if ContextKey(v.Context) != c.GetKey() {
2020-08-19 11:07:14 +02:00
return nil
}
if err := c.HandleRender(); err != nil {
return err
}
if gui.currentViewName() == c.GetViewName() {
if err := c.HandleFocus(); err != nil {
2020-08-19 10:41:57 +02:00
return err
}
}
return nil
}
2020-08-16 05:58:29 +02:00
func (gui *Gui) activateContext(c Context) error {
2020-08-16 10:17:16 +02:00
viewName := c.GetViewName()
2020-08-19 10:41:57 +02:00
v, err := gui.g.View(viewName)
2020-08-16 10:17:16 +02:00
if err != nil {
return err
2020-08-16 10:17:16 +02:00
}
2021-04-04 15:51:59 +02:00
originalViewContextKey := ContextKey(v.Context)
2020-08-19 10:41:57 +02:00
2020-08-21 11:53:45 +02:00
// ensure that any other window for which this view was active is now set to the default for that window.
2021-04-04 17:10:23 +02:00
gui.setViewAsActiveForWindow(v)
2020-08-19 11:07:14 +02:00
if viewName == "main" {
gui.changeMainViewsContext(c.GetKey())
2020-08-19 10:41:57 +02:00
} else {
2020-11-16 11:38:26 +02:00
gui.changeMainViewsContext(MAIN_NORMAL_CONTEXT_KEY)
}
gui.setViewTabForContext(c)
2020-08-16 10:17:16 +02:00
if _, err := gui.g.SetCurrentView(viewName); err != nil {
return err
2020-08-16 05:58:29 +02:00
}
2021-04-04 15:51:59 +02:00
v.Visible = true
2020-08-16 05:58:29 +02:00
2020-08-23 03:08:51 +02:00
// if the new context's view was previously displaying another context, render the new context
if originalViewContextKey != c.GetKey() {
if err := c.HandleRender(); err != nil {
return err
}
}
2021-04-04 15:51:59 +02:00
v.Context = string(c.GetKey())
2020-08-16 05:58:29 +02:00
2020-08-23 03:08:51 +02:00
gui.g.Cursor = v.Editable
2020-08-16 05:58:29 +02:00
// render the options available for the current context at the bottom of the screen
optionsMap := c.GetOptionsMap()
if optionsMap == nil {
optionsMap = gui.globalOptionsMap()
2020-08-16 05:58:29 +02:00
}
gui.renderOptionsMap(optionsMap)
2020-08-16 05:58:29 +02:00
if err := c.HandleFocus(); err != nil {
return err
}
2020-08-16 06:16:28 +02:00
// TODO: consider removing this and instead depending on the .Context field of views
2020-08-16 05:58:29 +02:00
gui.State.ViewContextMap[c.GetViewName()] = c
return nil
}
2021-04-05 04:45:27 +02:00
// // currently unused
// func (gui *Gui) renderContextStack() string {
// result := ""
// for _, context := range gui.State.ContextManager.ContextStack {
// result += string(context.GetKey()) + "\n"
// }
// return result
// }
2020-08-16 05:58:29 +02:00
func (gui *Gui) currentContext() Context {
gui.State.ContextManager.Lock()
defer gui.State.ContextManager.Unlock()
if len(gui.State.ContextManager.ContextStack) == 0 {
return gui.defaultSideContext()
}
return gui.State.ContextManager.ContextStack[len(gui.State.ContextManager.ContextStack)-1]
2020-08-16 02:05:45 +02:00
}
2021-04-04 15:51:59 +02:00
// the status panel is not yet a list context (and may never be), so this method is not
// quite the same as currentSideContext()
func (gui *Gui) currentSideListContext() *ListContext {
context := gui.currentSideContext()
listContext, ok := context.(*ListContext)
if !ok {
return nil
}
return listContext
}
func (gui *Gui) currentSideContext() Context {
gui.State.ContextManager.Lock()
defer gui.State.ContextManager.Unlock()
stack := gui.State.ContextManager.ContextStack
2020-08-22 00:49:02 +02:00
// on startup the stack can be empty so we'll return an empty string in that case
if len(stack) == 0 {
2021-04-04 15:51:59 +02:00
return gui.defaultSideContext()
2020-08-22 00:49:02 +02:00
}
// find the first context in the stack with the type of SIDE_CONTEXT
for i := range stack {
context := stack[len(stack)-1-i]
if context.GetKind() == SIDE_CONTEXT {
2021-04-04 15:51:59 +02:00
return context
2020-08-22 00:49:02 +02:00
}
}
2021-04-04 15:51:59 +02:00
return gui.defaultSideContext()
2020-08-22 00:49:02 +02:00
}
func (gui *Gui) defaultSideContext() Context {
2021-04-04 15:51:59 +02:00
if gui.State.Modes.Filtering.Active() {
return gui.State.Contexts.BranchCommits
} else {
return gui.State.Contexts.Files
}
2020-08-22 00:49:02 +02:00
}
2021-04-03 06:56:11 +02:00
// remove the need to do this: always use a mapping
func (gui *Gui) setInitialViewContexts() {
2020-08-17 14:00:23 +02:00
// arguably we should only have our ViewContextMap and we should do away with
// contexts on views, or vice versa
2020-08-17 13:58:30 +02:00
for viewName, context := range gui.State.ViewContextMap {
// see if the view exists. If it does, set the context on it
view, err := gui.g.View(viewName)
if err != nil {
continue
}
2021-04-04 15:51:59 +02:00
view.Context = string(context.GetKey())
2020-08-16 02:05:45 +02:00
}
}
2020-08-16 05:58:29 +02:00
// 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()
2020-08-16 09:29:06 +02:00
if err := gui.onViewFocusChange(); err != nil {
2020-08-16 05:58:29 +02:00
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
}
previousView = newView
}
return nil
}
}
2020-08-16 02:05:45 +02:00
2020-08-16 09:29:06 +02:00
func (gui *Gui) onViewFocusChange() error {
2020-09-30 22:54:29 +02:00
gui.g.Mutexes.ViewsMutex.Lock()
defer gui.g.Mutexes.ViewsMutex.Unlock()
2020-08-16 05:58:29 +02:00
currentView := gui.g.CurrentView()
for _, view := range gui.g.Views() {
view.Highlight = view.Name() != "main" && view == currentView
}
return nil
}
func (gui *Gui) onViewFocusLost(oldView *gocui.View, newView *gocui.View) error {
if oldView == nil {
2020-08-16 05:58:29 +02:00
return nil
}
if oldView == gui.Views.CommitFiles && newView != gui.Views.Main && newView != gui.Views.Secondary && newView != gui.Views.Search {
2021-04-04 17:10:23 +02:00
gui.resetWindowForView(gui.Views.CommitFiles)
2021-04-03 06:56:11 +02:00
if err := gui.deactivateContext(gui.State.Contexts.CommitFiles); err != nil {
return err
}
}
2020-08-16 05:58:29 +02:00
return nil
}
2020-08-16 10:17:16 +02:00
// changeContext is a helper function for when we want to change a 'main' context
// which currently just means a context that affects both the main and secondary views
// other views can have their context changed directly but this function helps
// keep the main and secondary views in sync
2021-04-04 15:51:59 +02:00
func (gui *Gui) changeMainViewsContext(contextKey ContextKey) {
2020-08-23 02:31:19 +02:00
if gui.State.MainContext == contextKey {
2020-08-16 10:17:16 +02:00
return
}
2020-08-23 02:31:19 +02:00
switch contextKey {
case MAIN_NORMAL_CONTEXT_KEY, MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY, MAIN_MERGING_CONTEXT_KEY:
2021-04-04 15:51:59 +02:00
gui.Views.Main.Context = string(contextKey)
gui.Views.Secondary.Context = string(contextKey)
default:
2020-08-23 02:31:19 +02:00
panic(fmt.Sprintf("unknown context for main: %s", contextKey))
2020-08-16 10:17:16 +02:00
}
2020-08-23 02:31:19 +02:00
gui.State.MainContext = contextKey
2020-08-16 10:17:16 +02:00
}
2020-08-19 10:41:57 +02:00
func (gui *Gui) viewTabNames(viewName string) []string {
tabContexts := gui.State.ViewTabContextMap[viewName]
2020-08-19 10:41:57 +02:00
2020-09-30 00:27:12 +02:00
if len(tabContexts) == 0 {
return nil
}
2020-08-19 10:41:57 +02:00
result := make([]string, len(tabContexts))
for i, tabContext := range tabContexts {
result[i] = tabContext.tab
}
return result
}
func (gui *Gui) setViewTabForContext(c Context) {
// search for the context in our map and if we find it, set the tab for the corresponding view
tabContexts, ok := gui.State.ViewTabContextMap[c.GetViewName()]
if !ok {
return
}
for tabIndex, tabContext := range tabContexts {
for _, context := range tabContext.contexts {
if context.GetKey() == c.GetKey() {
// get the view, set the tab
v, err := gui.g.View(c.GetViewName())
if err != nil {
gui.Log.Error(err)
return
}
v.TabIndex = tabIndex
return
}
}
}
}
2020-08-19 10:06:51 +02:00
type tabContext struct {
tab string
contexts []Context
}
2020-08-19 10:41:57 +02:00
2021-04-04 15:51:59 +02:00
func (gui *Gui) mustContextForContextKey(contextKey ContextKey) Context {
2020-09-27 01:37:22 +02:00
context, ok := gui.contextForContextKey(contextKey)
if !ok {
2020-10-07 12:19:38 +02:00
panic(fmt.Sprintf("context not found for key %s", contextKey))
2020-09-27 01:37:22 +02:00
}
return context
}
2021-04-04 15:51:59 +02:00
func (gui *Gui) contextForContextKey(contextKey ContextKey) (Context, bool) {
2020-08-19 11:07:14 +02:00
for _, context := range gui.allContexts() {
if context.GetKey() == contextKey {
2020-09-27 01:37:22 +02:00
return context, true
2020-08-19 11:07:14 +02:00
}
}
2020-09-27 01:37:22 +02:00
return nil, false
2020-08-19 11:07:14 +02:00
}
2021-04-04 16:44:13 +02:00
func (gui *Gui) rerenderView(view *gocui.View) error {
contextKey := ContextKey(view.Context)
2020-09-27 01:37:22 +02:00
context := gui.mustContextForContextKey(contextKey)
2020-08-19 10:41:57 +02:00
2020-08-19 11:07:14 +02:00
return context.HandleRender()
2020-08-19 10:41:57 +02:00
}
func (gui *Gui) getSideContextSelectedItemId() string {
2021-04-04 15:51:59 +02:00
currentSideContext := gui.currentSideListContext()
if currentSideContext == nil {
return ""
}
item, ok := currentSideContext.GetSelectedItem()
if ok {
return item.ID()
}
return ""
}
// currently unused
// func (gui *Gui) getCurrentSideView() *gocui.View {
// currentSideContext := gui.currentSideContext()
// if currentSideContext == nil {
// return nil
// }
// view, _ := gui.g.View(currentSideContext.GetViewName())
// return view
// }
// currently unused
// func (gui *Gui) renderContextStack() string {
// result := ""
// for _, context := range gui.State.ContextManager.ContextStack {
// result += context.GetViewName() + "\n"
// }
// return result
// }