From d82f175e79f18756769d91de94458b095130297c Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sat, 5 Feb 2022 17:04:10 +1100 Subject: [PATCH] refactor contexts --- pkg/gui/branches_panel.go | 55 ++- pkg/gui/commit_files_panel.go | 26 +- pkg/gui/commits_panel.go | 14 +- pkg/gui/confirmation_panel.go | 32 +- pkg/gui/context.go | 1 - pkg/gui/context/branches_context.go | 86 +++++ pkg/gui/context/commit_files_context.go | 58 ++-- pkg/gui/context/context.go | 32 +- pkg/gui/context/list_context_trait.go | 202 ++--------- pkg/gui/context/local_commits_context.go | 87 +++++ pkg/gui/context/menu_context.go | 108 ++++++ pkg/gui/context/reflog_commits_context.go | 86 +++++ pkg/gui/context/remote_branches_context.go | 86 +++++ pkg/gui/context/remotes_context.go | 86 +++++ pkg/gui/{ => context}/simple_context.go | 23 +- pkg/gui/context/stash_context.go | 86 +++++ pkg/gui/context/sub_commits_context.go | 87 +++++ pkg/gui/context/submodules_context.go | 86 +++++ pkg/gui/context/suggestions_context.go | 85 +++++ pkg/gui/context/tags_context.go | 84 ++--- pkg/gui/context/view_trait.go | 46 +-- .../context/viewport_list_context_trait.go | 22 ++ pkg/gui/context/working_tree_context.go | 56 ++- pkg/gui/context_config.go | 44 +-- pkg/gui/controllers/bisect_controller.go | 16 +- pkg/gui/controllers/files_controller.go | 8 +- pkg/gui/controllers/list_controller.go | 144 ++++++++ .../controllers/local_commits_controller.go | 51 ++- pkg/gui/controllers/menu_controller.go | 24 +- pkg/gui/controllers/remotes_controller.go | 18 +- pkg/gui/controllers/submodules_controller.go | 31 +- pkg/gui/controllers/tags_controller.go | 2 +- pkg/gui/custom_commands.go | 16 +- pkg/gui/diffing.go | 2 +- pkg/gui/filtering_menu_panel.go | 2 +- pkg/gui/git_flow.go | 2 +- pkg/gui/gui.go | 94 +---- pkg/gui/list_context.go | 267 -------------- pkg/gui/list_context_config.go | 327 ++++++------------ pkg/gui/menu_panel.go | 35 +- pkg/gui/options_menu_panel.go | 7 +- pkg/gui/patch_building_panel.go | 4 +- pkg/gui/patch_options_panel.go | 2 +- pkg/gui/presentation/menu.go | 7 + pkg/gui/reflog_panel.go | 22 +- pkg/gui/refresh.go | 4 +- pkg/gui/remote_branches_panel.go | 26 +- pkg/gui/remotes_panel.go | 12 +- pkg/gui/stash_panel.go | 7 +- pkg/gui/sub_commits_panel.go | 28 +- pkg/gui/submodules_panel.go | 11 +- pkg/gui/suggestions_panel.go | 9 +- pkg/gui/tags_panel.go | 2 +- pkg/gui/types/context.go | 52 +-- 54 files changed, 1562 insertions(+), 1248 deletions(-) create mode 100644 pkg/gui/context/branches_context.go create mode 100644 pkg/gui/context/local_commits_context.go create mode 100644 pkg/gui/context/menu_context.go create mode 100644 pkg/gui/context/reflog_commits_context.go create mode 100644 pkg/gui/context/remote_branches_context.go create mode 100644 pkg/gui/context/remotes_context.go rename pkg/gui/{ => context}/simple_context.go (86%) create mode 100644 pkg/gui/context/stash_context.go create mode 100644 pkg/gui/context/sub_commits_context.go create mode 100644 pkg/gui/context/submodules_context.go create mode 100644 pkg/gui/context/suggestions_context.go create mode 100644 pkg/gui/context/viewport_list_context_trait.go create mode 100644 pkg/gui/controllers/list_controller.go delete mode 100644 pkg/gui/list_context.go create mode 100644 pkg/gui/presentation/menu.go diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index f295c9470..072ee257b 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -13,22 +13,9 @@ import ( // list panel functions -func (gui *Gui) getSelectedBranch() *models.Branch { - if len(gui.State.Model.Branches) == 0 { - return nil - } - - selectedLine := gui.State.Panels.Branches.SelectedLineIdx - if selectedLine == -1 { - return nil - } - - return gui.State.Model.Branches[selectedLine] -} - func (gui *Gui) branchesRenderToMain() error { var task updateTask - branch := gui.getSelectedBranch() + branch := gui.State.Contexts.Branches.GetSelected() if branch == nil { task = NewRenderStringTask(gui.c.Tr.NoBranchesThisRepo) } else { @@ -48,24 +35,26 @@ func (gui *Gui) branchesRenderToMain() error { // specific functions func (gui *Gui) handleBranchPress() error { - if gui.State.Panels.Branches.SelectedLineIdx == -1 { + branch := gui.State.Contexts.Branches.GetSelected() + if branch == nil { return nil } - if gui.State.Panels.Branches.SelectedLineIdx == 0 { + + if branch == gui.getCheckedOutBranch() { return gui.c.ErrorMsg(gui.c.Tr.AlreadyCheckedOutBranch) } - branch := gui.getSelectedBranch() + gui.c.LogAction(gui.c.Tr.Actions.CheckoutBranch) return gui.helpers.Refs.CheckoutRef(branch.Name, types.CheckoutRefOptions{}) } func (gui *Gui) handleCreatePullRequestPress() error { - branch := gui.getSelectedBranch() + branch := gui.State.Contexts.Branches.GetSelected() return gui.createPullRequest(branch.Name, "") } func (gui *Gui) handleCreatePullRequestMenu() error { - selectedBranch := gui.getSelectedBranch() + selectedBranch := gui.State.Contexts.Branches.GetSelected() if selectedBranch == nil { return nil } @@ -77,7 +66,7 @@ func (gui *Gui) handleCreatePullRequestMenu() error { func (gui *Gui) handleCopyPullRequestURLPress() error { hostingServiceMgr := gui.getHostingServiceMgr() - branch := gui.getSelectedBranch() + branch := gui.State.Contexts.Branches.GetSelected() branchExistsOnRemote := gui.git.Remote.CheckRemoteBranchExists(branch.Name) @@ -109,7 +98,7 @@ func (gui *Gui) handleGitFetch() error { } func (gui *Gui) handleForceCheckout() error { - branch := gui.getSelectedBranch() + branch := gui.State.Contexts.Branches.GetSelected() message := gui.c.Tr.SureForceCheckout title := gui.c.Tr.ForceCheckoutBranch @@ -156,7 +145,7 @@ func (gui *Gui) getCheckedOutBranch() *models.Branch { } func (gui *Gui) createNewBranchWithName(newBranchName string) error { - branch := gui.getSelectedBranch() + branch := gui.State.Contexts.Branches.GetSelected() if branch == nil { return nil } @@ -165,7 +154,7 @@ func (gui *Gui) createNewBranchWithName(newBranchName string) error { return gui.c.Error(err) } - gui.State.Panels.Branches.SelectedLineIdx = 0 + gui.State.Contexts.Branches.SetSelectedLineIdx(0) return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) } @@ -174,7 +163,7 @@ func (gui *Gui) handleDeleteBranch() error { } func (gui *Gui) deleteBranch(force bool) error { - selectedBranch := gui.getSelectedBranch() + selectedBranch := gui.State.Contexts.Branches.GetSelected() if selectedBranch == nil { return nil } @@ -245,12 +234,12 @@ func (gui *Gui) mergeBranchIntoCheckedOutBranch(branchName string) error { } func (gui *Gui) handleMerge() error { - selectedBranchName := gui.getSelectedBranch().Name + selectedBranchName := gui.State.Contexts.Branches.GetSelected().Name return gui.mergeBranchIntoCheckedOutBranch(selectedBranchName) } func (gui *Gui) handleRebaseOntoLocalBranch() error { - selectedBranchName := gui.getSelectedBranch().Name + selectedBranchName := gui.State.Contexts.Branches.GetSelected().Name return gui.handleRebaseOntoBranch(selectedBranchName) } @@ -279,7 +268,7 @@ func (gui *Gui) handleRebaseOntoBranch(selectedBranchName string) error { } func (gui *Gui) handleFastForward() error { - branch := gui.getSelectedBranch() + branch := gui.State.Contexts.Branches.GetSelected() if branch == nil || !branch.IsRealBranch() { return nil } @@ -305,7 +294,7 @@ func (gui *Gui) handleFastForward() error { ) return gui.c.WithLoaderPanel(message, func() error { - if gui.State.Panels.Branches.SelectedLineIdx == 0 { + if branch == gui.getCheckedOutBranch() { gui.c.LogAction(action) err := gui.git.Sync.Pull( @@ -334,7 +323,7 @@ func (gui *Gui) handleFastForward() error { } func (gui *Gui) handleCreateResetToBranchMenu() error { - branch := gui.getSelectedBranch() + branch := gui.State.Contexts.Branches.GetSelected() if branch == nil { return nil } @@ -343,7 +332,7 @@ func (gui *Gui) handleCreateResetToBranchMenu() error { } func (gui *Gui) handleRenameBranch() error { - branch := gui.getSelectedBranch() + branch := gui.State.Contexts.Branches.GetSelected() if branch == nil || !branch.IsRealBranch() { return nil } @@ -364,7 +353,7 @@ func (gui *Gui) handleRenameBranch() error { // now that we've got our stuff again we need to find that branch and reselect it. for i, newBranch := range gui.State.Model.Branches { if newBranch.Name == newBranchName { - gui.State.Panels.Branches.SetSelectedLineIdx(i) + gui.State.Contexts.Branches.SetSelectedLineIdx(i) if err := gui.State.Contexts.Branches.HandleRender(); err != nil { return err } @@ -391,7 +380,7 @@ func (gui *Gui) handleRenameBranch() error { } func (gui *Gui) handleEnterBranch() error { - branch := gui.getSelectedBranch() + branch := gui.State.Contexts.Branches.GetSelected() if branch == nil { return nil } @@ -400,7 +389,7 @@ func (gui *Gui) handleEnterBranch() error { } func (gui *Gui) handleNewBranchOffBranch() error { - selectedBranch := gui.getSelectedBranch() + selectedBranch := gui.State.Contexts.Branches.GetSelected() if selectedBranch == nil { return nil } diff --git a/pkg/gui/commit_files_panel.go b/pkg/gui/commit_files_panel.go index d4cef7b14..fbbedcb6f 100644 --- a/pkg/gui/commit_files_panel.go +++ b/pkg/gui/commit_files_panel.go @@ -5,16 +5,11 @@ import ( "github.com/jesseduffield/lazygit/pkg/commands/patch" "github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/controllers" - "github.com/jesseduffield/lazygit/pkg/gui/filetree" "github.com/jesseduffield/lazygit/pkg/gui/types" ) -func (gui *Gui) getSelectedCommitFileNode() *filetree.CommitFileNode { - return gui.State.Contexts.CommitFiles.GetSelectedFileNode() -} - func (gui *Gui) getSelectedCommitFile() *models.CommitFile { - node := gui.getSelectedCommitFileNode() + node := gui.State.Contexts.CommitFiles.GetSelectedFileNode() if node == nil { return nil } @@ -22,20 +17,21 @@ func (gui *Gui) getSelectedCommitFile() *models.CommitFile { } func (gui *Gui) getSelectedCommitFilePath() string { - node := gui.getSelectedCommitFileNode() + node := gui.State.Contexts.CommitFiles.GetSelectedFileNode() if node == nil { return "" } return node.GetPath() } +// TODO: do we need this? func (gui *Gui) onCommitFileFocus() error { gui.escapeLineByLinePanel() return nil } func (gui *Gui) commitFilesRenderToMain() error { - node := gui.getSelectedCommitFileNode() + node := gui.State.Contexts.CommitFiles.GetSelectedFileNode() if node == nil { return nil } @@ -62,7 +58,7 @@ func (gui *Gui) commitFilesRenderToMain() error { } func (gui *Gui) handleCheckoutCommitFile() error { - node := gui.getSelectedCommitFileNode() + node := gui.State.Contexts.CommitFiles.GetSelectedFileNode() if node == nil { return nil } @@ -88,7 +84,7 @@ func (gui *Gui) handleDiscardOldFileChange() error { HandleConfirm: func() error { return gui.c.WithWaitingStatus(gui.c.Tr.RebasingStatus, func() error { gui.c.LogAction(gui.c.Tr.Actions.DiscardOldFileChange) - if err := gui.git.Rebase.DiscardOldFileChanges(gui.State.Model.Commits, gui.State.Panels.Commits.SelectedLineIdx, fileName); err != nil { + if err := gui.git.Rebase.DiscardOldFileChanges(gui.State.Model.Commits, gui.State.Contexts.BranchCommits.GetSelectedLineIdx(), fileName); err != nil { if err := gui.helpers.Rebase.CheckMergeOrRebase(err); err != nil { return err } @@ -122,7 +118,7 @@ func (gui *Gui) refreshCommitFilesView() error { } func (gui *Gui) handleOpenOldCommitFile() error { - node := gui.getSelectedCommitFileNode() + node := gui.State.Contexts.CommitFiles.GetSelectedFileNode() if node == nil { return nil } @@ -131,7 +127,7 @@ func (gui *Gui) handleOpenOldCommitFile() error { } func (gui *Gui) handleEditCommitFile() error { - node := gui.getSelectedCommitFileNode() + node := gui.State.Contexts.CommitFiles.GetSelectedFileNode() if node == nil { return nil } @@ -144,7 +140,7 @@ func (gui *Gui) handleEditCommitFile() error { } func (gui *Gui) handleToggleFileForPatch() error { - node := gui.getSelectedCommitFileNode() + node := gui.State.Contexts.CommitFiles.GetSelectedFileNode() if node == nil { return nil } @@ -212,7 +208,7 @@ func (gui *Gui) handleEnterCommitFile() error { } func (gui *Gui) enterCommitFile(opts types.OnFocusOpts) error { - node := gui.getSelectedCommitFileNode() + node := gui.State.Contexts.CommitFiles.GetSelectedFileNode() if node == nil { return nil } @@ -246,7 +242,7 @@ func (gui *Gui) enterCommitFile(opts types.OnFocusOpts) error { } func (gui *Gui) handleToggleCommitFileDirCollapsed() error { - node := gui.getSelectedCommitFileNode() + node := gui.State.Contexts.CommitFiles.GetSelectedFileNode() if node == nil { return nil } diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index 4175918ea..37e234e82 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -11,18 +11,12 @@ const COMMIT_THRESHOLD = 200 // list panel functions func (gui *Gui) getSelectedLocalCommit() *models.Commit { - selectedLine := gui.State.Panels.Commits.SelectedLineIdx - if selectedLine == -1 || selectedLine > len(gui.State.Model.Commits)-1 { - return nil - } - - return gui.State.Model.Commits[selectedLine] + return gui.State.Contexts.BranchCommits.GetSelected() } func (gui *Gui) onCommitFocus() error { - state := gui.State.Panels.Commits - if state.SelectedLineIdx > COMMIT_THRESHOLD && state.LimitCommits { - state.LimitCommits = false + if gui.State.Contexts.BranchCommits.GetSelectedLineIdx() > COMMIT_THRESHOLD && gui.State.LimitCommits { + gui.State.LimitCommits = false go utils.Safe(func() { if err := gui.refreshCommitsWithLimit(); err != nil { _ = gui.c.Error(err) @@ -37,7 +31,7 @@ func (gui *Gui) onCommitFocus() error { func (gui *Gui) branchCommitsRenderToMain() error { var task updateTask - commit := gui.getSelectedLocalCommit() + commit := gui.State.Contexts.BranchCommits.GetSelected() if commit == nil { task = NewRenderStringTask(gui.c.Tr.NoCommitsThisBranch) } else { diff --git a/pkg/gui/confirmation_panel.go b/pkg/gui/confirmation_panel.go index 8ed16ea39..b2cfabfab 100644 --- a/pkg/gui/confirmation_panel.go +++ b/pkg/gui/confirmation_panel.go @@ -77,7 +77,29 @@ func (gui *Gui) getMessageHeight(wrap bool, message string, width int) int { } func (gui *Gui) getConfirmationPanelDimensions(wrap bool, prompt string) (int, int, int, int) { + panelWidth := gui.getConfirmationPanelWidth() + panelHeight := gui.getMessageHeight(wrap, prompt, panelWidth) + return gui.getConfirmationPanelDimensionsAux(panelWidth, panelHeight) +} + +func (gui *Gui) getConfirmationPanelDimensionsForContentHeight(contentHeight int) (int, int, int, int) { + panelWidth := gui.getConfirmationPanelWidth() + return gui.getConfirmationPanelDimensionsAux(panelWidth, contentHeight) +} + +func (gui *Gui) getConfirmationPanelDimensionsAux(panelWidth int, panelHeight int) (int, int, int, int) { width, height := gui.g.Size() + if panelHeight > height*3/4 { + panelHeight = height * 3 / 4 + } + return width/2 - panelWidth/2, + height/2 - panelHeight/2 - panelHeight%2 - 1, + width/2 + panelWidth/2, + height/2 + panelHeight/2 +} + +func (gui *Gui) getConfirmationPanelWidth() int { + width, _ := gui.g.Size() // we want a minimum width up to a point, then we do it based on ratio. panelWidth := 4 * width / 7 minWidth := 80 @@ -88,14 +110,8 @@ func (gui *Gui) getConfirmationPanelDimensions(wrap bool, prompt string) (int, i panelWidth = minWidth } } - panelHeight := gui.getMessageHeight(wrap, prompt, panelWidth) - if panelHeight > height*3/4 { - panelHeight = height * 3 / 4 - } - return width/2 - panelWidth/2, - height/2 - panelHeight/2 - panelHeight%2 - 1, - width/2 + panelWidth/2, - height/2 + panelHeight/2 + + return panelWidth } func (gui *Gui) prepareConfirmationPanel( diff --git a/pkg/gui/context.go b/pkg/gui/context.go index f8aa9134e..b59f0a448 100644 --- a/pkg/gui/context.go +++ b/pkg/gui/context.go @@ -65,7 +65,6 @@ func (gui *Gui) pushContext(c types.Context, opts ...types.OnFocusOpts) error { } if !c.IsFocusable() { - panic(c.GetKey()) return nil } diff --git a/pkg/gui/context/branches_context.go b/pkg/gui/context/branches_context.go new file mode 100644 index 000000000..0be6e1dce --- /dev/null +++ b/pkg/gui/context/branches_context.go @@ -0,0 +1,86 @@ +package context + +import ( + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/gui/context/traits" + "github.com/jesseduffield/lazygit/pkg/gui/types" +) + +type BranchesContext struct { + *BranchesViewModel + *ListContextTrait +} + +var _ types.IListContext = (*BranchesContext)(nil) + +func NewBranchesContext( + getModel func() []*models.Branch, + view *gocui.View, + getDisplayStrings func(startIdx int, length int) [][]string, + + onFocus func(...types.OnFocusOpts) error, + onRenderToMain func(...types.OnFocusOpts) error, + onFocusLost func() error, + + c *types.ControllerCommon, +) *BranchesContext { + viewModel := NewBranchesViewModel(getModel) + + return &BranchesContext{ + BranchesViewModel: viewModel, + ListContextTrait: &ListContextTrait{ + Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ + ViewName: "branches", + WindowName: "branches", + Key: LOCAL_BRANCHES_CONTEXT_KEY, + Kind: types.SIDE_CONTEXT, + Focusable: true, + }), ContextCallbackOpts{ + OnFocus: onFocus, + OnFocusLost: onFocusLost, + OnRenderToMain: onRenderToMain, + }), + list: viewModel, + viewTrait: NewViewTrait(view), + getDisplayStrings: getDisplayStrings, + c: c, + }, + } +} + +func (self *BranchesContext) GetSelectedItemId() string { + item := self.GetSelected() + if item == nil { + return "" + } + + return item.ID() +} + +type BranchesViewModel struct { + *traits.ListCursor + getModel func() []*models.Branch +} + +func NewBranchesViewModel(getModel func() []*models.Branch) *BranchesViewModel { + self := &BranchesViewModel{ + getModel: getModel, + } + + self.ListCursor = traits.NewListCursor(self) + + return self +} + +func (self *BranchesViewModel) GetItemsLength() int { + return len(self.getModel()) +} + +func (self *BranchesViewModel) GetSelected() *models.Branch { + if self.GetItemsLength() == 0 { + return nil + } + + return self.getModel()[self.GetSelectedLineIdx()] +} diff --git a/pkg/gui/context/commit_files_context.go b/pkg/gui/context/commit_files_context.go index e729fb3c1..1c555387b 100644 --- a/pkg/gui/context/commit_files_context.go +++ b/pkg/gui/context/commit_files_context.go @@ -9,7 +9,6 @@ import ( type CommitFilesContext struct { *filetree.CommitFileTreeViewModel - *BaseContext *ListContextTrait } @@ -17,7 +16,7 @@ var _ types.IListContext = (*CommitFilesContext)(nil) func NewCommitFilesContext( getModel func() []*models.CommitFile, - getView func() *gocui.View, + view *gocui.View, getDisplayStrings func(startIdx int, length int) [][]string, onFocus func(...types.OnFocusOpts) error, @@ -26,43 +25,30 @@ func NewCommitFilesContext( c *types.ControllerCommon, ) *CommitFilesContext { - baseContext := NewBaseContext(NewBaseContextOpts{ - ViewName: "commitFiles", - WindowName: "commits", - Key: COMMIT_FILES_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, - }) - - self := &CommitFilesContext{} - takeFocus := func() error { return c.PushContext(self) } - viewModel := filetree.NewCommitFileTreeViewModel(getModel, c.Log, c.UserConfig.Gui.ShowFileTree) - viewTrait := NewViewTrait(getView) - listContextTrait := &ListContextTrait{ - base: baseContext, - list: viewModel, - viewTrait: viewTrait, - GetDisplayStrings: getDisplayStrings, - OnFocus: onFocus, - OnRenderToMain: onRenderToMain, - OnFocusLost: onFocusLost, - takeFocus: takeFocus, - - // TODO: handle this in a trait - RenderSelection: false, - - c: c, + return &CommitFilesContext{ + CommitFileTreeViewModel: viewModel, + ListContextTrait: &ListContextTrait{ + Context: NewSimpleContext( + NewBaseContext(NewBaseContextOpts{ + ViewName: "commitFiles", + WindowName: "commits", + Key: COMMIT_FILES_CONTEXT_KEY, + Kind: types.SIDE_CONTEXT, + Focusable: true, + }), + ContextCallbackOpts{ + OnFocus: onFocus, + OnFocusLost: onFocusLost, + OnRenderToMain: onRenderToMain, + }), + list: viewModel, + viewTrait: NewViewTrait(view), + getDisplayStrings: getDisplayStrings, + c: c, + }, } - - baseContext.AddKeybindingsFn(listContextTrait.keybindings) - - self.BaseContext = baseContext - self.ListContextTrait = listContextTrait - self.CommitFileTreeViewModel = viewModel - - return self } func (self *CommitFilesContext) GetSelectedItemId() string { diff --git a/pkg/gui/context/context.go b/pkg/gui/context/context.go index 710e9a590..5f7c8f163 100644 --- a/pkg/gui/context/context.go +++ b/pkg/gui/context/context.go @@ -1,6 +1,10 @@ package context -import "github.com/jesseduffield/lazygit/pkg/gui/types" +import ( + "sync" + + "github.com/jesseduffield/lazygit/pkg/gui/types" +) const ( GLOBAL_CONTEXT_KEY types.ContextKey = "global" @@ -60,18 +64,18 @@ type ContextTree struct { Global types.Context Status types.Context Files *WorkingTreeContext - Submodules types.IListContext - Menu types.IListContext - Branches types.IListContext - Remotes types.IListContext - RemoteBranches types.IListContext + Menu *MenuContext + Branches *BranchesContext Tags *TagsContext - BranchCommits types.IListContext + BranchCommits *LocalCommitsContext CommitFiles *CommitFilesContext - ReflogCommits types.IListContext - SubCommits types.IListContext - Stash types.IListContext - Suggestions types.IListContext + Remotes *RemotesContext + Submodules *SubmodulesContext + RemoteBranches *RemoteBranchesContext + ReflogCommits *ReflogCommitsContext + SubCommits *SubCommitsContext + Stash *StashContext + Suggestions *SuggestionsContext Normal types.Context Staging types.Context PatchBuilding types.Context @@ -113,6 +117,7 @@ func (self *ContextTree) Flatten() []types.Context { type ViewContextMap struct { content map[string]types.Context + sync.RWMutex } func NewViewContextMap() *ViewContextMap { @@ -120,10 +125,15 @@ func NewViewContextMap() *ViewContextMap { } func (self *ViewContextMap) Get(viewName string) types.Context { + self.RLock() + defer self.RUnlock() + return self.content[viewName] } func (self *ViewContextMap) Set(viewName string, context types.Context) { + self.Lock() + defer self.Unlock() self.content[viewName] = context } diff --git a/pkg/gui/context/list_context_trait.go b/pkg/gui/context/list_context_trait.go index 50a91b827..e4fab30bf 100644 --- a/pkg/gui/context/list_context_trait.go +++ b/pkg/gui/context/list_context_trait.go @@ -3,44 +3,35 @@ package context import ( "fmt" - "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/utils" ) type ListContextTrait struct { - base types.IBaseContext - list types.IList - viewTrait *ViewTrait + types.Context - takeFocus func() error - - GetDisplayStrings func(startIdx int, length int) [][]string - OnFocus func(...types.OnFocusOpts) error - OnRenderToMain func(...types.OnFocusOpts) error - OnFocusLost func() error - - // if this is true, we'll call GetDisplayStrings for just the visible part of the - // view and re-render that. This is useful when you need to render different - // content based on the selection (e.g. for showing the selected commit) - RenderSelection bool - - c *types.ControllerCommon + c *types.ControllerCommon + list types.IList + viewTrait *ViewTrait + getDisplayStrings func(startIdx int, length int) [][]string } +func (self *ListContextTrait) GetList() types.IList { + return self.list +} + +// TODO: remove func (self *ListContextTrait) GetPanelState() types.IListPanelState { return self.list } +func (self *ListContextTrait) GetViewTrait() types.IViewTrait { + return self.viewTrait +} + func (self *ListContextTrait) FocusLine() { // we need a way of knowing whether we've rendered to the view yet. self.viewTrait.FocusPoint(self.list.GetSelectedLineIdx()) - if self.RenderSelection { - min, max := self.viewTrait.ViewPortYBounds() - displayStrings := self.GetDisplayStrings(min, max) - content := utils.RenderDisplayStrings(displayStrings) - self.viewTrait.SetViewPortContent(content) - } self.viewTrait.SetFooter(formatListFooter(self.list.GetSelectedLineIdx(), self.list.GetItemsLength())) } @@ -48,164 +39,29 @@ func formatListFooter(selectedLineIdx int, length int) string { return fmt.Sprintf("%d of %d", selectedLineIdx+1, length) } -// OnFocus assumes that the content of the context has already been rendered to the view. OnRender is the function which actually renders the content to the view -func (self *ListContextTrait) HandleRender() error { - if self.GetDisplayStrings != nil { - self.list.RefreshSelectedIdx() - content := utils.RenderDisplayStrings(self.GetDisplayStrings(0, self.list.GetItemsLength())) - self.viewTrait.SetContent(content) - self.c.Render() - } - - return nil -} - -func (self *ListContextTrait) HandleFocusLost() error { - if self.OnFocusLost != nil { - return self.OnFocusLost() - } - - self.viewTrait.SetOriginX(0) - - return nil -} - func (self *ListContextTrait) HandleFocus(opts ...types.OnFocusOpts) error { self.FocusLine() - if self.OnFocus != nil { - if err := self.OnFocus(opts...); err != nil { - return err - } - } + return self.Context.HandleFocus(opts...) +} - if self.OnRenderToMain != nil { - if err := self.OnRenderToMain(opts...); err != nil { - return err - } - } +func (self *ListContextTrait) HandleFocusLost() error { + self.viewTrait.SetOriginX(0) + + return self.Context.HandleFocus() +} + +// OnFocus assumes that the content of the context has already been rendered to the view. OnRender is the function which actually renders the content to the view +func (self *ListContextTrait) HandleRender() error { + self.list.RefreshSelectedIdx() + content := utils.RenderDisplayStrings(self.getDisplayStrings(0, self.list.GetItemsLength())) + self.viewTrait.SetContent(content) + self.c.Render() return nil } -func (self *ListContextTrait) HandlePrevLine() error { - return self.handleLineChange(-1) -} - -func (self *ListContextTrait) HandleNextLine() error { - return self.handleLineChange(1) -} - -func (self *ListContextTrait) HandleScrollLeft() error { - return self.scroll(self.viewTrait.ScrollLeft) -} - -func (self *ListContextTrait) HandleScrollRight() error { - return self.scroll(self.viewTrait.ScrollRight) -} - -func (self *ListContextTrait) scroll(scrollFunc func()) error { - scrollFunc() - - return self.HandleFocus() -} - -func (self *ListContextTrait) handleLineChange(change int) error { - before := self.list.GetSelectedLineIdx() - self.list.MoveSelectedLine(change) - after := self.list.GetSelectedLineIdx() - - // doing this check so that if we're holding the up key at the start of the list - // we're not constantly re-rendering the main view. - if before != after { - return self.HandleFocus() - } - - return nil -} - -func (self *ListContextTrait) HandlePrevPage() error { - return self.handleLineChange(-self.viewTrait.PageDelta()) -} - -func (self *ListContextTrait) HandleNextPage() error { - return self.handleLineChange(self.viewTrait.PageDelta()) -} - -func (self *ListContextTrait) HandleGotoTop() error { - return self.handleLineChange(-self.list.GetItemsLength()) -} - -func (self *ListContextTrait) HandleGotoBottom() error { - return self.handleLineChange(self.list.GetItemsLength()) -} - -func (self *ListContextTrait) HandleClick(onClick func() error) error { - prevSelectedLineIdx := self.list.GetSelectedLineIdx() - // because we're handling a click, we need to determine the new line idx based - // on the view itself. - newSelectedLineIdx := self.viewTrait.SelectedLineIdx() - - currentContextKey := self.c.CurrentContext().GetKey() - alreadyFocused := currentContextKey == self.base.GetKey() - - // we need to focus the view - if !alreadyFocused { - if err := self.takeFocus(); err != nil { - return err - } - } - - if newSelectedLineIdx > self.list.GetItemsLength()-1 { - return nil - } - - self.list.SetSelectedLineIdx(newSelectedLineIdx) - - if prevSelectedLineIdx == newSelectedLineIdx && alreadyFocused && onClick != nil { - return onClick() - } - return self.HandleFocus() -} - func (self *ListContextTrait) OnSearchSelect(selectedLineIdx int) error { - self.list.SetSelectedLineIdx(selectedLineIdx) + self.GetList().SetSelectedLineIdx(selectedLineIdx) return self.HandleFocus() } - -func (self *ListContextTrait) HandleRenderToMain() error { - if self.OnRenderToMain != nil { - return self.OnRenderToMain() - } - - return nil -} - -func (self *ListContextTrait) keybindings(opts types.KeybindingsOpts) []*types.Binding { - return []*types.Binding{ - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.PrevItemAlt), Modifier: gocui.ModNone, Handler: self.HandlePrevLine}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.PrevItem), Modifier: gocui.ModNone, Handler: self.HandlePrevLine}, - {Tag: "navigation", Key: gocui.MouseWheelUp, Modifier: gocui.ModNone, Handler: self.HandlePrevLine}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.NextItemAlt), Modifier: gocui.ModNone, Handler: self.HandleNextLine}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.NextItem), Modifier: gocui.ModNone, Handler: self.HandleNextLine}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.PrevPage), Modifier: gocui.ModNone, Handler: self.HandlePrevPage, Description: self.c.Tr.LcPrevPage}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.NextPage), Modifier: gocui.ModNone, Handler: self.HandleNextPage, Description: self.c.Tr.LcNextPage}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.GotoTop), Modifier: gocui.ModNone, Handler: self.HandleGotoTop, Description: self.c.Tr.LcGotoTop}, - {Key: gocui.MouseLeft, Modifier: gocui.ModNone, Handler: func() error { return self.HandleClick(nil) }}, - {Tag: "navigation", Key: gocui.MouseWheelDown, Modifier: gocui.ModNone, Handler: self.HandleNextLine}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.ScrollLeft), Modifier: gocui.ModNone, Handler: self.HandleScrollLeft}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.ScrollRight), Modifier: gocui.ModNone, Handler: self.HandleScrollRight}, - { - Key: opts.GetKey(opts.Config.Universal.StartSearch), - Handler: func() error { self.c.OpenSearch(); return nil }, - Description: self.c.Tr.LcStartSearch, - Tag: "navigation", - }, - { - Key: opts.GetKey(opts.Config.Universal.GotoBottom), - Description: self.c.Tr.LcGotoBottom, - Handler: self.HandleGotoBottom, - Tag: "navigation", - }, - } -} diff --git a/pkg/gui/context/local_commits_context.go b/pkg/gui/context/local_commits_context.go new file mode 100644 index 000000000..0345ecb81 --- /dev/null +++ b/pkg/gui/context/local_commits_context.go @@ -0,0 +1,87 @@ +package context + +import ( + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/gui/context/traits" + "github.com/jesseduffield/lazygit/pkg/gui/types" +) + +type LocalCommitsContext struct { + *LocalCommitsViewModel + *ViewportListContextTrait +} + +var _ types.IListContext = (*LocalCommitsContext)(nil) + +func NewLocalCommitsContext( + getModel func() []*models.Commit, + view *gocui.View, + getDisplayStrings func(startIdx int, length int) [][]string, + + onFocus func(...types.OnFocusOpts) error, + onRenderToMain func(...types.OnFocusOpts) error, + onFocusLost func() error, + + c *types.ControllerCommon, +) *LocalCommitsContext { + viewModel := NewLocalCommitsViewModel(getModel) + + return &LocalCommitsContext{ + LocalCommitsViewModel: viewModel, + ViewportListContextTrait: &ViewportListContextTrait{ + ListContextTrait: &ListContextTrait{ + Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ + ViewName: "commits", + WindowName: "commits", + Key: BRANCH_COMMITS_CONTEXT_KEY, + Kind: types.SIDE_CONTEXT, + Focusable: true, + }), ContextCallbackOpts{ + OnFocus: onFocus, + OnFocusLost: onFocusLost, + OnRenderToMain: onRenderToMain, + }), + list: viewModel, + viewTrait: NewViewTrait(view), + getDisplayStrings: getDisplayStrings, + c: c, + }}, + } +} + +func (self *LocalCommitsContext) GetSelectedItemId() string { + item := self.GetSelected() + if item == nil { + return "" + } + + return item.ID() +} + +type LocalCommitsViewModel struct { + *traits.ListCursor + getModel func() []*models.Commit +} + +func NewLocalCommitsViewModel(getModel func() []*models.Commit) *LocalCommitsViewModel { + self := &LocalCommitsViewModel{ + getModel: getModel, + } + + self.ListCursor = traits.NewListCursor(self) + + return self +} + +func (self *LocalCommitsViewModel) GetItemsLength() int { + return len(self.getModel()) +} + +func (self *LocalCommitsViewModel) GetSelected() *models.Commit { + if self.GetItemsLength() == 0 { + return nil + } + + return self.getModel()[self.GetSelectedLineIdx()] +} diff --git a/pkg/gui/context/menu_context.go b/pkg/gui/context/menu_context.go new file mode 100644 index 000000000..47c6b885f --- /dev/null +++ b/pkg/gui/context/menu_context.go @@ -0,0 +1,108 @@ +package context + +import ( + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/gui/context/traits" + "github.com/jesseduffield/lazygit/pkg/gui/presentation" + "github.com/jesseduffield/lazygit/pkg/gui/types" +) + +type MenuContext struct { + *MenuViewModel + *ListContextTrait +} + +var _ types.IListContext = (*MenuContext)(nil) + +func NewMenuContext( + view *gocui.View, + + onFocus func(...types.OnFocusOpts) error, + onRenderToMain func(...types.OnFocusOpts) error, + onFocusLost func() error, + + c *types.ControllerCommon, + getOptionsMap func() map[string]string, +) *MenuContext { + viewModel := NewMenuViewModel() + + return &MenuContext{ + MenuViewModel: viewModel, + ListContextTrait: &ListContextTrait{ + Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ + ViewName: "menu", + Key: "menu", + Kind: types.PERSISTENT_POPUP, + OnGetOptionsMap: getOptionsMap, + Focusable: true, + }), ContextCallbackOpts{ + OnFocus: onFocus, + OnFocusLost: onFocusLost, + OnRenderToMain: onRenderToMain, + }), + getDisplayStrings: viewModel.GetDisplayStrings, + list: viewModel, + viewTrait: NewViewTrait(view), + c: c, + }, + } +} + +// TODO: remove this thing. +func (self *MenuContext) GetSelectedItemId() string { + item := self.GetSelected() + if item == nil { + return "" + } + + return item.DisplayString +} + +type MenuViewModel struct { + *traits.ListCursor + menuItems []*types.MenuItem +} + +func NewMenuViewModel() *MenuViewModel { + self := &MenuViewModel{ + menuItems: nil, + } + + self.ListCursor = traits.NewListCursor(self) + + return self +} + +func (self *MenuViewModel) GetItemsLength() int { + return len(self.menuItems) +} + +func (self *MenuViewModel) GetSelected() *types.MenuItem { + if self.GetItemsLength() == 0 { + return nil + } + + return self.menuItems[self.GetSelectedLineIdx()] +} + +func (self *MenuViewModel) SetMenuItems(items []*types.MenuItem) { + self.menuItems = items +} + +// TODO: move into presentation package +func (self *MenuViewModel) GetDisplayStrings(startIdx int, length int) [][]string { + stringArrays := make([][]string, len(self.menuItems)) + for i, item := range self.menuItems { + if item.DisplayStrings == nil { + styledStr := item.DisplayString + if item.OpensMenu { + styledStr = presentation.OpensMenuStyle(styledStr) + } + stringArrays[i] = []string{styledStr} + } else { + stringArrays[i] = item.DisplayStrings + } + } + + return stringArrays +} diff --git a/pkg/gui/context/reflog_commits_context.go b/pkg/gui/context/reflog_commits_context.go new file mode 100644 index 000000000..e3130c251 --- /dev/null +++ b/pkg/gui/context/reflog_commits_context.go @@ -0,0 +1,86 @@ +package context + +import ( + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/gui/context/traits" + "github.com/jesseduffield/lazygit/pkg/gui/types" +) + +type ReflogCommitsContext struct { + *ReflogCommitsViewModel + *ListContextTrait +} + +var _ types.IListContext = (*ReflogCommitsContext)(nil) + +func NewReflogCommitsContext( + getModel func() []*models.Commit, + view *gocui.View, + getDisplayStrings func(startIdx int, length int) [][]string, + + onFocus func(...types.OnFocusOpts) error, + onRenderToMain func(...types.OnFocusOpts) error, + onFocusLost func() error, + + c *types.ControllerCommon, +) *ReflogCommitsContext { + viewModel := NewReflogCommitsViewModel(getModel) + + return &ReflogCommitsContext{ + ReflogCommitsViewModel: viewModel, + ListContextTrait: &ListContextTrait{ + Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ + ViewName: "commits", + WindowName: "commits", + Key: REFLOG_COMMITS_CONTEXT_KEY, + Kind: types.SIDE_CONTEXT, + Focusable: true, + }), ContextCallbackOpts{ + OnFocus: onFocus, + OnFocusLost: onFocusLost, + OnRenderToMain: onRenderToMain, + }), + list: viewModel, + viewTrait: NewViewTrait(view), + getDisplayStrings: getDisplayStrings, + c: c, + }, + } +} + +func (self *ReflogCommitsContext) GetSelectedItemId() string { + item := self.GetSelected() + if item == nil { + return "" + } + + return item.ID() +} + +type ReflogCommitsViewModel struct { + *traits.ListCursor + getModel func() []*models.Commit +} + +func NewReflogCommitsViewModel(getModel func() []*models.Commit) *ReflogCommitsViewModel { + self := &ReflogCommitsViewModel{ + getModel: getModel, + } + + self.ListCursor = traits.NewListCursor(self) + + return self +} + +func (self *ReflogCommitsViewModel) GetItemsLength() int { + return len(self.getModel()) +} + +func (self *ReflogCommitsViewModel) GetSelected() *models.Commit { + if self.GetItemsLength() == 0 { + return nil + } + + return self.getModel()[self.GetSelectedLineIdx()] +} diff --git a/pkg/gui/context/remote_branches_context.go b/pkg/gui/context/remote_branches_context.go new file mode 100644 index 000000000..e15e80261 --- /dev/null +++ b/pkg/gui/context/remote_branches_context.go @@ -0,0 +1,86 @@ +package context + +import ( + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/gui/context/traits" + "github.com/jesseduffield/lazygit/pkg/gui/types" +) + +type RemoteBranchesContext struct { + *RemoteBranchesViewModel + *ListContextTrait +} + +var _ types.IListContext = (*RemoteBranchesContext)(nil) + +func NewRemoteBranchesContext( + getModel func() []*models.RemoteBranch, + view *gocui.View, + getDisplayStrings func(startIdx int, length int) [][]string, + + onFocus func(...types.OnFocusOpts) error, + onRenderToMain func(...types.OnFocusOpts) error, + onFocusLost func() error, + + c *types.ControllerCommon, +) *RemoteBranchesContext { + viewModel := NewRemoteBranchesViewModel(getModel) + + return &RemoteBranchesContext{ + RemoteBranchesViewModel: viewModel, + ListContextTrait: &ListContextTrait{ + Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ + ViewName: "branches", + WindowName: "branches", + Key: REMOTE_BRANCHES_CONTEXT_KEY, + Kind: types.SIDE_CONTEXT, + Focusable: true, + }), ContextCallbackOpts{ + OnFocus: onFocus, + OnFocusLost: onFocusLost, + OnRenderToMain: onRenderToMain, + }), + list: viewModel, + viewTrait: NewViewTrait(view), + getDisplayStrings: getDisplayStrings, + c: c, + }, + } +} + +func (self *RemoteBranchesContext) GetSelectedItemId() string { + item := self.GetSelected() + if item == nil { + return "" + } + + return item.ID() +} + +type RemoteBranchesViewModel struct { + *traits.ListCursor + getModel func() []*models.RemoteBranch +} + +func NewRemoteBranchesViewModel(getModel func() []*models.RemoteBranch) *RemoteBranchesViewModel { + self := &RemoteBranchesViewModel{ + getModel: getModel, + } + + self.ListCursor = traits.NewListCursor(self) + + return self +} + +func (self *RemoteBranchesViewModel) GetItemsLength() int { + return len(self.getModel()) +} + +func (self *RemoteBranchesViewModel) GetSelected() *models.RemoteBranch { + if self.GetItemsLength() == 0 { + return nil + } + + return self.getModel()[self.GetSelectedLineIdx()] +} diff --git a/pkg/gui/context/remotes_context.go b/pkg/gui/context/remotes_context.go new file mode 100644 index 000000000..28d0db20a --- /dev/null +++ b/pkg/gui/context/remotes_context.go @@ -0,0 +1,86 @@ +package context + +import ( + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/gui/context/traits" + "github.com/jesseduffield/lazygit/pkg/gui/types" +) + +type RemotesContext struct { + *RemotesViewModel + *ListContextTrait +} + +var _ types.IListContext = (*RemotesContext)(nil) + +func NewRemotesContext( + getModel func() []*models.Remote, + view *gocui.View, + getDisplayStrings func(startIdx int, length int) [][]string, + + onFocus func(...types.OnFocusOpts) error, + onRenderToMain func(...types.OnFocusOpts) error, + onFocusLost func() error, + + c *types.ControllerCommon, +) *RemotesContext { + viewModel := NewRemotesViewModel(getModel) + + return &RemotesContext{ + RemotesViewModel: viewModel, + ListContextTrait: &ListContextTrait{ + Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ + ViewName: "branches", + WindowName: "branches", + Key: REMOTES_CONTEXT_KEY, + Kind: types.SIDE_CONTEXT, + Focusable: true, + }), ContextCallbackOpts{ + OnFocus: onFocus, + OnFocusLost: onFocusLost, + OnRenderToMain: onRenderToMain, + }), + list: viewModel, + viewTrait: NewViewTrait(view), + getDisplayStrings: getDisplayStrings, + c: c, + }, + } +} + +func (self *RemotesContext) GetSelectedItemId() string { + item := self.GetSelected() + if item == nil { + return "" + } + + return item.ID() +} + +type RemotesViewModel struct { + *traits.ListCursor + getModel func() []*models.Remote +} + +func NewRemotesViewModel(getModel func() []*models.Remote) *RemotesViewModel { + self := &RemotesViewModel{ + getModel: getModel, + } + + self.ListCursor = traits.NewListCursor(self) + + return self +} + +func (self *RemotesViewModel) GetItemsLength() int { + return len(self.getModel()) +} + +func (self *RemotesViewModel) GetSelected() *models.Remote { + if self.GetItemsLength() == 0 { + return nil + } + + return self.getModel()[self.GetSelectedLineIdx()] +} diff --git a/pkg/gui/simple_context.go b/pkg/gui/context/simple_context.go similarity index 86% rename from pkg/gui/simple_context.go rename to pkg/gui/context/simple_context.go index e9a3ca933..ae201295b 100644 --- a/pkg/gui/simple_context.go +++ b/pkg/gui/context/simple_context.go @@ -1,7 +1,6 @@ -package gui +package context import ( - "github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/types" ) @@ -12,10 +11,10 @@ type SimpleContext struct { // this is for pushing some content to the main view OnRenderToMain func(opts ...types.OnFocusOpts) error - *context.BaseContext + *BaseContext } -type NewSimpleContextOpts struct { +type ContextCallbackOpts struct { OnFocus func(opts ...types.OnFocusOpts) error OnFocusLost func() error OnRender func() error @@ -23,7 +22,7 @@ type NewSimpleContextOpts struct { OnRenderToMain func(opts ...types.OnFocusOpts) error } -func NewSimpleContext(baseContext *context.BaseContext, opts NewSimpleContextOpts) *SimpleContext { +func NewSimpleContext(baseContext *BaseContext, opts ContextCallbackOpts) *SimpleContext { return &SimpleContext{ OnFocus: opts.OnFocus, OnFocusLost: opts.OnFocusLost, @@ -35,13 +34,6 @@ func NewSimpleContext(baseContext *context.BaseContext, opts NewSimpleContextOpt var _ types.Context = &SimpleContext{} -func (self *SimpleContext) HandleRender() error { - if self.OnRender != nil { - return self.OnRender() - } - return nil -} - func (self *SimpleContext) HandleFocus(opts ...types.OnFocusOpts) error { if self.OnFocus != nil { if err := self.OnFocus(opts...); err != nil { @@ -65,6 +57,13 @@ func (self *SimpleContext) HandleFocusLost() error { return nil } +func (self *SimpleContext) HandleRender() error { + if self.OnRender != nil { + return self.OnRender() + } + return nil +} + func (self *SimpleContext) HandleRenderToMain() error { if self.OnRenderToMain != nil { return self.OnRenderToMain() diff --git a/pkg/gui/context/stash_context.go b/pkg/gui/context/stash_context.go new file mode 100644 index 000000000..9c22e7b06 --- /dev/null +++ b/pkg/gui/context/stash_context.go @@ -0,0 +1,86 @@ +package context + +import ( + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/gui/context/traits" + "github.com/jesseduffield/lazygit/pkg/gui/types" +) + +type StashContext struct { + *StashViewModel + *ListContextTrait +} + +var _ types.IListContext = (*StashContext)(nil) + +func NewStashContext( + getModel func() []*models.StashEntry, + view *gocui.View, + getDisplayStrings func(startIdx int, length int) [][]string, + + onFocus func(...types.OnFocusOpts) error, + onRenderToMain func(...types.OnFocusOpts) error, + onFocusLost func() error, + + c *types.ControllerCommon, +) *StashContext { + viewModel := NewStashViewModel(getModel) + + return &StashContext{ + StashViewModel: viewModel, + ListContextTrait: &ListContextTrait{ + Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ + ViewName: "stash", + WindowName: "stash", + Key: STASH_CONTEXT_KEY, + Kind: types.SIDE_CONTEXT, + Focusable: true, + }), ContextCallbackOpts{ + OnFocus: onFocus, + OnFocusLost: onFocusLost, + OnRenderToMain: onRenderToMain, + }), + list: viewModel, + viewTrait: NewViewTrait(view), + getDisplayStrings: getDisplayStrings, + c: c, + }, + } +} + +func (self *StashContext) GetSelectedItemId() string { + item := self.GetSelected() + if item == nil { + return "" + } + + return item.ID() +} + +type StashViewModel struct { + *traits.ListCursor + getModel func() []*models.StashEntry +} + +func NewStashViewModel(getModel func() []*models.StashEntry) *StashViewModel { + self := &StashViewModel{ + getModel: getModel, + } + + self.ListCursor = traits.NewListCursor(self) + + return self +} + +func (self *StashViewModel) GetItemsLength() int { + return len(self.getModel()) +} + +func (self *StashViewModel) GetSelected() *models.StashEntry { + if self.GetItemsLength() == 0 { + return nil + } + + return self.getModel()[self.GetSelectedLineIdx()] +} diff --git a/pkg/gui/context/sub_commits_context.go b/pkg/gui/context/sub_commits_context.go new file mode 100644 index 000000000..aed0e01a2 --- /dev/null +++ b/pkg/gui/context/sub_commits_context.go @@ -0,0 +1,87 @@ +package context + +import ( + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/gui/context/traits" + "github.com/jesseduffield/lazygit/pkg/gui/types" +) + +type SubCommitsContext struct { + *SubCommitsViewModel + *ViewportListContextTrait +} + +var _ types.IListContext = (*SubCommitsContext)(nil) + +func NewSubCommitsContext( + getModel func() []*models.Commit, + view *gocui.View, + getDisplayStrings func(startIdx int, length int) [][]string, + + onFocus func(...types.OnFocusOpts) error, + onRenderToMain func(...types.OnFocusOpts) error, + onFocusLost func() error, + + c *types.ControllerCommon, +) *SubCommitsContext { + viewModel := NewSubCommitsViewModel(getModel) + + return &SubCommitsContext{ + SubCommitsViewModel: viewModel, + ViewportListContextTrait: &ViewportListContextTrait{ + ListContextTrait: &ListContextTrait{ + Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ + ViewName: "branches", + WindowName: "branches", + Key: SUB_COMMITS_CONTEXT_KEY, + Kind: types.SIDE_CONTEXT, + Focusable: true, + }), ContextCallbackOpts{ + OnFocus: onFocus, + OnFocusLost: onFocusLost, + OnRenderToMain: onRenderToMain, + }), + list: viewModel, + viewTrait: NewViewTrait(view), + getDisplayStrings: getDisplayStrings, + c: c, + }}, + } +} + +func (self *SubCommitsContext) GetSelectedItemId() string { + item := self.GetSelected() + if item == nil { + return "" + } + + return item.ID() +} + +type SubCommitsViewModel struct { + *traits.ListCursor + getModel func() []*models.Commit +} + +func NewSubCommitsViewModel(getModel func() []*models.Commit) *SubCommitsViewModel { + self := &SubCommitsViewModel{ + getModel: getModel, + } + + self.ListCursor = traits.NewListCursor(self) + + return self +} + +func (self *SubCommitsViewModel) GetItemsLength() int { + return len(self.getModel()) +} + +func (self *SubCommitsViewModel) GetSelected() *models.Commit { + if self.GetItemsLength() == 0 { + return nil + } + + return self.getModel()[self.GetSelectedLineIdx()] +} diff --git a/pkg/gui/context/submodules_context.go b/pkg/gui/context/submodules_context.go new file mode 100644 index 000000000..c58755985 --- /dev/null +++ b/pkg/gui/context/submodules_context.go @@ -0,0 +1,86 @@ +package context + +import ( + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/gui/context/traits" + "github.com/jesseduffield/lazygit/pkg/gui/types" +) + +type SubmodulesContext struct { + *SubmodulesViewModel + *ListContextTrait +} + +var _ types.IListContext = (*SubmodulesContext)(nil) + +func NewSubmodulesContext( + getModel func() []*models.SubmoduleConfig, + view *gocui.View, + getDisplayStrings func(startIdx int, length int) [][]string, + + onFocus func(...types.OnFocusOpts) error, + onRenderToMain func(...types.OnFocusOpts) error, + onFocusLost func() error, + + c *types.ControllerCommon, +) *SubmodulesContext { + viewModel := NewSubmodulesViewModel(getModel) + + return &SubmodulesContext{ + SubmodulesViewModel: viewModel, + ListContextTrait: &ListContextTrait{ + Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ + ViewName: "files", + WindowName: "files", + Key: SUBMODULES_CONTEXT_KEY, + Kind: types.SIDE_CONTEXT, + Focusable: true, + }), ContextCallbackOpts{ + OnFocus: onFocus, + OnFocusLost: onFocusLost, + OnRenderToMain: onRenderToMain, + }), + list: viewModel, + viewTrait: NewViewTrait(view), + getDisplayStrings: getDisplayStrings, + c: c, + }, + } +} + +func (self *SubmodulesContext) GetSelectedItemId() string { + item := self.GetSelected() + if item == nil { + return "" + } + + return item.ID() +} + +type SubmodulesViewModel struct { + *traits.ListCursor + getModel func() []*models.SubmoduleConfig +} + +func NewSubmodulesViewModel(getModel func() []*models.SubmoduleConfig) *SubmodulesViewModel { + self := &SubmodulesViewModel{ + getModel: getModel, + } + + self.ListCursor = traits.NewListCursor(self) + + return self +} + +func (self *SubmodulesViewModel) GetItemsLength() int { + return len(self.getModel()) +} + +func (self *SubmodulesViewModel) GetSelected() *models.SubmoduleConfig { + if self.GetItemsLength() == 0 { + return nil + } + + return self.getModel()[self.GetSelectedLineIdx()] +} diff --git a/pkg/gui/context/suggestions_context.go b/pkg/gui/context/suggestions_context.go new file mode 100644 index 000000000..5320e40c6 --- /dev/null +++ b/pkg/gui/context/suggestions_context.go @@ -0,0 +1,85 @@ +package context + +import ( + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/gui/context/traits" + "github.com/jesseduffield/lazygit/pkg/gui/types" +) + +type SuggestionsContext struct { + *SuggestionsViewModel + *ListContextTrait +} + +var _ types.IListContext = (*SuggestionsContext)(nil) + +func NewSuggestionsContext( + getModel func() []*types.Suggestion, + view *gocui.View, + getDisplayStrings func(startIdx int, length int) [][]string, + + onFocus func(...types.OnFocusOpts) error, + onRenderToMain func(...types.OnFocusOpts) error, + onFocusLost func() error, + + c *types.ControllerCommon, +) *SuggestionsContext { + viewModel := NewSuggestionsViewModel(getModel) + + return &SuggestionsContext{ + SuggestionsViewModel: viewModel, + ListContextTrait: &ListContextTrait{ + Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ + ViewName: "suggestions", + WindowName: "suggestions", + Key: SUGGESTIONS_CONTEXT_KEY, + Kind: types.PERSISTENT_POPUP, + Focusable: true, + }), ContextCallbackOpts{ + OnFocus: onFocus, + OnFocusLost: onFocusLost, + OnRenderToMain: onRenderToMain, + }), + list: viewModel, + viewTrait: NewViewTrait(view), + getDisplayStrings: getDisplayStrings, + c: c, + }, + } +} + +func (self *SuggestionsContext) GetSelectedItemId() string { + item := self.GetSelected() + if item == nil { + return "" + } + + return item.Value +} + +type SuggestionsViewModel struct { + *traits.ListCursor + getModel func() []*types.Suggestion +} + +func NewSuggestionsViewModel(getModel func() []*types.Suggestion) *SuggestionsViewModel { + self := &SuggestionsViewModel{ + getModel: getModel, + } + + self.ListCursor = traits.NewListCursor(self) + + return self +} + +func (self *SuggestionsViewModel) GetItemsLength() int { + return len(self.getModel()) +} + +func (self *SuggestionsViewModel) GetSelected() *types.Suggestion { + if self.GetItemsLength() == 0 { + return nil + } + + return self.getModel()[self.GetSelectedLineIdx()] +} diff --git a/pkg/gui/context/tags_context.go b/pkg/gui/context/tags_context.go index 2f20c4363..e0409cfba 100644 --- a/pkg/gui/context/tags_context.go +++ b/pkg/gui/context/tags_context.go @@ -9,7 +9,6 @@ import ( type TagsContext struct { *TagsViewModel - *BaseContext *ListContextTrait } @@ -17,7 +16,7 @@ var _ types.IListContext = (*TagsContext)(nil) func NewTagsContext( getModel func() []*models.Tag, - getView func() *gocui.View, + view *gocui.View, getDisplayStrings func(startIdx int, length int) [][]string, onFocus func(...types.OnFocusOpts) error, @@ -26,47 +25,32 @@ func NewTagsContext( c *types.ControllerCommon, ) *TagsContext { - baseContext := NewBaseContext(NewBaseContextOpts{ - ViewName: "branches", - WindowName: "branches", - Key: TAGS_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, - }) + viewModel := NewTagsViewModel(getModel) - self := &TagsContext{} - takeFocus := func() error { return c.PushContext(self) } - - list := NewTagsViewModel(getModel) - viewTrait := NewViewTrait(getView) - listContextTrait := &ListContextTrait{ - base: baseContext, - list: list, - viewTrait: viewTrait, - - GetDisplayStrings: getDisplayStrings, - OnFocus: onFocus, - OnRenderToMain: onRenderToMain, - OnFocusLost: onFocusLost, - takeFocus: takeFocus, - - // TODO: handle this in a trait - RenderSelection: false, - - c: c, + return &TagsContext{ + TagsViewModel: viewModel, + ListContextTrait: &ListContextTrait{ + Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ + ViewName: "branches", + WindowName: "branches", + Key: TAGS_CONTEXT_KEY, + Kind: types.SIDE_CONTEXT, + Focusable: true, + }), ContextCallbackOpts{ + OnFocus: onFocus, + OnFocusLost: onFocusLost, + OnRenderToMain: onRenderToMain, + }), + list: viewModel, + viewTrait: NewViewTrait(view), + getDisplayStrings: getDisplayStrings, + c: c, + }, } - - baseContext.AddKeybindingsFn(listContextTrait.keybindings) - - self.BaseContext = baseContext - self.ListContextTrait = listContextTrait - self.TagsViewModel = list - - return self } func (self *TagsContext) GetSelectedItemId() string { - item := self.GetSelectedTag() + item := self.GetSelected() if item == nil { return "" } @@ -79,18 +63,6 @@ type TagsViewModel struct { getModel func() []*models.Tag } -func (self *TagsViewModel) GetItemsLength() int { - return len(self.getModel()) -} - -func (self *TagsViewModel) GetSelectedTag() *models.Tag { - if self.GetItemsLength() == 0 { - return nil - } - - return self.getModel()[self.GetSelectedLineIdx()] -} - func NewTagsViewModel(getModel func() []*models.Tag) *TagsViewModel { self := &TagsViewModel{ getModel: getModel, @@ -100,3 +72,15 @@ func NewTagsViewModel(getModel func() []*models.Tag) *TagsViewModel { return self } + +func (self *TagsViewModel) GetItemsLength() int { + return len(self.getModel()) +} + +func (self *TagsViewModel) GetSelected() *models.Tag { + if self.GetItemsLength() == 0 { + return nil + } + + return self.getModel()[self.GetSelectedLineIdx()] +} diff --git a/pkg/gui/context/view_trait.go b/pkg/gui/context/view_trait.go index 1409ed561..f7981ebfa 100644 --- a/pkg/gui/context/view_trait.go +++ b/pkg/gui/context/view_trait.go @@ -2,70 +2,62 @@ package context import ( "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/utils" ) const HORIZONTAL_SCROLL_FACTOR = 3 type ViewTrait struct { - getView func() *gocui.View + view *gocui.View } -func NewViewTrait(getView func() *gocui.View) *ViewTrait { - return &ViewTrait{getView: getView} +var _ types.IViewTrait = &ViewTrait{} + +func NewViewTrait(view *gocui.View) *ViewTrait { + return &ViewTrait{view: view} } func (self *ViewTrait) FocusPoint(yIdx int) { - view := self.getView() - view.FocusPoint(view.OriginX(), yIdx) + self.view.FocusPoint(self.view.OriginX(), yIdx) } func (self *ViewTrait) SetViewPortContent(content string) { - view := self.getView() - - _, y := view.Origin() - view.OverwriteLines(y, content) + _, y := self.view.Origin() + self.view.OverwriteLines(y, content) } func (self *ViewTrait) SetContent(content string) { - self.getView().SetContent(content) + self.view.SetContent(content) } func (self *ViewTrait) SetFooter(value string) { - self.getView().Footer = value + self.view.Footer = value } func (self *ViewTrait) SetOriginX(value int) { - _ = self.getView().SetOriginX(value) + _ = self.view.SetOriginX(value) } // tells us the bounds of line indexes shown in the view currently func (self *ViewTrait) ViewPortYBounds() (int, int) { - view := self.getView() - - _, min := view.Origin() - max := view.InnerHeight() + 1 + _, min := self.view.Origin() + max := self.view.InnerHeight() + 1 return min, max } func (self *ViewTrait) ScrollLeft() { - view := self.getView() - - newOriginX := utils.Max(view.OriginX()-view.InnerWidth()/HORIZONTAL_SCROLL_FACTOR, 0) - _ = view.SetOriginX(newOriginX) + newOriginX := utils.Max(self.view.OriginX()-self.view.InnerWidth()/HORIZONTAL_SCROLL_FACTOR, 0) + _ = self.view.SetOriginX(newOriginX) } func (self *ViewTrait) ScrollRight() { - view := self.getView() - - _ = view.SetOriginX(view.OriginX() + view.InnerWidth()/HORIZONTAL_SCROLL_FACTOR) + _ = self.view.SetOriginX(self.view.OriginX() + self.view.InnerWidth()/HORIZONTAL_SCROLL_FACTOR) } // this returns the amount we'll scroll if we want to scroll by a page. func (self *ViewTrait) PageDelta() int { - view := self.getView() - - _, height := view.Size() + _, height := self.view.Size() delta := height - 1 if delta == 0 { @@ -76,5 +68,5 @@ func (self *ViewTrait) PageDelta() int { } func (self *ViewTrait) SelectedLineIdx() int { - return self.getView().SelectedLineIdx() + return self.view.SelectedLineIdx() } diff --git a/pkg/gui/context/viewport_list_context_trait.go b/pkg/gui/context/viewport_list_context_trait.go new file mode 100644 index 000000000..ab9b04fb8 --- /dev/null +++ b/pkg/gui/context/viewport_list_context_trait.go @@ -0,0 +1,22 @@ +package context + +import ( + "github.com/jesseduffield/lazygit/pkg/utils" +) + +// This embeds a list context trait and adds logic to re-render the viewport +// whenever a line is focused. We use this in the commits panel because different +// sections of the log graph need to be highlighted depending on the currently selected line + +type ViewportListContextTrait struct { + *ListContextTrait +} + +func (self *ViewportListContextTrait) FocusLine() { + self.ListContextTrait.FocusLine() + + min, max := self.GetViewTrait().ViewPortYBounds() + displayStrings := self.ListContextTrait.getDisplayStrings(min, max) + content := utils.RenderDisplayStrings(displayStrings) + self.GetViewTrait().SetViewPortContent(content) +} diff --git a/pkg/gui/context/working_tree_context.go b/pkg/gui/context/working_tree_context.go index 6179e7270..c1021ba23 100644 --- a/pkg/gui/context/working_tree_context.go +++ b/pkg/gui/context/working_tree_context.go @@ -9,7 +9,6 @@ import ( type WorkingTreeContext struct { *filetree.FileTreeViewModel - *BaseContext *ListContextTrait } @@ -17,7 +16,7 @@ var _ types.IListContext = (*WorkingTreeContext)(nil) func NewWorkingTreeContext( getModel func() []*models.File, - getView func() *gocui.View, + view *gocui.View, getDisplayStrings func(startIdx int, length int) [][]string, onFocus func(...types.OnFocusOpts) error, @@ -26,43 +25,28 @@ func NewWorkingTreeContext( c *types.ControllerCommon, ) *WorkingTreeContext { - baseContext := NewBaseContext(NewBaseContextOpts{ - ViewName: "files", - WindowName: "files", - Key: FILES_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, - }) - - self := &WorkingTreeContext{} - takeFocus := func() error { return c.PushContext(self) } - viewModel := filetree.NewFileTreeViewModel(getModel, c.Log, c.UserConfig.Gui.ShowFileTree) - viewTrait := NewViewTrait(getView) - listContextTrait := &ListContextTrait{ - base: baseContext, - list: viewModel, - viewTrait: viewTrait, - GetDisplayStrings: getDisplayStrings, - OnFocus: onFocus, - OnRenderToMain: onRenderToMain, - OnFocusLost: onFocusLost, - takeFocus: takeFocus, - - // TODO: handle this in a trait - RenderSelection: false, - - c: c, + return &WorkingTreeContext{ + FileTreeViewModel: viewModel, + ListContextTrait: &ListContextTrait{ + Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ + ViewName: "files", + WindowName: "files", + Key: FILES_CONTEXT_KEY, + Kind: types.SIDE_CONTEXT, + Focusable: true, + }), ContextCallbackOpts{ + OnFocus: onFocus, + OnFocusLost: onFocusLost, + OnRenderToMain: onRenderToMain, + }), + list: viewModel, + viewTrait: NewViewTrait(view), + getDisplayStrings: getDisplayStrings, + c: c, + }, } - - baseContext.AddKeybindingsFn(listContextTrait.keybindings) - - self.BaseContext = baseContext - self.ListContextTrait = listContextTrait - self.FileTreeViewModel = viewModel - - return self } func (self *WorkingTreeContext) GetSelectedItemId() string { diff --git a/pkg/gui/context_config.go b/pkg/gui/context_config.go index d62f9f5e1..54f139141 100644 --- a/pkg/gui/context_config.go +++ b/pkg/gui/context_config.go @@ -35,7 +35,7 @@ func (gui *Gui) allContexts2() []types.Context { func (gui *Gui) contextTree() *context.ContextTree { return &context.ContextTree{ - Global: NewSimpleContext( + Global: context.NewSimpleContext( context.NewBaseContext(context.NewBaseContextOpts{ Kind: types.GLOBAL_CONTEXT, ViewName: "", @@ -43,11 +43,11 @@ func (gui *Gui) contextTree() *context.ContextTree { Key: context.GLOBAL_CONTEXT_KEY, Focusable: false, }), - NewSimpleContextOpts{ + context.ContextCallbackOpts{ OnRenderToMain: OnFocusWrapper(gui.statusRenderToMain), }, ), - Status: NewSimpleContext( + Status: context.NewSimpleContext( context.NewBaseContext(context.NewBaseContextOpts{ Kind: types.SIDE_CONTEXT, ViewName: "status", @@ -55,7 +55,7 @@ func (gui *Gui) contextTree() *context.ContextTree { Key: context.STATUS_CONTEXT_KEY, Focusable: true, }), - NewSimpleContextOpts{ + context.ContextCallbackOpts{ OnRenderToMain: OnFocusWrapper(gui.statusRenderToMain), }, ), @@ -72,7 +72,7 @@ func (gui *Gui) contextTree() *context.ContextTree { Tags: gui.tagsListContext(), Stash: gui.stashListContext(), Suggestions: gui.suggestionsListContext(), - Normal: NewSimpleContext( + Normal: context.NewSimpleContext( context.NewBaseContext(context.NewBaseContextOpts{ Kind: types.MAIN_CONTEXT, ViewName: "main", @@ -80,13 +80,13 @@ func (gui *Gui) contextTree() *context.ContextTree { Key: context.MAIN_NORMAL_CONTEXT_KEY, Focusable: false, }), - NewSimpleContextOpts{ + context.ContextCallbackOpts{ OnFocus: func(opts ...types.OnFocusOpts) error { return nil // TODO: should we do something here? We should allow for scrolling the panel }, }, ), - Staging: NewSimpleContext( + Staging: context.NewSimpleContext( context.NewBaseContext(context.NewBaseContextOpts{ Kind: types.MAIN_CONTEXT, ViewName: "main", @@ -94,7 +94,7 @@ func (gui *Gui) contextTree() *context.ContextTree { Key: context.MAIN_STAGING_CONTEXT_KEY, Focusable: true, }), - NewSimpleContextOpts{ + context.ContextCallbackOpts{ OnFocus: func(opts ...types.OnFocusOpts) error { forceSecondaryFocused := false selectedLineIdx := -1 @@ -110,7 +110,7 @@ func (gui *Gui) contextTree() *context.ContextTree { }, }, ), - PatchBuilding: NewSimpleContext( + PatchBuilding: context.NewSimpleContext( context.NewBaseContext(context.NewBaseContextOpts{ Kind: types.MAIN_CONTEXT, ViewName: "main", @@ -118,7 +118,7 @@ func (gui *Gui) contextTree() *context.ContextTree { Key: context.MAIN_PATCH_BUILDING_CONTEXT_KEY, Focusable: true, }), - NewSimpleContextOpts{ + context.ContextCallbackOpts{ OnFocus: func(opts ...types.OnFocusOpts) error { selectedLineIdx := -1 if len(opts) > 0 && (opts[0].ClickedViewName == "main" || opts[0].ClickedViewName == "secondary") { @@ -129,7 +129,7 @@ func (gui *Gui) contextTree() *context.ContextTree { }, }, ), - Merging: NewSimpleContext( + Merging: context.NewSimpleContext( context.NewBaseContext(context.NewBaseContextOpts{ Kind: types.MAIN_CONTEXT, ViewName: "main", @@ -138,11 +138,11 @@ func (gui *Gui) contextTree() *context.ContextTree { OnGetOptionsMap: gui.getMergingOptions, Focusable: true, }), - NewSimpleContextOpts{ + context.ContextCallbackOpts{ OnFocus: OnFocusWrapper(func() error { return gui.renderConflictsWithLock(true) }), }, ), - Credentials: NewSimpleContext( + Credentials: context.NewSimpleContext( context.NewBaseContext(context.NewBaseContextOpts{ Kind: types.PERSISTENT_POPUP, ViewName: "credentials", @@ -150,11 +150,11 @@ func (gui *Gui) contextTree() *context.ContextTree { Key: context.CREDENTIALS_CONTEXT_KEY, Focusable: true, }), - NewSimpleContextOpts{ + context.ContextCallbackOpts{ OnFocus: OnFocusWrapper(gui.handleAskFocused), }, ), - Confirmation: NewSimpleContext( + Confirmation: context.NewSimpleContext( context.NewBaseContext(context.NewBaseContextOpts{ Kind: types.TEMPORARY_POPUP, ViewName: "confirmation", @@ -162,11 +162,11 @@ func (gui *Gui) contextTree() *context.ContextTree { Key: context.CONFIRMATION_CONTEXT_KEY, Focusable: true, }), - NewSimpleContextOpts{ + context.ContextCallbackOpts{ OnFocus: OnFocusWrapper(gui.handleAskFocused), }, ), - CommitMessage: NewSimpleContext( + CommitMessage: context.NewSimpleContext( context.NewBaseContext(context.NewBaseContextOpts{ Kind: types.PERSISTENT_POPUP, ViewName: "commitMessage", @@ -174,11 +174,11 @@ func (gui *Gui) contextTree() *context.ContextTree { Key: context.COMMIT_MESSAGE_CONTEXT_KEY, Focusable: true, }), - NewSimpleContextOpts{ + context.ContextCallbackOpts{ OnFocus: OnFocusWrapper(gui.handleCommitMessageFocused), }, ), - Search: NewSimpleContext( + Search: context.NewSimpleContext( context.NewBaseContext(context.NewBaseContextOpts{ Kind: types.PERSISTENT_POPUP, ViewName: "search", @@ -186,9 +186,9 @@ func (gui *Gui) contextTree() *context.ContextTree { Key: context.SEARCH_CONTEXT_KEY, Focusable: true, }), - NewSimpleContextOpts{}, + context.ContextCallbackOpts{}, ), - CommandLog: NewSimpleContext( + CommandLog: context.NewSimpleContext( context.NewBaseContext(context.NewBaseContextOpts{ Kind: types.EXTRAS_CONTEXT, ViewName: "extras", @@ -197,7 +197,7 @@ func (gui *Gui) contextTree() *context.ContextTree { OnGetOptionsMap: gui.getMergingOptions, Focusable: true, }), - NewSimpleContextOpts{ + context.ContextCallbackOpts{ OnFocusLost: func() error { gui.Views.Extras.Autoscroll = true return nil diff --git a/pkg/gui/controllers/bisect_controller.go b/pkg/gui/controllers/bisect_controller.go index 58c8a6db7..99ae9c2df 100644 --- a/pkg/gui/controllers/bisect_controller.go +++ b/pkg/gui/controllers/bisect_controller.go @@ -7,6 +7,7 @@ import ( "github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/types" ) @@ -14,23 +15,21 @@ type BisectController struct { baseController c *types.ControllerCommon - context types.IListContext + context *context.LocalCommitsContext git *commands.GitCommand bisectHelper *BisectHelper - getSelectedLocalCommit func() *models.Commit - getCommits func() []*models.Commit + getCommits func() []*models.Commit } var _ types.IController = &BisectController{} func NewBisectController( c *types.ControllerCommon, - context types.IListContext, + context *context.LocalCommitsContext, git *commands.GitCommand, bisectHelper *BisectHelper, - getSelectedLocalCommit func() *models.Commit, getCommits func() []*models.Commit, ) *BisectController { return &BisectController{ @@ -40,8 +39,7 @@ func NewBisectController( git: git, bisectHelper: bisectHelper, - getSelectedLocalCommit: getSelectedLocalCommit, - getCommits: getCommits, + getCommits: getCommits, } } @@ -234,7 +232,7 @@ func (self *BisectController) selectCurrentBisectCommit() { // find index of commit with that sha, move cursor to that. for i, commit := range self.getCommits() { if commit.Sha == info.GetCurrentSha() { - self.context.GetPanelState().SetSelectedLineIdx(i) + self.context.SetSelectedLineIdx(i) _ = self.context.HandleFocus() break } @@ -244,7 +242,7 @@ func (self *BisectController) selectCurrentBisectCommit() { func (self *BisectController) checkSelected(callback func(*models.Commit) error) func() error { return func() error { - commit := self.getSelectedLocalCommit() + commit := self.context.GetSelected() if commit == nil { return nil } diff --git a/pkg/gui/controllers/files_controller.go b/pkg/gui/controllers/files_controller.go index d853ba731..de085607d 100644 --- a/pkg/gui/controllers/files_controller.go +++ b/pkg/gui/controllers/files_controller.go @@ -94,10 +94,10 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types Handler: self.checkSelectedFileNode(self.press), Description: self.c.Tr.LcToggleStaged, }, - { - Key: gocui.MouseLeft, - Handler: func() error { return self.context.HandleClick(self.checkSelectedFileNode(self.press)) }, - }, + // { + // Key: gocui.MouseLeft, + // Handler: func() error { return self.context.HandleClick(self.checkSelectedFileNode(self.press)) }, + // }, { Key: opts.GetKey(""), // TODO: softcode Handler: self.handleStatusFilterPressed, diff --git a/pkg/gui/controllers/list_controller.go b/pkg/gui/controllers/list_controller.go new file mode 100644 index 000000000..8473fad83 --- /dev/null +++ b/pkg/gui/controllers/list_controller.go @@ -0,0 +1,144 @@ +package controllers + +import ( + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/gui/types" +) + +type ListControllerFactory struct { + c *types.ControllerCommon +} + +func NewListControllerFactory(c *types.ControllerCommon) *ListControllerFactory { + return &ListControllerFactory{ + c: c, + } +} + +func (self *ListControllerFactory) Create(context types.IListContext) *ListController { + return &ListController{ + baseController: baseController{}, + c: self.c, + context: context, + } +} + +type ListController struct { + baseController + c *types.ControllerCommon + + context types.IListContext +} + +func (self *ListController) Context() types.Context { + return self.context +} + +func (self *ListController) HandlePrevLine() error { + return self.handleLineChange(-1) +} + +func (self *ListController) HandleNextLine() error { + return self.handleLineChange(1) +} + +func (self *ListController) HandleScrollLeft() error { + return self.scroll(self.context.GetViewTrait().ScrollLeft) +} + +func (self *ListController) HandleScrollRight() error { + return self.scroll(self.context.GetViewTrait().ScrollRight) +} + +func (self *ListController) scroll(scrollFunc func()) error { + scrollFunc() + + return self.context.HandleFocus() +} + +func (self *ListController) handleLineChange(change int) error { + before := self.context.GetList().GetSelectedLineIdx() + self.context.GetList().MoveSelectedLine(change) + after := self.context.GetList().GetSelectedLineIdx() + + // doing this check so that if we're holding the up key at the start of the list + // we're not constantly re-rendering the main view. + if before != after { + return self.context.HandleFocus() + } + + return nil +} + +func (self *ListController) HandlePrevPage() error { + return self.handleLineChange(-self.context.GetViewTrait().PageDelta()) +} + +func (self *ListController) HandleNextPage() error { + return self.handleLineChange(self.context.GetViewTrait().PageDelta()) +} + +func (self *ListController) HandleGotoTop() error { + return self.handleLineChange(-self.context.GetList().GetItemsLength()) +} + +func (self *ListController) HandleGotoBottom() error { + return self.handleLineChange(self.context.GetList().GetItemsLength()) +} + +func (self *ListController) HandleClick(onClick func() error) error { + prevSelectedLineIdx := self.context.GetList().GetSelectedLineIdx() + // because we're handling a click, we need to determine the new line idx based + // on the view itself. + newSelectedLineIdx := self.context.GetViewTrait().SelectedLineIdx() + + currentContextKey := self.c.CurrentContext().GetKey() + alreadyFocused := currentContextKey == self.context.GetKey() + + // we need to focus the view + if !alreadyFocused { + if err := self.c.PushContext(self.context); err != nil { + return err + } + } + + if newSelectedLineIdx > self.context.GetList().GetItemsLength()-1 { + return nil + } + + self.context.GetList().SetSelectedLineIdx(newSelectedLineIdx) + + if prevSelectedLineIdx == newSelectedLineIdx && alreadyFocused && onClick != nil { + return onClick() + } + return self.context.HandleFocus() +} + +func (self *ListController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { + return []*types.Binding{ + {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.PrevItemAlt), Modifier: gocui.ModNone, Handler: self.HandlePrevLine}, + {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.PrevItem), Modifier: gocui.ModNone, Handler: self.HandlePrevLine}, + {Tag: "navigation", Key: gocui.MouseWheelUp, Modifier: gocui.ModNone, Handler: self.HandlePrevLine}, + {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.NextItemAlt), Modifier: gocui.ModNone, Handler: self.HandleNextLine}, + {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.NextItem), Modifier: gocui.ModNone, Handler: self.HandleNextLine}, + {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.PrevPage), Modifier: gocui.ModNone, Handler: self.HandlePrevPage, Description: self.c.Tr.LcPrevPage}, + {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.NextPage), Modifier: gocui.ModNone, Handler: self.HandleNextPage, Description: self.c.Tr.LcNextPage}, + {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.GotoTop), Modifier: gocui.ModNone, Handler: self.HandleGotoTop, Description: self.c.Tr.LcGotoTop}, + {Key: gocui.MouseLeft, Modifier: gocui.ModNone, Handler: func() error { return self.HandleClick(nil) }}, + {Tag: "navigation", Key: gocui.MouseWheelDown, Modifier: gocui.ModNone, Handler: self.HandleNextLine}, + {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.ScrollLeft), Modifier: gocui.ModNone, Handler: self.HandleScrollLeft}, + {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.ScrollRight), Modifier: gocui.ModNone, Handler: self.HandleScrollRight}, + { + Key: opts.GetKey(opts.Config.Universal.StartSearch), + Handler: func() error { self.c.OpenSearch(); return nil }, + Description: self.c.Tr.LcStartSearch, + Tag: "navigation", + }, + { + Key: opts.GetKey(opts.Config.Universal.GotoBottom), + Description: self.c.Tr.LcGotoBottom, + Handler: self.HandleGotoBottom, + Tag: "navigation", + }, + } +} diff --git a/pkg/gui/controllers/local_commits_controller.go b/pkg/gui/controllers/local_commits_controller.go index e962ea48e..4ce7b88da 100644 --- a/pkg/gui/controllers/local_commits_controller.go +++ b/pkg/gui/controllers/local_commits_controller.go @@ -8,6 +8,7 @@ import ( "github.com/jesseduffield/lazygit/pkg/commands/hosting_service" "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/oscommands" + "github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/utils" ) @@ -24,7 +25,7 @@ type ( type LocalCommitsController struct { baseController c *types.ControllerCommon - context types.IListContext + context *context.LocalCommitsContext os *oscommands.OSCommand git *commands.GitCommand tagsHelper *TagsHelper @@ -32,9 +33,7 @@ type LocalCommitsController struct { cherryPickHelper *CherryPickHelper rebaseHelper *RebaseHelper - getSelectedLocalCommit func() *models.Commit model *types.Model - getSelectedLocalCommitIdx func() int CheckMergeOrRebase CheckMergeOrRebase pullFiles PullFilesFn getHostingServiceMgr GetHostingServiceMgrFn @@ -49,16 +48,14 @@ var _ types.IController = &LocalCommitsController{} func NewLocalCommitsController( c *types.ControllerCommon, - context types.IListContext, + context *context.LocalCommitsContext, os *oscommands.OSCommand, git *commands.GitCommand, tagsHelper *TagsHelper, refsHelper IRefsHelper, cherryPickHelper *CherryPickHelper, rebaseHelper *RebaseHelper, - getSelectedLocalCommit func() *models.Commit, model *types.Model, - getSelectedLocalCommitIdx func() int, CheckMergeOrRebase CheckMergeOrRebase, pullFiles PullFilesFn, getHostingServiceMgr GetHostingServiceMgrFn, @@ -78,9 +75,7 @@ func NewLocalCommitsController( refsHelper: refsHelper, cherryPickHelper: cherryPickHelper, rebaseHelper: rebaseHelper, - getSelectedLocalCommit: getSelectedLocalCommit, model: model, - getSelectedLocalCommitIdx: getSelectedLocalCommitIdx, CheckMergeOrRebase: CheckMergeOrRebase, pullFiles: pullFiles, getHostingServiceMgr: getHostingServiceMgr, @@ -194,10 +189,10 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [ Description: self.c.Tr.LcGotoBottom, Tag: "navigation", }, - { - Key: gocui.MouseLeft, - Handler: func() error { return self.context.HandleClick(self.checkSelected(self.enter)) }, - }, + // { + // Key: gocui.MouseLeft, + // Handler: func() error { return self.context.HandleClick(self.checkSelected(self.enter)) }, + // }, } for _, binding := range outsideFilterModeBindings { @@ -316,7 +311,7 @@ func (self *LocalCommitsController) reword(commit *models.Commit) error { InitialContent: message, HandleConfirm: func(response string) error { self.c.LogAction(self.c.Tr.Actions.RewordCommit) - if err := self.git.Rebase.RewordCommit(self.model.Commits, self.getSelectedLocalCommitIdx(), response); err != nil { + if err := self.git.Rebase.RewordCommit(self.model.Commits, self.context.GetSelectedLineIdx(), response); err != nil { return self.c.Error(err) } @@ -336,7 +331,7 @@ func (self *LocalCommitsController) rewordEditor() error { self.c.LogAction(self.c.Tr.Actions.RewordCommit) subProcess, err := self.git.Rebase.RewordCommitInEditor( - self.model.Commits, self.getSelectedLocalCommitIdx(), + self.model.Commits, self.context.GetSelectedLineIdx(), ) if err != nil { return self.c.Error(err) @@ -399,7 +394,7 @@ func (self *LocalCommitsController) pick() error { } func (self *LocalCommitsController) interactiveRebase(action string) error { - err := self.git.Rebase.InteractiveRebase(self.model.Commits, self.getSelectedLocalCommitIdx(), action) + err := self.git.Rebase.InteractiveRebase(self.model.Commits, self.context.GetSelectedLineIdx(), action) return self.CheckMergeOrRebase(err) } @@ -407,7 +402,7 @@ func (self *LocalCommitsController) interactiveRebase(action string) error { // commit meaning you are trying to edit the todo file rather than actually // begin a rebase. It then updates the todo file with that action func (self *LocalCommitsController) handleMidRebaseCommand(action string) (bool, error) { - selectedCommit := self.getSelectedLocalCommit() + selectedCommit := self.context.GetSelected() if selectedCommit.Status != "rebasing" { return false, nil } @@ -427,7 +422,7 @@ func (self *LocalCommitsController) handleMidRebaseCommand(action string) (bool, ) if err := self.git.Rebase.EditRebaseTodo( - self.getSelectedLocalCommitIdx(), action, + self.context.GetSelectedLineIdx(), action, ); err != nil { return false, self.c.Error(err) } @@ -438,7 +433,7 @@ func (self *LocalCommitsController) handleMidRebaseCommand(action string) (bool, } func (self *LocalCommitsController) handleCommitMoveDown() error { - index := self.context.GetPanelState().GetSelectedLineIdx() + index := self.context.GetSelectedLineIdx() commits := self.model.Commits selectedCommit := self.model.Commits[index] if selectedCommit.Status == "rebasing" { @@ -454,8 +449,7 @@ func (self *LocalCommitsController) handleCommitMoveDown() error { if err := self.git.Rebase.MoveTodoDown(index); err != nil { return self.c.Error(err) } - // TODO: use MoveSelectedLine - _ = self.context.HandleNextLine() + self.context.MoveSelectedLine(1) return self.c.Refresh(types.RefreshOptions{ Mode: types.SYNC, Scope: []types.RefreshableView{types.REBASE_COMMITS}, }) @@ -465,8 +459,7 @@ func (self *LocalCommitsController) handleCommitMoveDown() error { self.c.LogAction(self.c.Tr.Actions.MoveCommitDown) err := self.git.Rebase.MoveCommitDown(self.model.Commits, index) if err == nil { - // TODO: use MoveSelectedLine - _ = self.context.HandleNextLine() + self.context.MoveSelectedLine(1) } return self.CheckMergeOrRebase(err) }) @@ -491,7 +484,7 @@ func (self *LocalCommitsController) handleCommitMoveUp() error { if err := self.git.Rebase.MoveTodoDown(index - 1); err != nil { return self.c.Error(err) } - _ = self.context.HandlePrevLine() + self.context.MoveSelectedLine(-1) return self.c.Refresh(types.RefreshOptions{ Mode: types.SYNC, Scope: []types.RefreshableView{types.REBASE_COMMITS}, }) @@ -501,7 +494,7 @@ func (self *LocalCommitsController) handleCommitMoveUp() error { self.c.LogAction(self.c.Tr.Actions.MoveCommitUp) err := self.git.Rebase.MoveCommitDown(self.model.Commits, index-1) if err == nil { - _ = self.context.HandlePrevLine() + self.context.MoveSelectedLine(-1) } return self.CheckMergeOrRebase(err) }) @@ -514,7 +507,7 @@ func (self *LocalCommitsController) handleCommitAmendTo() error { HandleConfirm: func() error { return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func() error { self.c.LogAction(self.c.Tr.Actions.AmendCommit) - err := self.git.Rebase.AmendTo(self.getSelectedLocalCommit().Sha) + err := self.git.Rebase.AmendTo(self.context.GetSelected().Sha) return self.CheckMergeOrRebase(err) }) }, @@ -569,7 +562,7 @@ func (self *LocalCommitsController) createRevertMergeCommitMenu(commit *models.C } func (self *LocalCommitsController) afterRevertCommit() error { - _ = self.context.HandleNextLine() + self.context.MoveSelectedLine(1) return self.c.Refresh(types.RefreshOptions{ Mode: types.BLOCK_UI, Scope: []types.RefreshableView{types.COMMITS, types.BRANCHES}, }) @@ -669,7 +662,7 @@ func (self *LocalCommitsController) gotoBottom() error { } } - _ = self.context.HandleGotoBottom() + self.context.SetSelectedLineIdx(self.context.GetItemsLength() - 1) return nil } @@ -791,7 +784,7 @@ func (self *LocalCommitsController) handleOpenCommitInBrowser(commit *models.Com func (self *LocalCommitsController) checkSelected(callback func(*models.Commit) error) func() error { return func() error { - commit := self.getSelectedLocalCommit() + commit := self.context.GetSelected() if commit == nil { return nil } @@ -813,7 +806,7 @@ func (self *LocalCommitsController) copy(commit *models.Commit) error { } func (self *LocalCommitsController) copyRange(*models.Commit) error { - return self.cherryPickHelper.CopyRange(self.context.GetPanelState().GetSelectedLineIdx(), self.model.Commits, self.context) + return self.cherryPickHelper.CopyRange(self.context.GetSelectedLineIdx(), self.model.Commits, self.context) } func (self *LocalCommitsController) paste() error { diff --git a/pkg/gui/controllers/menu_controller.go b/pkg/gui/controllers/menu_controller.go index cbd24e188..392fe3da6 100644 --- a/pkg/gui/controllers/menu_controller.go +++ b/pkg/gui/controllers/menu_controller.go @@ -1,7 +1,7 @@ package controllers import ( - "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/types" ) @@ -9,24 +9,20 @@ type MenuController struct { baseController c *types.ControllerCommon - context types.IListContext - - getSelectedMenuItem func() *types.MenuItem + context *context.MenuContext } var _ types.IController = &MenuController{} func NewMenuController( c *types.ControllerCommon, - context types.IListContext, - getSelectedMenuItem func() *types.MenuItem, + context *context.MenuContext, ) *MenuController { return &MenuController{ baseController: baseController{}, - c: c, - context: context, - getSelectedMenuItem: getSelectedMenuItem, + c: c, + context: context, } } @@ -44,17 +40,17 @@ func (self *MenuController) GetKeybindings(opts types.KeybindingsOpts) []*types. Key: opts.GetKey(opts.Config.Universal.ConfirmAlt1), Handler: self.press, }, - { - Key: gocui.MouseLeft, - Handler: func() error { return self.context.HandleClick(self.press) }, - }, + // { + // Key: gocui.MouseLeft, + // Handler: func() error { return self.context.HandleClick(self.press) }, + // }, } return bindings } func (self *MenuController) press() error { - selectedItem := self.getSelectedMenuItem() + selectedItem := self.context.GetSelected() if err := self.c.PopContext(); err != nil { return err diff --git a/pkg/gui/controllers/remotes_controller.go b/pkg/gui/controllers/remotes_controller.go index 73b1c57ab..ff3b943fb 100644 --- a/pkg/gui/controllers/remotes_controller.go +++ b/pkg/gui/controllers/remotes_controller.go @@ -1,7 +1,6 @@ package controllers import ( - "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/gui/context" @@ -13,10 +12,9 @@ type RemotesController struct { baseController c *types.ControllerCommon - context types.IListContext + context *context.RemotesContext git *commands.GitCommand - getSelectedRemote func() *models.Remote setRemoteBranches func([]*models.RemoteBranch) contexts *context.ContextTree } @@ -25,10 +23,9 @@ var _ types.IController = &RemotesController{} func NewRemotesController( c *types.ControllerCommon, - context types.IListContext, + context *context.RemotesContext, git *commands.GitCommand, contexts *context.ContextTree, - getSelectedRemote func() *models.Remote, setRemoteBranches func([]*models.RemoteBranch), ) *RemotesController { return &RemotesController{ @@ -37,7 +34,6 @@ func NewRemotesController( git: git, contexts: contexts, context: context, - getSelectedRemote: getSelectedRemote, setRemoteBranches: setRemoteBranches, } } @@ -48,10 +44,10 @@ func (self *RemotesController) GetKeybindings(opts types.KeybindingsOpts) []*typ Key: opts.GetKey(opts.Config.Universal.GoInto), Handler: self.checkSelected(self.enter), }, - { - Key: gocui.MouseLeft, - Handler: func() error { return self.context.HandleClick(self.checkSelected(self.enter)) }, - }, + // { + // Key: gocui.MouseLeft, + // Handler: func() error { return self.context.HandleClick(self.checkSelected(self.enter)) }, + // }, { Key: opts.GetKey(opts.Config.Branches.FetchRemote), Handler: self.checkSelected(self.fetch), @@ -183,7 +179,7 @@ func (self *RemotesController) fetch(remote *models.Remote) error { func (self *RemotesController) checkSelected(callback func(*models.Remote) error) func() error { return func() error { - file := self.getSelectedRemote() + file := self.context.GetSelected() if file == nil { return nil } diff --git a/pkg/gui/controllers/submodules_controller.go b/pkg/gui/controllers/submodules_controller.go index 1db27f6e6..2eba02953 100644 --- a/pkg/gui/controllers/submodules_controller.go +++ b/pkg/gui/controllers/submodules_controller.go @@ -5,9 +5,9 @@ import ( "path/filepath" "strings" - "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/style" "github.com/jesseduffield/lazygit/pkg/gui/types" ) @@ -16,29 +16,26 @@ type SubmodulesController struct { baseController c *types.ControllerCommon - context types.IListContext + context *context.SubmodulesContext git *commands.GitCommand - enterSubmodule func(submodule *models.SubmoduleConfig) error - getSelectedSubmodule func() *models.SubmoduleConfig + enterSubmodule func(submodule *models.SubmoduleConfig) error } var _ types.IController = &SubmodulesController{} func NewSubmodulesController( c *types.ControllerCommon, - context types.IListContext, + context *context.SubmodulesContext, git *commands.GitCommand, enterSubmodule func(submodule *models.SubmoduleConfig) error, - getSelectedSubmodule func() *models.SubmoduleConfig, ) *SubmodulesController { return &SubmodulesController{ - baseController: baseController{}, - c: c, - context: context, - git: git, - enterSubmodule: enterSubmodule, - getSelectedSubmodule: getSelectedSubmodule, + baseController: baseController{}, + c: c, + context: context, + git: git, + enterSubmodule: enterSubmodule, } } @@ -80,10 +77,10 @@ func (self *SubmodulesController) GetKeybindings(opts types.KeybindingsOpts) []* Description: self.c.Tr.LcViewBulkSubmoduleOptions, OpensMenu: true, }, - { - Key: gocui.MouseLeft, - Handler: func() error { return self.context.HandleClick(self.checkSelected(self.enter)) }, - }, + // { + // Key: gocui.MouseLeft, + // Handler: func() error { return self.context.HandleClick(self.checkSelected(self.enter)) }, + // }, } } @@ -230,7 +227,7 @@ func (self *SubmodulesController) remove(submodule *models.SubmoduleConfig) erro func (self *SubmodulesController) checkSelected(callback func(*models.SubmoduleConfig) error) func() error { return func() error { - submodule := self.getSelectedSubmodule() + submodule := self.context.GetSelected() if submodule == nil { return nil } diff --git a/pkg/gui/controllers/tags_controller.go b/pkg/gui/controllers/tags_controller.go index 508820061..18135db02 100644 --- a/pkg/gui/controllers/tags_controller.go +++ b/pkg/gui/controllers/tags_controller.go @@ -158,7 +158,7 @@ func (self *TagsController) create() error { func (self *TagsController) withSelectedTag(f func(tag *models.Tag) error) func() error { return func() error { - tag := self.context.GetSelectedTag() + tag := self.context.GetSelected() if tag == nil { return nil } diff --git a/pkg/gui/custom_commands.go b/pkg/gui/custom_commands.go index 45713968e..8111dd06c 100644 --- a/pkg/gui/custom_commands.go +++ b/pkg/gui/custom_commands.go @@ -44,16 +44,16 @@ func (gui *Gui) resolveTemplate(templateStr string, promptResponses []string) (s objects := CustomCommandObjects{ SelectedFile: gui.getSelectedFile(), SelectedPath: gui.getSelectedPath(), - SelectedLocalCommit: gui.getSelectedLocalCommit(), - SelectedReflogCommit: gui.getSelectedReflogCommit(), - SelectedLocalBranch: gui.getSelectedBranch(), - SelectedRemoteBranch: gui.getSelectedRemoteBranch(), - SelectedRemote: gui.getSelectedRemote(), - SelectedTag: gui.State.Contexts.Tags.GetSelectedTag(), - SelectedStashEntry: gui.getSelectedStashEntry(), + SelectedLocalCommit: gui.State.Contexts.BranchCommits.GetSelected(), + SelectedReflogCommit: gui.State.Contexts.ReflogCommits.GetSelected(), + SelectedLocalBranch: gui.State.Contexts.Branches.GetSelected(), + SelectedRemoteBranch: gui.State.Contexts.RemoteBranches.GetSelected(), + SelectedRemote: gui.State.Contexts.Remotes.GetSelected(), + SelectedTag: gui.State.Contexts.Tags.GetSelected(), + SelectedStashEntry: gui.State.Contexts.Stash.GetSelected(), SelectedCommitFile: gui.getSelectedCommitFile(), SelectedCommitFilePath: gui.getSelectedCommitFilePath(), - SelectedSubCommit: gui.getSelectedSubCommit(), + SelectedSubCommit: gui.State.Contexts.SubCommits.GetSelected(), CheckedOutBranch: gui.getCheckedOutBranch(), PromptResponses: promptResponses, } diff --git a/pkg/gui/diffing.go b/pkg/gui/diffing.go index 30c7d8789..30af99882 100644 --- a/pkg/gui/diffing.go +++ b/pkg/gui/diffing.go @@ -43,7 +43,7 @@ func (gui *Gui) currentDiffTerminals() []string { return []string{gui.State.Contexts.CommitFiles.GetRefName()} case context.LOCAL_BRANCHES_CONTEXT_KEY: // for our local branches we want to include both the branch and its upstream - branch := gui.getSelectedBranch() + branch := gui.State.Contexts.Branches.GetSelected() if branch != nil { names := []string{branch.ID()} if branch.IsTrackingRemote() { diff --git a/pkg/gui/filtering_menu_panel.go b/pkg/gui/filtering_menu_panel.go index b9b5bc685..fefe6a892 100644 --- a/pkg/gui/filtering_menu_panel.go +++ b/pkg/gui/filtering_menu_panel.go @@ -16,7 +16,7 @@ func (gui *Gui) handleCreateFilteringMenuPanel() error { fileName = node.GetPath() } case gui.State.Contexts.CommitFiles: - node := gui.getSelectedCommitFileNode() + node := gui.State.Contexts.CommitFiles.GetSelectedFileNode() if node != nil { fileName = node.GetPath() } diff --git a/pkg/gui/git_flow.go b/pkg/gui/git_flow.go index eecd5328c..c26b94a70 100644 --- a/pkg/gui/git_flow.go +++ b/pkg/gui/git_flow.go @@ -8,7 +8,7 @@ import ( ) func (gui *Gui) handleCreateGitFlowMenu() error { - branch := gui.getSelectedBranch() + branch := gui.State.Contexts.Branches.GetSelected() if branch == nil { return nil } diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index f8479c111..3cc6129e3 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -179,11 +179,11 @@ type GuiRepoState struct { // Suggestions will sometimes appear when typing into a prompt Suggestions []*types.Suggestion - MenuItems []*types.MenuItem Updating bool Panels *panelStates SplitMainPanel bool + LimitCommits bool IsRefreshingFiles bool Searching searchingState @@ -253,68 +253,11 @@ type MergingPanelState struct { UserVerticalScrolling bool } -// TODO: consider splitting this out into the window and the branches view -type branchPanelState struct { - listPanelState -} - -type remotePanelState struct { - listPanelState -} - -type remoteBranchesState struct { - listPanelState -} - -type commitPanelState struct { - listPanelState - - LimitCommits bool -} - -type reflogCommitPanelState struct { - listPanelState -} - -type subCommitPanelState struct { - listPanelState - - // e.g. name of branch whose commits we're looking at - refName string -} - -type stashPanelState struct { - listPanelState -} - -type menuPanelState struct { - listPanelState - OnPress func() error -} - -type submodulePanelState struct { - listPanelState -} - -type suggestionsPanelState struct { - listPanelState -} - // as we move things to the new context approach we're going to eventually // remove this struct altogether and store this state on the contexts. type panelStates struct { - Branches *branchPanelState - Remotes *remotePanelState - RemoteBranches *remoteBranchesState - Commits *commitPanelState - ReflogCommits *reflogCommitPanelState - SubCommits *subCommitPanelState - Stash *stashPanelState - Menu *menuPanelState - LineByLine *LblPanelState - Merging *MergingPanelState - Submodules *submodulePanelState - Suggestions *suggestionsPanelState + LineByLine *LblPanelState + Merging *MergingPanelState } type Views struct { @@ -449,23 +392,13 @@ func (gui *Gui) resetState(filterPath string, reuseState bool) { }, Panels: &panelStates{ - // TODO: work out why some of these are -1 and some are 0. Last time I checked there was a good reason but I'm less certain now - Submodules: &submodulePanelState{listPanelState{SelectedLineIdx: -1}}, - Branches: &branchPanelState{listPanelState{SelectedLineIdx: 0}}, - Remotes: &remotePanelState{listPanelState{SelectedLineIdx: 0}}, - RemoteBranches: &remoteBranchesState{listPanelState{SelectedLineIdx: -1}}, - Commits: &commitPanelState{listPanelState: listPanelState{SelectedLineIdx: 0}, LimitCommits: true}, - ReflogCommits: &reflogCommitPanelState{listPanelState{SelectedLineIdx: 0}}, - SubCommits: &subCommitPanelState{listPanelState: listPanelState{SelectedLineIdx: 0}, refName: ""}, - Stash: &stashPanelState{listPanelState{SelectedLineIdx: -1}}, - Menu: &menuPanelState{listPanelState: listPanelState{SelectedLineIdx: 0}, OnPress: nil}, - Suggestions: &suggestionsPanelState{listPanelState: listPanelState{SelectedLineIdx: 0}}, Merging: &MergingPanelState{ State: mergeconflicts.NewState(), UserVerticalScrolling: false, }, }, - Ptmx: nil, + LimitCommits: true, + Ptmx: nil, Modes: Modes{ Filtering: filtering.New(filterPath), CherryPicking: cherrypicking.New(), @@ -584,7 +517,7 @@ func (gui *Gui) resetControllers() { controllerCommon, gui.git, gui.State.Contexts, - func() { gui.State.Panels.Commits.LimitCommits = true }, + func() { gui.State.LimitCommits = true }, ), Bisect: controllers.NewBisectHelper(controllerCommon, gui.git), Suggestions: controllers.NewSuggestionsHelper(controllerCommon, model, gui.refreshSuggestions), @@ -615,7 +548,6 @@ func (gui *Gui) resetControllers() { gui.State.Contexts.Submodules, gui.git, gui.enterSubmodule, - gui.getSelectedSubmodule, ) bisectController := controllers.NewBisectController( @@ -623,7 +555,6 @@ func (gui *Gui) resetControllers() { gui.State.Contexts.BranchCommits, gui.git, gui.helpers.Bisect, - gui.getSelectedLocalCommit, func() []*models.Commit { return gui.State.Model.Commits }, ) @@ -672,15 +603,13 @@ func (gui *Gui) resetControllers() { gui.helpers.Refs, gui.helpers.CherryPick, gui.helpers.Rebase, - gui.getSelectedLocalCommit, model, - func() int { return gui.State.Panels.Commits.SelectedLineIdx }, gui.helpers.Rebase.CheckMergeOrRebase, syncController.HandlePull, gui.getHostingServiceMgr, gui.SwitchToCommitFilesContext, - func() bool { return gui.State.Panels.Commits.LimitCommits }, - func(value bool) { gui.State.Panels.Commits.LimitCommits = value }, + func() bool { return gui.State.LimitCommits }, + func(value bool) { gui.State.LimitCommits = value }, func() bool { return gui.ShowWholeGitGraph }, func(value bool) { gui.ShowWholeGitGraph = value }, ), @@ -689,13 +618,11 @@ func (gui *Gui) resetControllers() { gui.State.Contexts.Remotes, gui.git, gui.State.Contexts, - gui.getSelectedRemote, func(branches []*models.RemoteBranch) { gui.State.Model.RemoteBranches = branches }, ), Menu: controllers.NewMenuController( controllerCommon, gui.State.Contexts.Menu, - gui.getSelectedMenuItem, ), Undo: controllers.NewUndoController( controllerCommon, @@ -714,6 +641,11 @@ func (gui *Gui) resetControllers() { controllers.AttachControllers(gui.State.Contexts.Remotes, gui.Controllers.Remotes) controllers.AttachControllers(gui.State.Contexts.Menu, gui.Controllers.Menu) controllers.AttachControllers(gui.State.Contexts.Global, gui.Controllers.Sync, gui.Controllers.Undo, gui.Controllers.Global) + + listControllerFactory := controllers.NewListControllerFactory(gui.c) + for _, context := range gui.getListContexts() { + controllers.AttachControllers(context, listControllerFactory.Create(context)) + } } var RuneReplacements = map[rune]string{ diff --git a/pkg/gui/list_context.go b/pkg/gui/list_context.go deleted file mode 100644 index 7644df6e6..000000000 --- a/pkg/gui/list_context.go +++ /dev/null @@ -1,267 +0,0 @@ -package gui - -import ( - "fmt" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type ListContext struct { - GetItemsLength func() int - GetDisplayStrings func(startIdx int, length int) [][]string - OnFocus func(...types.OnFocusOpts) error - OnRenderToMain func(...types.OnFocusOpts) error - OnFocusLost func() error - - OnGetSelectedItemId func() string - OnGetPanelState func() types.IListPanelState - // if this is true, we'll call GetDisplayStrings for just the visible part of the - // view and re-render that. This is useful when you need to render different - // content based on the selection (e.g. for showing the selected commit) - RenderSelection bool - - Gui *Gui - - *context.BaseContext -} - -var _ types.IListContext = &ListContext{} - -func (self *ListContext) GetPanelState() types.IListPanelState { - return self.OnGetPanelState() -} - -func (self *ListContext) FocusLine() { - view, err := self.Gui.g.View(self.ViewName) - if err != nil { - // ignoring error for now - return - } - - // we need a way of knowing whether we've rendered to the view yet. - view.FocusPoint(view.OriginX(), self.GetPanelState().GetSelectedLineIdx()) - if self.RenderSelection { - _, originY := view.Origin() - displayStrings := self.GetDisplayStrings(originY, view.InnerHeight()+1) - self.Gui.renderDisplayStringsInViewPort(view, displayStrings) - } - view.Footer = formatListFooter(self.GetPanelState().GetSelectedLineIdx(), self.GetItemsLength()) -} - -func formatListFooter(selectedLineIdx int, length int) string { - return fmt.Sprintf("%d of %d", selectedLineIdx+1, length) -} - -func (self *ListContext) GetSelectedItemId() string { - return self.OnGetSelectedItemId() -} - -// OnFocus assumes that the content of the context has already been rendered to the view. OnRender is the function which actually renders the content to the view -func (self *ListContext) HandleRender() error { - view, err := self.Gui.g.View(self.ViewName) - if err != nil { - return nil - } - - if self.GetDisplayStrings != nil { - self.Gui.refreshSelectedLine(self.GetPanelState(), self.GetItemsLength()) - self.Gui.renderDisplayStrings(view, self.GetDisplayStrings(0, self.GetItemsLength())) - self.Gui.render() - } - - return nil -} - -func (self *ListContext) HandleFocusLost() error { - if self.OnFocusLost != nil { - return self.OnFocusLost() - } - - view, err := self.Gui.g.View(self.ViewName) - if err != nil { - return nil - } - - _ = view.SetOriginX(0) - - return nil -} - -func (self *ListContext) HandleFocus(opts ...types.OnFocusOpts) error { - self.FocusLine() - - if self.OnFocus != nil { - if err := self.OnFocus(opts...); err != nil { - return err - } - } - - if self.OnRenderToMain != nil { - if err := self.OnRenderToMain(opts...); err != nil { - return err - } - } - - return nil -} - -func (self *ListContext) HandlePrevLine() error { - return self.handleLineChange(-1) -} - -func (self *ListContext) HandleNextLine() error { - return self.handleLineChange(1) -} - -func (self *ListContext) HandleScrollLeft() error { - return self.scroll(self.Gui.scrollLeft) -} - -func (self *ListContext) HandleScrollRight() error { - return self.scroll(self.Gui.scrollRight) -} - -func (self *ListContext) scroll(scrollFunc func(*gocui.View)) error { - if self.ignoreKeybinding() { - return nil - } - - // get the view, move the origin - view, err := self.Gui.g.View(self.ViewName) - if err != nil { - return nil - } - - scrollFunc(view) - - return self.HandleFocus() -} - -func (self *ListContext) ignoreKeybinding() bool { - return !self.Gui.isPopupPanel(self.ViewName) && self.Gui.popupPanelFocused() -} - -func (self *ListContext) handleLineChange(change int) error { - if self.ignoreKeybinding() { - return nil - } - - selectedLineIdx := self.GetPanelState().GetSelectedLineIdx() - if (change < 0 && selectedLineIdx == 0) || (change > 0 && selectedLineIdx == self.GetItemsLength()-1) { - return nil - } - - self.Gui.changeSelectedLine(self.GetPanelState(), self.GetItemsLength(), change) - - return self.HandleFocus() -} - -func (self *ListContext) HandleNextPage() error { - view, err := self.Gui.g.View(self.ViewName) - if err != nil { - return nil - } - delta := self.Gui.pageDelta(view) - - return self.handleLineChange(delta) -} - -func (self *ListContext) HandleGotoTop() error { - return self.handleLineChange(-self.GetItemsLength()) -} - -func (self *ListContext) HandleGotoBottom() error { - return self.handleLineChange(self.GetItemsLength()) -} - -func (self *ListContext) HandlePrevPage() error { - view, err := self.Gui.g.View(self.ViewName) - if err != nil { - return nil - } - - delta := self.Gui.pageDelta(view) - - return self.handleLineChange(-delta) -} - -func (self *ListContext) HandleClick(onClick func() error) error { - if self.ignoreKeybinding() { - return nil - } - - view, err := self.Gui.g.View(self.ViewName) - if err != nil { - return nil - } - - prevSelectedLineIdx := self.GetPanelState().GetSelectedLineIdx() - newSelectedLineIdx := view.SelectedLineIdx() - - // we need to focus the view - if err := self.Gui.c.PushContext(self); err != nil { - return err - } - - if newSelectedLineIdx > self.GetItemsLength()-1 { - return nil - } - - self.GetPanelState().SetSelectedLineIdx(newSelectedLineIdx) - - prevViewName := self.Gui.currentViewName() - if prevSelectedLineIdx == newSelectedLineIdx && prevViewName == self.ViewName && onClick != nil { - return onClick() - } - return self.HandleFocus() -} - -func (self *ListContext) OnSearchSelect(selectedLineIdx int) error { - self.GetPanelState().SetSelectedLineIdx(selectedLineIdx) - return self.HandleFocus() -} - -func (self *ListContext) HandleRenderToMain() error { - if self.OnRenderToMain != nil { - return self.OnRenderToMain() - } - - return nil -} - -func (self *ListContext) attachKeybindings() *ListContext { - self.BaseContext.AddKeybindingsFn(self.keybindings) - - return self -} - -func (self *ListContext) keybindings(opts types.KeybindingsOpts) []*types.Binding { - return []*types.Binding{ - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.PrevItemAlt), Modifier: gocui.ModNone, Handler: self.HandlePrevLine}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.PrevItem), Modifier: gocui.ModNone, Handler: self.HandlePrevLine}, - {Tag: "navigation", Key: gocui.MouseWheelUp, Modifier: gocui.ModNone, Handler: self.HandlePrevLine}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.NextItemAlt), Modifier: gocui.ModNone, Handler: self.HandleNextLine}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.NextItem), Modifier: gocui.ModNone, Handler: self.HandleNextLine}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.PrevPage), Modifier: gocui.ModNone, Handler: self.HandlePrevPage, Description: self.Gui.c.Tr.LcPrevPage}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.NextPage), Modifier: gocui.ModNone, Handler: self.HandleNextPage, Description: self.Gui.c.Tr.LcNextPage}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.GotoTop), Modifier: gocui.ModNone, Handler: self.HandleGotoTop, Description: self.Gui.c.Tr.LcGotoTop}, - {Key: gocui.MouseLeft, Modifier: gocui.ModNone, Handler: func() error { return self.HandleClick(nil) }}, - {Tag: "navigation", Key: gocui.MouseWheelDown, Modifier: gocui.ModNone, Handler: self.HandleNextLine}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.ScrollLeft), Modifier: gocui.ModNone, Handler: self.HandleScrollLeft}, - {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.ScrollRight), Modifier: gocui.ModNone, Handler: self.HandleScrollRight}, - { - Key: opts.GetKey(opts.Config.Universal.StartSearch), - Handler: func() error { return self.Gui.handleOpenSearch(self.GetViewName()) }, - Description: self.Gui.c.Tr.LcStartSearch, - Tag: "navigation", - }, - { - Key: opts.GetKey(opts.Config.Universal.GotoBottom), - Description: self.Gui.c.Tr.LcGotoBottom, - Handler: self.HandleGotoBottom, - Tag: "navigation", - }, - } -} diff --git a/pkg/gui/list_context_config.go b/pkg/gui/list_context_config.go index dedcab4cc..b8609e9b7 100644 --- a/pkg/gui/list_context_config.go +++ b/pkg/gui/list_context_config.go @@ -3,7 +3,6 @@ package gui import ( "log" - "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/gui/context" @@ -12,27 +11,21 @@ import ( "github.com/jesseduffield/lazygit/pkg/gui/types" ) -func (gui *Gui) menuListContext() types.IListContext { - return (&ListContext{ - BaseContext: context.NewBaseContext(context.NewBaseContextOpts{ - ViewName: "menu", - Key: "menu", - Kind: types.PERSISTENT_POPUP, - OnGetOptionsMap: gui.getMenuOptions, - Focusable: true, - }), - GetItemsLength: func() int { return gui.Views.Menu.LinesHeight() }, - OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.Menu }, - Gui: gui, - - // no GetDisplayStrings field because we do a custom render on menu creation - }).attachKeybindings() +func (gui *Gui) menuListContext() *context.MenuContext { + return context.NewMenuContext( + gui.Views.Menu, + nil, + nil, + nil, + gui.c, + gui.getMenuOptions, + ) } func (gui *Gui) filesListContext() *context.WorkingTreeContext { return context.NewWorkingTreeContext( func() []*models.File { return gui.State.Model.Files }, - func() *gocui.View { return gui.Views.Files }, + gui.Views.Files, func(startIdx int, length int) [][]string { lines := presentation.RenderFileTree(gui.State.Contexts.Files.FileTreeViewModel, gui.State.Modes.Diffing.Ref, gui.State.Model.Submodules) mappedLines := make([][]string, len(lines)) @@ -49,82 +42,46 @@ func (gui *Gui) filesListContext() *context.WorkingTreeContext { ) } -func (gui *Gui) branchesListContext() types.IListContext { - return (&ListContext{ - BaseContext: context.NewBaseContext(context.NewBaseContextOpts{ - ViewName: "branches", - WindowName: "branches", - Key: context.LOCAL_BRANCHES_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, - }), - GetItemsLength: func() int { return len(gui.State.Model.Branches) }, - OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.Branches }, - OnRenderToMain: OnFocusWrapper(gui.withDiffModeCheck(gui.branchesRenderToMain)), - Gui: gui, - GetDisplayStrings: func(startIdx int, length int) [][]string { +func (gui *Gui) branchesListContext() *context.BranchesContext { + return context.NewBranchesContext( + func() []*models.Branch { return gui.State.Model.Branches }, + gui.Views.Branches, + func(startIdx int, length int) [][]string { return presentation.GetBranchListDisplayStrings(gui.State.Model.Branches, gui.State.ScreenMode != SCREEN_NORMAL, gui.State.Modes.Diffing.Ref) }, - OnGetSelectedItemId: func() string { - item := gui.getSelectedBranch() - if item == nil { - return "" - } - return item.ID() - }, - }).attachKeybindings() + nil, + OnFocusWrapper(gui.withDiffModeCheck(gui.branchesRenderToMain)), + nil, + gui.c, + ) } -func (gui *Gui) remotesListContext() types.IListContext { - return (&ListContext{ - BaseContext: context.NewBaseContext(context.NewBaseContextOpts{ - ViewName: "branches", - WindowName: "branches", - Key: context.REMOTES_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, - }), - GetItemsLength: func() int { return len(gui.State.Model.Remotes) }, - OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.Remotes }, - OnRenderToMain: OnFocusWrapper(gui.withDiffModeCheck(gui.remotesRenderToMain)), - Gui: gui, - GetDisplayStrings: func(startIdx int, length int) [][]string { +func (gui *Gui) remotesListContext() *context.RemotesContext { + return context.NewRemotesContext( + func() []*models.Remote { return gui.State.Model.Remotes }, + gui.Views.Branches, + func(startIdx int, length int) [][]string { return presentation.GetRemoteListDisplayStrings(gui.State.Model.Remotes, gui.State.Modes.Diffing.Ref) }, - OnGetSelectedItemId: func() string { - item := gui.getSelectedRemote() - if item == nil { - return "" - } - return item.ID() - }, - }).attachKeybindings() + nil, + OnFocusWrapper(gui.withDiffModeCheck(gui.remotesRenderToMain)), + nil, + gui.c, + ) } -func (gui *Gui) remoteBranchesListContext() types.IListContext { - return (&ListContext{ - BaseContext: context.NewBaseContext(context.NewBaseContextOpts{ - ViewName: "branches", - WindowName: "branches", - Key: context.REMOTE_BRANCHES_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, - }), - GetItemsLength: func() int { return len(gui.State.Model.RemoteBranches) }, - OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.RemoteBranches }, - OnRenderToMain: OnFocusWrapper(gui.withDiffModeCheck(gui.remoteBranchesRenderToMain)), - Gui: gui, - GetDisplayStrings: func(startIdx int, length int) [][]string { +func (gui *Gui) remoteBranchesListContext() *context.RemoteBranchesContext { + return context.NewRemoteBranchesContext( + func() []*models.RemoteBranch { return gui.State.Model.RemoteBranches }, + gui.Views.Branches, + func(startIdx int, length int) [][]string { return presentation.GetRemoteBranchListDisplayStrings(gui.State.Model.RemoteBranches, gui.State.Modes.Diffing.Ref) }, - OnGetSelectedItemId: func() string { - item := gui.getSelectedRemoteBranch() - if item == nil { - return "" - } - return item.ID() - }, - }).attachKeybindings() + nil, + OnFocusWrapper(gui.withDiffModeCheck(gui.remoteBranchesRenderToMain)), + nil, + gui.c, + ) } func (gui *Gui) withDiffModeCheck(f func() error) func() error { @@ -140,7 +97,7 @@ func (gui *Gui) withDiffModeCheck(f func() error) func() error { func (gui *Gui) tagsListContext() *context.TagsContext { return context.NewTagsContext( func() []*models.Tag { return gui.State.Model.Tags }, - func() *gocui.View { return gui.Views.Branches }, + gui.Views.Branches, func(startIdx int, length int) [][]string { return presentation.GetTagListDisplayStrings(gui.State.Model.Tags, gui.State.Modes.Diffing.Ref) }, @@ -151,25 +108,14 @@ func (gui *Gui) tagsListContext() *context.TagsContext { ) } -func (gui *Gui) branchCommitsListContext() types.IListContext { - parseEmoji := gui.c.UserConfig.Git.ParseEmoji - return (&ListContext{ - BaseContext: context.NewBaseContext(context.NewBaseContextOpts{ - ViewName: "commits", - WindowName: "commits", - Key: context.BRANCH_COMMITS_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, - }), - GetItemsLength: func() int { return len(gui.State.Model.Commits) }, - OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.Commits }, - OnFocus: OnFocusWrapper(gui.onCommitFocus), - OnRenderToMain: OnFocusWrapper(gui.withDiffModeCheck(gui.branchCommitsRenderToMain)), - Gui: gui, - GetDisplayStrings: func(startIdx int, length int) [][]string { +func (gui *Gui) branchCommitsListContext() *context.LocalCommitsContext { + return context.NewLocalCommitsContext( + func() []*models.Commit { return gui.State.Model.Commits }, + gui.Views.Commits, + func(startIdx int, length int) [][]string { selectedCommitSha := "" if gui.currentContext().GetKey() == context.BRANCH_COMMITS_CONTEXT_KEY { - selectedCommit := gui.getSelectedLocalCommit() + selectedCommit := gui.State.Contexts.BranchCommits.GetSelected() if selectedCommit != nil { selectedCommitSha = selectedCommit.Sha } @@ -179,7 +125,7 @@ func (gui *Gui) branchCommitsListContext() types.IListContext { gui.State.ScreenMode != SCREEN_NORMAL, gui.helpers.CherryPick.CherryPickedCommitShaMap(), gui.State.Modes.Diffing.Ref, - parseEmoji, + gui.c.UserConfig.Git.ParseEmoji, selectedCommitSha, startIdx, length, @@ -187,35 +133,21 @@ func (gui *Gui) branchCommitsListContext() types.IListContext { gui.State.Model.BisectInfo, ) }, - OnGetSelectedItemId: func() string { - item := gui.getSelectedLocalCommit() - if item == nil { - return "" - } - return item.ID() - }, - RenderSelection: true, - }).attachKeybindings() + OnFocusWrapper(gui.onCommitFocus), + OnFocusWrapper(gui.withDiffModeCheck(gui.branchCommitsRenderToMain)), + nil, + gui.c, + ) } -func (gui *Gui) subCommitsListContext() types.IListContext { - parseEmoji := gui.c.UserConfig.Git.ParseEmoji - return (&ListContext{ - BaseContext: context.NewBaseContext(context.NewBaseContextOpts{ - ViewName: "branches", - WindowName: "branches", - Key: context.SUB_COMMITS_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, - }), - GetItemsLength: func() int { return len(gui.State.Model.SubCommits) }, - OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.SubCommits }, - OnRenderToMain: OnFocusWrapper(gui.withDiffModeCheck(gui.subCommitsRenderToMain)), - Gui: gui, - GetDisplayStrings: func(startIdx int, length int) [][]string { +func (gui *Gui) subCommitsListContext() *context.SubCommitsContext { + return context.NewSubCommitsContext( + func() []*models.Commit { return gui.State.Model.SubCommits }, + gui.Views.Branches, + func(startIdx int, length int) [][]string { selectedCommitSha := "" if gui.currentContext().GetKey() == context.SUB_COMMITS_CONTEXT_KEY { - selectedCommit := gui.getSelectedSubCommit() + selectedCommit := gui.State.Contexts.SubCommits.GetSelected() if selectedCommit != nil { selectedCommitSha = selectedCommit.Sha } @@ -225,7 +157,7 @@ func (gui *Gui) subCommitsListContext() types.IListContext { gui.State.ScreenMode != SCREEN_NORMAL, gui.helpers.CherryPick.CherryPickedCommitShaMap(), gui.State.Modes.Diffing.Ref, - parseEmoji, + gui.c.UserConfig.Git.ParseEmoji, selectedCommitSha, startIdx, length, @@ -233,15 +165,11 @@ func (gui *Gui) subCommitsListContext() types.IListContext { git_commands.NewNullBisectInfo(), ) }, - OnGetSelectedItemId: func() string { - item := gui.getSelectedSubCommit() - if item == nil { - return "" - } - return item.ID() - }, - RenderSelection: true, - }).attachKeybindings() + nil, + OnFocusWrapper(gui.withDiffModeCheck(gui.subCommitsRenderToMain)), + nil, + gui.c, + ) } func (gui *Gui) shouldShowGraph() bool { @@ -263,69 +191,44 @@ func (gui *Gui) shouldShowGraph() bool { return false } -func (gui *Gui) reflogCommitsListContext() types.IListContext { - parseEmoji := gui.c.UserConfig.Git.ParseEmoji - return (&ListContext{ - BaseContext: context.NewBaseContext(context.NewBaseContextOpts{ - ViewName: "commits", - WindowName: "commits", - Key: context.REFLOG_COMMITS_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, - }), - GetItemsLength: func() int { return len(gui.State.Model.FilteredReflogCommits) }, - OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.ReflogCommits }, - OnRenderToMain: OnFocusWrapper(gui.withDiffModeCheck(gui.reflogCommitsRenderToMain)), - Gui: gui, - GetDisplayStrings: func(startIdx int, length int) [][]string { +func (gui *Gui) reflogCommitsListContext() *context.ReflogCommitsContext { + return context.NewReflogCommitsContext( + func() []*models.Commit { return gui.State.Model.FilteredReflogCommits }, + gui.Views.Commits, + func(startIdx int, length int) [][]string { return presentation.GetReflogCommitListDisplayStrings( gui.State.Model.FilteredReflogCommits, gui.State.ScreenMode != SCREEN_NORMAL, gui.helpers.CherryPick.CherryPickedCommitShaMap(), gui.State.Modes.Diffing.Ref, - parseEmoji, + gui.c.UserConfig.Git.ParseEmoji, ) }, - OnGetSelectedItemId: func() string { - item := gui.getSelectedReflogCommit() - if item == nil { - return "" - } - return item.ID() - }, - }).attachKeybindings() + nil, + OnFocusWrapper(gui.withDiffModeCheck(gui.reflogCommitsRenderToMain)), + nil, + gui.c, + ) } -func (gui *Gui) stashListContext() types.IListContext { - return (&ListContext{ - BaseContext: context.NewBaseContext(context.NewBaseContextOpts{ - ViewName: "stash", - WindowName: "stash", - Key: context.STASH_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, - }), - GetItemsLength: func() int { return len(gui.State.Model.StashEntries) }, - OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.Stash }, - OnRenderToMain: OnFocusWrapper(gui.withDiffModeCheck(gui.stashRenderToMain)), - Gui: gui, - GetDisplayStrings: func(startIdx int, length int) [][]string { +func (gui *Gui) stashListContext() *context.StashContext { + return context.NewStashContext( + func() []*models.StashEntry { return gui.State.Model.StashEntries }, + gui.Views.Stash, + func(startIdx int, length int) [][]string { return presentation.GetStashEntryListDisplayStrings(gui.State.Model.StashEntries, gui.State.Modes.Diffing.Ref) }, - OnGetSelectedItemId: func() string { - item := gui.getSelectedStashEntry() - if item == nil { - return "" - } - return item.ID() - }, - }).attachKeybindings() + nil, + OnFocusWrapper(gui.withDiffModeCheck(gui.stashRenderToMain)), + nil, + gui.c, + ) } func (gui *Gui) commitFilesListContext() *context.CommitFilesContext { return context.NewCommitFilesContext( func() []*models.CommitFile { return gui.State.Model.CommitFiles }, - func() *gocui.View { return gui.Views.CommitFiles }, + gui.Views.CommitFiles, func(startIdx int, length int) [][]string { if gui.State.Contexts.CommitFiles.CommitFileTreeViewModel.GetItemsLength() == 0 { return [][]string{{style.FgRed.Sprint("(none)")}} @@ -346,48 +249,32 @@ func (gui *Gui) commitFilesListContext() *context.CommitFilesContext { ) } -func (gui *Gui) submodulesListContext() types.IListContext { - return (&ListContext{ - BaseContext: context.NewBaseContext(context.NewBaseContextOpts{ - ViewName: "files", - WindowName: "files", - Key: context.SUBMODULES_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, - }), - GetItemsLength: func() int { return len(gui.State.Model.Submodules) }, - OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.Submodules }, - OnRenderToMain: OnFocusWrapper(gui.withDiffModeCheck(gui.submodulesRenderToMain)), - Gui: gui, - GetDisplayStrings: func(startIdx int, length int) [][]string { +func (gui *Gui) submodulesListContext() *context.SubmodulesContext { + return context.NewSubmodulesContext( + func() []*models.SubmoduleConfig { return gui.State.Model.Submodules }, + gui.Views.Files, + func(startIdx int, length int) [][]string { return presentation.GetSubmoduleListDisplayStrings(gui.State.Model.Submodules) }, - OnGetSelectedItemId: func() string { - item := gui.getSelectedSubmodule() - if item == nil { - return "" - } - return item.ID() - }, - }).attachKeybindings() + nil, + OnFocusWrapper(gui.withDiffModeCheck(gui.submodulesRenderToMain)), + nil, + gui.c, + ) } -func (gui *Gui) suggestionsListContext() types.IListContext { - return (&ListContext{ - BaseContext: context.NewBaseContext(context.NewBaseContextOpts{ - ViewName: "suggestions", - WindowName: "suggestions", - Key: context.SUGGESTIONS_CONTEXT_KEY, - Kind: types.PERSISTENT_POPUP, - Focusable: true, - }), - GetItemsLength: func() int { return len(gui.State.Suggestions) }, - OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.Suggestions }, - Gui: gui, - GetDisplayStrings: func(startIdx int, length int) [][]string { +func (gui *Gui) suggestionsListContext() *context.SuggestionsContext { + return context.NewSuggestionsContext( + func() []*types.Suggestion { return gui.State.Suggestions }, + gui.Views.Files, + func(startIdx int, length int) [][]string { return presentation.GetSuggestionListDisplayStrings(gui.State.Suggestions) }, - }).attachKeybindings() + nil, + nil, + nil, + gui.c, + ) } func (gui *Gui) getListContexts() []types.IListContext { diff --git a/pkg/gui/menu_panel.go b/pkg/gui/menu_panel.go index 3e20baebb..9388be279 100644 --- a/pkg/gui/menu_panel.go +++ b/pkg/gui/menu_panel.go @@ -6,7 +6,6 @@ import ( "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/theme" - "github.com/jesseduffield/lazygit/pkg/utils" ) func (gui *Gui) getMenuOptions() map[string]string { @@ -35,44 +34,24 @@ func (gui *Gui) createMenu(opts types.CreateMenuOptions) error { }) } - gui.State.MenuItems = opts.Items - - stringArrays := make([][]string, len(opts.Items)) - for i, item := range opts.Items { + for _, item := range opts.Items { if item.OpensMenu && item.DisplayStrings != nil { return errors.New("Message for the developer of this app: you've set opensMenu with displaystrings on the menu panel. Bad developer!. Apologies, user") } - - if item.DisplayStrings == nil { - styledStr := item.DisplayString - if item.OpensMenu { - styledStr = opensMenuStyle(styledStr) - } - stringArrays[i] = []string{styledStr} - } else { - stringArrays[i] = item.DisplayStrings - } } - list := utils.RenderDisplayStrings(stringArrays) - - x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(false, list) + x0, y0, x1, y1 := gui.getConfirmationPanelDimensionsForContentHeight(len(opts.Items)) menuView, _ := gui.g.SetView("menu", x0, y0, x1, y1, 0) menuView.Title = opts.Title menuView.FgColor = theme.GocuiDefaultTextColor menuView.SetOnSelectItem(gui.onSelectItemWrapper(func(selectedLine int) error { return nil })) - menuView.SetContent(list) - gui.State.Panels.Menu.SelectedLineIdx = 0 + gui.State.Contexts.Menu.SetMenuItems(opts.Items) + gui.State.Contexts.Menu.GetPanelState().SetSelectedLineIdx(0) + _ = gui.c.PostRefreshUpdate(gui.State.Contexts.Menu) + + // TODO: ensure that if we're opened a menu from within a menu that it renders correctly return gui.c.PushContext(gui.State.Contexts.Menu) } - -func (gui *Gui) getSelectedMenuItem() *types.MenuItem { - if len(gui.State.MenuItems) == 0 { - return nil - } - - return gui.State.MenuItems[gui.State.Panels.Menu.SelectedLineIdx] -} diff --git a/pkg/gui/options_menu_panel.go b/pkg/gui/options_menu_panel.go index b9df16722..3ca6ea388 100644 --- a/pkg/gui/options_menu_panel.go +++ b/pkg/gui/options_menu_panel.go @@ -3,6 +3,7 @@ package gui import ( "strings" + "github.com/jesseduffield/lazygit/pkg/gui/presentation" "github.com/jesseduffield/lazygit/pkg/gui/style" "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/utils" @@ -34,16 +35,12 @@ func (gui *Gui) getBindings(context types.Context) []*types.Binding { func (gui *Gui) displayDescription(binding *types.Binding) string { if binding.OpensMenu { - return opensMenuStyle(binding.Description) + return presentation.OpensMenuStyle(binding.Description) } return style.FgCyan.Sprint(binding.Description) } -func opensMenuStyle(str string) string { - return style.FgMagenta.Sprintf("%s...", str) -} - func (gui *Gui) handleCreateOptionsMenu() error { context := gui.currentContext() bindings := gui.getBindings(context) diff --git a/pkg/gui/patch_building_panel.go b/pkg/gui/patch_building_panel.go index fc41bd603..6b48d2bc2 100644 --- a/pkg/gui/patch_building_panel.go +++ b/pkg/gui/patch_building_panel.go @@ -26,7 +26,7 @@ func (gui *Gui) refreshPatchBuildingPanel(selectedLineIdx int) error { gui.Views.Secondary.Title = "Custom Patch" // get diff from commit file that's currently selected - node := gui.getSelectedCommitFileNode() + node := gui.State.Contexts.CommitFiles.GetSelectedFileNode() if node == nil { return nil } @@ -87,7 +87,7 @@ func (gui *Gui) handleToggleSelectionForPatch() error { } // add range of lines to those set for the file - node := gui.getSelectedCommitFileNode() + node := gui.State.Contexts.CommitFiles.GetSelectedFileNode() if node == nil { return nil } diff --git a/pkg/gui/patch_options_panel.go b/pkg/gui/patch_options_panel.go index 4eda367bd..d69dbda27 100644 --- a/pkg/gui/patch_options_panel.go +++ b/pkg/gui/patch_options_panel.go @@ -118,7 +118,7 @@ func (gui *Gui) handleMovePatchToSelectedCommit() error { return gui.c.WithWaitingStatus(gui.c.Tr.RebasingStatus, func() error { commitIndex := gui.getPatchCommitIndex() gui.c.LogAction(gui.c.Tr.Actions.MovePatchToSelectedCommit) - err := gui.git.Patch.MovePatchToSelectedCommit(gui.State.Model.Commits, commitIndex, gui.State.Panels.Commits.SelectedLineIdx) + err := gui.git.Patch.MovePatchToSelectedCommit(gui.State.Model.Commits, commitIndex, gui.State.Contexts.BranchCommits.GetSelectedLineIdx()) return gui.helpers.Rebase.CheckMergeOrRebase(err) }) } diff --git a/pkg/gui/presentation/menu.go b/pkg/gui/presentation/menu.go new file mode 100644 index 000000000..c43896c22 --- /dev/null +++ b/pkg/gui/presentation/menu.go @@ -0,0 +1,7 @@ +package presentation + +import "github.com/jesseduffield/lazygit/pkg/gui/style" + +func OpensMenuStyle(str string) string { + return style.FgMagenta.Sprintf("%s...", str) +} diff --git a/pkg/gui/reflog_panel.go b/pkg/gui/reflog_panel.go index d3569ee81..57b4e0a35 100644 --- a/pkg/gui/reflog_panel.go +++ b/pkg/gui/reflog_panel.go @@ -9,17 +9,11 @@ import ( // list panel functions func (gui *Gui) getSelectedReflogCommit() *models.Commit { - selectedLine := gui.State.Panels.ReflogCommits.SelectedLineIdx - reflogComits := gui.State.Model.FilteredReflogCommits - if selectedLine == -1 || len(reflogComits) == 0 { - return nil - } - - return reflogComits[selectedLine] + return gui.State.Contexts.ReflogCommits.GetSelected() } func (gui *Gui) reflogCommitsRenderToMain() error { - commit := gui.getSelectedReflogCommit() + commit := gui.State.Contexts.ReflogCommits.GetSelected() var task updateTask if commit == nil { task = NewRenderStringTask("No reflog history") @@ -38,7 +32,7 @@ func (gui *Gui) reflogCommitsRenderToMain() error { } func (gui *Gui) CheckoutReflogCommit() error { - commit := gui.getSelectedReflogCommit() + commit := gui.State.Contexts.ReflogCommits.GetSelected() if commit == nil { return nil } @@ -55,19 +49,17 @@ func (gui *Gui) CheckoutReflogCommit() error { return err } - gui.State.Panels.ReflogCommits.SelectedLineIdx = 0 - return nil } func (gui *Gui) handleCreateReflogResetMenu() error { - commit := gui.getSelectedReflogCommit() + commit := gui.State.Contexts.ReflogCommits.GetSelected() return gui.helpers.Refs.CreateGitResetMenu(commit.Sha) } func (gui *Gui) handleViewReflogCommitFiles() error { - commit := gui.getSelectedReflogCommit() + commit := gui.State.Contexts.ReflogCommits.GetSelected() if commit == nil { return nil } @@ -81,7 +73,7 @@ func (gui *Gui) handleViewReflogCommitFiles() error { } func (gui *Gui) handleCopyReflogCommit() error { - commit := gui.getSelectedReflogCommit() + commit := gui.State.Contexts.ReflogCommits.GetSelected() if commit == nil { return nil } @@ -91,7 +83,7 @@ func (gui *Gui) handleCopyReflogCommit() error { func (gui *Gui) handleCopyReflogCommitRange() error { // just doing this to ensure something is selected - commit := gui.getSelectedReflogCommit() + commit := gui.State.Contexts.ReflogCommits.GetSelected() if commit == nil { return nil } diff --git a/pkg/gui/refresh.go b/pkg/gui/refresh.go index a19a685dc..d4ed2d2f9 100644 --- a/pkg/gui/refresh.go +++ b/pkg/gui/refresh.go @@ -211,7 +211,7 @@ func (gui *Gui) refreshCommitsWithLimit() error { commits, err := gui.git.Loaders.Commits.GetCommits( loaders.GetCommitsOptions{ - Limit: gui.State.Panels.Commits.LimitCommits, + Limit: gui.State.LimitCommits, FilterPath: gui.State.Modes.Filtering.GetPath(), IncludeRebaseCommits: true, RefName: gui.refForLog(), @@ -484,7 +484,7 @@ func (gui *Gui) refreshReflogCommits() error { } func (gui *Gui) refreshRemotes() error { - prevSelectedRemote := gui.getSelectedRemote() + prevSelectedRemote := gui.State.Contexts.Remotes.GetSelected() remotes, err := gui.git.Loaders.Remotes.GetRemotes() if err != nil { diff --git a/pkg/gui/remote_branches_panel.go b/pkg/gui/remote_branches_panel.go index 0c00f8d80..243a7ca4d 100644 --- a/pkg/gui/remote_branches_panel.go +++ b/pkg/gui/remote_branches_panel.go @@ -4,25 +4,15 @@ import ( "fmt" "strings" - "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/utils" ) // list panel functions -func (gui *Gui) getSelectedRemoteBranch() *models.RemoteBranch { - selectedLine := gui.State.Panels.RemoteBranches.SelectedLineIdx - if selectedLine == -1 || len(gui.State.Model.RemoteBranches) == 0 { - return nil - } - - return gui.State.Model.RemoteBranches[selectedLine] -} - func (gui *Gui) remoteBranchesRenderToMain() error { var task updateTask - remoteBranch := gui.getSelectedRemoteBranch() + remoteBranch := gui.State.Contexts.RemoteBranches.GetSelected() if remoteBranch == nil { task = NewRenderStringTask("No branches for this remote") } else { @@ -43,12 +33,12 @@ func (gui *Gui) handleRemoteBranchesEscape() error { } func (gui *Gui) handleMergeRemoteBranch() error { - selectedBranchName := gui.getSelectedRemoteBranch().FullName() + selectedBranchName := gui.State.Contexts.RemoteBranches.GetSelected().FullName() return gui.mergeBranchIntoCheckedOutBranch(selectedBranchName) } func (gui *Gui) handleDeleteRemoteBranch() error { - remoteBranch := gui.getSelectedRemoteBranch() + remoteBranch := gui.State.Contexts.RemoteBranches.GetSelected() if remoteBranch == nil { return nil } @@ -72,12 +62,12 @@ func (gui *Gui) handleDeleteRemoteBranch() error { } func (gui *Gui) handleRebaseOntoRemoteBranch() error { - selectedBranchName := gui.getSelectedRemoteBranch().FullName() + selectedBranchName := gui.State.Contexts.RemoteBranches.GetSelected().FullName() return gui.handleRebaseOntoBranch(selectedBranchName) } func (gui *Gui) handleSetBranchUpstream() error { - selectedBranch := gui.getSelectedRemoteBranch() + selectedBranch := gui.State.Contexts.RemoteBranches.GetSelected() checkedOutBranch := gui.getCheckedOutBranch() message := utils.ResolvePlaceholderString( @@ -103,7 +93,7 @@ func (gui *Gui) handleSetBranchUpstream() error { } func (gui *Gui) handleCreateResetToRemoteBranchMenu() error { - selectedBranch := gui.getSelectedRemoteBranch() + selectedBranch := gui.State.Contexts.RemoteBranches.GetSelected() if selectedBranch == nil { return nil } @@ -112,7 +102,7 @@ func (gui *Gui) handleCreateResetToRemoteBranchMenu() error { } func (gui *Gui) handleEnterRemoteBranch() error { - selectedBranch := gui.getSelectedRemoteBranch() + selectedBranch := gui.State.Contexts.RemoteBranches.GetSelected() if selectedBranch == nil { return nil } @@ -121,7 +111,7 @@ func (gui *Gui) handleEnterRemoteBranch() error { } func (gui *Gui) handleNewBranchOffRemoteBranch() error { - selectedBranch := gui.getSelectedRemoteBranch() + selectedBranch := gui.State.Contexts.RemoteBranches.GetSelected() if selectedBranch == nil { return nil } diff --git a/pkg/gui/remotes_panel.go b/pkg/gui/remotes_panel.go index 1273ee6ad..f47bd1a01 100644 --- a/pkg/gui/remotes_panel.go +++ b/pkg/gui/remotes_panel.go @@ -4,24 +4,14 @@ import ( "fmt" "strings" - "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/gui/style" ) // list panel functions -func (gui *Gui) getSelectedRemote() *models.Remote { - selectedLine := gui.State.Panels.Remotes.SelectedLineIdx - if selectedLine == -1 || len(gui.State.Model.Remotes) == 0 { - return nil - } - - return gui.State.Model.Remotes[selectedLine] -} - func (gui *Gui) remotesRenderToMain() error { var task updateTask - remote := gui.getSelectedRemote() + remote := gui.State.Contexts.Remotes.GetSelected() if remote == nil { task = NewRenderStringTask("No remotes") } else { diff --git a/pkg/gui/stash_panel.go b/pkg/gui/stash_panel.go index ed68d3cd4..e51fcc054 100644 --- a/pkg/gui/stash_panel.go +++ b/pkg/gui/stash_panel.go @@ -9,12 +9,7 @@ import ( // list panel functions func (gui *Gui) getSelectedStashEntry() *models.StashEntry { - selectedLine := gui.State.Panels.Stash.SelectedLineIdx - if selectedLine == -1 { - return nil - } - - return gui.State.Model.StashEntries[selectedLine] + return gui.State.Contexts.Stash.GetSelected() } func (gui *Gui) stashRenderToMain() error { diff --git a/pkg/gui/sub_commits_panel.go b/pkg/gui/sub_commits_panel.go index a5756649f..04c131625 100644 --- a/pkg/gui/sub_commits_panel.go +++ b/pkg/gui/sub_commits_panel.go @@ -2,25 +2,14 @@ package gui import ( "github.com/jesseduffield/lazygit/pkg/commands/loaders" - "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/gui/controllers" "github.com/jesseduffield/lazygit/pkg/gui/types" ) // list panel functions -func (gui *Gui) getSelectedSubCommit() *models.Commit { - selectedLine := gui.State.Panels.SubCommits.SelectedLineIdx - commits := gui.State.Model.SubCommits - if selectedLine == -1 || len(commits) == 0 { - return nil - } - - return commits[selectedLine] -} - func (gui *Gui) subCommitsRenderToMain() error { - commit := gui.getSelectedSubCommit() + commit := gui.State.Contexts.SubCommits.GetSelected() var task updateTask if commit == nil { task = NewRenderStringTask("No commits") @@ -39,7 +28,7 @@ func (gui *Gui) subCommitsRenderToMain() error { } func (gui *Gui) handleCheckoutSubCommit() error { - commit := gui.getSelectedSubCommit() + commit := gui.State.Contexts.SubCommits.GetSelected() if commit == nil { return nil } @@ -62,13 +51,13 @@ func (gui *Gui) handleCheckoutSubCommit() error { } func (gui *Gui) handleCreateSubCommitResetMenu() error { - commit := gui.getSelectedSubCommit() + commit := gui.State.Contexts.SubCommits.GetSelected() return gui.helpers.Refs.CreateGitResetMenu(commit.Sha) } func (gui *Gui) handleViewSubCommitFiles() error { - commit := gui.getSelectedSubCommit() + commit := gui.State.Contexts.SubCommits.GetSelected() if commit == nil { return nil } @@ -85,7 +74,7 @@ func (gui *Gui) switchToSubCommitsContext(refName string) error { // need to populate my sub commits commits, err := gui.git.Loaders.Commits.GetCommits( loaders.GetCommitsOptions{ - Limit: gui.State.Panels.Commits.LimitCommits, + Limit: gui.State.LimitCommits, FilterPath: gui.State.Modes.Filtering.GetPath(), IncludeRebaseCommits: false, RefName: refName, @@ -96,7 +85,6 @@ func (gui *Gui) switchToSubCommitsContext(refName string) error { } gui.State.Model.SubCommits = commits - gui.State.Panels.SubCommits.refName = refName gui.State.Contexts.SubCommits.GetPanelState().SetSelectedLineIdx(0) gui.State.Contexts.SubCommits.SetParentContext(gui.currentSideListContext()) @@ -104,7 +92,7 @@ func (gui *Gui) switchToSubCommitsContext(refName string) error { } func (gui *Gui) handleNewBranchOffSubCommit() error { - commit := gui.getSelectedSubCommit() + commit := gui.State.Contexts.SubCommits.GetSelected() if commit == nil { return nil } @@ -113,7 +101,7 @@ func (gui *Gui) handleNewBranchOffSubCommit() error { } func (gui *Gui) handleCopySubCommit() error { - commit := gui.getSelectedSubCommit() + commit := gui.State.Contexts.SubCommits.GetSelected() if commit == nil { return nil } @@ -123,7 +111,7 @@ func (gui *Gui) handleCopySubCommit() error { func (gui *Gui) handleCopySubCommitRange() error { // just doing this to ensure something is selected - commit := gui.getSelectedSubCommit() + commit := gui.State.Contexts.SubCommits.GetSelected() if commit == nil { return nil } diff --git a/pkg/gui/submodules_panel.go b/pkg/gui/submodules_panel.go index 490347c5d..3f25e077d 100644 --- a/pkg/gui/submodules_panel.go +++ b/pkg/gui/submodules_panel.go @@ -8,18 +8,9 @@ import ( "github.com/jesseduffield/lazygit/pkg/gui/style" ) -func (gui *Gui) getSelectedSubmodule() *models.SubmoduleConfig { - selectedLine := gui.State.Panels.Submodules.SelectedLineIdx - if selectedLine == -1 || len(gui.State.Model.Submodules) == 0 { - return nil - } - - return gui.State.Model.Submodules[selectedLine] -} - func (gui *Gui) submodulesRenderToMain() error { var task updateTask - submodule := gui.getSelectedSubmodule() + submodule := gui.State.Contexts.Submodules.GetSelected() if submodule == nil { task = NewRenderStringTask("No submodules") } else { diff --git a/pkg/gui/suggestions_panel.go b/pkg/gui/suggestions_panel.go index c11145ded..d7b8b0d2b 100644 --- a/pkg/gui/suggestions_panel.go +++ b/pkg/gui/suggestions_panel.go @@ -15,17 +15,12 @@ func (gui *Gui) getSelectedSuggestionValue() string { } func (gui *Gui) getSelectedSuggestion() *types.Suggestion { - selectedLine := gui.State.Panels.Suggestions.SelectedLineIdx - if selectedLine == -1 { - return nil - } - - return gui.State.Suggestions[selectedLine] + return gui.State.Contexts.Suggestions.GetSelected() } func (gui *Gui) setSuggestions(suggestions []*types.Suggestion) { gui.State.Suggestions = suggestions - gui.State.Panels.Suggestions.SelectedLineIdx = 0 + gui.State.Contexts.Suggestions.SetSelectedLineIdx(0) _ = 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 f452974d7..9757fdc77 100644 --- a/pkg/gui/tags_panel.go +++ b/pkg/gui/tags_panel.go @@ -2,7 +2,7 @@ package gui func (self *Gui) tagsRenderToMain() error { var task updateTask - tag := self.State.Contexts.Tags.GetSelectedTag() + tag := self.State.Contexts.Tags.GetSelected() if tag == nil { task = NewRenderStringTask("No tags") } else { diff --git a/pkg/gui/types/context.go b/pkg/gui/types/context.go index 7b9f47001..2f59b15f5 100644 --- a/pkg/gui/types/context.go +++ b/pkg/gui/types/context.go @@ -24,6 +24,7 @@ type ParentContexter interface { } type IBaseContext interface { + HasKeybindings ParentContexter GetKind() ContextKind @@ -35,9 +36,7 @@ type IBaseContext interface { GetOptionsMap() map[string]string - GetKeybindings(opts KeybindingsOpts) []*Binding AddKeybindingsFn(KeybindingsFn) - GetMouseKeybindings(opts KeybindingsOpts) []*gocui.ViewMouseBinding AddMouseKeybindingsFn(MouseKeybindingsFn) } @@ -50,6 +49,33 @@ type Context interface { HandleRenderToMain() error } +type IListContext interface { + Context + + GetSelectedItemId() string + + GetList() IList + + OnSearchSelect(selectedLineIdx int) error + FocusLine() + + GetPanelState() IListPanelState + GetViewTrait() IViewTrait +} + +type IViewTrait interface { + FocusPoint(yIdx int) + SetViewPortContent(content string) + SetContent(content string) + SetFooter(value string) + SetOriginX(value int) + ViewPortYBounds() (int, int) + ScrollLeft() + ScrollRight() + PageDelta() int + SelectedLineIdx() int +} + type OnFocusOpts struct { ClickedViewName string ClickedViewLineIdx int @@ -76,28 +102,6 @@ type IController interface { Context() Context } -type IListContext interface { - HasKeybindings - - GetSelectedItemId() string - HandlePrevLine() error - HandleNextLine() error - HandleScrollLeft() error - HandleScrollRight() error - HandlePrevPage() error - HandleNextPage() error - HandleGotoTop() error - HandleGotoBottom() error - HandleClick(onClick func() error) error - - OnSearchSelect(selectedLineIdx int) error - FocusLine() - - GetPanelState() IListPanelState - - Context -} - type IList interface { IListCursor GetItemsLength() int