2019-02-16 12:01:17 +02:00
|
|
|
package gui
|
|
|
|
|
2020-08-16 02:05:45 +02:00
|
|
|
import (
|
2020-08-19 00:04:39 +02:00
|
|
|
"fmt"
|
|
|
|
|
2020-08-16 05:58:29 +02:00
|
|
|
"github.com/jesseduffield/gocui"
|
2020-08-16 02:05:45 +02:00
|
|
|
)
|
|
|
|
|
2020-08-16 05:58:29 +02:00
|
|
|
const (
|
|
|
|
SIDE_CONTEXT int = iota
|
|
|
|
MAIN_CONTEXT
|
|
|
|
TEMPORARY_POPUP
|
|
|
|
PERSISTENT_POPUP
|
|
|
|
)
|
|
|
|
|
2020-08-20 01:00:55 +02:00
|
|
|
const (
|
|
|
|
STATUS_CONTEXT_KEY = "status"
|
|
|
|
FILES_CONTEXT_KEY = "files"
|
|
|
|
LOCAL_BRANCHES_CONTEXT_KEY = "localBranches"
|
|
|
|
REMOTES_CONTEXT_KEY = "remotes"
|
|
|
|
REMOTE_BRANCHES_CONTEXT_KEY = "remoteBranches"
|
|
|
|
TAGS_CONTEXT_KEY = "tags"
|
|
|
|
BRANCH_COMMITS_CONTEXT_KEY = "commits"
|
|
|
|
REFLOG_COMMITS_CONTEXT_KEY = "reflogCommits"
|
2020-08-22 00:49:02 +02:00
|
|
|
SUB_COMMITS_CONTEXT_KEY = "subCommits"
|
2020-08-20 01:00:55 +02:00
|
|
|
COMMIT_FILES_CONTEXT_KEY = "commitFiles"
|
|
|
|
STASH_CONTEXT_KEY = "stash"
|
|
|
|
MAIN_NORMAL_CONTEXT_KEY = "normal"
|
|
|
|
MAIN_MERGING_CONTEXT_KEY = "merging"
|
|
|
|
MAIN_PATCH_BUILDING_CONTEXT_KEY = "patchBuilding"
|
|
|
|
MAIN_STAGING_CONTEXT_KEY = "staging"
|
|
|
|
MENU_CONTEXT_KEY = "menu"
|
|
|
|
CREDENTIALS_CONTEXT_KEY = "credentials"
|
|
|
|
CONFIRMATION_CONTEXT_KEY = "confirmation"
|
2020-09-26 09:50:22 +02:00
|
|
|
SEARCH_CONTEXT_KEY = "search"
|
2020-08-20 01:00:55 +02:00
|
|
|
COMMIT_MESSAGE_CONTEXT_KEY = "commitMessage"
|
2020-09-30 00:27:23 +02:00
|
|
|
SUBMODULES_CONTEXT_KEY = "submodules"
|
2020-11-28 04:14:48 +02:00
|
|
|
SUGGESTIONS_CONTEXT_KEY = "suggestions"
|
2020-08-20 01:00:55 +02:00
|
|
|
)
|
|
|
|
|
2020-09-27 01:37:22 +02:00
|
|
|
var allContextKeys = []string{
|
|
|
|
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,
|
2020-11-28 04:14:48 +02:00
|
|
|
SUGGESTIONS_CONTEXT_KEY,
|
2020-09-30 00:27:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type SimpleContextNode struct {
|
|
|
|
Context Context
|
|
|
|
}
|
|
|
|
|
|
|
|
type RemotesContextNode struct {
|
|
|
|
Context Context
|
|
|
|
Branches SimpleContextNode
|
|
|
|
}
|
|
|
|
|
|
|
|
type ContextTree struct {
|
|
|
|
Status SimpleContextNode
|
|
|
|
Files SimpleContextNode
|
|
|
|
Submodules SimpleContextNode
|
|
|
|
Menu SimpleContextNode
|
|
|
|
Branches SimpleContextNode
|
|
|
|
Remotes RemotesContextNode
|
|
|
|
Tags SimpleContextNode
|
|
|
|
BranchCommits SimpleContextNode
|
|
|
|
CommitFiles SimpleContextNode
|
|
|
|
ReflogCommits SimpleContextNode
|
|
|
|
SubCommits SimpleContextNode
|
|
|
|
Stash SimpleContextNode
|
|
|
|
Normal SimpleContextNode
|
|
|
|
Staging SimpleContextNode
|
|
|
|
PatchBuilding SimpleContextNode
|
|
|
|
Merging SimpleContextNode
|
|
|
|
Credentials SimpleContextNode
|
|
|
|
Confirmation SimpleContextNode
|
|
|
|
CommitMessage SimpleContextNode
|
|
|
|
Search SimpleContextNode
|
2020-11-28 04:14:48 +02:00
|
|
|
Suggestions SimpleContextNode
|
2020-09-30 00:27:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (gui *Gui) allContexts() []Context {
|
|
|
|
return []Context{
|
|
|
|
gui.Contexts.Status.Context,
|
|
|
|
gui.Contexts.Files.Context,
|
|
|
|
gui.Contexts.Submodules.Context,
|
|
|
|
gui.Contexts.Branches.Context,
|
|
|
|
gui.Contexts.Remotes.Context,
|
|
|
|
gui.Contexts.Remotes.Branches.Context,
|
|
|
|
gui.Contexts.Tags.Context,
|
|
|
|
gui.Contexts.BranchCommits.Context,
|
|
|
|
gui.Contexts.CommitFiles.Context,
|
|
|
|
gui.Contexts.ReflogCommits.Context,
|
|
|
|
gui.Contexts.Stash.Context,
|
|
|
|
gui.Contexts.Menu.Context,
|
|
|
|
gui.Contexts.Confirmation.Context,
|
|
|
|
gui.Contexts.Credentials.Context,
|
|
|
|
gui.Contexts.CommitMessage.Context,
|
|
|
|
gui.Contexts.Normal.Context,
|
|
|
|
gui.Contexts.Staging.Context,
|
|
|
|
gui.Contexts.Merging.Context,
|
|
|
|
gui.Contexts.PatchBuilding.Context,
|
|
|
|
gui.Contexts.SubCommits.Context,
|
2020-11-28 04:14:48 +02:00
|
|
|
gui.Contexts.Suggestions.Context,
|
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
|
2020-08-16 05:58:29 +02:00
|
|
|
GetKind() int
|
|
|
|
GetViewName() string
|
2020-08-21 11:53:45 +02:00
|
|
|
GetWindowName() string
|
|
|
|
SetWindowName(string)
|
2020-08-16 05:58:29 +02:00
|
|
|
GetKey() string
|
2020-08-21 01:12:45 +02:00
|
|
|
SetParentContext(Context)
|
2020-08-24 00:26:42 +02:00
|
|
|
|
|
|
|
// we return a bool here to tell us whether or not the returned value just wraps a nil
|
|
|
|
GetParentContext() (Context, bool)
|
2020-08-23 02:50:27 +02:00
|
|
|
GetOptionsMap() map[string]string
|
2020-08-16 02:05:45 +02:00
|
|
|
}
|
|
|
|
|
2020-08-16 05:58:29 +02:00
|
|
|
type BasicContext struct {
|
2020-08-23 02:50:27 +02:00
|
|
|
OnFocus func() error
|
|
|
|
OnFocusLost func() error
|
|
|
|
OnRender func() error
|
|
|
|
OnGetOptionsMap func() map[string]string
|
|
|
|
Kind int
|
|
|
|
Key string
|
|
|
|
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()
|
|
|
|
}
|
|
|
|
|
2020-08-21 01:12:45 +02:00
|
|
|
func (c BasicContext) SetParentContext(Context) {
|
|
|
|
panic("can't set parent context on basic context")
|
|
|
|
}
|
|
|
|
|
2020-08-24 00:26:42 +02:00
|
|
|
func (c BasicContext) GetParentContext() (Context, bool) {
|
|
|
|
return nil, false
|
2020-08-21 01:12:45 +02:00
|
|
|
}
|
|
|
|
|
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() int {
|
|
|
|
return c.Kind
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c BasicContext) GetKey() string {
|
|
|
|
return c.Key
|
|
|
|
}
|
|
|
|
|
2020-08-19 11:07:14 +02:00
|
|
|
func (gui *Gui) contextTree() ContextTree {
|
|
|
|
return ContextTree{
|
|
|
|
Status: SimpleContextNode{
|
|
|
|
Context: BasicContext{
|
|
|
|
OnFocus: gui.handleStatusSelect,
|
|
|
|
Kind: SIDE_CONTEXT,
|
|
|
|
ViewName: "status",
|
2020-08-20 11:42:58 +02:00
|
|
|
Key: STATUS_CONTEXT_KEY,
|
2020-08-19 11:07:14 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
Files: SimpleContextNode{
|
|
|
|
Context: gui.filesListContext(),
|
|
|
|
},
|
2020-09-30 00:27:23 +02:00
|
|
|
Submodules: SimpleContextNode{
|
|
|
|
Context: gui.submodulesListContext(),
|
|
|
|
},
|
2020-08-19 11:07:14 +02:00
|
|
|
Menu: SimpleContextNode{
|
|
|
|
Context: gui.menuListContext(),
|
|
|
|
},
|
|
|
|
Remotes: RemotesContextNode{
|
|
|
|
Context: gui.remotesListContext(),
|
|
|
|
Branches: SimpleContextNode{
|
|
|
|
Context: gui.remoteBranchesListContext(),
|
|
|
|
},
|
|
|
|
},
|
2020-08-21 01:12:45 +02:00
|
|
|
BranchCommits: SimpleContextNode{
|
2020-08-19 11:07:14 +02:00
|
|
|
Context: gui.branchCommitsListContext(),
|
2020-08-21 01:12:45 +02:00
|
|
|
},
|
|
|
|
CommitFiles: SimpleContextNode{
|
|
|
|
Context: gui.commitFilesListContext(),
|
2020-08-19 11:07:14 +02:00
|
|
|
},
|
|
|
|
ReflogCommits: SimpleContextNode{
|
|
|
|
Context: gui.reflogCommitsListContext(),
|
|
|
|
},
|
2020-08-22 00:49:02 +02:00
|
|
|
SubCommits: SimpleContextNode{
|
|
|
|
Context: gui.subCommitsListContext(),
|
|
|
|
},
|
2020-08-19 11:07:14 +02:00
|
|
|
Branches: SimpleContextNode{
|
|
|
|
Context: gui.branchesListContext(),
|
|
|
|
},
|
|
|
|
Tags: SimpleContextNode{
|
|
|
|
Context: gui.tagsListContext(),
|
|
|
|
},
|
|
|
|
Stash: SimpleContextNode{
|
|
|
|
Context: gui.stashListContext(),
|
|
|
|
},
|
|
|
|
Normal: SimpleContextNode{
|
|
|
|
Context: BasicContext{
|
|
|
|
OnFocus: func() error {
|
|
|
|
return nil // TODO: should we do something here? We should allow for scrolling the panel
|
|
|
|
},
|
|
|
|
Kind: MAIN_CONTEXT,
|
|
|
|
ViewName: "main",
|
2020-08-23 02:13:56 +02:00
|
|
|
Key: MAIN_NORMAL_CONTEXT_KEY,
|
2020-08-19 11:07:14 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
Staging: SimpleContextNode{
|
|
|
|
Context: BasicContext{
|
|
|
|
OnFocus: func() error {
|
|
|
|
return nil
|
2020-08-22 23:37:46 +02:00
|
|
|
// TODO: centralise the code here
|
2020-08-19 11:07:14 +02:00
|
|
|
// return gui.refreshStagingPanel(false, -1)
|
|
|
|
},
|
|
|
|
Kind: MAIN_CONTEXT,
|
|
|
|
ViewName: "main",
|
2020-08-23 02:13:56 +02:00
|
|
|
Key: MAIN_STAGING_CONTEXT_KEY,
|
2020-08-19 11:07:14 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
PatchBuilding: SimpleContextNode{
|
|
|
|
Context: BasicContext{
|
|
|
|
OnFocus: func() error {
|
2020-08-22 23:37:46 +02:00
|
|
|
return nil
|
|
|
|
// TODO: centralise the code here
|
|
|
|
// return gui.refreshPatchBuildingPanel(-1)
|
2020-08-19 11:07:14 +02:00
|
|
|
},
|
|
|
|
Kind: MAIN_CONTEXT,
|
|
|
|
ViewName: "main",
|
2020-08-23 02:13:56 +02:00
|
|
|
Key: MAIN_PATCH_BUILDING_CONTEXT_KEY,
|
2020-08-19 11:07:14 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
Merging: SimpleContextNode{
|
|
|
|
Context: BasicContext{
|
2020-11-16 11:38:26 +02:00
|
|
|
OnFocus: gui.refreshMergePanel,
|
2020-08-23 02:50:27 +02:00
|
|
|
Kind: MAIN_CONTEXT,
|
|
|
|
ViewName: "main",
|
|
|
|
Key: MAIN_MERGING_CONTEXT_KEY,
|
|
|
|
OnGetOptionsMap: gui.getMergingOptions,
|
2020-08-19 11:07:14 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
Credentials: SimpleContextNode{
|
|
|
|
Context: BasicContext{
|
2020-11-16 11:38:26 +02:00
|
|
|
OnFocus: gui.handleCredentialsViewFocused,
|
2020-08-19 11:07:14 +02:00
|
|
|
Kind: PERSISTENT_POPUP,
|
|
|
|
ViewName: "credentials",
|
2020-08-23 02:13:56 +02:00
|
|
|
Key: CREDENTIALS_CONTEXT_KEY,
|
2020-08-19 11:07:14 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
Confirmation: SimpleContextNode{
|
|
|
|
Context: BasicContext{
|
|
|
|
OnFocus: func() error { return nil },
|
|
|
|
Kind: TEMPORARY_POPUP,
|
|
|
|
ViewName: "confirmation",
|
2020-08-23 02:13:56 +02:00
|
|
|
Key: CONFIRMATION_CONTEXT_KEY,
|
2020-08-19 11:07:14 +02:00
|
|
|
},
|
|
|
|
},
|
2020-11-28 04:14:48 +02:00
|
|
|
Suggestions: SimpleContextNode{
|
|
|
|
Context: gui.suggestionsListContext(),
|
|
|
|
},
|
2020-08-19 11:07:14 +02:00
|
|
|
CommitMessage: SimpleContextNode{
|
|
|
|
Context: BasicContext{
|
2020-11-16 11:38:26 +02:00
|
|
|
OnFocus: gui.handleCommitMessageFocused,
|
2020-08-19 11:07:14 +02:00
|
|
|
Kind: PERSISTENT_POPUP,
|
|
|
|
ViewName: "commitMessage",
|
2020-08-23 02:13:56 +02:00
|
|
|
Key: COMMIT_MESSAGE_CONTEXT_KEY,
|
2020-08-19 11:07:14 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
Search: SimpleContextNode{
|
|
|
|
Context: BasicContext{
|
|
|
|
OnFocus: func() error { return nil },
|
|
|
|
Kind: PERSISTENT_POPUP,
|
|
|
|
ViewName: "search",
|
2020-08-23 02:13:56 +02:00
|
|
|
Key: SEARCH_CONTEXT_KEY,
|
2020-08-19 11:07:14 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (gui *Gui) initialViewContextMap() map[string]Context {
|
|
|
|
return map[string]Context{
|
|
|
|
"status": gui.Contexts.Status.Context,
|
|
|
|
"files": gui.Contexts.Files.Context,
|
|
|
|
"branches": gui.Contexts.Branches.Context,
|
|
|
|
"commits": gui.Contexts.BranchCommits.Context,
|
2020-08-21 01:12:45 +02:00
|
|
|
"commitFiles": gui.Contexts.CommitFiles.Context,
|
2020-08-19 11:07:14 +02:00
|
|
|
"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.Normal.Context,
|
|
|
|
"secondary": gui.Contexts.Normal.Context,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-19 13:13:43 +02:00
|
|
|
func (gui *Gui) viewTabContextMap() map[string][]tabContext {
|
|
|
|
return map[string][]tabContext{
|
|
|
|
"branches": {
|
|
|
|
{
|
|
|
|
tab: "Local Branches",
|
|
|
|
contexts: []Context{gui.Contexts.Branches.Context},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
tab: "Remotes",
|
|
|
|
contexts: []Context{
|
|
|
|
gui.Contexts.Remotes.Context,
|
|
|
|
gui.Contexts.Remotes.Branches.Context,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
tab: "Tags",
|
|
|
|
contexts: []Context{gui.Contexts.Tags.Context},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"commits": {
|
|
|
|
{
|
|
|
|
tab: "Commits",
|
|
|
|
contexts: []Context{gui.Contexts.BranchCommits.Context},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
tab: "Reflog",
|
|
|
|
contexts: []Context{
|
|
|
|
gui.Contexts.ReflogCommits.Context,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2020-09-30 00:27:23 +02:00
|
|
|
"files": {
|
|
|
|
{
|
|
|
|
tab: "Files",
|
|
|
|
contexts: []Context{gui.Contexts.Files.Context},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
tab: "Submodules",
|
|
|
|
contexts: []Context{
|
|
|
|
gui.Contexts.Submodules.Context,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2020-08-19 13:13:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-23 03:23:32 +02:00
|
|
|
func (gui *Gui) currentContextKeyIgnoringPopups() string {
|
|
|
|
stack := gui.State.ContextStack
|
|
|
|
|
|
|
|
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 ""
|
|
|
|
}
|
|
|
|
|
2020-11-28 04:14:48 +02:00
|
|
|
// 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 {
|
|
|
|
if len(gui.State.ContextStack) == 0 {
|
|
|
|
gui.State.ContextStack = []Context{c}
|
|
|
|
} else {
|
|
|
|
// replace the last item with the given item
|
|
|
|
gui.State.ContextStack = append(gui.State.ContextStack[0:len(gui.State.ContextStack)-1], c)
|
|
|
|
}
|
|
|
|
|
|
|
|
return gui.activateContext(c)
|
|
|
|
})
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (gui *Gui) pushContext(c Context) error {
|
2020-08-18 14:41:27 +02:00
|
|
|
gui.g.Update(func(*gocui.Gui) error {
|
|
|
|
// 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.ContextStack {
|
|
|
|
if stackContext.GetKey() != c.GetKey() {
|
|
|
|
if err := gui.deactivateContext(stackContext); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-08-18 00:04:15 +02:00
|
|
|
}
|
|
|
|
}
|
2020-08-18 14:41:27 +02:00
|
|
|
gui.State.ContextStack = []Context{c}
|
|
|
|
} else {
|
|
|
|
// TODO: think about other exceptional cases
|
|
|
|
gui.State.ContextStack = append(gui.State.ContextStack, c)
|
2020-08-18 00:04:15 +02:00
|
|
|
}
|
2020-08-16 05:58:29 +02:00
|
|
|
|
2020-08-18 14:41:27 +02:00
|
|
|
return gui.activateContext(c)
|
|
|
|
})
|
|
|
|
|
|
|
|
return nil
|
2020-08-16 05:58:29 +02:00
|
|
|
}
|
|
|
|
|
2020-11-28 04:14:48 +02:00
|
|
|
// 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
|
2020-11-28 04:14:48 +02:00
|
|
|
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 {
|
2020-08-18 14:41:27 +02:00
|
|
|
gui.g.Update(func(*gocui.Gui) error {
|
|
|
|
// TODO: add mutexes
|
2020-08-16 10:17:16 +02:00
|
|
|
|
2020-08-18 14:41:27 +02:00
|
|
|
if len(gui.State.ContextStack) == 1 {
|
|
|
|
// cannot escape from bottommost context
|
|
|
|
return nil
|
|
|
|
}
|
2020-08-16 10:17:16 +02:00
|
|
|
|
2020-08-18 14:41:27 +02:00
|
|
|
n := len(gui.State.ContextStack) - 1
|
2020-08-16 10:17:16 +02:00
|
|
|
|
2020-08-18 14:41:27 +02:00
|
|
|
currentContext := gui.State.ContextStack[n]
|
|
|
|
newContext := gui.State.ContextStack[n-1]
|
2020-08-16 10:17:16 +02:00
|
|
|
|
2020-08-18 14:41:27 +02:00
|
|
|
gui.State.ContextStack = gui.State.ContextStack[:n]
|
2020-08-16 10:17:16 +02:00
|
|
|
|
2020-08-18 14:41:27 +02:00
|
|
|
if err := gui.deactivateContext(currentContext); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-08-16 10:17:16 +02:00
|
|
|
|
2020-08-18 14:41:27 +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 {
|
|
|
|
// if we are the kind of context that is sent to back upon deactivation, we should do that
|
|
|
|
if c.GetKind() == TEMPORARY_POPUP || c.GetKind() == PERSISTENT_POPUP {
|
|
|
|
_, _ = gui.g.SetViewOnBottom(c.GetViewName())
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-08-19 11:07:14 +02:00
|
|
|
if v.Context != c.GetKey() {
|
|
|
|
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 view no longer exists, pop again
|
|
|
|
if err != nil {
|
|
|
|
return gui.returnFromContext()
|
|
|
|
}
|
2020-08-23 03:08:51 +02:00
|
|
|
originalViewContextKey := 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.
|
2020-08-19 11:07:14 +02:00
|
|
|
gui.setViewAsActiveForWindow(viewName)
|
|
|
|
|
2020-08-19 01:05:43 +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)
|
2020-08-19 01:05:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
gui.setViewTabForContext(c)
|
|
|
|
|
2020-08-16 10:17:16 +02:00
|
|
|
if _, err := gui.g.SetCurrentView(viewName); err != nil {
|
2020-09-26 03:57:06 +02:00
|
|
|
// if view no longer exists, pop again
|
|
|
|
return gui.returnFromContext()
|
2020-08-16 05:58:29 +02:00
|
|
|
}
|
|
|
|
|
2020-08-16 10:17:16 +02:00
|
|
|
if _, err := gui.g.SetViewOnTop(viewName); err != nil {
|
2020-09-26 03:57:06 +02:00
|
|
|
// if view no longer exists, pop again
|
|
|
|
return gui.returnFromContext()
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
v.Context = 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
|
|
|
|
2020-08-23 02:50:27 +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
|
|
|
}
|
2020-08-23 02:50:27 +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
|
|
|
|
}
|
|
|
|
|
2020-11-16 11:38:26 +02:00
|
|
|
// currently unused
|
|
|
|
// func (gui *Gui) renderContextStack() string {
|
|
|
|
// result := ""
|
|
|
|
// for _, context := range gui.State.ContextStack {
|
|
|
|
// result += context.GetKey() + "\n"
|
|
|
|
// }
|
|
|
|
// return result
|
|
|
|
// }
|
2020-08-16 05:58:29 +02:00
|
|
|
|
2020-08-21 01:12:45 +02:00
|
|
|
func (gui *Gui) currentContext() Context {
|
2020-08-19 00:26:22 +02:00
|
|
|
if len(gui.State.ContextStack) == 0 {
|
2020-08-23 02:17:17 +02:00
|
|
|
return gui.defaultSideContext()
|
2020-08-19 00:26:22 +02:00
|
|
|
}
|
|
|
|
|
2020-08-21 01:12:45 +02:00
|
|
|
return gui.State.ContextStack[len(gui.State.ContextStack)-1]
|
2020-08-16 02:05:45 +02:00
|
|
|
}
|
|
|
|
|
2020-08-22 00:49:02 +02:00
|
|
|
func (gui *Gui) currentSideContext() *ListContext {
|
|
|
|
stack := gui.State.ContextStack
|
|
|
|
|
|
|
|
// on startup the stack can be empty so we'll return an empty string in that case
|
|
|
|
if len(stack) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 {
|
2020-08-22 07:56:30 +02:00
|
|
|
// not all side contexts are list contexts (e.g. the status panel)
|
|
|
|
listContext, ok := context.(*ListContext)
|
|
|
|
if !ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return listContext
|
2020-08-22 00:49:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (gui *Gui) defaultSideContext() Context {
|
|
|
|
return gui.Contexts.Files.Context
|
|
|
|
}
|
|
|
|
|
2020-08-19 00:26:22 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
view.Context = 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(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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2020-08-23 02:31:19 +02:00
|
|
|
func (gui *Gui) changeMainViewsContext(contextKey string) {
|
|
|
|
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:
|
|
|
|
gui.getMainView().Context = contextKey
|
|
|
|
gui.getSecondaryView().Context = contextKey
|
2020-08-19 00:04:39 +02:00
|
|
|
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 01:05:43 +02:00
|
|
|
|
2020-08-19 10:41:57 +02:00
|
|
|
func (gui *Gui) viewTabNames(viewName string) []string {
|
|
|
|
tabContexts := gui.ViewTabContextMap[viewName]
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-08-19 01:05:43 +02:00
|
|
|
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.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 {
|
2020-08-19 01:05:43 +02:00
|
|
|
tab string
|
|
|
|
contexts []Context
|
|
|
|
}
|
2020-08-19 10:41:57 +02:00
|
|
|
|
2020-09-27 01:37:22 +02:00
|
|
|
func (gui *Gui) mustContextForContextKey(contextKey string) Context {
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
func (gui *Gui) contextForContextKey(contextKey string) (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
|
|
|
}
|
|
|
|
|
|
|
|
func (gui *Gui) rerenderView(viewName string) error {
|
|
|
|
v, err := gui.g.View(viewName)
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
contextKey := v.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
|
|
|
}
|
2020-08-22 01:55:49 +02:00
|
|
|
|
2020-11-16 11:38:26 +02:00
|
|
|
// currently unused
|
|
|
|
// func (gui *Gui) getCurrentSideView() *gocui.View {
|
|
|
|
// currentSideContext := gui.currentSideContext()
|
|
|
|
// if currentSideContext == nil {
|
|
|
|
// return nil
|
|
|
|
// }
|
2020-08-22 01:55:49 +02:00
|
|
|
|
2020-11-16 11:38:26 +02:00
|
|
|
// view, _ := gui.g.View(currentSideContext.GetViewName())
|
2020-08-22 01:55:49 +02:00
|
|
|
|
2020-11-16 11:38:26 +02:00
|
|
|
// return view
|
|
|
|
// }
|
2020-08-22 04:07:03 +02:00
|
|
|
|
2020-08-22 07:56:30 +02:00
|
|
|
func (gui *Gui) getSideContextSelectedItemId() string {
|
2020-08-22 04:07:03 +02:00
|
|
|
currentSideContext := gui.currentSideContext()
|
|
|
|
if currentSideContext == nil {
|
2020-08-22 07:56:30 +02:00
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
item, ok := currentSideContext.GetSelectedItem()
|
|
|
|
|
|
|
|
if ok {
|
|
|
|
return item.ID()
|
2020-08-22 04:07:03 +02:00
|
|
|
}
|
|
|
|
|
2020-08-22 07:56:30 +02:00
|
|
|
return ""
|
2020-08-22 04:07:03 +02:00
|
|
|
}
|