From 4fe512ff3a299102c2bd78bc83b8644582127aae Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sun, 4 Apr 2021 23:51:59 +1000 Subject: [PATCH] test type safe view access --- pkg/gui/branches_panel.go | 6 +- pkg/gui/cherry_picking.go | 10 +- pkg/gui/commit_files_panel.go | 5 +- pkg/gui/commit_message_panel.go | 9 +- pkg/gui/commits_panel.go | 4 +- pkg/gui/confirmation_panel.go | 27 ++- pkg/gui/context.go | 120 ++++++------ pkg/gui/credentials_panel.go | 2 +- pkg/gui/custom_commands.go | 9 +- pkg/gui/diffing.go | 4 +- pkg/gui/files_panel.go | 28 ++- pkg/gui/global_handlers.go | 57 +++--- pkg/gui/gui.go | 28 ++- pkg/gui/keybindings.go | 8 +- pkg/gui/layout.go | 311 +++++++++++++++++++------------ pkg/gui/line_by_line_panel.go | 24 ++- pkg/gui/list_context.go | 34 ++-- pkg/gui/main_panels.go | 42 ++--- pkg/gui/menu_panel.go | 1 - pkg/gui/merge_panel.go | 9 +- pkg/gui/patch_building_panel.go | 8 +- pkg/gui/pty.go | 5 +- pkg/gui/recent_repos_panel.go | 2 +- pkg/gui/reflog_panel.go | 4 +- pkg/gui/remote_branches_panel.go | 4 +- pkg/gui/remotes_panel.go | 11 +- pkg/gui/searching.go | 2 +- pkg/gui/side_window.go | 4 +- pkg/gui/staging_panel.go | 10 +- pkg/gui/stash_panel.go | 4 +- pkg/gui/status_panel.go | 6 +- pkg/gui/sub_commits_panel.go | 8 +- pkg/gui/submodules_panel.go | 6 +- pkg/gui/suggestions_panel.go | 7 +- pkg/gui/tags_panel.go | 185 +++++++++--------- pkg/gui/view_helpers.go | 82 -------- 36 files changed, 531 insertions(+), 555 deletions(-) diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index 4d0c69a99..6f88e3843 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -30,13 +30,13 @@ func (gui *Gui) handleBranchSelect() error { var task updateTask branch := gui.getSelectedBranch() if branch == nil { - task = gui.createRenderStringTask(gui.Tr.NoBranchesThisRepo) + task = NewRenderStringTask(gui.Tr.NoBranchesThisRepo) } else { cmd := gui.OSCommand.ExecutableFromString( gui.GitCommand.GetBranchGraphCmdStr(branch.Name), ) - task = gui.createRunPtyTask(cmd) + task = NewRunPtyTask(cmd) } return gui.refreshMainViews(refreshMainOpts{ @@ -473,7 +473,7 @@ func (gui *Gui) currentBranch() *models.Branch { } func (gui *Gui) handleNewBranchOffCurrentItem() error { - context := gui.currentSideContext() + context := gui.currentSideListContext() item, ok := context.GetSelectedItem() if !ok { diff --git a/pkg/gui/cherry_picking.go b/pkg/gui/cherry_picking.go index a9620896a..3ad52a73c 100644 --- a/pkg/gui/cherry_picking.go +++ b/pkg/gui/cherry_picking.go @@ -24,7 +24,7 @@ func (gui *Gui) handleCopyCommit() error { } // get currently selected commit, add the sha to state. - context := gui.currentSideContext() + context := gui.currentSideListContext() if context == nil { return nil } @@ -63,7 +63,7 @@ func (gui *Gui) cherryPickedCommitShaMap() map[string]bool { } func (gui *Gui) commitsListForContext() []*models.Commit { - context := gui.currentSideContext() + context := gui.currentSideListContext() if context == nil { return nil } @@ -104,7 +104,7 @@ func (gui *Gui) handleCopyCommitRange() error { } // get currently selected commit, add the sha to state. - context := gui.currentSideContext() + context := gui.currentSideListContext() if context == nil { return nil } @@ -169,7 +169,7 @@ func (gui *Gui) exitCherryPickingMode() error { return gui.rerenderContextViewIfPresent(contextKey) } -func (gui *Gui) rerenderContextViewIfPresent(contextKey string) error { +func (gui *Gui) rerenderContextViewIfPresent(contextKey ContextKey) error { if contextKey == "" { return nil } @@ -184,7 +184,7 @@ func (gui *Gui) rerenderContextViewIfPresent(contextKey string) error { return nil } - if view.Context == contextKey { + if ContextKey(view.Context) == contextKey { if err := context.HandleRender(); err != nil { return err } diff --git a/pkg/gui/commit_files_panel.go b/pkg/gui/commit_files_panel.go index f0223a15e..b0bd16547 100644 --- a/pkg/gui/commit_files_panel.go +++ b/pkg/gui/commit_files_panel.go @@ -45,7 +45,7 @@ func (gui *Gui) handleCommitFileSelect() error { cmd := gui.OSCommand.ExecutableFromString( gui.GitCommand.ShowFileDiffCmdStr(from, to, reverse, node.GetPath(), false), ) - task := gui.createRunPtyTask(cmd) + task := NewRunPtyTask(cmd) return gui.refreshMainViews(refreshMainOpts{ main: &viewUpdateOpts{ @@ -287,7 +287,8 @@ func (gui *Gui) handleToggleCommitFileTreeView() error { } } - if gui.getCommitFilesView().Context == COMMIT_FILES_CONTEXT_KEY { + // TODO: pretty sure this view only ever has this context. Is this if condition necessary? + if gui.Views.CommitFiles.Context == COMMIT_FILES_CONTEXT_KEY { if err := gui.State.Contexts.CommitFiles.HandleRender(); err != nil { return err } diff --git a/pkg/gui/commit_message_panel.go b/pkg/gui/commit_message_panel.go index 35d3b0c80..346fbcff3 100644 --- a/pkg/gui/commit_message_panel.go +++ b/pkg/gui/commit_message_panel.go @@ -23,8 +23,7 @@ func (gui *Gui) runSyncOrAsyncCommand(sub *exec.Cmd, err error) (bool, error) { } func (gui *Gui) handleCommitConfirm() error { - commitMessageView := gui.getCommitMessageView() - message := gui.trimmedContent(commitMessageView) + message := gui.trimmedContent(gui.Views.CommitMessage) if message == "" { return gui.createErrorPanel(gui.Tr.CommitWithoutMessageErr) } @@ -41,7 +40,7 @@ func (gui *Gui) handleCommitConfirm() error { return nil } - gui.clearEditorView(commitMessageView) + gui.clearEditorView(gui.Views.CommitMessage) _ = gui.returnFromContext() return gui.refreshSidePanels(refreshOptions{mode: ASYNC}) } @@ -73,6 +72,6 @@ func (gui *Gui) RenderCommitLength() { if !gui.Config.GetUserConfig().Gui.CommitLength.Show { return } - v := gui.getCommitMessageView() - v.Subtitle = gui.getBufferLength(v) + + gui.Views.CommitMessage.Subtitle = gui.getBufferLength(gui.Views.CommitMessage) } diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index 4da9e1922..026316700 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -35,12 +35,12 @@ func (gui *Gui) handleCommitSelect() error { var task updateTask commit := gui.getSelectedLocalCommit() if commit == nil { - task = gui.createRenderStringTask(gui.Tr.NoCommitsThisBranch) + task = NewRenderStringTask(gui.Tr.NoCommitsThisBranch) } else { cmd := gui.OSCommand.ExecutableFromString( gui.GitCommand.ShowCmdStr(commit.Sha, gui.State.Modes.Filtering.GetPath()), ) - task = gui.createRunPtyTask(cmd) + task = NewRunPtyTask(cmd) } return gui.refreshMainViews(refreshMainOpts{ diff --git a/pkg/gui/confirmation_panel.go b/pkg/gui/confirmation_panel.go index f4b62f219..737ee3ff6 100644 --- a/pkg/gui/confirmation_panel.go +++ b/pkg/gui/confirmation_panel.go @@ -108,19 +108,17 @@ func (gui *Gui) wrappedPromptConfirmationFunction(handlersManageFocus bool, func } } -func (gui *Gui) deleteConfirmationView() { +func (gui *Gui) clearConfirmationViewKeyBindings() { keybindingConfig := gui.Config.GetUserConfig().Keybinding _ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.Confirm), gocui.ModNone) _ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone) _ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.Return), gocui.ModNone) - - _ = gui.g.DeleteView("confirmation") } func (gui *Gui) closeConfirmationPrompt(handlersManageFocus bool) error { - view := gui.getConfirmationView() - if view == nil { - return nil // if it's already been closed we can just return + // we've already closed it so we can just return + if !gui.Views.Confirmation.Visible { + return nil } if !handlersManageFocus { @@ -129,9 +127,9 @@ func (gui *Gui) closeConfirmationPrompt(handlersManageFocus bool) error { } } - gui.deleteConfirmationView() - - _, _ = gui.g.SetViewOnBottom("suggestions") + gui.clearConfirmationViewKeyBindings() + gui.Views.Confirmation.Visible = false + gui.Views.Suggestions.Visible = false return nil } @@ -200,7 +198,7 @@ func (gui *Gui) prepareConfirmationPanel(title, prompt string, hasLoader bool, f suggestionsView.FgColor = theme.GocuiDefaultTextColor } gui.setSuggestions([]*types.Suggestion{}) - _, _ = gui.g.SetViewOnTop("suggestions") + suggestionsView.Visible = true } gui.g.Update(func(g *gocui.Gui) error { @@ -211,10 +209,9 @@ func (gui *Gui) prepareConfirmationPanel(title, prompt string, hasLoader bool, f func (gui *Gui) createPopupPanel(opts createPopupPanelOpts) error { gui.g.Update(func(g *gocui.Gui) error { - // delete the existing confirmation panel if it exists - if view, _ := g.View("confirmation"); view != nil { - gui.deleteConfirmationView() - } + // remove any previous keybindings + gui.clearConfirmationViewKeyBindings() + confirmationView, err := gui.prepareConfirmationPanel(opts.title, opts.prompt, opts.hasLoader, opts.findSuggestionsFunc) if err != nil { return err @@ -251,7 +248,7 @@ func (gui *Gui) setKeyBindings(opts createPopupPanelOpts) error { gui.renderString("options", actions) var onConfirm func() error if opts.handleConfirmPrompt != nil { - onConfirm = gui.wrappedPromptConfirmationFunction(opts.handlersManageFocus, opts.handleConfirmPrompt, func() string { return gui.getConfirmationView().Buffer() }) + onConfirm = gui.wrappedPromptConfirmationFunction(opts.handlersManageFocus, opts.handleConfirmPrompt, func() string { return gui.Views.Confirmation.Buffer() }) } else { onConfirm = gui.wrappedConfirmationFunction(opts.handlersManageFocus, opts.handleConfirm) } diff --git a/pkg/gui/context.go b/pkg/gui/context.go index 8d2d92cc9..94e227d0e 100644 --- a/pkg/gui/context.go +++ b/pkg/gui/context.go @@ -15,32 +15,34 @@ const ( PERSISTENT_POPUP ) +type ContextKey string + 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" - SUB_COMMITS_CONTEXT_KEY = "subCommits" - 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" - SEARCH_CONTEXT_KEY = "search" - COMMIT_MESSAGE_CONTEXT_KEY = "commitMessage" - SUBMODULES_CONTEXT_KEY = "submodules" - SUGGESTIONS_CONTEXT_KEY = "suggestions" + STATUS_CONTEXT_KEY ContextKey = "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" + SUB_COMMITS_CONTEXT_KEY = "subCommits" + 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" + SEARCH_CONTEXT_KEY = "search" + COMMIT_MESSAGE_CONTEXT_KEY = "commitMessage" + SUBMODULES_CONTEXT_KEY = "submodules" + SUGGESTIONS_CONTEXT_KEY = "suggestions" ) -var allContextKeys = []string{ +var allContextKeys = []ContextKey{ STATUS_CONTEXT_KEY, FILES_CONTEXT_KEY, LOCAL_BRANCHES_CONTEXT_KEY, @@ -124,7 +126,7 @@ type Context interface { GetViewName() string GetWindowName() string SetWindowName(string) - GetKey() string + GetKey() ContextKey SetParentContext(Context) // we return a bool here to tell us whether or not the returned value just wraps a nil @@ -138,7 +140,7 @@ type BasicContext struct { OnRender func() error OnGetOptionsMap func() map[string]string Kind ContextKind - Key string + Key ContextKey ViewName string } @@ -192,7 +194,7 @@ func (c BasicContext) GetKind() ContextKind { return c.Kind } -func (c BasicContext) GetKey() string { +func (c BasicContext) GetKey() ContextKey { return c.Key } @@ -353,7 +355,7 @@ func (tree ContextTree) initialViewTabContextMap() map[string][]tabContext { } } -func (gui *Gui) currentContextKeyIgnoringPopups() string { +func (gui *Gui) currentContextKeyIgnoringPopups() ContextKey { gui.State.ContextManager.Lock() defer gui.State.ContextManager.Unlock() @@ -461,7 +463,10 @@ func (gui *Gui) returnFromContext() error { 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 || c.GetKey() == COMMIT_FILES_CONTEXT_KEY { - _, _ = gui.g.SetViewOnBottom(c.GetViewName()) + view, err := gui.g.View(c.GetViewName()) + if err == nil { + view.Visible = false + } } if err := c.HandleFocusLost(); err != nil { @@ -480,7 +485,7 @@ func (gui *Gui) postRefreshUpdate(c Context) error { return nil } - if v.Context != c.GetKey() { + if ContextKey(v.Context) != c.GetKey() { return nil } @@ -504,7 +509,7 @@ func (gui *Gui) activateContext(c Context) error { if err != nil { return gui.returnFromContext() } - originalViewContextKey := v.Context + originalViewContextKey := ContextKey(v.Context) // ensure that any other window for which this view was active is now set to the default for that window. gui.setViewAsActiveForWindow(viewName) @@ -522,10 +527,7 @@ func (gui *Gui) activateContext(c Context) error { return gui.returnFromContext() } - if _, err := gui.g.SetViewOnTop(viewName); err != nil { - // if view no longer exists, pop again - return gui.returnFromContext() - } + v.Visible = true // if the new context's view was previously displaying another context, render the new context if originalViewContextKey != c.GetKey() { @@ -534,7 +536,7 @@ func (gui *Gui) activateContext(c Context) error { } } - v.Context = c.GetKey() + v.Context = string(c.GetKey()) gui.g.Cursor = v.Editable @@ -559,7 +561,7 @@ func (gui *Gui) activateContext(c Context) error { func (gui *Gui) renderContextStack() string { result := "" for _, context := range gui.State.ContextManager.ContextStack { - result += context.GetKey() + "\n" + result += string(context.GetKey()) + "\n" } return result } @@ -575,7 +577,18 @@ func (gui *Gui) currentContext() Context { return gui.State.ContextManager.ContextStack[len(gui.State.ContextManager.ContextStack)-1] } -func (gui *Gui) currentSideContext() *ListContext { +// 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() @@ -583,7 +596,7 @@ func (gui *Gui) currentSideContext() *ListContext { // on startup the stack can be empty so we'll return an empty string in that case if len(stack) == 0 { - return nil + return gui.defaultSideContext() } // find the first context in the stack with the type of SIDE_CONTEXT @@ -591,20 +604,19 @@ func (gui *Gui) currentSideContext() *ListContext { context := stack[len(stack)-1-i] if context.GetKind() == SIDE_CONTEXT { - // not all side contexts are list contexts (e.g. the status panel) - listContext, ok := context.(*ListContext) - if !ok { - return nil - } - return listContext + return context } } - return nil + return gui.defaultSideContext() } func (gui *Gui) defaultSideContext() Context { - return gui.State.Contexts.Files + if gui.State.Modes.Filtering.Active() { + return gui.State.Contexts.BranchCommits + } else { + return gui.State.Contexts.Files + } } // remove the need to do this: always use a mapping @@ -618,7 +630,7 @@ func (gui *Gui) setInitialViewContexts() { continue } - view.Context = context.GetKey() + view.Context = string(context.GetKey()) } } @@ -678,15 +690,15 @@ func (gui *Gui) onViewFocusLost(v *gocui.View, newView *gocui.View) error { // 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 -func (gui *Gui) changeMainViewsContext(contextKey string) { +func (gui *Gui) changeMainViewsContext(contextKey ContextKey) { if gui.State.MainContext == contextKey { return } 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 + gui.Views.Main.Context = string(contextKey) + gui.Views.Secondary.Context = string(contextKey) default: panic(fmt.Sprintf("unknown context for main: %s", contextKey)) } @@ -737,7 +749,7 @@ type tabContext struct { contexts []Context } -func (gui *Gui) mustContextForContextKey(contextKey string) Context { +func (gui *Gui) mustContextForContextKey(contextKey ContextKey) Context { context, ok := gui.contextForContextKey(contextKey) if !ok { @@ -747,7 +759,7 @@ func (gui *Gui) mustContextForContextKey(contextKey string) Context { return context } -func (gui *Gui) contextForContextKey(contextKey string) (Context, bool) { +func (gui *Gui) contextForContextKey(contextKey ContextKey) (Context, bool) { for _, context := range gui.allContexts() { if context.GetKey() == contextKey { return context, true @@ -763,7 +775,7 @@ func (gui *Gui) rerenderView(viewName string) error { return nil } - contextKey := v.Context + contextKey := ContextKey(v.Context) context := gui.mustContextForContextKey(contextKey) return context.HandleRender() @@ -782,7 +794,7 @@ func (gui *Gui) rerenderView(viewName string) error { // } func (gui *Gui) getSideContextSelectedItemId() string { - currentSideContext := gui.currentSideContext() + currentSideContext := gui.currentSideListContext() if currentSideContext == nil { return "" } diff --git a/pkg/gui/credentials_panel.go b/pkg/gui/credentials_panel.go index 3410d3dee..107a5038c 100644 --- a/pkg/gui/credentials_panel.go +++ b/pkg/gui/credentials_panel.go @@ -40,7 +40,7 @@ func (gui *Gui) promptUserForCredential(passOrUname string) string { } func (gui *Gui) handleSubmitCredential() error { - credentialsView := gui.getCredentialsView() + credentialsView := gui.Views.Credentials message := gui.trimmedContent(credentialsView) gui.credentials <- message gui.clearEditorView(credentialsView) diff --git a/pkg/gui/custom_commands.go b/pkg/gui/custom_commands.go index 09af7065c..59c08bcf1 100644 --- a/pkg/gui/custom_commands.go +++ b/pkg/gui/custom_commands.go @@ -176,9 +176,14 @@ func (gui *Gui) GetCustomCommandKeybindings() []*Binding { case "": log.Fatalf("Error parsing custom command keybindings: context not provided (use context: 'global' for the global context). Key: %s, Command: %s", customCommand.Key, customCommand.Command) default: - context, ok := gui.contextForContextKey(customCommand.Context) + context, ok := gui.contextForContextKey(ContextKey(customCommand.Context)) + // stupid golang making me build an array of strings for this. + allContextKeyStrings := make([]string, len(allContextKeys)) + for i := range allContextKeys { + allContextKeyStrings[i] = string(allContextKeys[i]) + } if !ok { - log.Fatalf("Error when setting custom command keybindings: unknown context: %s. Key: %s, Command: %s.\nPermitted contexts: %s", customCommand.Context, customCommand.Key, customCommand.Command, strings.Join(allContextKeys, ", ")) + log.Fatalf("Error when setting custom command keybindings: unknown context: %s. Key: %s, Command: %s.\nPermitted contexts: %s", customCommand.Context, customCommand.Key, customCommand.Command, strings.Join(allContextKeyStrings, ", ")) } // here we assume that a given context will always belong to the same view. // Currently this is a safe bet but it's by no means guaranteed in the long term diff --git a/pkg/gui/diffing.go b/pkg/gui/diffing.go index bd29664fe..5ac83fe24 100644 --- a/pkg/gui/diffing.go +++ b/pkg/gui/diffing.go @@ -14,7 +14,7 @@ func (gui *Gui) renderDiff() error { cmd := gui.OSCommand.ExecutableFromString( fmt.Sprintf("git diff --submodule --no-ext-diff --color %s", gui.diffStr()), ) - task := gui.createRunPtyTask(cmd) + task := NewRunPtyTask(cmd) return gui.refreshMainViews(refreshMainOpts{ main: &viewUpdateOpts{ @@ -49,7 +49,7 @@ func (gui *Gui) currentDiffTerminals() []string { } return nil default: - context := gui.currentSideContext() + context := gui.currentSideListContext() if context == nil { return nil } diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 5d8601861..261635770 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -42,7 +42,7 @@ func (gui *Gui) getSelectedPath() string { } func (gui *Gui) selectFile(alreadySelected bool) error { - gui.getFilesView().FocusPoint(0, gui.State.Panels.Files.SelectedLineIdx) + gui.Views.Files.FocusPoint(0, gui.State.Panels.Files.SelectedLineIdx) node := gui.getSelectedFileNode() @@ -50,17 +50,17 @@ func (gui *Gui) selectFile(alreadySelected bool) error { return gui.refreshMainViews(refreshMainOpts{ main: &viewUpdateOpts{ title: "", - task: gui.createRenderStringTask(gui.Tr.NoChangedFiles), + task: NewRenderStringTask(gui.Tr.NoChangedFiles), }, }) } if !alreadySelected { // TODO: pull into update task interface - if err := gui.resetOrigin(gui.getMainView()); err != nil { + if err := gui.resetOrigin(gui.Views.Main); err != nil { return err } - if err := gui.resetOrigin(gui.getSecondaryView()); err != nil { + if err := gui.resetOrigin(gui.Views.Secondary); err != nil { return err } gui.takeOverMergeConflictScrolling() @@ -75,7 +75,7 @@ func (gui *Gui) selectFile(alreadySelected bool) error { refreshOpts := refreshMainOpts{main: &viewUpdateOpts{ title: gui.Tr.UnstagedChanges, - task: gui.createRunPtyTask(cmd), + task: NewRunPtyTask(cmd), }} if node.GetHasUnstagedChanges() { @@ -85,7 +85,7 @@ func (gui *Gui) selectFile(alreadySelected bool) error { refreshOpts.secondary = &viewUpdateOpts{ title: gui.Tr.StagedChanges, - task: gui.createRunPtyTask(cmd), + task: NewRunPtyTask(cmd), } } } else { @@ -105,11 +105,6 @@ func (gui *Gui) refreshFilesAndSubmodules() error { selectedPath := gui.getSelectedPath() - filesView := gui.getFilesView() - if filesView == nil { - // if the filesView hasn't been instantiated yet we just return - return nil - } if err := gui.refreshStateSubmoduleConfigs(); err != nil { return err } @@ -122,14 +117,14 @@ func (gui *Gui) refreshFilesAndSubmodules() error { gui.Log.Error(err) } - if gui.getFilesView().Context == FILES_CONTEXT_KEY { + if gui.Views.Files.Context == FILES_CONTEXT_KEY { // doing this a little custom (as opposed to using gui.postRefreshUpdate) because we handle selecting the file explicitly below if err := gui.State.Contexts.Files.HandleRender(); err != nil { return err } } - if gui.currentContext().GetKey() == FILES_CONTEXT_KEY || (g.CurrentView() == gui.getMainView() && g.CurrentView().Context == MAIN_MERGING_CONTEXT_KEY) { + if gui.currentContext().GetKey() == FILES_CONTEXT_KEY || (g.CurrentView() == gui.Views.Main && g.CurrentView().Context == MAIN_MERGING_CONTEXT_KEY) { newSelectedPath := gui.getSelectedPath() alreadySelected := selectedPath != "" && newSelectedPath == selectedPath if err := gui.selectFile(alreadySelected); err != nil { @@ -350,7 +345,7 @@ func (gui *Gui) handleWIPCommitPress() error { } _ = gui.renderStringSync("commitMessage", skipHookPreifx) - if err := gui.getCommitMessageView().SetCursor(len(skipHookPreifx), 0); err != nil { + if err := gui.Views.CommitMessage.SetCursor(len(skipHookPreifx), 0); err != nil { return err } @@ -389,7 +384,6 @@ func (gui *Gui) handleCommitPress() error { return gui.promptToStageAllAndRetry(gui.handleCommitPress) } - commitMessageView := gui.getCommitMessageView() commitPrefixConfig := gui.commitPrefixConfigForRepo() if commitPrefixConfig != nil { prefixPattern := commitPrefixConfig.Pattern @@ -400,7 +394,7 @@ func (gui *Gui) handleCommitPress() error { } prefix := rgx.ReplaceAllString(gui.getCheckedOutBranch().Name, prefixReplace) gui.renderString("commitMessage", prefix) - if err := commitMessageView.SetCursor(len(prefix), 0); err != nil { + if err := gui.Views.CommitMessage.SetCursor(len(prefix), 0); err != nil { return err } } @@ -862,7 +856,7 @@ func (gui *Gui) handleToggleFileTreeView() error { } } - if gui.getFilesView().Context == FILES_CONTEXT_KEY { + if gui.Views.Files.Context == FILES_CONTEXT_KEY { if err := gui.State.Contexts.Files.HandleRender(); err != nil { return err } diff --git a/pkg/gui/global_handlers.go b/pkg/gui/global_handlers.go index b32981f0c..d4e0923b7 100644 --- a/pkg/gui/global_handlers.go +++ b/pkg/gui/global_handlers.go @@ -5,6 +5,7 @@ import ( "math" "strings" + "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/utils" ) @@ -59,30 +60,22 @@ func (gui *Gui) prevScreenMode() error { return gui.rerenderViewsWithScreenModeDependentContent() } -func (gui *Gui) scrollUpView(viewName string) error { - mainView, err := gui.g.View(viewName) - if err != nil { - return nil - } - ox, oy := mainView.Origin() +func (gui *Gui) scrollUpView(view *gocui.View) error { + ox, oy := view.Origin() newOy := int(math.Max(0, float64(oy-gui.Config.GetUserConfig().Gui.ScrollHeight))) - return mainView.SetOrigin(ox, newOy) + return view.SetOrigin(ox, newOy) } -func (gui *Gui) scrollDownView(viewName string) error { - mainView, err := gui.g.View(viewName) - if err != nil { - return nil - } - ox, oy := mainView.Origin() +func (gui *Gui) scrollDownView(view *gocui.View) error { + ox, oy := view.Origin() y := oy canScrollPastBottom := gui.Config.GetUserConfig().Gui.ScrollPastBottom if !canScrollPastBottom { - _, sy := mainView.Size() + _, sy := view.Size() y += sy } scrollHeight := gui.Config.GetUserConfig().Gui.ScrollHeight - scrollableLines := mainView.ViewLinesHeight() - y + scrollableLines := view.ViewLinesHeight() - y if scrollableLines > 0 { // margin is about how many lines must still appear if you scroll // all the way down. In practice every file ends in a newline so it will really @@ -95,12 +88,12 @@ func (gui *Gui) scrollDownView(viewName string) error { scrollHeight = scrollableLines - margin } if oy+scrollHeight >= 0 { - if err := mainView.SetOrigin(ox, oy+scrollHeight); err != nil { + if err := view.SetOrigin(ox, oy+scrollHeight); err != nil { return err } } } - if manager, ok := gui.viewBufferManagerMap[viewName]; ok { + if manager, ok := gui.viewBufferManagerMap[view.Name()]; ok { manager.ReadLines(scrollHeight) } return nil @@ -111,7 +104,7 @@ func (gui *Gui) scrollUpMain() error { gui.State.Panels.Merging.UserScrolling = true } - return gui.scrollUpView("main") + return gui.scrollUpView(gui.Views.Main) } func (gui *Gui) scrollDownMain() error { @@ -119,33 +112,31 @@ func (gui *Gui) scrollDownMain() error { gui.State.Panels.Merging.UserScrolling = true } - return gui.scrollDownView("main") + return gui.scrollDownView(gui.Views.Main) } func (gui *Gui) scrollUpSecondary() error { - return gui.scrollUpView("secondary") + return gui.scrollUpView(gui.Views.Secondary) } func (gui *Gui) scrollDownSecondary() error { - return gui.scrollDownView("secondary") + return gui.scrollDownView(gui.Views.Secondary) } func (gui *Gui) scrollUpConfirmationPanel() error { - view := gui.getConfirmationView() - if view != nil || view.Editable { + if gui.Views.Confirmation.Editable { return nil } - return gui.scrollUpView("confirmation") + return gui.scrollUpView(gui.Views.Confirmation) } func (gui *Gui) scrollDownConfirmationPanel() error { - view := gui.getConfirmationView() - if view != nil || view.Editable { + if gui.Views.Confirmation.Editable { return nil } - return gui.scrollDownView("confirmation") + return gui.scrollDownView(gui.Views.Confirmation) } func (gui *Gui) handleRefresh() error { @@ -157,16 +148,14 @@ func (gui *Gui) handleMouseDownMain() error { return nil } - view := gui.getMainView() - switch gui.g.CurrentView().Name() { case "files": // set filename, set primary/secondary selected, set line number, then switch context // I'll need to know it was changed though. // Could I pass something along to the context change? - return gui.enterFile(false, view.SelectedLineIdx()) + return gui.enterFile(false, gui.Views.Main.SelectedLineIdx()) case "commitFiles": - return gui.enterCommitFile(view.SelectedLineIdx()) + return gui.enterCommitFile(gui.Views.Main.SelectedLineIdx()) } return nil @@ -177,11 +166,9 @@ func (gui *Gui) handleMouseDownSecondary() error { return nil } - view := gui.getSecondaryView() - switch gui.g.CurrentView().Name() { case "files": - return gui.enterFile(true, view.SelectedLineIdx()) + return gui.enterFile(true, gui.Views.Secondary.SelectedLineIdx()) } return nil @@ -192,7 +179,7 @@ func (gui *Gui) handleInfoClick() error { return nil } - view := gui.getInformationView() + view := gui.Views.Information cx, _ := view.Cursor() width, _ := view.Size() diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 93fed0525..80f73dba0 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -106,6 +106,8 @@ type Gui struct { // this tells us whether our views have been initially set up ViewsSetup bool + + Views Views } type RecordedEvent struct { @@ -232,6 +234,28 @@ type panelStates struct { Suggestions *suggestionsPanelState } +type Views struct { + Status *gocui.View + Files *gocui.View + Branches *gocui.View + Commits *gocui.View + Stash *gocui.View + Main *gocui.View + Secondary *gocui.View + Options *gocui.View + Confirmation *gocui.View + Menu *gocui.View + Credentials *gocui.View + CommitMessage *gocui.View + CommitFiles *gocui.View + Information *gocui.View + AppStatus *gocui.View + Search *gocui.View + SearchPrefix *gocui.View + Limit *gocui.View + Suggestions *gocui.View +} + type searchingState struct { view *gocui.View isSearching bool @@ -260,7 +284,7 @@ type CherryPicking struct { CherryPickedCommits []*models.Commit // we only allow cherry picking from one context at a time, so you can't copy a commit from the local commits context and then also copy a commit in the reflog context - ContextKey string + ContextKey ContextKey } func (m *CherryPicking) Active() bool { @@ -306,8 +330,8 @@ type guiState struct { MenuItems []*menuItem Updating bool Panels *panelStates - MainContext string // used to keep the main and secondary views' contexts in sync SplitMainPanel bool + MainContext ContextKey // used to keep the main and secondary views' contexts in sync RetainOriginalDir bool IsRefreshingFiles bool Searching searchingState diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index d49151cfb..a99143215 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -625,21 +625,21 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { ViewName: "branches", Contexts: []string{TAGS_CONTEXT_KEY}, Key: gui.getKey(config.Universal.Select), - Handler: gui.handleCheckoutTag, + Handler: gui.withSelectedTag(gui.handleCheckoutTag), Description: gui.Tr.LcCheckout, }, { ViewName: "branches", Contexts: []string{TAGS_CONTEXT_KEY}, Key: gui.getKey(config.Universal.Remove), - Handler: gui.handleDeleteTag, + Handler: gui.withSelectedTag(gui.handleDeleteTag), Description: gui.Tr.LcDeleteTag, }, { ViewName: "branches", Contexts: []string{TAGS_CONTEXT_KEY}, Key: gui.getKey(config.Branches.PushTag), - Handler: gui.handlePushTag, + Handler: gui.withSelectedTag(gui.handlePushTag), Description: gui.Tr.LcPushTag, }, { @@ -653,7 +653,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { ViewName: "branches", Contexts: []string{TAGS_CONTEXT_KEY}, Key: gui.getKey(config.Commits.ViewResetOptions), - Handler: gui.handleCreateResetToTagMenu, + Handler: gui.withSelectedTag(gui.handleCreateResetToTagMenu), Description: gui.Tr.LcViewResetOptions, OpensMenu: true, }, diff --git a/pkg/gui/layout.go b/pkg/gui/layout.go index 6d1c956d3..d319873c7 100644 --- a/pkg/gui/layout.go +++ b/pkg/gui/layout.go @@ -31,29 +31,22 @@ func (gui *Gui) layout(g *gocui.Gui) error { minimumHeight := 9 minimumWidth := 10 - if height < minimumHeight || width < minimumWidth { - v, err := g.SetView("limit", 0, 0, width-1, height-1, 0) - if err != nil { - if err.Error() != UNKNOWN_VIEW_ERROR_MSG { - return err - } - v.Title = gui.Tr.NotEnoughSpace - v.Wrap = true - _, _ = g.SetViewOnTop("limit") + var err error + gui.Views.Limit, err = g.SetView("limit", 0, 0, width-1, height-1, 0) + if err != nil { + if err.Error() != UNKNOWN_VIEW_ERROR_MSG { + return err } - return nil + gui.Views.Limit.Title = gui.Tr.NotEnoughSpace + gui.Views.Limit.Wrap = true } + gui.Views.Limit.Visible = height < minimumHeight || width < minimumWidth informationStr := gui.informationStr() appStatus := gui.statusManager.getStatusString() viewDimensions := gui.getWindowDimensions(informationStr, appStatus) - _, _ = g.SetViewOnBottom("limit") - _ = g.DeleteView("limit") - - textColor := theme.GocuiDefaultTextColor - // reading more lines into main view buffers upon resize prevMainView, err := gui.g.View("main") if err == nil { @@ -79,17 +72,17 @@ func (gui *Gui) layout(g *gocui.Gui) error { // to render content as soon as it appears, because lazyloaded content (via a pty task) // cares about the size of the view. view, err := g.SetView(viewName, 0, 0, width, height, 0) - if err != nil { - return view, err + if view != nil { + view.Visible = false } - return g.SetViewOnBottom(viewName) + return view, err } frameOffset := 1 if frame { frameOffset = 0 } - return g.SetView( + view, err := g.SetView( viewName, dimensionsObj.X0-frameOffset, dimensionsObj.Y0-frameOffset, @@ -97,172 +90,150 @@ func (gui *Gui) layout(g *gocui.Gui) error { dimensionsObj.Y1+frameOffset, 0, ) + + if view != nil { + view.Visible = true + } + + return view, err } - v, err := setViewFromDimensions("main", "main", true) + gui.Views.Main, err = setViewFromDimensions("main", "main", true) if err != nil { if err.Error() != UNKNOWN_VIEW_ERROR_MSG { return err } - v.Title = gui.Tr.DiffTitle - v.Wrap = true - v.FgColor = textColor - v.IgnoreCarriageReturns = true + gui.Views.Main.Title = gui.Tr.DiffTitle + gui.Views.Main.Wrap = true + gui.Views.Main.FgColor = theme.GocuiDefaultTextColor + gui.Views.Main.IgnoreCarriageReturns = true } - secondaryView, err := setViewFromDimensions("secondary", "secondary", true) + gui.Views.Secondary, err = setViewFromDimensions("secondary", "secondary", true) if err != nil { if err.Error() != UNKNOWN_VIEW_ERROR_MSG { return err } - secondaryView.Title = gui.Tr.DiffTitle - secondaryView.Wrap = true - secondaryView.FgColor = textColor - secondaryView.IgnoreCarriageReturns = true + gui.Views.Secondary.Title = gui.Tr.DiffTitle + gui.Views.Secondary.Wrap = true + gui.Views.Secondary.FgColor = theme.GocuiDefaultTextColor + gui.Views.Secondary.IgnoreCarriageReturns = true } - hiddenViewOffset := 9999 - - if v, err := setViewFromDimensions("status", "status", true); err != nil { + if gui.Views.Status, err = setViewFromDimensions("status", "status", true); err != nil { if err.Error() != UNKNOWN_VIEW_ERROR_MSG { return err } - v.Title = gui.Tr.StatusTitle - v.FgColor = textColor + gui.Views.Status.Title = gui.Tr.StatusTitle + gui.Views.Status.FgColor = theme.GocuiDefaultTextColor } - filesView, err := setViewFromDimensions("files", "files", true) + gui.Views.Files, err = setViewFromDimensions("files", "files", true) if err != nil { if err.Error() != UNKNOWN_VIEW_ERROR_MSG { return err } - filesView.Highlight = true - filesView.Title = gui.Tr.FilesTitle - filesView.FgColor = textColor - filesView.ContainsList = true + gui.Views.Files.Highlight = true + gui.Views.Files.Title = gui.Tr.FilesTitle + gui.Views.Files.FgColor = theme.GocuiDefaultTextColor + gui.Views.Files.ContainsList = true } - branchesView, err := setViewFromDimensions("branches", "branches", true) + gui.Views.Branches, err = setViewFromDimensions("branches", "branches", true) if err != nil { if err.Error() != UNKNOWN_VIEW_ERROR_MSG { return err } - branchesView.Title = gui.Tr.BranchesTitle - branchesView.FgColor = textColor - branchesView.ContainsList = true + gui.Views.Branches.Title = gui.Tr.BranchesTitle + gui.Views.Branches.FgColor = theme.GocuiDefaultTextColor + gui.Views.Branches.ContainsList = true } - commitFilesView, err := setViewFromDimensions("commitFiles", gui.State.Contexts.CommitFiles.GetWindowName(), true) + gui.Views.CommitFiles, err = setViewFromDimensions("commitFiles", gui.State.Contexts.CommitFiles.GetWindowName(), true) if err != nil { if err.Error() != UNKNOWN_VIEW_ERROR_MSG { return err } - commitFilesView.Title = gui.Tr.CommitFiles - commitFilesView.FgColor = textColor - commitFilesView.ContainsList = true - _, _ = gui.g.SetViewOnBottom("commitFiles") + gui.Views.CommitFiles.Title = gui.Tr.CommitFiles + gui.Views.CommitFiles.FgColor = theme.GocuiDefaultTextColor + gui.Views.CommitFiles.ContainsList = true } + // if the commit files view is the view to be displayed for its window, we'll display it + gui.Views.CommitFiles.Visible = gui.getViewNameForWindow(gui.State.Contexts.CommitFiles.GetWindowName()) == "commitFiles" - commitsView, err := setViewFromDimensions("commits", "commits", true) + gui.Views.Commits, err = setViewFromDimensions("commits", "commits", true) if err != nil { if err.Error() != UNKNOWN_VIEW_ERROR_MSG { return err } - commitsView.Title = gui.Tr.CommitsTitle - commitsView.FgColor = textColor - commitsView.ContainsList = true + gui.Views.Commits.Title = gui.Tr.CommitsTitle + gui.Views.Commits.FgColor = theme.GocuiDefaultTextColor + gui.Views.Commits.ContainsList = true } - stashView, err := setViewFromDimensions("stash", "stash", true) + gui.Views.Stash, err = setViewFromDimensions("stash", "stash", true) if err != nil { if err.Error() != UNKNOWN_VIEW_ERROR_MSG { return err } - stashView.Title = gui.Tr.StashTitle - stashView.FgColor = textColor - stashView.ContainsList = true + gui.Views.Stash.Title = gui.Tr.StashTitle + gui.Views.Stash.FgColor = theme.GocuiDefaultTextColor + gui.Views.Stash.ContainsList = true } - if gui.getCommitMessageView() == nil { - // doesn't matter where this view starts because it will be hidden - if commitMessageView, err := g.SetView("commitMessage", hiddenViewOffset, hiddenViewOffset, hiddenViewOffset+10, hiddenViewOffset+10, 0); err != nil { - if err.Error() != UNKNOWN_VIEW_ERROR_MSG { - return err - } - _, _ = g.SetViewOnBottom("commitMessage") - commitMessageView.Title = gui.Tr.CommitMessage - commitMessageView.FgColor = textColor - commitMessageView.Editable = true - commitMessageView.Editor = gocui.EditorFunc(gui.commitMessageEditor) - } - } - - if check, _ := g.View("credentials"); check == nil { - // doesn't matter where this view starts because it will be hidden - if credentialsView, err := g.SetView("credentials", hiddenViewOffset, hiddenViewOffset, hiddenViewOffset+10, hiddenViewOffset+10, 0); err != nil { - if err.Error() != UNKNOWN_VIEW_ERROR_MSG { - return err - } - _, _ = g.SetViewOnBottom("credentials") - credentialsView.Title = gui.Tr.CredentialsUsername - credentialsView.FgColor = textColor - credentialsView.Editable = true - } - } - - if v, err := setViewFromDimensions("options", "options", false); err != nil { + if gui.Views.Options, err = setViewFromDimensions("options", "options", false); err != nil { if err.Error() != UNKNOWN_VIEW_ERROR_MSG { return err } - v.Frame = false - v.FgColor = theme.OptionsColor + gui.Views.Options.Frame = false + gui.Views.Options.FgColor = theme.OptionsColor } // this view takes up one character. Its only purpose is to show the slash when searching - if searchPrefixView, err := setViewFromDimensions("searchPrefix", "searchPrefix", false); err != nil { + if gui.Views.SearchPrefix, err = setViewFromDimensions("searchPrefix", "searchPrefix", false); err != nil { if err.Error() != UNKNOWN_VIEW_ERROR_MSG { return err } - searchPrefixView.BgColor = gocui.ColorDefault - searchPrefixView.FgColor = gocui.ColorGreen - searchPrefixView.Frame = false - gui.setViewContent(searchPrefixView, SEARCH_PREFIX) + gui.Views.SearchPrefix.BgColor = gocui.ColorDefault + gui.Views.SearchPrefix.FgColor = gocui.ColorGreen + gui.Views.SearchPrefix.Frame = false + gui.setViewContent(gui.Views.SearchPrefix, SEARCH_PREFIX) } - if searchView, err := setViewFromDimensions("search", "search", false); err != nil { + if gui.Views.Search, err = setViewFromDimensions("search", "search", false); err != nil { if err.Error() != UNKNOWN_VIEW_ERROR_MSG { return err } - searchView.BgColor = gocui.ColorDefault - searchView.FgColor = gocui.ColorGreen - searchView.Frame = false - searchView.Editable = true + gui.Views.Search.BgColor = gocui.ColorDefault + gui.Views.Search.FgColor = gocui.ColorGreen + gui.Views.Search.Frame = false + gui.Views.Search.Editable = true } - if appStatusView, err := setViewFromDimensions("appStatus", "appStatus", false); err != nil { + if gui.Views.AppStatus, err = setViewFromDimensions("appStatus", "appStatus", false); err != nil { if err.Error() != UNKNOWN_VIEW_ERROR_MSG { return err } - appStatusView.BgColor = gocui.ColorDefault - appStatusView.FgColor = gocui.ColorCyan - appStatusView.Frame = false - _, _ = g.SetViewOnBottom("appStatus") + gui.Views.AppStatus.BgColor = gocui.ColorDefault + gui.Views.AppStatus.FgColor = gocui.ColorCyan + gui.Views.AppStatus.Frame = false + gui.Views.AppStatus.Visible = false } - informationView, err := setViewFromDimensions("information", "information", false) + gui.Views.Information, err = setViewFromDimensions("information", "information", false) if err != nil { if err.Error() != UNKNOWN_VIEW_ERROR_MSG { return err } - informationView.BgColor = gocui.ColorDefault - informationView.FgColor = gocui.ColorGreen - informationView.Frame = false + gui.Views.Information.BgColor = gocui.ColorDefault + gui.Views.Information.FgColor = gocui.ColorGreen + gui.Views.Information.Frame = false gui.renderString("information", INFO_SECTION_PADDING+informationStr) } if gui.State.OldInformation != informationStr { - gui.setViewContent(informationView, informationStr) + gui.setViewContent(gui.Views.Information, informationStr) gui.State.OldInformation = informationStr } @@ -289,7 +260,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { } // ignore contexts whose view is owned by another context right now - if view.Context != listContext.GetKey() { + if ContextKey(view.Context) != listContext.GetKey() { continue } @@ -302,9 +273,9 @@ func (gui *Gui) layout(g *gocui.Gui) error { view.SetOnSelectItem(gui.onSelectItemWrapper(listContext.onSearchSelect)) } - gui.getMainView().SetOnSelectItem(gui.onSelectItemWrapper(gui.handlelineByLineNavigateTo)) + gui.Views.Main.SetOnSelectItem(gui.onSelectItemWrapper(gui.handlelineByLineNavigateTo)) - mainViewWidth, mainViewHeight := gui.getMainView().Size() + mainViewWidth, mainViewHeight := gui.Views.Main.Size() if mainViewWidth != gui.State.PrevMainWidth || mainViewHeight != gui.State.PrevMainHeight { gui.State.PrevMainWidth = mainViewWidth gui.State.PrevMainHeight = mainViewHeight @@ -320,28 +291,24 @@ func (gui *Gui) layout(g *gocui.Gui) error { return gui.resizeCurrentPopupPanel() } +func (gui *Gui) setHiddenView(viewName string) (*gocui.View, error) { + // arbitrarily giving the view enough size so that we don't get an error, but + // it's expected that the view will be given the correct size before being shown + return gui.g.SetView(viewName, 0, 0, 10, 10, 0) +} + func (gui *Gui) onInitialViewsCreationForRepo() error { gui.setInitialViewContexts() // hide any popup views. This only applies when we've just switched repos for _, viewName := range gui.popupViewNames() { - _, _ = gui.g.SetViewOnBottom(viewName) - } - - // the status panel is not actually a list context at the moment, so it is excluded - // here. Arguably that's quite convenient because it means we're back to starting - // in the files panel when landing in a new repo, but when returning from a submodule - // we'll be back in the submodules context. This still seems awkward though, and it's - // definitely going to break when (if) we make the status context a list context - initialContext := gui.currentSideContext() - if initialContext == nil { - if gui.State.Modes.Filtering.Active() { - initialContext = gui.State.Contexts.BranchCommits - } else { - initialContext = gui.State.Contexts.Files + view, err := gui.g.View(viewName) + if err == nil { + view.Visible = false } } + initialContext := gui.currentSideContext() if err := gui.pushContext(initialContext); err != nil { return err } @@ -350,6 +317,49 @@ func (gui *Gui) onInitialViewsCreationForRepo() error { } func (gui *Gui) onInitialViewsCreation() error { + // creating some views which are hidden at the start but we need to exist so that we can set an initial ordering + if err := gui.createHiddenViews(); err != nil { + return err + } + + // now we order the views (in order of bottom first) + layerOneViews := []*gocui.View{ + // first layer. Ordering within this layer does not matter because there are + // no overlapping views + gui.Views.Status, + gui.Views.Files, + gui.Views.Branches, + gui.Views.Commits, + gui.Views.Stash, + gui.Views.CommitFiles, + gui.Views.Main, + gui.Views.Secondary, + + // bottom line + gui.Views.Options, + gui.Views.AppStatus, + gui.Views.Information, + gui.Views.Search, + gui.Views.SearchPrefix, + + // popups. Ordering within this layer does not matter because there should + // only be one popup shown at a time + gui.Views.CommitMessage, + gui.Views.Credentials, + gui.Views.Menu, + gui.Views.Confirmation, + gui.Views.Suggestions, + + // this guy will cover everything else when it appears + gui.Views.Limit, + } + + for _, view := range layerOneViews { + if _, err := gui.g.SetViewOnTop(view.Name()); err != nil { + return err + } + } + gui.g.Mutexes.ViewsMutex.Lock() // add tabs to views for _, view := range gui.g.Views() { @@ -378,3 +388,58 @@ func (gui *Gui) onInitialViewsCreation() error { return nil } + +func (gui *Gui) createHiddenViews() error { + // doesn't matter where this view starts because it will be hidden + var err error + if gui.Views.CommitMessage, err = gui.setHiddenView("commitMessage"); err != nil { + if err.Error() != UNKNOWN_VIEW_ERROR_MSG { + return err + } + gui.Views.CommitMessage.Visible = false + gui.Views.CommitMessage.Title = gui.Tr.CommitMessage + gui.Views.CommitMessage.FgColor = theme.GocuiDefaultTextColor + gui.Views.CommitMessage.Editable = true + gui.Views.CommitMessage.Editor = gocui.EditorFunc(gui.commitMessageEditor) + } + + // doesn't matter where this view starts because it will be hidden + if gui.Views.Credentials, err = gui.setHiddenView("credentials"); err != nil { + if err.Error() != UNKNOWN_VIEW_ERROR_MSG { + return err + } + gui.Views.Credentials.Visible = false + gui.Views.Credentials.Title = gui.Tr.CredentialsUsername + gui.Views.Credentials.FgColor = theme.GocuiDefaultTextColor + gui.Views.Credentials.Editable = true + } + + // not worrying about setting attributes because that will be done when the view is actually shown + gui.Views.Confirmation, err = gui.setHiddenView("confirmation") + if err != nil { + if err.Error() != UNKNOWN_VIEW_ERROR_MSG { + return err + } + gui.Views.Confirmation.Visible = false + } + + // not worrying about setting attributes because that will be done when the view is actually shown + gui.Views.Suggestions, err = gui.setHiddenView("suggestions") + if err != nil { + if err.Error() != UNKNOWN_VIEW_ERROR_MSG { + return err + } + gui.Views.Suggestions.Visible = false + } + + // not worrying about setting attributes because that will be done when the view is actually shown + gui.Views.Menu, err = gui.setHiddenView("menu") + if err != nil { + if err.Error() != UNKNOWN_VIEW_ERROR_MSG { + return err + } + gui.Views.Menu.Visible = false + } + + return nil +} diff --git a/pkg/gui/line_by_line_panel.go b/pkg/gui/line_by_line_panel.go index f8c21ab8d..86cd426a4 100644 --- a/pkg/gui/line_by_line_panel.go +++ b/pkg/gui/line_by_line_panel.go @@ -82,9 +82,8 @@ func (gui *Gui) refreshLineByLinePanel(diff string, secondaryDiff string, second return false, err } - secondaryView := gui.getSecondaryView() - secondaryView.Highlight = true - secondaryView.Wrap = false + gui.Views.Secondary.Highlight = true + gui.Views.Secondary.Wrap = false secondaryPatchParser, err := patch.NewPatchParser(gui.Log, secondaryDiff) if err != nil { @@ -92,7 +91,7 @@ func (gui *Gui) refreshLineByLinePanel(diff string, secondaryDiff string, second } gui.g.Update(func(*gocui.Gui) error { - gui.setViewContent(gui.getSecondaryView(), secondaryPatchParser.Render(-1, -1, nil)) + gui.setViewContent(gui.Views.Secondary, secondaryPatchParser.Render(-1, -1, nil)) return nil }) @@ -184,7 +183,7 @@ func (gui *Gui) handleLBLMouseDown() error { return nil } - newSelectedLineIdx := gui.getMainView().SelectedLineIdx() + newSelectedLineIdx := gui.Views.Main.SelectedLineIdx() state.FirstLineIdx = newSelectedLineIdx state.LastLineIdx = newSelectedLineIdx @@ -200,7 +199,7 @@ func (gui *Gui) handleMouseDrag() error { return nil } - return gui.LBLSelectLine(gui.getMainView().SelectedLineIdx(), state) + return gui.LBLSelectLine(gui.Views.Main.SelectedLineIdx(), state) }) } @@ -248,12 +247,11 @@ func (gui *Gui) refreshMainViewForLineByLine(state *lBlPanelState) error { } colorDiff := state.PatchParser.Render(state.FirstLineIdx, state.LastLineIdx, includedLineIndices) - mainView := gui.getMainView() - mainView.Highlight = true - mainView.Wrap = false + gui.Views.Main.Highlight = true + gui.Views.Main.Wrap = false gui.g.Update(func(*gocui.Gui) error { - gui.setViewContent(gui.getMainView(), colorDiff) + gui.setViewContent(gui.Views.Main, colorDiff) return nil }) @@ -263,7 +261,7 @@ func (gui *Gui) refreshMainViewForLineByLine(state *lBlPanelState) error { // focusSelection works out the best focus for the staging panel given the // selected line and size of the hunk func (gui *Gui) focusSelection(includeCurrentHunk bool, state *lBlPanelState) error { - stagingView := gui.getMainView() + stagingView := gui.Views.Main _, viewHeight := stagingView.Size() bufferHeight := viewHeight - 1 @@ -367,7 +365,7 @@ func (gui *Gui) handleOpenFileAtLine() error { func (gui *Gui) handleLineByLineNextPage() error { return gui.withLBLActiveCheck(func(state *lBlPanelState) error { - newSelectedLineIdx := state.SelectedLineIdx + gui.pageDelta(gui.getMainView()) + newSelectedLineIdx := state.SelectedLineIdx + gui.pageDelta(gui.Views.Main) return gui.lineByLineNavigateTo(newSelectedLineIdx, state) }) @@ -375,7 +373,7 @@ func (gui *Gui) handleLineByLineNextPage() error { func (gui *Gui) handleLineByLinePrevPage() error { return gui.withLBLActiveCheck(func(state *lBlPanelState) error { - newSelectedLineIdx := state.SelectedLineIdx - gui.pageDelta(gui.getMainView()) + newSelectedLineIdx := state.SelectedLineIdx - gui.pageDelta(gui.Views.Main) return gui.lineByLineNavigateTo(newSelectedLineIdx, state) }) diff --git a/pkg/gui/list_context.go b/pkg/gui/list_context.go index 9c11ee51d..356d6661c 100644 --- a/pkg/gui/list_context.go +++ b/pkg/gui/list_context.go @@ -9,7 +9,7 @@ import ( type ListContext struct { ViewName string - ContextKey string + ContextKey ContextKey GetItemsLength func() int GetDisplayStrings func() [][]string OnFocus func() error @@ -103,7 +103,7 @@ func (lc *ListContext) OnRender() error { return nil } -func (lc *ListContext) GetKey() string { +func (lc *ListContext) GetKey() ContextKey { return lc.ContextKey } @@ -136,10 +136,10 @@ func (lc *ListContext) HandleFocus() error { view.FocusPoint(0, lc.GetPanelState().GetSelectedLineIdx()) if lc.ResetMainViewOriginOnFocus { - if err := lc.Gui.resetOrigin(lc.Gui.getMainView()); err != nil { + if err := lc.Gui.resetOrigin(lc.Gui.Views.Main); err != nil { return err } - if err := lc.Gui.resetOrigin(lc.Gui.getSecondaryView()); err != nil { + if err := lc.Gui.resetOrigin(lc.Gui.Views.Secondary); err != nil { return err } } @@ -257,7 +257,7 @@ func (gui *Gui) menuListContext() *ListContext { return &ListContext{ ViewName: "menu", ContextKey: "menu", - GetItemsLength: func() int { return gui.getMenuView().LinesHeight() }, + GetItemsLength: func() int { return gui.Views.Menu.LinesHeight() }, GetPanelState: func() IListPanelState { return gui.State.Panels.Menu }, OnFocus: gui.handleMenuSelect, OnClickSelectedItem: gui.onMenuPress, @@ -556,16 +556,16 @@ func (gui *Gui) getListContextKeyBindings() []*Binding { listContext := listContext bindings = append(bindings, []*Binding{ - {ViewName: listContext.ViewName, Tag: "navigation", Contexts: []string{listContext.ContextKey}, Key: gui.getKey(keybindingConfig.Universal.PrevItemAlt), Modifier: gocui.ModNone, Handler: listContext.handlePrevLine}, - {ViewName: listContext.ViewName, Tag: "navigation", Contexts: []string{listContext.ContextKey}, Key: gui.getKey(keybindingConfig.Universal.PrevItem), Modifier: gocui.ModNone, Handler: listContext.handlePrevLine}, - {ViewName: listContext.ViewName, Tag: "navigation", Contexts: []string{listContext.ContextKey}, Key: gocui.MouseWheelUp, Modifier: gocui.ModNone, Handler: listContext.handlePrevLine}, - {ViewName: listContext.ViewName, Tag: "navigation", Contexts: []string{listContext.ContextKey}, Key: gui.getKey(keybindingConfig.Universal.NextItemAlt), Modifier: gocui.ModNone, Handler: listContext.handleNextLine}, - {ViewName: listContext.ViewName, Tag: "navigation", Contexts: []string{listContext.ContextKey}, Key: gui.getKey(keybindingConfig.Universal.NextItem), Modifier: gocui.ModNone, Handler: listContext.handleNextLine}, - {ViewName: listContext.ViewName, Tag: "navigation", Contexts: []string{listContext.ContextKey}, Key: gui.getKey(keybindingConfig.Universal.PrevPage), Modifier: gocui.ModNone, Handler: listContext.handlePrevPage, Description: gui.Tr.LcPrevPage}, - {ViewName: listContext.ViewName, Tag: "navigation", Contexts: []string{listContext.ContextKey}, Key: gui.getKey(keybindingConfig.Universal.NextPage), Modifier: gocui.ModNone, Handler: listContext.handleNextPage, Description: gui.Tr.LcNextPage}, - {ViewName: listContext.ViewName, Tag: "navigation", Contexts: []string{listContext.ContextKey}, Key: gui.getKey(keybindingConfig.Universal.GotoTop), Modifier: gocui.ModNone, Handler: listContext.handleGotoTop, Description: gui.Tr.LcGotoTop}, - {ViewName: listContext.ViewName, Tag: "navigation", Contexts: []string{listContext.ContextKey}, Key: gocui.MouseWheelDown, Modifier: gocui.ModNone, Handler: listContext.handleNextLine}, - {ViewName: listContext.ViewName, Contexts: []string{listContext.ContextKey}, Key: gocui.MouseLeft, Modifier: gocui.ModNone, Handler: listContext.handleClick}, + {ViewName: listContext.ViewName, Tag: "navigation", Contexts: []string{string(listContext.ContextKey)}, Key: gui.getKey(keybindingConfig.Universal.PrevItemAlt), Modifier: gocui.ModNone, Handler: listContext.handlePrevLine}, + {ViewName: listContext.ViewName, Tag: "navigation", Contexts: []string{string(listContext.ContextKey)}, Key: gui.getKey(keybindingConfig.Universal.PrevItem), Modifier: gocui.ModNone, Handler: listContext.handlePrevLine}, + {ViewName: listContext.ViewName, Tag: "navigation", Contexts: []string{string(listContext.ContextKey)}, Key: gocui.MouseWheelUp, Modifier: gocui.ModNone, Handler: listContext.handlePrevLine}, + {ViewName: listContext.ViewName, Tag: "navigation", Contexts: []string{string(listContext.ContextKey)}, Key: gui.getKey(keybindingConfig.Universal.NextItemAlt), Modifier: gocui.ModNone, Handler: listContext.handleNextLine}, + {ViewName: listContext.ViewName, Tag: "navigation", Contexts: []string{string(listContext.ContextKey)}, Key: gui.getKey(keybindingConfig.Universal.NextItem), Modifier: gocui.ModNone, Handler: listContext.handleNextLine}, + {ViewName: listContext.ViewName, Tag: "navigation", Contexts: []string{string(listContext.ContextKey)}, Key: gui.getKey(keybindingConfig.Universal.PrevPage), Modifier: gocui.ModNone, Handler: listContext.handlePrevPage, Description: gui.Tr.LcPrevPage}, + {ViewName: listContext.ViewName, Tag: "navigation", Contexts: []string{string(listContext.ContextKey)}, Key: gui.getKey(keybindingConfig.Universal.NextPage), Modifier: gocui.ModNone, Handler: listContext.handleNextPage, Description: gui.Tr.LcNextPage}, + {ViewName: listContext.ViewName, Tag: "navigation", Contexts: []string{string(listContext.ContextKey)}, Key: gui.getKey(keybindingConfig.Universal.GotoTop), Modifier: gocui.ModNone, Handler: listContext.handleGotoTop, Description: gui.Tr.LcGotoTop}, + {ViewName: listContext.ViewName, Tag: "navigation", Contexts: []string{string(listContext.ContextKey)}, Key: gocui.MouseWheelDown, Modifier: gocui.ModNone, Handler: listContext.handleNextLine}, + {ViewName: listContext.ViewName, Contexts: []string{string(listContext.ContextKey)}, Key: gocui.MouseLeft, Modifier: gocui.ModNone, Handler: listContext.handleClick}, }...) // the commits panel needs to lazyload things so it has a couple of its own handlers @@ -579,7 +579,7 @@ func (gui *Gui) getListContextKeyBindings() []*Binding { bindings = append(bindings, []*Binding{ { ViewName: listContext.ViewName, - Contexts: []string{listContext.ContextKey}, + Contexts: []string{string(listContext.ContextKey)}, Key: gui.getKey(keybindingConfig.Universal.StartSearch), Handler: func() error { return openSearchHandler(listContext.ViewName) }, Description: gui.Tr.LcStartSearch, @@ -587,7 +587,7 @@ func (gui *Gui) getListContextKeyBindings() []*Binding { }, { ViewName: listContext.ViewName, - Contexts: []string{listContext.ContextKey}, + Contexts: []string{string(listContext.ContextKey)}, Key: gui.getKey(keybindingConfig.Universal.GotoBottom), Handler: gotoBottomHandler, Description: gui.Tr.LcGotoBottom, diff --git a/pkg/gui/main_panels.go b/pkg/gui/main_panels.go index 3277a4de5..5305d7322 100644 --- a/pkg/gui/main_panels.go +++ b/pkg/gui/main_panels.go @@ -1,6 +1,10 @@ package gui -import "os/exec" +import ( + "os/exec" + + "github.com/jesseduffield/gocui" +) type viewUpdateOpts struct { title string @@ -42,7 +46,7 @@ func (t *renderStringTask) GetKind() TaskKind { return RENDER_STRING } -func (gui *Gui) createRenderStringTask(str string) *renderStringTask { +func NewRenderStringTask(str string) *renderStringTask { return &renderStringTask{str: str} } @@ -54,7 +58,7 @@ func (t *renderStringWithoutScrollTask) GetKind() TaskKind { return RENDER_STRING_WITHOUT_SCROLL } -func (gui *Gui) createRenderStringWithoutScrollTask(str string) *renderStringWithoutScrollTask { +func NewRenderStringWithoutScrollTask(str string) *renderStringWithoutScrollTask { return &renderStringWithoutScrollTask{str: str} } @@ -67,11 +71,11 @@ func (t *runCommandTask) GetKind() TaskKind { return RUN_COMMAND } -func (gui *Gui) createRunCommandTask(cmd *exec.Cmd) *runCommandTask { +func NewRunCommandTask(cmd *exec.Cmd) *runCommandTask { return &runCommandTask{cmd: cmd} } -func (gui *Gui) createRunCommandTaskWithPrefix(cmd *exec.Cmd, prefix string) *runCommandTask { +func NewRunCommandTaskWithPrefix(cmd *exec.Cmd, prefix string) *runCommandTask { return &runCommandTask{cmd: cmd, prefix: prefix} } @@ -84,7 +88,7 @@ func (t *runPtyTask) GetKind() TaskKind { return RUN_PTY } -func (gui *Gui) createRunPtyTask(cmd *exec.Cmd) *runPtyTask { +func NewRunPtyTask(cmd *exec.Cmd) *runPtyTask { return &runPtyTask{cmd: cmd} } @@ -134,18 +138,12 @@ func (gui *Gui) runTaskForView(viewName string, task updateTask) error { return nil } -func (gui *Gui) refreshMainView(opts *viewUpdateOpts, viewName string) error { - view, err := gui.g.View(viewName) - if err != nil { - gui.Log.Error(err) - return nil - } - +func (gui *Gui) refreshMainView(opts *viewUpdateOpts, view *gocui.View) error { view.Title = opts.title view.Wrap = !opts.noWrap view.Highlight = opts.highlight - if err := gui.runTaskForView(viewName, opts.task); err != nil { + if err := gui.runTaskForView(view.Name(), opts.task); err != nil { gui.Log.Error(err) return nil } @@ -155,19 +153,19 @@ func (gui *Gui) refreshMainView(opts *viewUpdateOpts, viewName string) error { func (gui *Gui) refreshMainViews(opts refreshMainOpts) error { if opts.main != nil { - if err := gui.refreshMainView(opts.main, "main"); err != nil { + if err := gui.refreshMainView(opts.main, gui.Views.Main); err != nil { + return err + } + } + + if opts.secondary != nil { + if err := gui.refreshMainView(opts.secondary, gui.Views.Secondary); err != nil { return err } } gui.splitMainPanel(opts.secondary != nil) - if opts.secondary != nil { - if err := gui.refreshMainView(opts.secondary, "secondary"); err != nil { - return err - } - } - return nil } @@ -176,7 +174,7 @@ func (gui *Gui) splitMainPanel(splitMainPanel bool) { // no need to set view on bottom when splitMainPanel is false: it will have zero size anyway thanks to our view arrangement code. if splitMainPanel { - _, _ = gui.g.SetViewOnTop("secondary") + gui.Views.Secondary.Visible = false } } diff --git a/pkg/gui/menu_panel.go b/pkg/gui/menu_panel.go index eb9f4b9d3..54877872a 100644 --- a/pkg/gui/menu_panel.go +++ b/pkg/gui/menu_panel.go @@ -43,7 +43,6 @@ func (gui *Gui) getMenuOptions() map[string]string { } func (gui *Gui) handleMenuClose() error { - _ = gui.g.DeleteView("menu") return gui.returnFromContext() } diff --git a/pkg/gui/merge_panel.go b/pkg/gui/merge_panel.go index 8afcb3c64..e48f3c6d1 100644 --- a/pkg/gui/merge_panel.go +++ b/pkg/gui/merge_panel.go @@ -179,7 +179,7 @@ func (gui *Gui) refreshMergePanel() error { return gui.refreshMainViews(refreshMainOpts{ main: &viewUpdateOpts{ title: "", - task: gui.createRenderStringTask(err.Error()), + task: NewRenderStringTask(err.Error()), }, }) } @@ -203,7 +203,7 @@ func (gui *Gui) refreshMergePanel() error { return gui.refreshMainViews(refreshMainOpts{ main: &viewUpdateOpts{ title: gui.Tr.MergeConflictsTitle, - task: gui.createRenderStringWithoutScrollTask(content), + task: NewRenderStringWithoutScrollTask(content), noWrap: true, }, }) @@ -236,7 +236,8 @@ func (gui *Gui) scrollToConflict() error { if len(panelState.Conflicts) == 0 { return nil } - mergingView := gui.getMainView() + + mergingView := gui.Views.Main conflict := panelState.Conflicts[panelState.ConflictIndex] ox, _ := mergingView.Origin() _, height := mergingView.Size() @@ -269,7 +270,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() { + if gui.g.CurrentView() == gui.Views.Main { return gui.pushContext(gui.State.Contexts.Files) } return nil diff --git a/pkg/gui/patch_building_panel.go b/pkg/gui/patch_building_panel.go index 0b1635c5a..b7ff83f3c 100644 --- a/pkg/gui/patch_building_panel.go +++ b/pkg/gui/patch_building_panel.go @@ -22,10 +22,8 @@ func (gui *Gui) refreshPatchBuildingPanel(selectedLineIdx int, state *lBlPanelSt return gui.handleEscapePatchBuildingPanel() } - gui.splitMainPanel(true) - - gui.getMainView().Title = "Patch" - gui.getSecondaryView().Title = "Custom Patch" + gui.Views.Main.Title = "Patch" + gui.Views.Secondary.Title = "Custom Patch" // get diff from commit file that's currently selected node := gui.getSelectedCommitFileNode() @@ -125,7 +123,7 @@ func (gui *Gui) secondaryPatchPanelUpdateOpts() *viewUpdateOpts { title: "Custom Patch", noWrap: true, highlight: true, - task: gui.createRenderStringWithoutScrollTask(patch), + task: NewRenderStringWithoutScrollTask(patch), } } diff --git a/pkg/gui/pty.go b/pkg/gui/pty.go index 03590cd40..d25c71af2 100644 --- a/pkg/gui/pty.go +++ b/pkg/gui/pty.go @@ -12,8 +12,7 @@ func (gui *Gui) onResize() error { if gui.State.Ptmx == nil { return nil } - mainView := gui.getMainView() - width, height := mainView.Size() + width, height := gui.Views.Main.Size() if err := pty.Setsize(gui.State.Ptmx, &pty.Winsize{Cols: uint16(width), Rows: uint16(height)}); err != nil { return err @@ -31,7 +30,7 @@ func (gui *Gui) onResize() error { // pseudo-terminal meaning we'll get the behaviour we want from the underlying // command. func (gui *Gui) newPtyTask(viewName string, cmd *exec.Cmd, prefix string) error { - width, _ := gui.getMainView().Size() + width, _ := gui.Views.Main.Size() pager := gui.GitCommand.GetPager(width) if pager == "" { diff --git a/pkg/gui/recent_repos_panel.go b/pkg/gui/recent_repos_panel.go index 9dc0e29b1..eecd0e11f 100644 --- a/pkg/gui/recent_repos_panel.go +++ b/pkg/gui/recent_repos_panel.go @@ -40,7 +40,7 @@ func (gui *Gui) handleShowAllBranchLogs() error { cmd := gui.OSCommand.ExecutableFromString( gui.Config.GetUserConfig().Git.AllBranchesLogCmd, ) - task := gui.createRunPtyTask(cmd) + task := NewRunPtyTask(cmd) return gui.refreshMainViews(refreshMainOpts{ main: &viewUpdateOpts{ diff --git a/pkg/gui/reflog_panel.go b/pkg/gui/reflog_panel.go index a4b8526ca..19237d429 100644 --- a/pkg/gui/reflog_panel.go +++ b/pkg/gui/reflog_panel.go @@ -20,13 +20,13 @@ func (gui *Gui) handleReflogCommitSelect() error { commit := gui.getSelectedReflogCommit() var task updateTask if commit == nil { - task = gui.createRenderStringTask("No reflog history") + task = NewRenderStringTask("No reflog history") } else { cmd := gui.OSCommand.ExecutableFromString( gui.GitCommand.ShowCmdStr(commit.Sha, gui.State.Modes.Filtering.GetPath()), ) - task = gui.createRunPtyTask(cmd) + task = NewRunPtyTask(cmd) } return gui.refreshMainViews(refreshMainOpts{ diff --git a/pkg/gui/remote_branches_panel.go b/pkg/gui/remote_branches_panel.go index 75a7ae9bc..c462160cf 100644 --- a/pkg/gui/remote_branches_panel.go +++ b/pkg/gui/remote_branches_panel.go @@ -22,12 +22,12 @@ func (gui *Gui) handleRemoteBranchSelect() error { var task updateTask remoteBranch := gui.getSelectedRemoteBranch() if remoteBranch == nil { - task = gui.createRenderStringTask("No branches for this remote") + task = NewRenderStringTask("No branches for this remote") } else { cmd := gui.OSCommand.ExecutableFromString( gui.GitCommand.GetBranchGraphCmdStr(remoteBranch.FullName()), ) - task = gui.createRunCommandTask(cmd) + task = NewRunCommandTask(cmd) } return gui.refreshMainViews(refreshMainOpts{ diff --git a/pkg/gui/remotes_panel.go b/pkg/gui/remotes_panel.go index 87a401673..5ba52c2ac 100644 --- a/pkg/gui/remotes_panel.go +++ b/pkg/gui/remotes_panel.go @@ -24,9 +24,9 @@ func (gui *Gui) handleRemoteSelect() error { var task updateTask remote := gui.getSelectedRemote() if remote == nil { - task = gui.createRenderStringTask("No remotes") + task = NewRenderStringTask("No remotes") } else { - task = gui.createRenderStringTask(fmt.Sprintf("%s\nUrls:\n%s", utils.ColoredString(remote.Name, color.FgGreen), strings.Join(remote.Urls, "\n"))) + task = NewRenderStringTask(fmt.Sprintf("%s\nUrls:\n%s", utils.ColoredString(remote.Name, color.FgGreen), strings.Join(remote.Urls, "\n"))) } return gui.refreshMainViews(refreshMainOpts{ @@ -57,12 +57,7 @@ func (gui *Gui) refreshRemotes() error { } } - branchesView := gui.getBranchesView() - if branchesView != nil { - return gui.postRefreshUpdate(gui.mustContextForContextKey(branchesView.Context)) - } - - return nil + return gui.postRefreshUpdate(gui.mustContextForContextKey(ContextKey(gui.Views.Branches.Context))) } func (gui *Gui) handleRemoteEnter() error { diff --git a/pkg/gui/searching.go b/pkg/gui/searching.go index 4596401e1..79a7eb127 100644 --- a/pkg/gui/searching.go +++ b/pkg/gui/searching.go @@ -26,7 +26,7 @@ func (gui *Gui) handleOpenSearch(viewName string) error { } func (gui *Gui) handleSearch() error { - gui.State.Searching.searchString = gui.getSearchView().Buffer() + gui.State.Searching.searchString = gui.Views.Search.Buffer() if err := gui.returnFromContext(); err != nil { return err } diff --git a/pkg/gui/side_window.go b/pkg/gui/side_window.go index be55942cd..2aad00c37 100644 --- a/pkg/gui/side_window.go +++ b/pkg/gui/side_window.go @@ -17,7 +17,7 @@ func (gui *Gui) nextSideWindow() error { } } } - if err := gui.resetOrigin(gui.getMainView()); err != nil { + if err := gui.resetOrigin(gui.Views.Main); err != nil { return err } @@ -43,7 +43,7 @@ func (gui *Gui) previousSideWindow() error { } } } - if err := gui.resetOrigin(gui.getMainView()); err != nil { + if err := gui.resetOrigin(gui.Views.Main); err != nil { return err } diff --git a/pkg/gui/staging_panel.go b/pkg/gui/staging_panel.go index f8c65627c..af50d1c2a 100644 --- a/pkg/gui/staging_panel.go +++ b/pkg/gui/staging_panel.go @@ -26,11 +26,11 @@ func (gui *Gui) refreshStagingPanel(forceSecondaryFocused bool, selectedLineIdx } if secondaryFocused { - gui.getMainView().Title = gui.Tr.StagedChanges - gui.getSecondaryView().Title = gui.Tr.UnstagedChanges + gui.Views.Main.Title = gui.Tr.StagedChanges + gui.Views.Secondary.Title = gui.Tr.UnstagedChanges } else { - gui.getMainView().Title = gui.Tr.UnstagedChanges - gui.getSecondaryView().Title = gui.Tr.StagedChanges + gui.Views.Main.Title = gui.Tr.UnstagedChanges + gui.Views.Secondary.Title = gui.Tr.StagedChanges } // note for custom diffs, we'll need to send a flag here saying not to use the custom diff @@ -63,7 +63,7 @@ func (gui *Gui) handleTogglePanelClick() error { return gui.withLBLActiveCheck(func(state *lBlPanelState) error { state.SecondaryFocused = !state.SecondaryFocused - return gui.refreshStagingPanel(false, gui.getSecondaryView().SelectedLineIdx(), state) + return gui.refreshStagingPanel(false, gui.Views.Secondary.SelectedLineIdx(), state) }) } diff --git a/pkg/gui/stash_panel.go b/pkg/gui/stash_panel.go index 6b2662bf6..8cf2e28f5 100644 --- a/pkg/gui/stash_panel.go +++ b/pkg/gui/stash_panel.go @@ -20,12 +20,12 @@ func (gui *Gui) handleStashEntrySelect() error { var task updateTask stashEntry := gui.getSelectedStashEntry() if stashEntry == nil { - task = gui.createRenderStringTask(gui.Tr.NoStashEntries) + task = NewRenderStringTask(gui.Tr.NoStashEntries) } else { cmd := gui.OSCommand.ExecutableFromString( gui.GitCommand.ShowStashEntryCmdStr(stashEntry.Index), ) - task = gui.createRunPtyTask(cmd) + task = NewRunPtyTask(cmd) } return gui.refreshMainViews(refreshMainOpts{ diff --git a/pkg/gui/status_panel.go b/pkg/gui/status_panel.go index 94190279c..20a7a05b5 100644 --- a/pkg/gui/status_panel.go +++ b/pkg/gui/status_panel.go @@ -43,7 +43,7 @@ func (gui *Gui) refreshStatus() { status += fmt.Sprintf("%s → %s ", repoName, name) gui.g.Update(func(*gocui.Gui) error { - gui.setViewContent(gui.getStatusView(), status) + gui.setViewContent(gui.Views.Status, status) return nil }) } @@ -77,7 +77,7 @@ func (gui *Gui) handleStatusClick() error { return err } - cx, _ := gui.getStatusView().Cursor() + cx, _ := gui.Views.Status.Cursor() upstreamStatus := fmt.Sprintf("↑%s↓%s", currentBranch.Pushables, currentBranch.Pullables) repoName := utils.GetCurrentRepoName() switch gui.GitCommand.WorkingTreeState() { @@ -121,7 +121,7 @@ func (gui *Gui) handleStatusSelect() error { return gui.refreshMainViews(refreshMainOpts{ main: &viewUpdateOpts{ title: "", - task: gui.createRenderStringTask(dashboardString), + task: NewRenderStringTask(dashboardString), }, }) } diff --git a/pkg/gui/sub_commits_panel.go b/pkg/gui/sub_commits_panel.go index 964209c8d..41859143b 100644 --- a/pkg/gui/sub_commits_panel.go +++ b/pkg/gui/sub_commits_panel.go @@ -21,13 +21,13 @@ func (gui *Gui) handleSubCommitSelect() error { commit := gui.getSelectedSubCommit() var task updateTask if commit == nil { - task = gui.createRenderStringTask("No commits") + task = NewRenderStringTask("No commits") } else { cmd := gui.OSCommand.ExecutableFromString( gui.GitCommand.ShowCmdStr(commit.Sha, gui.State.Modes.Filtering.GetPath()), ) - task = gui.createRunPtyTask(cmd) + task = NewRunPtyTask(cmd) } return gui.refreshMainViews(refreshMainOpts{ @@ -94,13 +94,13 @@ func (gui *Gui) switchToSubCommitsContext(refName string) error { gui.State.SubCommits = commits gui.State.Panels.SubCommits.refName = refName gui.State.Panels.SubCommits.SelectedLineIdx = 0 - gui.State.Contexts.SubCommits.SetParentContext(gui.currentSideContext()) + gui.State.Contexts.SubCommits.SetParentContext(gui.currentSideListContext()) return gui.pushContext(gui.State.Contexts.SubCommits) } func (gui *Gui) handleSwitchToSubCommits() error { - currentContext := gui.currentSideContext() + currentContext := gui.currentSideListContext() if currentContext == nil { return nil } diff --git a/pkg/gui/submodules_panel.go b/pkg/gui/submodules_panel.go index 65ce2c2d5..261d8af20 100644 --- a/pkg/gui/submodules_panel.go +++ b/pkg/gui/submodules_panel.go @@ -24,7 +24,7 @@ func (gui *Gui) handleSubmoduleSelect() error { var task updateTask submodule := gui.getSelectedSubmodule() if submodule == nil { - task = gui.createRenderStringTask("No submodules") + task = NewRenderStringTask("No submodules") } else { prefix := fmt.Sprintf( "Name: %s\nPath: %s\nUrl: %s\n\n", @@ -35,11 +35,11 @@ func (gui *Gui) handleSubmoduleSelect() error { file := gui.fileForSubmodule(submodule) if file == nil { - task = gui.createRenderStringTask(prefix) + task = NewRenderStringTask(prefix) } else { cmdStr := gui.GitCommand.WorktreeFileDiffCmdStr(file, false, !file.HasUnstagedChanges && file.HasStagedChanges) cmd := gui.OSCommand.ExecutableFromString(cmdStr) - task = gui.createRunCommandTaskWithPrefix(cmd, prefix) + task = NewRunCommandTaskWithPrefix(cmd, prefix) } } diff --git a/pkg/gui/suggestions_panel.go b/pkg/gui/suggestions_panel.go index 84cc89a7f..c11145ded 100644 --- a/pkg/gui/suggestions_panel.go +++ b/pkg/gui/suggestions_panel.go @@ -24,13 +24,8 @@ func (gui *Gui) getSelectedSuggestion() *types.Suggestion { } func (gui *Gui) setSuggestions(suggestions []*types.Suggestion) { - view := gui.getSuggestionsView() - if view == nil { - return - } - gui.State.Suggestions = suggestions gui.State.Panels.Suggestions.SelectedLineIdx = 0 - _ = gui.resetOrigin(view) + _ = gui.resetOrigin(gui.Views.Suggestions) _ = gui.State.Contexts.Suggestions.HandleRender() } diff --git a/pkg/gui/tags_panel.go b/pkg/gui/tags_panel.go index 117fdc655..7e667f50a 100644 --- a/pkg/gui/tags_panel.go +++ b/pkg/gui/tags_panel.go @@ -5,8 +5,6 @@ import ( "github.com/jesseduffield/lazygit/pkg/utils" ) -// list panel functions - func (gui *Gui) getSelectedTag() *models.Tag { selectedLine := gui.State.Panels.Tags.SelectedLineIdx if selectedLine == -1 || len(gui.State.Tags) == 0 { @@ -16,100 +14,6 @@ func (gui *Gui) getSelectedTag() *models.Tag { return gui.State.Tags[selectedLine] } -func (gui *Gui) handleTagSelect() error { - var task updateTask - tag := gui.getSelectedTag() - if tag == nil { - task = gui.createRenderStringTask("No tags") - } else { - cmd := gui.OSCommand.ExecutableFromString( - gui.GitCommand.GetBranchGraphCmdStr(tag.Name), - ) - task = gui.createRunCommandTask(cmd) - } - - return gui.refreshMainViews(refreshMainOpts{ - main: &viewUpdateOpts{ - title: "Tag", - task: task, - }, - }) -} - -func (gui *Gui) refreshTags() error { - tags, err := gui.GitCommand.GetTags() - if err != nil { - return gui.surfaceError(err) - } - - gui.State.Tags = tags - - return gui.postRefreshUpdate(gui.State.Contexts.Tags) -} - -func (gui *Gui) handleCheckoutTag() error { - tag := gui.getSelectedTag() - if tag == nil { - return nil - } - if err := gui.handleCheckoutRef(tag.Name, handleCheckoutRefOptions{}); err != nil { - return err - } - return gui.pushContext(gui.State.Contexts.Branches) -} - -func (gui *Gui) handleDeleteTag() error { - tag := gui.getSelectedTag() - if tag == nil { - return nil - } - - prompt := utils.ResolvePlaceholderString( - gui.Tr.DeleteTagPrompt, - map[string]string{ - "tagName": tag.Name, - }, - ) - - return gui.ask(askOpts{ - title: gui.Tr.DeleteTagTitle, - prompt: prompt, - handleConfirm: func() error { - if err := gui.GitCommand.DeleteTag(tag.Name); err != nil { - return gui.surfaceError(err) - } - return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{COMMITS, TAGS}}) - }, - }) -} - -func (gui *Gui) handlePushTag() error { - tag := gui.getSelectedTag() - if tag == nil { - return nil - } - - title := utils.ResolvePlaceholderString( - gui.Tr.PushTagTitle, - map[string]string{ - "tagName": tag.Name, - }, - ) - - return gui.prompt(promptOpts{ - title: title, - initialContent: "origin", - handleConfirm: func(response string) error { - return gui.WithWaitingStatus(gui.Tr.PushingTagStatus, func() error { - err := gui.GitCommand.PushTag(response, tag.Name, gui.promptUserForCredential) - gui.handleCredentialsPopup(err) - - return nil - }) - }, - }) -} - func (gui *Gui) handleCreateTag() error { return gui.prompt(promptOpts{ title: gui.Tr.CreateTagTitle, @@ -136,11 +40,98 @@ func (gui *Gui) handleCreateTag() error { }) } -func (gui *Gui) handleCreateResetToTagMenu() error { +// tag-specific handlers +// view model would need to raise an event called 'tag selected', perhaps containing a tag. The listener would _be_ the main view, or the main context, and it would be able to render to itself. +func (gui *Gui) handleTagSelect() error { + var task updateTask + tag := gui.getSelectedTag() + if tag == nil { + task = NewRenderStringTask("No tags") + } else { + cmd := gui.OSCommand.ExecutableFromString( + gui.GitCommand.GetBranchGraphCmdStr(tag.Name), + ) + task = NewRunCommandTask(cmd) + } + + return gui.refreshMainViews(refreshMainOpts{ + main: &viewUpdateOpts{ + title: "Tag", + task: task, + }, + }) +} + +// this is a controller: it can't access tags directly. Or can it? It should be able to get but not set. But that's exactly what I'm doing here, setting it. but through a mutator which encapsulates the event. +func (gui *Gui) refreshTags() error { + tags, err := gui.GitCommand.GetTags() + if err != nil { + return gui.surfaceError(err) + } + + gui.State.Tags = tags + + return gui.postRefreshUpdate(gui.State.Contexts.Tags) +} + +func (gui *Gui) withSelectedTag(f func(tag *models.Tag) error) func() error { tag := gui.getSelectedTag() if tag == nil { return nil } + return func() error { return f(tag) } +} + +func (gui *Gui) handleCheckoutTag(tag *models.Tag) error { + if err := gui.handleCheckoutRef(tag.Name, handleCheckoutRefOptions{}); err != nil { + return err + } + return gui.pushContext(gui.State.Contexts.Branches) +} + +func (gui *Gui) handleDeleteTag(tag *models.Tag) error { + prompt := utils.ResolvePlaceholderString( + gui.Tr.DeleteTagPrompt, + map[string]string{ + "tagName": tag.Name, + }, + ) + + return gui.ask(askOpts{ + title: gui.Tr.DeleteTagTitle, + prompt: prompt, + handleConfirm: func() error { + if err := gui.GitCommand.DeleteTag(tag.Name); err != nil { + return gui.surfaceError(err) + } + return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{COMMITS, TAGS}}) + }, + }) +} + +func (gui *Gui) handlePushTag(tag *models.Tag) error { + title := utils.ResolvePlaceholderString( + gui.Tr.PushTagTitle, + map[string]string{ + "tagName": tag.Name, + }, + ) + + return gui.prompt(promptOpts{ + title: title, + initialContent: "origin", + handleConfirm: func(response string) error { + return gui.WithWaitingStatus(gui.Tr.PushingTagStatus, func() error { + err := gui.GitCommand.PushTag(response, tag.Name, gui.promptUserForCredential) + gui.handleCredentialsPopup(err) + + return nil + }) + }, + }) +} + +func (gui *Gui) handleCreateResetToTagMenu(tag *models.Tag) error { return gui.createResetMenu(tag.Name) } diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index 39a9396b6..15d513509 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -241,88 +241,6 @@ func (gui *Gui) renderOptionsMap(optionsMap map[string]string) { gui.renderString("options", gui.optionsMapToString(optionsMap)) } -func (gui *Gui) getFilesView() *gocui.View { - v, _ := gui.g.View("files") - return v -} - -func (gui *Gui) getCommitFilesView() *gocui.View { - v, _ := gui.g.View("commitFiles") - return v -} - -func (gui *Gui) getCommitsView() *gocui.View { - v, _ := gui.g.View("commits") - return v -} - -func (gui *Gui) getCredentialsView() *gocui.View { - v, _ := gui.g.View("commits") - return v -} - -func (gui *Gui) getCommitMessageView() *gocui.View { - v, _ := gui.g.View("commitMessage") - return v -} - -func (gui *Gui) getBranchesView() *gocui.View { - v, _ := gui.g.View("branches") - return v -} - -func (gui *Gui) getMainView() *gocui.View { - v, _ := gui.g.View("main") - return v -} - -func (gui *Gui) getSuggestionsView() *gocui.View { - v, _ := gui.g.View("suggestions") - return v -} - -func (gui *Gui) getSecondaryView() *gocui.View { - v, _ := gui.g.View("secondary") - return v -} - -// currently unused -// func (gui *Gui) getStashView() *gocui.View { -// v, _ := gui.g.View("stash") -// return v -// } - -// currently unused -// func (gui *Gui) getCommitFilesView() *gocui.View { -// v, _ := gui.g.View("commitFiles") -// return v -// } - -func (gui *Gui) getMenuView() *gocui.View { - v, _ := gui.g.View("menu") - return v -} - -func (gui *Gui) getSearchView() *gocui.View { - v, _ := gui.g.View("search") - return v -} - -func (gui *Gui) getStatusView() *gocui.View { - v, _ := gui.g.View("status") - return v -} - -func (gui *Gui) getConfirmationView() *gocui.View { - v, _ := gui.g.View("confirmation") - return v -} - -func (gui *Gui) getInformationView() *gocui.View { - v, _ := gui.g.View("information") - return v -} - func (gui *Gui) trimmedContent(v *gocui.View) string { return strings.TrimSpace(v.Buffer()) }