From d82f175e79f18756769d91de94458b095130297c Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sat, 5 Feb 2022 17:04:10 +1100 Subject: 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 | 192 ++------------ 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 | 73 ++++++ 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 ++-- pkg/gui/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 +++++++++++ pkg/gui/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/simple_context.go | 74 ------ 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 ++-- 55 files changed, 1619 insertions(+), 1305 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 create mode 100644 pkg/gui/context/simple_context.go 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 delete mode 100644 pkg/gui/simple_context.go (limited to 'pkg/gui') 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 + list types.IList + viewTrait *ViewTrait + getDisplayStrings func(startIdx int, length int) [][]string +} - c *types.ControllerCommon +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 - } - } - - if self.OnRenderToMain != nil { - if err := self.OnRenderToMain(opts...); err != nil { - return err - } - } - - 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) + return self.Context.HandleFocus(opts...) } -func (self *ListContextTrait) scroll(scrollFunc func()) error { - scrollFunc() +func (self *ListContextTrait) HandleFocusLost() error { + self.viewTrait.SetOriginX(0) - return self.HandleFocus() + return self.Context.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() - } +// 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) 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/context/simple_context.go b/pkg/gui/context/simple_context.go new file mode 100644 index 000000000..ae201295b --- /dev/null +++ b/pkg/gui/context/simple_context.go @@ -0,0 +1,73 @@ +package context + +import ( + "github.com/jesseduffield/lazygit/pkg/gui/types" +) + +type SimpleContext struct { + OnFocus func(opts ...types.OnFocusOpts) error + OnFocusLost func() error + OnRender func() error + // this is for pushing some content to the main view + OnRenderToMain func(opts ...types.OnFocusOpts) error + + *BaseContext +} + +type ContextCallbackOpts struct { + OnFocus func(opts ...types.OnFocusOpts) error + OnFocusLost func() error + OnRender func() error + // this is for pushing some content to the main view + OnRenderToMain func(opts ...types.OnFocusOpts) error +} + +func NewSimpleContext(baseContext *BaseContext, opts ContextCallbackOpts) *SimpleContext { + return &SimpleContext{ + OnFocus: opts.OnFocus, + OnFocusLost: opts.OnFocusLost, + OnRender: opts.OnRender, + OnRenderToMain: opts.OnRenderToMain, + BaseContext: baseContext, + } +} + +var _ types.Context = &SimpleContext{} + +func (self *SimpleContext) HandleFocus(opts ...types.OnFocusOpts) error { + 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 *SimpleContext) HandleFocusLost() error { + if self.OnFocusLost != nil { + return self.OnFocusLost() + } + 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() + } + + return nil +} 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, - }) - - 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, + viewModel := NewTagsViewModel(getModel) + + 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,24 +63,24 @@ type TagsViewModel struct { getModel func() []*models.Tag } +func NewTagsViewModel(getModel func() []*models.Tag) *TagsViewModel { + self := &TagsViewModel{ + getModel: getModel, + } + + self.ListCursor = traits.NewListCursor(self) + + return self +} + func (self *TagsViewModel) GetItemsLength() int { return len(self.getModel()) } -func (self *TagsViewModel) GetSelectedTag() *models.Tag { +func (self *TagsViewModel) GetSelected() *models.Tag { if self.GetItemsLength() == 0 { return nil } return self.getModel()[self.GetSelectedLineIdx()] } - -func NewTagsViewModel(getModel func() []*models.Tag) *TagsViewModel { - self := &TagsViewModel{ - getModel: getModel, - } - - self.ListCursor = traits.NewListCursor(self) - - return self -} 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: