1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-07-30 03:23:08 +03:00

Enforce single-item selection in various actions

We want to show an error when the user tries to invoke an action that expects only
a single item to be selected.

We're using the GetDisabledReason field to enforce this (as well as DisabledReason
on menu items).

I've created a ListControllerTrait to store some shared convenience functions for this.
This commit is contained in:
Jesse Duffield
2024-01-14 13:51:20 +11:00
parent 280b4d60f8
commit 51fb82d6bf
45 changed files with 854 additions and 757 deletions

View File

@ -22,50 +22,61 @@ type ContainsCommits interface {
type BasicCommitsController struct {
baseController
*ListControllerTrait[*models.Commit]
c *ControllerCommon
context ContainsCommits
}
func NewBasicCommitsController(controllerCommon *ControllerCommon, context ContainsCommits) *BasicCommitsController {
func NewBasicCommitsController(c *ControllerCommon, context ContainsCommits) *BasicCommitsController {
return &BasicCommitsController{
baseController: baseController{},
c: controllerCommon,
c: c,
context: context,
ListControllerTrait: NewListControllerTrait[*models.Commit](
c,
context,
context.GetSelected,
),
}
}
func (self *BasicCommitsController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
bindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.Commits.CheckoutCommit),
Handler: self.checkSelected(self.checkout),
Description: self.c.Tr.CheckoutCommit,
Key: opts.GetKey(opts.Config.Commits.CheckoutCommit),
Handler: self.withItem(self.checkout),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.CheckoutCommit,
},
{
Key: opts.GetKey(opts.Config.Commits.CopyCommitAttributeToClipboard),
Handler: self.checkSelected(self.copyCommitAttribute),
Description: self.c.Tr.CopyCommitAttributeToClipboard,
OpensMenu: true,
Key: opts.GetKey(opts.Config.Commits.CopyCommitAttributeToClipboard),
Handler: self.withItem(self.copyCommitAttribute),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.CopyCommitAttributeToClipboard,
OpensMenu: true,
},
{
Key: opts.GetKey(opts.Config.Commits.OpenInBrowser),
Handler: self.checkSelected(self.openInBrowser),
Description: self.c.Tr.OpenCommitInBrowser,
Key: opts.GetKey(opts.Config.Commits.OpenInBrowser),
Handler: self.withItem(self.openInBrowser),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.OpenCommitInBrowser,
},
{
Key: opts.GetKey(opts.Config.Universal.New),
Handler: self.checkSelected(self.newBranch),
Description: self.c.Tr.CreateNewBranchFromCommit,
Key: opts.GetKey(opts.Config.Universal.New),
Handler: self.withItem(self.newBranch),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.CreateNewBranchFromCommit,
},
{
Key: opts.GetKey(opts.Config.Commits.ViewResetOptions),
Handler: self.checkSelected(self.createResetMenu),
Description: self.c.Tr.ViewResetOptions,
OpensMenu: true,
Key: opts.GetKey(opts.Config.Commits.ViewResetOptions),
Handler: self.withItem(self.createResetMenu),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.ViewResetOptions,
OpensMenu: true,
},
{
Key: opts.GetKey(opts.Config.Commits.CherryPickCopy),
Handler: self.checkSelected(self.copyRange),
Handler: self.withItem(self.copyRange),
Description: self.c.Tr.CherryPickCopy,
},
{
@ -74,30 +85,16 @@ func (self *BasicCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
Description: self.c.Tr.ResetCherryPick,
},
{
Key: opts.GetKey(opts.Config.Universal.OpenDiffTool),
Handler: self.checkSelected(self.openDiffTool),
Description: self.c.Tr.OpenDiffTool,
Key: opts.GetKey(opts.Config.Universal.OpenDiffTool),
Handler: self.withItem(self.openDiffTool),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.OpenDiffTool,
},
}
return bindings
}
func (self *BasicCommitsController) checkSelected(callback func(*models.Commit) error) func() error {
return func() error {
commit := self.context.GetSelected()
if commit == nil {
return nil
}
return callback(commit)
}
}
func (self *BasicCommitsController) Context() types.Context {
return self.context
}
func (self *BasicCommitsController) copyCommitAttribute(commit *models.Commit) error {
return self.c.Menu(types.CreateMenuOptions{
Title: self.c.Tr.Actions.CopyCommitAttributeToClipboard,

View File

@ -14,17 +14,23 @@ import (
type BisectController struct {
baseController
*ListControllerTrait[*models.Commit]
c *ControllerCommon
}
var _ types.IController = &BisectController{}
func NewBisectController(
common *ControllerCommon,
c *ControllerCommon,
) *BisectController {
return &BisectController{
baseController: baseController{},
c: common,
c: c,
ListControllerTrait: NewListControllerTrait[*models.Commit](
c,
c.Contexts().LocalCommits,
c.Contexts().LocalCommits.GetSelected,
),
}
}
@ -32,7 +38,7 @@ func (self *BisectController) GetKeybindings(opts types.KeybindingsOpts) []*type
bindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.Commits.ViewBisectOptions),
Handler: opts.Guards.OutsideFilterMode(self.checkSelected(self.openMenu)),
Handler: opts.Guards.OutsideFilterMode(self.withItem(self.openMenu)),
Description: self.c.Tr.ViewBisectOptions,
OpensMenu: true,
},
@ -70,9 +76,19 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
// If we have a current sha already, then we always want to use that one. If
// not, we're still picking the initial commits before we really start, so
// use the selected commit in that case.
shaToMark := lo.Ternary(info.GetCurrentSha() != "", info.GetCurrentSha(), commit.Sha)
bisecting := info.GetCurrentSha() != ""
shaToMark := lo.Ternary(bisecting, info.GetCurrentSha(), commit.Sha)
shortShaToMark := utils.ShortSha(shaToMark)
// For marking a commit as bad, when we're not already bisecting, we require
// a single item selected, but once we are bisecting, it doesn't matter because
// the action applies to the HEAD commit rather than the selected commit.
var singleItemIfNotBisecting *types.DisabledReason
if !bisecting {
singleItemIfNotBisecting = self.require(self.singleItemSelected())()
}
menuItems := []*types.MenuItem{
{
Label: fmt.Sprintf(self.c.Tr.Bisect.Mark, shortShaToMark, info.NewTerm()),
@ -84,7 +100,8 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
return self.afterMark(selectCurrentAfter, waitToReselect)
},
Key: 'b',
DisabledReason: singleItemIfNotBisecting,
Key: 'b',
},
{
Label: fmt.Sprintf(self.c.Tr.Bisect.Mark, shortShaToMark, info.OldTerm()),
@ -96,7 +113,8 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
return self.afterMark(selectCurrentAfter, waitToReselect)
},
Key: 'g',
DisabledReason: singleItemIfNotBisecting,
Key: 'g',
},
{
Label: fmt.Sprintf(self.c.Tr.Bisect.SkipCurrent, shortShaToMark),
@ -108,7 +126,8 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
return self.afterMark(selectCurrentAfter, waitToReselect)
},
Key: 's',
DisabledReason: singleItemIfNotBisecting,
Key: 's',
},
}
if info.GetCurrentSha() != "" && info.GetCurrentSha() != commit.Sha {
@ -122,7 +141,8 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
return self.afterMark(selectCurrentAfter, waitToReselect)
},
Key: 'S',
DisabledReason: self.require(self.singleItemSelected())(),
Key: 'S',
}))
}
menuItems = append(menuItems, lo.ToPtr(types.MenuItem{
@ -157,7 +177,8 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
},
Key: 'b',
DisabledReason: self.require(self.singleItemSelected())(),
Key: 'b',
},
{
Label: fmt.Sprintf(self.c.Tr.Bisect.MarkStart, commit.ShortSha(), info.OldTerm()),
@ -173,7 +194,8 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
},
Key: 'g',
DisabledReason: self.require(self.singleItemSelected())(),
Key: 'g',
},
{
Label: self.c.Tr.Bisect.ChooseTerms,
@ -273,21 +295,6 @@ func (self *BisectController) selectCurrentBisectCommit() {
}
}
func (self *BisectController) checkSelected(callback func(*models.Commit) error) func() error {
return func() error {
commit := self.context().GetSelected()
if commit == nil {
return nil
}
return callback(commit)
}
}
func (self *BisectController) Context() types.Context {
return self.context()
}
func (self *BisectController) context() *context.LocalCommitsContext {
return self.c.Contexts().LocalCommits
}

View File

@ -17,48 +17,61 @@ import (
type BranchesController struct {
baseController
*ListControllerTrait[*models.Branch]
c *ControllerCommon
}
var _ types.IController = &BranchesController{}
func NewBranchesController(
common *ControllerCommon,
c *ControllerCommon,
) *BranchesController {
return &BranchesController{
baseController: baseController{},
c: common,
c: c,
ListControllerTrait: NewListControllerTrait[*models.Branch](
c,
c.Contexts().Branches,
c.Contexts().Branches.GetSelected,
),
}
}
func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
return []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.checkSelected(self.press),
GetDisabledReason: self.getDisabledReasonForPress,
Description: self.c.Tr.Checkout,
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.withItem(self.press),
GetDisabledReason: self.require(
self.singleItemSelected(),
self.notPulling,
),
Description: self.c.Tr.Checkout,
},
{
Key: opts.GetKey(opts.Config.Universal.New),
Handler: self.checkSelected(self.newBranch),
Description: self.c.Tr.NewBranch,
Key: opts.GetKey(opts.Config.Universal.New),
Handler: self.withItem(self.newBranch),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.NewBranch,
},
{
Key: opts.GetKey(opts.Config.Branches.CreatePullRequest),
Handler: self.checkSelected(self.handleCreatePullRequest),
Description: self.c.Tr.CreatePullRequest,
Key: opts.GetKey(opts.Config.Branches.CreatePullRequest),
Handler: self.withItem(self.handleCreatePullRequest),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.CreatePullRequest,
},
{
Key: opts.GetKey(opts.Config.Branches.ViewPullRequestOptions),
Handler: self.checkSelected(self.handleCreatePullRequestMenu),
Description: self.c.Tr.CreatePullRequestOptions,
OpensMenu: true,
Key: opts.GetKey(opts.Config.Branches.ViewPullRequestOptions),
Handler: self.withItem(self.handleCreatePullRequestMenu),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.CreatePullRequestOptions,
OpensMenu: true,
},
{
Key: opts.GetKey(opts.Config.Branches.CopyPullRequestURL),
Handler: self.copyPullRequestURL,
Description: self.c.Tr.CopyPullRequestURL,
Key: opts.GetKey(opts.Config.Branches.CopyPullRequestURL),
Handler: self.copyPullRequestURL,
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.CopyPullRequestURL,
},
{
Key: opts.GetKey(opts.Config.Branches.CheckoutBranchByName),
@ -66,60 +79,69 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
Description: self.c.Tr.CheckoutByName,
},
{
Key: opts.GetKey(opts.Config.Branches.ForceCheckoutBranch),
Handler: self.forceCheckout,
Description: self.c.Tr.ForceCheckout,
Key: opts.GetKey(opts.Config.Branches.ForceCheckoutBranch),
Handler: self.forceCheckout,
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.ForceCheckout,
},
{
Key: opts.GetKey(opts.Config.Universal.Remove),
Handler: self.checkSelectedAndReal(self.delete),
Description: self.c.Tr.ViewDeleteOptions,
OpensMenu: true,
Key: opts.GetKey(opts.Config.Universal.Remove),
Handler: self.withItem(self.delete),
GetDisabledReason: self.require(self.singleItemSelected(self.branchIsReal)),
Description: self.c.Tr.ViewDeleteOptions,
OpensMenu: true,
},
{
Key: opts.GetKey(opts.Config.Branches.RebaseBranch),
Handler: opts.Guards.OutsideFilterMode(self.rebase),
Description: self.c.Tr.RebaseBranch,
GetDisabledReason: self.getDisabledReasonForRebase,
Key: opts.GetKey(opts.Config.Branches.RebaseBranch),
Handler: opts.Guards.OutsideFilterMode(self.rebase),
GetDisabledReason: self.require(
self.singleItemSelected(self.notRebasingOntoSelf),
),
Description: self.c.Tr.RebaseBranch,
},
{
Key: opts.GetKey(opts.Config.Branches.MergeIntoCurrentBranch),
Handler: opts.Guards.OutsideFilterMode(self.merge),
Description: self.c.Tr.MergeIntoCurrentBranch,
Key: opts.GetKey(opts.Config.Branches.MergeIntoCurrentBranch),
Handler: opts.Guards.OutsideFilterMode(self.merge),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.MergeIntoCurrentBranch,
},
{
Key: opts.GetKey(opts.Config.Branches.FastForward),
Handler: self.checkSelectedAndReal(self.fastForward),
Description: self.c.Tr.FastForward,
Key: opts.GetKey(opts.Config.Branches.FastForward),
Handler: self.withItem(self.fastForward),
GetDisabledReason: self.require(self.singleItemSelected(self.branchIsReal)),
Description: self.c.Tr.FastForward,
},
{
Key: opts.GetKey(opts.Config.Branches.CreateTag),
Handler: self.checkSelected(self.createTag),
Description: self.c.Tr.CreateTag,
Key: opts.GetKey(opts.Config.Branches.CreateTag),
Handler: self.withItem(self.createTag),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.CreateTag,
},
{
Key: opts.GetKey(opts.Config.Branches.SortOrder),
Handler: self.createSortMenu,
Description: self.c.Tr.SortOrder,
OpensMenu: true,
},
{
Key: opts.GetKey(opts.Config.Commits.ViewResetOptions),
Handler: self.checkSelected(self.createResetMenu),
Description: self.c.Tr.ViewResetOptions,
OpensMenu: true,
Key: opts.GetKey(opts.Config.Commits.ViewResetOptions),
Handler: self.withItem(self.createResetMenu),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.ViewResetOptions,
OpensMenu: true,
},
{
Key: opts.GetKey(opts.Config.Branches.RenameBranch),
Handler: self.checkSelectedAndReal(self.rename),
Description: self.c.Tr.RenameBranch,
Key: opts.GetKey(opts.Config.Branches.RenameBranch),
Handler: self.withItem(self.rename),
GetDisabledReason: self.require(self.singleItemSelected(self.branchIsReal)),
Description: self.c.Tr.RenameBranch,
},
{
Key: opts.GetKey(opts.Config.Branches.SetUpstream),
Handler: self.checkSelected(self.viewUpstreamOptions),
Description: self.c.Tr.ViewBranchUpstreamOptions,
Tooltip: self.c.Tr.ViewBranchUpstreamOptionsTooltip,
OpensMenu: true,
Key: opts.GetKey(opts.Config.Branches.SetUpstream),
Handler: self.withItem(self.viewUpstreamOptions),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.ViewBranchUpstreamOptions,
Tooltip: self.c.Tr.ViewBranchUpstreamOptionsTooltip,
OpensMenu: true,
},
}
}
@ -308,7 +330,7 @@ func (self *BranchesController) press(selectedBranch *models.Branch) error {
return self.c.Helpers().Refs.CheckoutRef(selectedBranch.Name, types.CheckoutRefOptions{})
}
func (self *BranchesController) getDisabledReasonForPress() *types.DisabledReason {
func (self *BranchesController) notPulling() *types.DisabledReason {
currentBranch := self.c.Helpers().Refs.GetCheckedOutRef()
if currentBranch != nil {
op := self.c.State().GetItemOperation(currentBranch)
@ -561,8 +583,8 @@ func (self *BranchesController) rebase() error {
return self.c.Helpers().MergeAndRebase.RebaseOntoRef(selectedBranchName)
}
func (self *BranchesController) getDisabledReasonForRebase() *types.DisabledReason {
selectedBranchName := self.context().GetSelected().Name
func (self *BranchesController) notRebasingOntoSelf(branch *models.Branch) *types.DisabledReason {
selectedBranchName := branch.Name
checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef().Name
if selectedBranchName == checkedOutBranch {
return &types.DisabledReason{Text: self.c.Tr.CantRebaseOntoSelf}
@ -753,24 +775,10 @@ func (self *BranchesController) createPullRequest(from string, to string) error
return nil
}
func (self *BranchesController) checkSelected(callback func(*models.Branch) error) func() error {
return func() error {
selectedItem := self.context().GetSelected()
if selectedItem == nil {
return nil
}
return callback(selectedItem)
func (self *BranchesController) branchIsReal(branch *models.Branch) *types.DisabledReason {
if !branch.IsRealBranch() {
return &types.DisabledReason{Text: self.c.Tr.SelectedItemIsNotABranch}
}
}
func (self *BranchesController) checkSelectedAndReal(callback func(*models.Branch) error) func() error {
return func() error {
selectedItem := self.context().GetSelected()
if selectedItem == nil || !selectedItem.IsRealBranch() {
return nil
}
return callback(selectedItem)
}
return nil
}

View File

@ -12,11 +12,11 @@ type CommandLogController struct {
var _ types.IController = &CommandLogController{}
func NewCommandLogController(
common *ControllerCommon,
c *ControllerCommon,
) *CommandLogController {
return &CommandLogController{
baseController: baseController{},
c: common,
c: c,
}
}

View File

@ -13,11 +13,11 @@ type CommitDescriptionController struct {
var _ types.IController = &CommitMessageController{}
func NewCommitDescriptionController(
common *ControllerCommon,
c *ControllerCommon,
) *CommitDescriptionController {
return &CommitDescriptionController{
baseController: baseController{},
c: common,
c: c,
}
}

View File

@ -14,11 +14,11 @@ type CommitMessageController struct {
var _ types.IController = &CommitMessageController{}
func NewCommitMessageController(
common *ControllerCommon,
c *ControllerCommon,
) *CommitMessageController {
return &CommitMessageController{
baseController: baseController{},
c: common,
c: c,
}
}

View File

@ -12,61 +12,74 @@ import (
type CommitFilesController struct {
baseController
*ListControllerTrait[*filetree.CommitFileNode]
c *ControllerCommon
}
var _ types.IController = &CommitFilesController{}
func NewCommitFilesController(
common *ControllerCommon,
c *ControllerCommon,
) *CommitFilesController {
return &CommitFilesController{
baseController: baseController{},
c: common,
c: c,
ListControllerTrait: NewListControllerTrait[*filetree.CommitFileNode](
c,
c.Contexts().CommitFiles,
c.Contexts().CommitFiles.GetSelected,
),
}
}
func (self *CommitFilesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
bindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.CommitFiles.CheckoutCommitFile),
Handler: self.checkSelected(self.checkout),
Description: self.c.Tr.CheckoutCommitFile,
Key: opts.GetKey(opts.Config.CommitFiles.CheckoutCommitFile),
Handler: self.withItem(self.checkout),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.CheckoutCommitFile,
},
{
Key: opts.GetKey(opts.Config.Universal.Remove),
Handler: self.checkSelected(self.discard),
Description: self.c.Tr.DiscardOldFileChange,
Key: opts.GetKey(opts.Config.Universal.Remove),
Handler: self.withItem(self.discard),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.DiscardOldFileChange,
},
{
Key: opts.GetKey(opts.Config.Universal.OpenFile),
Handler: self.checkSelected(self.open),
Description: self.c.Tr.OpenFile,
Key: opts.GetKey(opts.Config.Universal.OpenFile),
Handler: self.withItem(self.open),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.OpenFile,
},
{
Key: opts.GetKey(opts.Config.Universal.Edit),
Handler: self.checkSelected(self.edit),
Description: self.c.Tr.EditFile,
Key: opts.GetKey(opts.Config.Universal.Edit),
Handler: self.withItem(self.edit),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.EditFile,
},
{
Key: opts.GetKey(opts.Config.Universal.OpenDiffTool),
Handler: self.checkSelected(self.openDiffTool),
Description: self.c.Tr.OpenDiffTool,
Key: opts.GetKey(opts.Config.Universal.OpenDiffTool),
Handler: self.withItem(self.openDiffTool),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.OpenDiffTool,
},
{
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.checkSelected(self.toggleForPatch),
Description: self.c.Tr.ToggleAddToPatch,
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.withItem(self.toggleForPatch),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.ToggleAddToPatch,
},
{
Key: opts.GetKey(opts.Config.Files.ToggleStagedAll),
Handler: self.checkSelected(self.toggleAllForPatch),
Handler: self.withItem(self.toggleAllForPatch),
Description: self.c.Tr.ToggleAllInPatch,
},
{
Key: opts.GetKey(opts.Config.Universal.GoInto),
Handler: self.checkSelected(self.enter),
Description: self.c.Tr.EnterFile,
Key: opts.GetKey(opts.Config.Universal.GoInto),
Handler: self.withItem(self.enter),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.EnterFile,
},
{
Key: opts.GetKey(opts.Config.Files.ToggleTreeView),
@ -89,21 +102,6 @@ func (self *CommitFilesController) GetMouseKeybindings(opts types.KeybindingsOpt
}
}
func (self *CommitFilesController) checkSelected(callback func(*filetree.CommitFileNode) error) func() error {
return func() error {
selected := self.context().GetSelected()
if selected == nil {
return nil
}
return callback(selected)
}
}
func (self *CommitFilesController) Context() types.Context {
return self.context()
}
func (self *CommitFilesController) context() *context.CommitFilesContext {
return self.c.Contexts().CommitFiles
}

View File

@ -13,11 +13,11 @@ type ConfirmationController struct {
var _ types.IController = &ConfirmationController{}
func NewConfirmationController(
common *ControllerCommon,
c *ControllerCommon,
) *ConfirmationController {
return &ConfirmationController{
baseController: baseController{},
c: common,
c: c,
}
}

View File

@ -31,11 +31,11 @@ type ContextLinesController struct {
var _ types.IController = &ContextLinesController{}
func NewContextLinesController(
common *ControllerCommon,
c *ControllerCommon,
) *ContextLinesController {
return &ContextLinesController{
baseController: baseController{},
c: common,
c: c,
}
}

View File

@ -62,15 +62,22 @@ func (self *CustomPatchOptionsMenuAction) Call() error {
if self.c.CurrentContext().GetKey() == self.c.Contexts().LocalCommits.GetKey() {
selectedCommit := self.c.Contexts().LocalCommits.GetSelected()
if selectedCommit != nil && self.c.Git().Patch.PatchBuilder.To != selectedCommit.Sha {
var disabledReason *types.DisabledReason
if self.c.Contexts().LocalCommits.AreMultipleItemsSelected() {
disabledReason = &types.DisabledReason{Text: self.c.Tr.RangeSelectNotSupported}
}
// adding this option to index 1
menuItems = append(
menuItems[:1],
append(
[]*types.MenuItem{
{
Label: fmt.Sprintf(self.c.Tr.MovePatchToSelectedCommit, selectedCommit.Sha),
OnPress: self.handleMovePatchToSelectedCommit,
Key: 'm',
Label: fmt.Sprintf(self.c.Tr.MovePatchToSelectedCommit, selectedCommit.Sha),
OnPress: self.handleMovePatchToSelectedCommit,
Key: 'm',
DisabledReason: disabledReason,
},
}, menuItems[1:]...,
)...,

View File

@ -13,25 +13,32 @@ import (
type FilesController struct {
baseController // nolint: unused
c *ControllerCommon
*ListControllerTrait[*filetree.FileNode]
c *ControllerCommon
}
var _ types.IController = &FilesController{}
func NewFilesController(
common *ControllerCommon,
c *ControllerCommon,
) *FilesController {
return &FilesController{
c: common,
c: c,
ListControllerTrait: NewListControllerTrait[*filetree.FileNode](
c,
c.Contexts().Files,
c.Contexts().Files.GetSelected,
),
}
}
func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
return []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.checkSelectedFileNode(self.press),
Description: self.c.Tr.ToggleStaged,
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.withItem(self.press),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.ToggleStaged,
},
{
Key: opts.GetKey(opts.Config.Files.OpenStatusFilter),
@ -71,20 +78,23 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
Tooltip: self.c.Tr.FindBaseCommitForFixupTooltip,
},
{
Key: opts.GetKey(opts.Config.Universal.Edit),
Handler: self.checkSelectedFileNode(self.edit),
Description: self.c.Tr.EditFile,
Key: opts.GetKey(opts.Config.Universal.Edit),
Handler: self.withItem(self.edit),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.EditFile,
},
{
Key: opts.GetKey(opts.Config.Universal.OpenFile),
Handler: self.Open,
Description: self.c.Tr.OpenFile,
Key: opts.GetKey(opts.Config.Universal.OpenFile),
Handler: self.Open,
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.OpenFile,
},
{
Key: opts.GetKey(opts.Config.Files.IgnoreFile),
Handler: self.checkSelectedFileNode(self.ignoreOrExcludeMenu),
Description: self.c.Tr.Actions.IgnoreExcludeFile,
OpensMenu: true,
Key: opts.GetKey(opts.Config.Files.IgnoreFile),
Handler: self.withItem(self.ignoreOrExcludeMenu),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Actions.IgnoreExcludeFile,
OpensMenu: true,
},
{
Key: opts.GetKey(opts.Config.Files.RefreshFiles),
@ -108,9 +118,10 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
Description: self.c.Tr.ToggleStagedAll,
},
{
Key: opts.GetKey(opts.Config.Universal.GoInto),
Handler: self.enter,
Description: self.c.Tr.FileEnter,
Key: opts.GetKey(opts.Config.Universal.GoInto),
Handler: self.enter,
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.FileEnter,
},
{
Key: opts.GetKey(opts.Config.Commits.ViewResetOptions),
@ -130,9 +141,10 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
Description: self.c.Tr.ToggleTreeView,
},
{
Key: opts.GetKey(opts.Config.Universal.OpenDiffTool),
Handler: self.checkSelectedFileNode(self.openDiffTool),
Description: self.c.Tr.OpenDiffTool,
Key: opts.GetKey(opts.Config.Universal.OpenDiffTool),
Handler: self.withItem(self.openDiffTool),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.OpenDiffTool,
},
{
Key: opts.GetKey(opts.Config.Files.OpenMergeTool),
@ -254,7 +266,7 @@ func (self *FilesController) GetOnRenderToMain() func() error {
}
func (self *FilesController) GetOnClick() func() error {
return self.checkSelectedFileNode(self.press)
return self.withItemGraceful(self.press)
}
// if we are dealing with a status for which there is no key in this map,
@ -411,17 +423,6 @@ func (self *FilesController) press(node *filetree.FileNode) error {
return self.context().HandleFocus(types.OnFocusOpts{})
}
func (self *FilesController) checkSelectedFileNode(callback func(*filetree.FileNode) error) func() error {
return func() error {
node := self.context().GetSelected()
if node == nil {
return nil
}
return callback(node)
}
}
func (self *FilesController) Context() types.Context {
return self.context()
}
@ -798,7 +799,8 @@ func (self *FilesController) openCopyMenu() error {
self.c.Toast(self.c.Tr.FileNameCopiedToast)
return nil
},
Key: 'n',
DisabledReason: self.require(self.singleItemSelected())(),
Key: 'n',
}
copyPathItem := &types.MenuItem{
Label: self.c.Tr.CopyFilePath,
@ -809,7 +811,8 @@ func (self *FilesController) openCopyMenu() error {
self.c.Toast(self.c.Tr.FilePathCopiedToast)
return nil
},
Key: 'p',
DisabledReason: self.require(self.singleItemSelected())(),
Key: 'p',
}
copyFileDiffItem := &types.MenuItem{
Label: self.c.Tr.CopySelectedDiff,
@ -827,6 +830,14 @@ func (self *FilesController) openCopyMenu() error {
self.c.Toast(self.c.Tr.FileDiffCopiedToast)
return nil
},
DisabledReason: self.require(self.singleItemSelected(
func(file *filetree.FileNode) *types.DisabledReason {
if !node.GetHasStagedOrTrackedChanges() {
return &types.DisabledReason{Text: self.c.Tr.NoContentToCopyError}
}
return nil
},
))(),
Key: 's',
}
copyAllDiff := &types.MenuItem{
@ -844,21 +855,17 @@ func (self *FilesController) openCopyMenu() error {
self.c.Toast(self.c.Tr.AllFilesDiffCopiedToast)
return nil
},
DisabledReason: self.require(
func() *types.DisabledReason {
if !self.anyStagedOrTrackedFile() {
return &types.DisabledReason{Text: self.c.Tr.NoContentToCopyError}
}
return nil
},
)(),
Key: 'a',
}
if node == nil {
copyNameItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.NoContentToCopyError}
copyPathItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.NoContentToCopyError}
copyFileDiffItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.NoContentToCopyError}
}
if node != nil && !node.GetHasStagedOrTrackedChanges() {
copyFileDiffItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.NoContentToCopyError}
}
if !self.anyStagedOrTrackedFile() {
copyAllDiff.DisabledReason = &types.DisabledReason{Text: self.c.Tr.NoContentToCopyError}
}
return self.c.Menu(types.CreateMenuOptions{
Title: self.c.Tr.CopyToClipboardMenu,
Items: []*types.MenuItem{

View File

@ -3,7 +3,6 @@ package controllers
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
@ -13,27 +12,34 @@ import (
type FilesRemoveController struct {
baseController
*ListControllerTrait[*filetree.FileNode]
c *ControllerCommon
}
var _ types.IController = &FilesRemoveController{}
func NewFilesRemoveController(
common *ControllerCommon,
c *ControllerCommon,
) *FilesRemoveController {
return &FilesRemoveController{
baseController: baseController{},
c: common,
c: c,
ListControllerTrait: NewListControllerTrait[*filetree.FileNode](
c,
c.Contexts().Files,
c.Contexts().Files.GetSelected,
),
}
}
func (self *FilesRemoveController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
bindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.Remove),
Handler: self.checkSelectedFileNode(self.remove),
Description: self.c.Tr.ViewDiscardOptions,
OpensMenu: true,
Key: opts.GetKey(opts.Config.Universal.Remove),
Handler: self.withItem(self.remove),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.ViewDiscardOptions,
OpensMenu: true,
},
}
@ -166,22 +172,3 @@ func (self *FilesRemoveController) ResetSubmodule(submodule *models.SubmoduleCon
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.SUBMODULES}})
})
}
func (self *FilesRemoveController) checkSelectedFileNode(callback func(*filetree.FileNode) error) func() error {
return func() error {
node := self.context().GetSelected()
if node == nil {
return nil
}
return callback(node)
}
}
func (self *FilesRemoveController) Context() types.Context {
return self.context()
}
func (self *FilesRemoveController) context() *context.WorkingTreeContext {
return self.c.Contexts().Files
}

View File

@ -4,24 +4,29 @@ import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
type GitFlowController struct {
baseController
*ListControllerTrait[*models.Branch]
c *ControllerCommon
}
var _ types.IController = &GitFlowController{}
func NewGitFlowController(
common *ControllerCommon,
c *ControllerCommon,
) *GitFlowController {
return &GitFlowController{
baseController: baseController{},
c: common,
ListControllerTrait: NewListControllerTrait[*models.Branch](
c,
c.Contexts().Branches,
c.Contexts().Branches.GetSelected,
),
c: c,
}
}
@ -29,7 +34,7 @@ func (self *GitFlowController) GetKeybindings(opts types.KeybindingsOpts) []*typ
bindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.Branches.ViewGitFlowOptions),
Handler: self.checkSelected(self.handleCreateGitFlowMenu),
Handler: self.withItem(self.handleCreateGitFlowMenu),
Description: self.c.Tr.GitFlowOptions,
OpensMenu: true,
},
@ -68,6 +73,7 @@ func (self *GitFlowController) handleCreateGitFlowMenu(branch *models.Branch) er
OnPress: func() error {
return self.gitFlowFinishBranch(branch.Name)
},
DisabledReason: self.require(self.singleItemSelected())(),
},
{
Label: "start feature",
@ -102,22 +108,3 @@ func (self *GitFlowController) gitFlowFinishBranch(branchName string) error {
self.c.LogAction(self.c.Tr.Actions.GitFlowFinish)
return self.c.RunSubprocessAndRefresh(cmdObj)
}
func (self *GitFlowController) checkSelected(callback func(*models.Branch) error) func() error {
return func() error {
node := self.context().GetSelected()
if node == nil {
return nil
}
return callback(node)
}
}
func (self *GitFlowController) Context() types.Context {
return self.context()
}
func (self *GitFlowController) context() *context.BranchesContext {
return self.c.Contexts().Branches
}

View File

@ -11,11 +11,11 @@ type GlobalController struct {
}
func NewGlobalController(
common *ControllerCommon,
c *ControllerCommon,
) *GlobalController {
return &GlobalController{
baseController: baseController{},
c: common,
c: c,
}
}

View File

@ -14,11 +14,11 @@ type JumpToSideWindowController struct {
}
func NewJumpToSideWindowController(
common *ControllerCommon,
c *ControllerCommon,
) *JumpToSideWindowController {
return &JumpToSideWindowController{
baseController: baseController{},
c: common,
c: c,
}
}

View File

@ -0,0 +1,95 @@
package controllers
import "github.com/jesseduffield/lazygit/pkg/gui/types"
// Embed this into your list controller to get some convenience methods for
// ensuring a single item is selected, etc.
type ListControllerTrait[T comparable] struct {
c *ControllerCommon
context types.IListContext
getSelected func() T
}
func NewListControllerTrait[T comparable](
c *ControllerCommon,
context types.IListContext,
getSelected func() T,
) *ListControllerTrait[T] {
return &ListControllerTrait[T]{
c: c,
context: context,
getSelected: getSelected,
}
}
// Convenience function for combining multiple disabledReason callbacks.
// The first callback to return a disabled reason will be the one returned.
func (self *ListControllerTrait[T]) require(callbacks ...func() *types.DisabledReason) func() *types.DisabledReason {
return func() *types.DisabledReason {
for _, callback := range callbacks {
if disabledReason := callback(); disabledReason != nil {
return disabledReason
}
}
return nil
}
}
// Convenience function for enforcing that a single item is selected.
// Also takes callbacks for additional disabled reasons, and passes the selected
// item into each one.
func (self *ListControllerTrait[T]) singleItemSelected(callbacks ...func(T) *types.DisabledReason) func() *types.DisabledReason {
return func() *types.DisabledReason {
if self.context.GetList().AreMultipleItemsSelected() {
return &types.DisabledReason{Text: self.c.Tr.RangeSelectNotSupported}
}
var zeroValue T
item := self.getSelected()
if item == zeroValue {
return &types.DisabledReason{Text: self.c.Tr.NoItemSelected}
}
for _, callback := range callbacks {
if reason := callback(item); reason != nil {
return reason
}
}
return nil
}
}
// Passes the selected item to the callback. Used for handler functions.
func (self *ListControllerTrait[T]) withItem(callback func(T) error) func() error {
return func() error {
var zeroValue T
commit := self.getSelected()
if commit == zeroValue {
return self.c.ErrorMsg(self.c.Tr.NoItemSelected)
}
return callback(commit)
}
}
// Like withItem, but doesn't show an error message if no item is selected.
// Use this for click actions (it's a no-op to click empty space)
func (self *ListControllerTrait[T]) withItemGraceful(callback func(T) error) func() error {
return func() error {
var zeroValue T
commit := self.getSelected()
if commit == zeroValue {
return nil
}
return callback(commit)
}
}
// All controllers must implement this method so we're defining it here for convenience
func (self *ListControllerTrait[T]) Context() types.Context {
return self.context
}

View File

@ -25,6 +25,7 @@ type (
type LocalCommitsController struct {
baseController
*ListControllerTrait[*models.Commit]
c *ControllerCommon
pullFiles PullFilesFn
@ -33,13 +34,18 @@ type LocalCommitsController struct {
var _ types.IController = &LocalCommitsController{}
func NewLocalCommitsController(
common *ControllerCommon,
c *ControllerCommon,
pullFiles PullFilesFn,
) *LocalCommitsController {
return &LocalCommitsController{
baseController: baseController{},
c: common,
c: c,
pullFiles: pullFiles,
ListControllerTrait: NewListControllerTrait[*models.Commit](
c,
c.Contexts().LocalCommits,
c.Contexts().LocalCommits.GetSelected,
),
}
}
@ -48,47 +54,59 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
outsideFilterModeBindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.Commits.SquashDown),
Handler: self.checkSelected(self.squashDown),
GetDisabledReason: self.callGetDisabledReasonFuncWithSelectedCommit(self.getDisabledReasonForSquashDown),
Description: self.c.Tr.SquashDown,
Key: opts.GetKey(opts.Config.Commits.SquashDown),
Handler: self.withItem(self.squashDown),
GetDisabledReason: self.require(
self.singleItemSelected(self.getDisabledReasonForSquashDown),
),
Description: self.c.Tr.SquashDown,
},
{
Key: opts.GetKey(opts.Config.Commits.MarkCommitAsFixup),
Handler: self.checkSelected(self.fixup),
GetDisabledReason: self.callGetDisabledReasonFuncWithSelectedCommit(self.getDisabledReasonForFixup),
Description: self.c.Tr.FixupCommit,
Key: opts.GetKey(opts.Config.Commits.MarkCommitAsFixup),
Handler: self.withItem(self.fixup),
GetDisabledReason: self.require(
self.singleItemSelected(self.getDisabledReasonForFixup),
),
Description: self.c.Tr.FixupCommit,
},
{
Key: opts.GetKey(opts.Config.Commits.RenameCommit),
Handler: self.checkSelected(self.reword),
GetDisabledReason: self.getDisabledReasonForRebaseCommandWithSelectedCommit(todo.Reword),
Description: self.c.Tr.RewordCommit,
Key: opts.GetKey(opts.Config.Commits.RenameCommit),
Handler: self.withItem(self.reword),
GetDisabledReason: self.require(
self.singleItemSelected(self.rebaseCommandEnabled(todo.Reword)),
),
Description: self.c.Tr.RewordCommit,
},
{
Key: opts.GetKey(opts.Config.Commits.RenameCommitWithEditor),
Handler: self.checkSelected(self.rewordEditor),
GetDisabledReason: self.getDisabledReasonForRebaseCommandWithSelectedCommit(todo.Reword),
Description: self.c.Tr.RenameCommitEditor,
Key: opts.GetKey(opts.Config.Commits.RenameCommitWithEditor),
Handler: self.withItem(self.rewordEditor),
GetDisabledReason: self.require(
self.singleItemSelected(self.rebaseCommandEnabled(todo.Reword)),
),
Description: self.c.Tr.RenameCommitEditor,
},
{
Key: opts.GetKey(opts.Config.Universal.Remove),
Handler: self.checkSelected(self.drop),
GetDisabledReason: self.getDisabledReasonForRebaseCommandWithSelectedCommit(todo.Drop),
Description: self.c.Tr.DeleteCommit,
Key: opts.GetKey(opts.Config.Universal.Remove),
Handler: self.withItem(self.drop),
GetDisabledReason: self.require(
self.singleItemSelected(self.rebaseCommandEnabled(todo.Drop)),
),
Description: self.c.Tr.DeleteCommit,
},
{
Key: opts.GetKey(editCommitKey),
Handler: self.checkSelected(self.edit),
GetDisabledReason: self.getDisabledReasonForRebaseCommandWithSelectedCommit(todo.Edit),
Description: self.c.Tr.EditCommit,
Key: opts.GetKey(editCommitKey),
Handler: self.withItem(self.edit),
GetDisabledReason: self.require(
self.singleItemSelected(self.rebaseCommandEnabled(todo.Edit)),
),
Description: self.c.Tr.EditCommit,
},
{
// The user-facing description here is 'Start interactive rebase' but internally
// we're calling it 'quick-start interactive rebase' to differentiate it from
// when you manually select the base commit.
Key: opts.GetKey(opts.Config.Commits.StartInteractiveRebase),
Handler: self.checkSelected(self.quickStartInteractiveRebase),
Handler: self.withItem(self.quickStartInteractiveRebase),
GetDisabledReason: self.require(self.notMidRebase, self.canFindCommitForQuickStart),
Description: self.c.Tr.QuickStartInteractiveRebase,
Tooltip: utils.ResolvePlaceholderString(self.c.Tr.QuickStartInteractiveRebaseTooltip, map[string]string{
@ -96,45 +114,50 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
}),
},
{
Key: opts.GetKey(opts.Config.Commits.PickCommit),
Handler: self.checkSelected(self.pick),
GetDisabledReason: self.getDisabledReasonForRebaseCommandWithSelectedCommit(todo.Pick),
Description: self.c.Tr.PickCommit,
Key: opts.GetKey(opts.Config.Commits.PickCommit),
Handler: self.withItem(self.pick),
GetDisabledReason: self.require(
self.singleItemSelected(self.rebaseCommandEnabled(todo.Pick)),
),
Description: self.c.Tr.PickCommit,
},
{
Key: opts.GetKey(opts.Config.Commits.CreateFixupCommit),
Handler: self.checkSelected(self.createFixupCommit),
GetDisabledReason: self.disabledIfNoSelectedCommit(),
Handler: self.withItem(self.createFixupCommit),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.CreateFixupCommitDescription,
},
{
Key: opts.GetKey(opts.Config.Commits.SquashAboveCommits),
Handler: self.checkSelected(self.squashAllAboveFixupCommits),
GetDisabledReason: self.callGetDisabledReasonFuncWithSelectedCommit(self.getDisabledReasonForSquashAllAboveFixupCommits),
Description: self.c.Tr.SquashAboveCommits,
Key: opts.GetKey(opts.Config.Commits.SquashAboveCommits),
Handler: self.withItem(self.squashAllAboveFixupCommits),
GetDisabledReason: self.require(
self.notMidRebase,
self.singleItemSelected(),
),
Description: self.c.Tr.SquashAboveCommits,
},
{
Key: opts.GetKey(opts.Config.Commits.MoveDownCommit),
Handler: self.checkSelected(self.moveDown),
GetDisabledReason: self.disabledIfNoSelectedCommit(),
Handler: self.withItem(self.moveDown),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.MoveDownCommit,
},
{
Key: opts.GetKey(opts.Config.Commits.MoveUpCommit),
Handler: self.checkSelected(self.moveUp),
GetDisabledReason: self.disabledIfNoSelectedCommit(),
Handler: self.withItem(self.moveUp),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.MoveUpCommit,
},
{
Key: opts.GetKey(opts.Config.Commits.PasteCommits),
Handler: self.paste,
GetDisabledReason: self.getDisabledReasonForPaste,
GetDisabledReason: self.require(self.canPaste),
Description: self.c.Tr.PasteCommits,
},
{
Key: opts.GetKey(opts.Config.Commits.MarkCommitAsBaseForRebase),
Handler: self.checkSelected(self.markAsBaseCommit),
GetDisabledReason: self.disabledIfNoSelectedCommit(),
Handler: self.withItem(self.markAsBaseCommit),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.MarkAsBaseCommit,
Tooltip: self.c.Tr.MarkAsBaseCommitTooltip,
},
@ -161,27 +184,27 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
bindings := append(outsideFilterModeBindings, []*types.Binding{
{
Key: opts.GetKey(opts.Config.Commits.AmendToCommit),
Handler: self.checkSelected(self.amendTo),
GetDisabledReason: self.callGetDisabledReasonFuncWithSelectedCommit(self.getDisabledReasonForAmendTo),
Handler: self.withItem(self.amendTo),
GetDisabledReason: self.require(self.singleItemSelected(self.canAmend)),
Description: self.c.Tr.AmendToCommit,
},
{
Key: opts.GetKey(opts.Config.Commits.ResetCommitAuthor),
Handler: self.checkSelected(self.amendAttribute),
GetDisabledReason: self.callGetDisabledReasonFuncWithSelectedCommit(self.getDisabledReasonForAmendTo),
Handler: self.withItem(self.amendAttribute),
GetDisabledReason: self.require(self.singleItemSelected(self.canAmend)),
Description: self.c.Tr.SetResetCommitAuthor,
OpensMenu: true,
},
{
Key: opts.GetKey(opts.Config.Commits.RevertCommit),
Handler: self.checkSelected(self.revert),
GetDisabledReason: self.disabledIfNoSelectedCommit(),
Handler: self.withItem(self.revert),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.RevertCommit,
},
{
Key: opts.GetKey(opts.Config.Commits.CreateTag),
Handler: self.checkSelected(self.createTag),
GetDisabledReason: self.disabledIfNoSelectedCommit(),
Handler: self.withItem(self.createTag),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.TagCommit,
},
{
@ -266,7 +289,7 @@ func (self *LocalCommitsController) getDisabledReasonForSquashDown(commit *model
return &types.DisabledReason{Text: self.c.Tr.CannotSquashOrFixupFirstCommit}
}
return self.rebaseCommandEnabled(todo.Squash, commit)
return self.rebaseCommandEnabled(todo.Squash)(commit)
}
func (self *LocalCommitsController) fixup(commit *models.Commit) error {
@ -295,7 +318,7 @@ func (self *LocalCommitsController) getDisabledReasonForFixup(commit *models.Com
return &types.DisabledReason{Text: self.c.Tr.CannotSquashOrFixupFirstCommit}
}
return self.rebaseCommandEnabled(todo.Squash, commit)
return self.rebaseCommandEnabled(todo.Squash)(commit)
}
func (self *LocalCommitsController) reword(commit *models.Commit) error {
@ -528,36 +551,38 @@ func (self *LocalCommitsController) handleMidRebaseCommand(action todo.TodoComma
})
}
func (self *LocalCommitsController) rebaseCommandEnabled(action todo.TodoCommand, commit *models.Commit) *types.DisabledReason {
if commit.Action == models.ActionConflict {
return &types.DisabledReason{Text: self.c.Tr.ChangingThisActionIsNotAllowed}
}
func (self *LocalCommitsController) rebaseCommandEnabled(action todo.TodoCommand) func(*models.Commit) *types.DisabledReason {
return func(commit *models.Commit) *types.DisabledReason {
if commit.Action == models.ActionConflict {
return &types.DisabledReason{Text: self.c.Tr.ChangingThisActionIsNotAllowed}
}
if !commit.IsTODO() {
if self.c.Model().WorkingTreeStateAtLastCommitRefresh != enums.REBASE_MODE_NONE {
// If we are in a rebase, the only action that is allowed for
// non-todo commits is rewording the current head commit
if !(action == todo.Reword && self.isHeadCommit()) {
return &types.DisabledReason{Text: self.c.Tr.AlreadyRebasing}
if !commit.IsTODO() {
if self.c.Model().WorkingTreeStateAtLastCommitRefresh != enums.REBASE_MODE_NONE {
// If we are in a rebase, the only action that is allowed for
// non-todo commits is rewording the current head commit
if !(action == todo.Reword && self.isHeadCommit()) {
return &types.DisabledReason{Text: self.c.Tr.AlreadyRebasing}
}
}
return nil
}
// for now we do not support setting 'reword' because it requires an editor
// and that means we either unconditionally wait around for the subprocess to ask for
// our input or we set a lazygit client as the EDITOR env variable and have it
// request us to edit the commit message when prompted.
if action == todo.Reword {
return &types.DisabledReason{Text: self.c.Tr.RewordNotSupported}
}
if allowed := isChangeOfRebaseTodoAllowed(action); !allowed {
return &types.DisabledReason{Text: self.c.Tr.ChangingThisActionIsNotAllowed}
}
return nil
}
// for now we do not support setting 'reword' because it requires an editor
// and that means we either unconditionally wait around for the subprocess to ask for
// our input or we set a lazygit client as the EDITOR env variable and have it
// request us to edit the commit message when prompted.
if action == todo.Reword {
return &types.DisabledReason{Text: self.c.Tr.RewordNotSupported}
}
if allowed := isChangeOfRebaseTodoAllowed(action); !allowed {
return &types.DisabledReason{Text: self.c.Tr.ChangingThisActionIsNotAllowed}
}
return nil
}
func (self *LocalCommitsController) moveDown(commit *models.Commit) error {
@ -687,7 +712,7 @@ func (self *LocalCommitsController) amendTo(commit *models.Commit) error {
})
}
func (self *LocalCommitsController) getDisabledReasonForAmendTo(commit *models.Commit) *types.DisabledReason {
func (self *LocalCommitsController) canAmend(commit *models.Commit) *types.DisabledReason {
if !self.isHeadCommit() && self.c.Model().WorkingTreeStateAtLastCommitRefresh != enums.REBASE_MODE_NONE {
return &types.DisabledReason{Text: self.c.Tr.AlreadyRebasing}
}
@ -870,14 +895,6 @@ func (self *LocalCommitsController) squashAllAboveFixupCommits(commit *models.Co
})
}
func (self *LocalCommitsController) getDisabledReasonForSquashAllAboveFixupCommits(commit *models.Commit) *types.DisabledReason {
if self.c.Model().WorkingTreeStateAtLastCommitRefresh != enums.REBASE_MODE_NONE {
return &types.DisabledReason{Text: self.c.Tr.AlreadyRebasing}
}
return nil
}
// For getting disabled reason
func (self *LocalCommitsController) notMidRebase() *types.DisabledReason {
if self.c.Model().WorkingTreeStateAtLastCommitRefresh != enums.REBASE_MODE_NONE {
@ -1016,39 +1033,6 @@ func (self *LocalCommitsController) handleOpenLogMenu() error {
})
}
func (self *LocalCommitsController) checkSelected(callback func(*models.Commit) error) func() error {
return func() error {
commit := self.context().GetSelected()
if commit == nil {
// The enabled callback should have checked for this
panic("no commit selected")
}
return callback(commit)
}
}
func (self *LocalCommitsController) callGetDisabledReasonFuncWithSelectedCommit(callback func(*models.Commit) *types.DisabledReason) func() *types.DisabledReason {
return func() *types.DisabledReason {
commit := self.context().GetSelected()
if commit == nil {
return &types.DisabledReason{Text: self.c.Tr.NoCommitSelected}
}
return callback(commit)
}
}
func (self *LocalCommitsController) disabledIfNoSelectedCommit() func() *types.DisabledReason {
return self.callGetDisabledReasonFuncWithSelectedCommit(func(*models.Commit) *types.DisabledReason { return nil })
}
func (self *LocalCommitsController) getDisabledReasonForRebaseCommandWithSelectedCommit(action todo.TodoCommand) func() *types.DisabledReason {
return self.callGetDisabledReasonFuncWithSelectedCommit(func(commit *models.Commit) *types.DisabledReason {
return self.rebaseCommandEnabled(action, commit)
})
}
func (self *LocalCommitsController) GetOnFocus() func(types.OnFocusOpts) error {
return func(types.OnFocusOpts) error {
context := self.context()
@ -1065,10 +1049,6 @@ func (self *LocalCommitsController) GetOnFocus() func(types.OnFocusOpts) error {
}
}
func (self *LocalCommitsController) Context() types.Context {
return self.context()
}
func (self *LocalCommitsController) context() *context.LocalCommitsContext {
return self.c.Contexts().LocalCommits
}
@ -1077,7 +1057,7 @@ func (self *LocalCommitsController) paste() error {
return self.c.Helpers().CherryPick.Paste()
}
func (self *LocalCommitsController) getDisabledReasonForPaste() *types.DisabledReason {
func (self *LocalCommitsController) canPaste() *types.DisabledReason {
if !self.c.Helpers().CherryPick.CanPaste() {
return &types.DisabledReason{Text: self.c.Tr.NoCopiedCommits}
}
@ -1099,19 +1079,6 @@ func (self *LocalCommitsController) isHeadCommit() bool {
return models.IsHeadCommit(self.c.Model().Commits, self.context().GetSelectedLineIdx())
}
// Convenience function for composing multiple disabled reason functions
func (self *LocalCommitsController) require(callbacks ...func() *types.DisabledReason) func() *types.DisabledReason {
return func() *types.DisabledReason {
for _, callback := range callbacks {
if disabledReason := callback(); disabledReason != nil {
return disabledReason
}
}
return nil
}
}
func isChangeOfRebaseTodoAllowed(action todo.TodoCommand) bool {
allowedActions := []todo.TodoCommand{
todo.Pick,

View File

@ -7,17 +7,23 @@ import (
type MenuController struct {
baseController
*ListControllerTrait[*types.MenuItem]
c *ControllerCommon
}
var _ types.IController = &MenuController{}
func NewMenuController(
common *ControllerCommon,
c *ControllerCommon,
) *MenuController {
return &MenuController{
baseController: baseController{},
c: common,
ListControllerTrait: NewListControllerTrait[*types.MenuItem](
c,
c.Contexts().Menu,
c.Contexts().Menu.GetSelected,
),
c: c,
}
}
@ -26,14 +32,16 @@ func NewMenuController(
func (self *MenuController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
bindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.press,
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.withItem(self.press),
GetDisabledReason: self.require(self.singleItemSelected()),
},
{
Key: opts.GetKey(opts.Config.Universal.Confirm),
Handler: self.press,
Description: self.c.Tr.Execute,
Display: true,
Key: opts.GetKey(opts.Config.Universal.Confirm),
Handler: self.withItem(self.press),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Execute,
Display: true,
},
{
Key: opts.GetKey(opts.Config.Universal.Return),
@ -47,7 +55,7 @@ func (self *MenuController) GetKeybindings(opts types.KeybindingsOpts) []*types.
}
func (self *MenuController) GetOnClick() func() error {
return self.press
return self.withItemGraceful(self.press)
}
func (self *MenuController) GetOnFocus() func(types.OnFocusOpts) error {
@ -60,8 +68,8 @@ func (self *MenuController) GetOnFocus() func(types.OnFocusOpts) error {
}
}
func (self *MenuController) press() error {
return self.context().OnMenuPress(self.context().GetSelected())
func (self *MenuController) press(selectedItem *types.MenuItem) error {
return self.context().OnMenuPress(selectedItem)
}
func (self *MenuController) close() error {
@ -73,10 +81,6 @@ func (self *MenuController) close() error {
return self.c.PopContext()
}
func (self *MenuController) Context() types.Context {
return self.context()
}
func (self *MenuController) context() *context.MenuContext {
return self.c.Contexts().Menu
}

View File

@ -17,11 +17,11 @@ type MergeConflictsController struct {
var _ types.IController = &MergeConflictsController{}
func NewMergeConflictsController(
common *ControllerCommon,
c *ControllerCommon,
) *MergeConflictsController {
return &MergeConflictsController{
baseController: baseController{},
c: common,
c: c,
}
}

View File

@ -14,11 +14,11 @@ type PatchBuildingController struct {
var _ types.IController = &PatchBuildingController{}
func NewPatchBuildingController(
common *ControllerCommon,
c *ControllerCommon,
) *PatchBuildingController {
return &PatchBuildingController{
baseController: baseController{},
c: common,
c: c,
}
}

View File

@ -1,23 +1,30 @@
package controllers
import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type ReflogCommitsController struct {
baseController
*ListControllerTrait[*models.Commit]
c *ControllerCommon
}
var _ types.IController = &ReflogCommitsController{}
func NewReflogCommitsController(
common *ControllerCommon,
c *ControllerCommon,
) *ReflogCommitsController {
return &ReflogCommitsController{
baseController: baseController{},
c: common,
ListControllerTrait: NewListControllerTrait[*models.Commit](
c,
c.Contexts().ReflogCommits,
c.Contexts().ReflogCommits.GetSelected,
),
c: c,
}
}

View File

@ -11,17 +11,23 @@ import (
type RemoteBranchesController struct {
baseController
*ListControllerTrait[*models.RemoteBranch]
c *ControllerCommon
}
var _ types.IController = &RemoteBranchesController{}
func NewRemoteBranchesController(
common *ControllerCommon,
c *ControllerCommon,
) *RemoteBranchesController {
return &RemoteBranchesController{
baseController: baseController{},
c: common,
ListControllerTrait: NewListControllerTrait[*models.RemoteBranch](
c,
c.Contexts().RemoteBranches,
c.Contexts().RemoteBranches.GetSelected,
),
c: c,
}
}
@ -30,33 +36,39 @@ func (self *RemoteBranchesController) GetKeybindings(opts types.KeybindingsOpts)
{
Key: opts.GetKey(opts.Config.Universal.Select),
// gonna use the exact same handler as the 'n' keybinding because everybody wants this to happen when they checkout a remote branch
Handler: self.checkSelected(self.newLocalBranch),
Description: self.c.Tr.Checkout,
Handler: self.withItem(self.newLocalBranch),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Checkout,
},
{
Key: opts.GetKey(opts.Config.Universal.New),
Handler: self.checkSelected(self.newLocalBranch),
Description: self.c.Tr.NewBranch,
Key: opts.GetKey(opts.Config.Universal.New),
Handler: self.withItem(self.newLocalBranch),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.NewBranch,
},
{
Key: opts.GetKey(opts.Config.Branches.MergeIntoCurrentBranch),
Handler: opts.Guards.OutsideFilterMode(self.checkSelected(self.merge)),
Description: self.c.Tr.MergeIntoCurrentBranch,
Key: opts.GetKey(opts.Config.Branches.MergeIntoCurrentBranch),
Handler: opts.Guards.OutsideFilterMode(self.withItem(self.merge)),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.MergeIntoCurrentBranch,
},
{
Key: opts.GetKey(opts.Config.Branches.RebaseBranch),
Handler: opts.Guards.OutsideFilterMode(self.checkSelected(self.rebase)),
Description: self.c.Tr.RebaseBranch,
Key: opts.GetKey(opts.Config.Branches.RebaseBranch),
Handler: opts.Guards.OutsideFilterMode(self.withItem(self.rebase)),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.RebaseBranch,
},
{
Key: opts.GetKey(opts.Config.Universal.Remove),
Handler: self.checkSelected(self.delete),
Description: self.c.Tr.DeleteRemoteTag,
Key: opts.GetKey(opts.Config.Universal.Remove),
Handler: self.withItem(self.delete),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.DeleteRemoteTag,
},
{
Key: opts.GetKey(opts.Config.Branches.SetUpstream),
Handler: self.checkSelected(self.setAsUpstream),
Description: self.c.Tr.SetAsUpstream,
Key: opts.GetKey(opts.Config.Branches.SetUpstream),
Handler: self.withItem(self.setAsUpstream),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.SetAsUpstream,
},
{
Key: opts.GetKey(opts.Config.Branches.SortOrder),
@ -65,10 +77,11 @@ func (self *RemoteBranchesController) GetKeybindings(opts types.KeybindingsOpts)
OpensMenu: true,
},
{
Key: opts.GetKey(opts.Config.Commits.ViewResetOptions),
Handler: self.checkSelected(self.createResetMenu),
Description: self.c.Tr.ViewResetOptions,
OpensMenu: true,
Key: opts.GetKey(opts.Config.Commits.ViewResetOptions),
Handler: self.withItem(self.createResetMenu),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.ViewResetOptions,
OpensMenu: true,
},
}
}
@ -96,25 +109,10 @@ func (self *RemoteBranchesController) GetOnRenderToMain() func() error {
}
}
func (self *RemoteBranchesController) Context() types.Context {
return self.context()
}
func (self *RemoteBranchesController) context() *context.RemoteBranchesContext {
return self.c.Contexts().RemoteBranches
}
func (self *RemoteBranchesController) checkSelected(callback func(*models.RemoteBranch) error) func() error {
return func() error {
selectedItem := self.context().GetSelected()
if selectedItem == nil {
return nil
}
return callback(selectedItem)
}
}
func (self *RemoteBranchesController) delete(selectedBranch *models.RemoteBranch) error {
return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(selectedBranch.RemoteName, selectedBranch.Name)
}

View File

@ -14,6 +14,7 @@ import (
type RemotesController struct {
baseController
*ListControllerTrait[*models.Remote]
c *ControllerCommon
setRemoteBranches func([]*models.RemoteBranch)
@ -22,12 +23,17 @@ type RemotesController struct {
var _ types.IController = &RemotesController{}
func NewRemotesController(
common *ControllerCommon,
c *ControllerCommon,
setRemoteBranches func([]*models.RemoteBranch),
) *RemotesController {
return &RemotesController{
baseController: baseController{},
c: common,
baseController: baseController{},
ListControllerTrait: NewListControllerTrait[*models.Remote](
c,
c.Contexts().Remotes,
c.Contexts().Remotes.GetSelected,
),
c: c,
setRemoteBranches: setRemoteBranches,
}
}
@ -35,13 +41,15 @@ func NewRemotesController(
func (self *RemotesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
bindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.GoInto),
Handler: self.checkSelected(self.enter),
Key: opts.GetKey(opts.Config.Universal.GoInto),
Handler: self.withItem(self.enter),
GetDisabledReason: self.require(self.singleItemSelected()),
},
{
Key: opts.GetKey(opts.Config.Branches.FetchRemote),
Handler: self.checkSelected(self.fetch),
Description: self.c.Tr.FetchRemote,
Key: opts.GetKey(opts.Config.Branches.FetchRemote),
Handler: self.withItem(self.fetch),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.FetchRemote,
},
{
Key: opts.GetKey(opts.Config.Universal.New),
@ -49,24 +57,22 @@ func (self *RemotesController) GetKeybindings(opts types.KeybindingsOpts) []*typ
Description: self.c.Tr.AddNewRemote,
},
{
Key: opts.GetKey(opts.Config.Universal.Remove),
Handler: self.checkSelected(self.remove),
Description: self.c.Tr.RemoveRemote,
Key: opts.GetKey(opts.Config.Universal.Remove),
Handler: self.withItem(self.remove),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.RemoveRemote,
},
{
Key: opts.GetKey(opts.Config.Universal.Edit),
Handler: self.checkSelected(self.edit),
Description: self.c.Tr.EditRemote,
Key: opts.GetKey(opts.Config.Universal.Edit),
Handler: self.withItem(self.edit),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.EditRemote,
},
}
return bindings
}
func (self *RemotesController) Context() types.Context {
return self.context()
}
func (self *RemotesController) context() *context.RemotesContext {
return self.c.Contexts().Remotes
}
@ -94,7 +100,7 @@ func (self *RemotesController) GetOnRenderToMain() func() error {
}
func (self *RemotesController) GetOnClick() func() error {
return self.checkSelected(self.enter)
return self.withItemGraceful(self.enter)
}
func (self *RemotesController) enter(remote *models.Remote) error {
@ -208,14 +214,3 @@ func (self *RemotesController) fetch(remote *models.Remote) error {
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
})
}
func (self *RemotesController) checkSelected(callback func(*models.Remote) error) func() error {
return func() error {
file := self.context().GetSelected()
if file == nil {
return nil
}
return callback(file)
}
}

View File

@ -13,11 +13,11 @@ type SearchPromptController struct {
var _ types.IController = &SearchPromptController{}
func NewSearchPromptController(
common *ControllerCommon,
c *ControllerCommon,
) *SearchPromptController {
return &SearchPromptController{
baseController: baseController{},
c: common,
c: c,
}
}

View File

@ -9,8 +9,8 @@ type SideWindowControllerFactory struct {
c *ControllerCommon
}
func NewSideWindowControllerFactory(common *ControllerCommon) *SideWindowControllerFactory {
return &SideWindowControllerFactory{c: common}
func NewSideWindowControllerFactory(c *ControllerCommon) *SideWindowControllerFactory {
return &SideWindowControllerFactory{c: c}
}
func (self *SideWindowControllerFactory) Create(context types.Context) types.IController {
@ -24,12 +24,12 @@ type SideWindowController struct {
}
func NewSideWindowController(
common *ControllerCommon,
c *ControllerCommon,
context types.Context,
) *SideWindowController {
return &SideWindowController{
baseController: baseController{},
c: common,
c: c,
context: context,
}
}

View File

@ -13,11 +13,11 @@ type SnakeController struct {
var _ types.IController = &SnakeController{}
func NewSnakeController(
common *ControllerCommon,
c *ControllerCommon,
) *SnakeController {
return &SnakeController{
baseController: baseController{},
c: common,
c: c,
}
}

View File

@ -23,14 +23,14 @@ type StagingController struct {
var _ types.IController = &StagingController{}
func NewStagingController(
common *ControllerCommon,
c *ControllerCommon,
context types.IPatchExplorerContext,
otherContext types.IPatchExplorerContext,
staged bool,
) *StagingController {
return &StagingController{
baseController: baseController{},
c: common,
c: c,
context: context,
otherContext: otherContext,
staged: staged,

View File

@ -9,46 +9,57 @@ import (
type StashController struct {
baseController
*ListControllerTrait[*models.StashEntry]
c *ControllerCommon
}
var _ types.IController = &StashController{}
func NewStashController(
common *ControllerCommon,
c *ControllerCommon,
) *StashController {
return &StashController{
baseController: baseController{},
c: common,
ListControllerTrait: NewListControllerTrait[*models.StashEntry](
c,
c.Contexts().Stash,
c.Contexts().Stash.GetSelected,
),
c: c,
}
}
func (self *StashController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
bindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.checkSelected(self.handleStashApply),
Description: self.c.Tr.Apply,
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.withItem(self.handleStashApply),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Apply,
},
{
Key: opts.GetKey(opts.Config.Stash.PopStash),
Handler: self.checkSelected(self.handleStashPop),
Description: self.c.Tr.Pop,
Key: opts.GetKey(opts.Config.Stash.PopStash),
Handler: self.withItem(self.handleStashPop),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Pop,
},
{
Key: opts.GetKey(opts.Config.Universal.Remove),
Handler: self.checkSelected(self.handleStashDrop),
Description: self.c.Tr.Drop,
Key: opts.GetKey(opts.Config.Universal.Remove),
Handler: self.withItem(self.handleStashDrop),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Drop,
},
{
Key: opts.GetKey(opts.Config.Universal.New),
Handler: self.checkSelected(self.handleNewBranchOffStashEntry),
Description: self.c.Tr.NewBranch,
Key: opts.GetKey(opts.Config.Universal.New),
Handler: self.withItem(self.handleNewBranchOffStashEntry),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.NewBranch,
},
{
Key: opts.GetKey(opts.Config.Stash.RenameStash),
Handler: self.checkSelected(self.handleRenameStashEntry),
Description: self.c.Tr.RenameStash,
Key: opts.GetKey(opts.Config.Stash.RenameStash),
Handler: self.withItem(self.handleRenameStashEntry),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.RenameStash,
},
}
@ -80,21 +91,6 @@ func (self *StashController) GetOnRenderToMain() func() error {
}
}
func (self *StashController) checkSelected(callback func(*models.StashEntry) error) func() error {
return func() error {
item := self.context().GetSelected()
if item == nil {
return nil
}
return callback(item)
}
}
func (self *StashController) Context() types.Context {
return self.context()
}
func (self *StashController) context() *context.StashContext {
return self.c.Contexts().Stash
}

View File

@ -22,11 +22,11 @@ type StatusController struct {
var _ types.IController = &StatusController{}
func NewStatusController(
common *ControllerCommon,
c *ControllerCommon,
) *StatusController {
return &StatusController{
baseController: baseController{},
c: common,
c: c,
}
}

View File

@ -2,23 +2,30 @@ package controllers
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type SubCommitsController struct {
baseController
*ListControllerTrait[*models.Commit]
c *ControllerCommon
}
var _ types.IController = &SubCommitsController{}
func NewSubCommitsController(
common *ControllerCommon,
c *ControllerCommon,
) *SubCommitsController {
return &SubCommitsController{
baseController: baseController{},
c: common,
ListControllerTrait: NewListControllerTrait[*models.Commit](
c,
c.Contexts().SubCommits,
c.Contexts().SubCommits.GetSelected,
),
c: c,
}
}

View File

@ -14,41 +14,51 @@ import (
type SubmodulesController struct {
baseController
*ListControllerTrait[*models.SubmoduleConfig]
c *ControllerCommon
}
var _ types.IController = &SubmodulesController{}
func NewSubmodulesController(
controllerCommon *ControllerCommon,
c *ControllerCommon,
) *SubmodulesController {
return &SubmodulesController{
baseController: baseController{},
c: controllerCommon,
ListControllerTrait: NewListControllerTrait[*models.SubmoduleConfig](
c,
c.Contexts().Submodules,
c.Contexts().Submodules.GetSelected,
),
c: c,
}
}
func (self *SubmodulesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
return []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.GoInto),
Handler: self.checkSelected(self.enter),
Description: self.c.Tr.EnterSubmodule,
Key: opts.GetKey(opts.Config.Universal.GoInto),
Handler: self.withItem(self.enter),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.EnterSubmodule,
},
{
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.checkSelected(self.enter),
Description: self.c.Tr.EnterSubmodule,
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.withItem(self.enter),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.EnterSubmodule,
},
{
Key: opts.GetKey(opts.Config.Universal.Remove),
Handler: self.checkSelected(self.remove),
Description: self.c.Tr.RemoveSubmodule,
Key: opts.GetKey(opts.Config.Universal.Remove),
Handler: self.withItem(self.remove),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.RemoveSubmodule,
},
{
Key: opts.GetKey(opts.Config.Submodules.Update),
Handler: self.checkSelected(self.update),
Description: self.c.Tr.SubmoduleUpdate,
Key: opts.GetKey(opts.Config.Submodules.Update),
Handler: self.withItem(self.update),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.SubmoduleUpdate,
},
{
Key: opts.GetKey(opts.Config.Universal.New),
@ -56,14 +66,16 @@ func (self *SubmodulesController) GetKeybindings(opts types.KeybindingsOpts) []*
Description: self.c.Tr.AddSubmodule,
},
{
Key: opts.GetKey(opts.Config.Universal.Edit),
Handler: self.checkSelected(self.editURL),
Description: self.c.Tr.EditSubmoduleUrl,
Key: opts.GetKey(opts.Config.Universal.Edit),
Handler: self.withItem(self.editURL),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.EditSubmoduleUrl,
},
{
Key: opts.GetKey(opts.Config.Submodules.Init),
Handler: self.checkSelected(self.init),
Description: self.c.Tr.InitSubmodule,
Key: opts.GetKey(opts.Config.Submodules.Init),
Handler: self.withItem(self.init),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.InitSubmodule,
},
{
Key: opts.GetKey(opts.Config.Submodules.BulkMenu),
@ -80,7 +92,7 @@ func (self *SubmodulesController) GetKeybindings(opts types.KeybindingsOpts) []*
}
func (self *SubmodulesController) GetOnClick() func() error {
return self.checkSelected(self.enter)
return self.withItemGraceful(self.enter)
}
func (self *SubmodulesController) GetOnRenderToMain() func() error {
@ -265,21 +277,6 @@ func (self *SubmodulesController) easterEgg() error {
return self.c.PushContext(self.c.Contexts().Snake)
}
func (self *SubmodulesController) checkSelected(callback func(*models.SubmoduleConfig) error) func() error {
return func() error {
submodule := self.context().GetSelected()
if submodule == nil {
return nil
}
return callback(submodule)
}
}
func (self *SubmodulesController) Context() types.Context {
return self.context()
}
func (self *SubmodulesController) context() *context.SubmodulesContext {
return self.c.Contexts().Submodules
}

View File

@ -7,25 +7,32 @@ import (
type SuggestionsController struct {
baseController
*ListControllerTrait[*types.Suggestion]
c *ControllerCommon
}
var _ types.IController = &SuggestionsController{}
func NewSuggestionsController(
common *ControllerCommon,
c *ControllerCommon,
) *SuggestionsController {
return &SuggestionsController{
baseController: baseController{},
c: common,
ListControllerTrait: NewListControllerTrait[*types.Suggestion](
c,
c.Contexts().Suggestions,
c.Contexts().Suggestions.GetSelected,
),
c: c,
}
}
func (self *SuggestionsController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
bindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.Confirm),
Handler: func() error { return self.context().State.OnConfirm() },
Key: opts.GetKey(opts.Config.Universal.Confirm),
Handler: func() error { return self.context().State.OnConfirm() },
GetDisabledReason: self.require(self.singleItemSelected()),
},
{
Key: opts.GetKey(opts.Config.Universal.Return),
@ -47,10 +54,6 @@ func (self *SuggestionsController) GetOnFocusLost() func(types.OnFocusLostOpts)
}
}
func (self *SuggestionsController) Context() types.Context {
return self.context()
}
func (self *SuggestionsController) context() *context.SuggestionsContext {
return self.c.Contexts().Suggestions
}

View File

@ -10,13 +10,16 @@ import (
var _ types.IController = &SwitchToDiffFilesController{}
type CanSwitchToDiffFiles interface {
types.Context
types.IListContext
CanRebase() bool
GetSelectedRef() types.Ref
}
// Not using our ListControllerTrait because our 'selected' item is not a list item
// but an attribute on it i.e. the ref of an item.
type SwitchToDiffFilesController struct {
baseController
*ListControllerTrait[types.Ref]
c *ControllerCommon
context CanSwitchToDiffFiles
diffFilesContext *context.CommitFilesContext
@ -28,7 +31,12 @@ func NewSwitchToDiffFilesController(
diffFilesContext *context.CommitFilesContext,
) *SwitchToDiffFilesController {
return &SwitchToDiffFilesController{
baseController: baseController{},
baseController: baseController{},
ListControllerTrait: NewListControllerTrait[types.Ref](
c,
context,
context.GetSelectedRef,
),
c: c,
context: context,
diffFilesContext: diffFilesContext,
@ -38,9 +46,10 @@ func NewSwitchToDiffFilesController(
func (self *SwitchToDiffFilesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
bindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.GoInto),
Handler: self.checkSelected(self.enter),
Description: self.c.Tr.ViewItemFiles,
Key: opts.GetKey(opts.Config.Universal.GoInto),
Handler: self.withItem(self.enter),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.ViewItemFiles,
},
}
@ -48,18 +57,7 @@ func (self *SwitchToDiffFilesController) GetKeybindings(opts types.KeybindingsOp
}
func (self *SwitchToDiffFilesController) GetOnClick() func() error {
return self.checkSelected(self.enter)
}
func (self *SwitchToDiffFilesController) checkSelected(callback func(types.Ref) error) func() error {
return func() error {
ref := self.context.GetSelectedRef()
if ref == nil {
return nil
}
return callback(ref)
}
return self.withItemGraceful(self.enter)
}
func (self *SwitchToDiffFilesController) enter(ref types.Ref) error {
@ -70,10 +68,6 @@ func (self *SwitchToDiffFilesController) enter(ref types.Ref) error {
})
}
func (self *SwitchToDiffFilesController) Context() types.Context {
return self.context
}
func (self *SwitchToDiffFilesController) viewFiles(opts SwitchToCommitFilesContextOpts) error {
diffFilesContext := self.diffFilesContext

View File

@ -8,34 +8,43 @@ import (
var _ types.IController = &SwitchToSubCommitsController{}
type CanSwitchToSubCommits interface {
types.Context
types.IListContext
GetSelectedRef() types.Ref
ShowBranchHeadsInSubCommits() bool
}
// Not using our ListControllerTrait because our 'selected' item is not a list item
// but an attribute on it i.e. the ref of an item.
type SwitchToSubCommitsController struct {
baseController
*ListControllerTrait[types.Ref]
c *ControllerCommon
context CanSwitchToSubCommits
}
func NewSwitchToSubCommitsController(
controllerCommon *ControllerCommon,
c *ControllerCommon,
context CanSwitchToSubCommits,
) *SwitchToSubCommitsController {
return &SwitchToSubCommitsController{
baseController: baseController{},
c: controllerCommon,
context: context,
ListControllerTrait: NewListControllerTrait[types.Ref](
c,
context,
context.GetSelectedRef,
),
c: c,
context: context,
}
}
func (self *SwitchToSubCommitsController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
bindings := []*types.Binding{
{
Handler: self.viewCommits,
Key: opts.GetKey(opts.Config.Universal.GoInto),
Description: self.c.Tr.ViewCommits,
Handler: self.viewCommits,
GetDisabledReason: self.require(self.singleItemSelected()),
Key: opts.GetKey(opts.Config.Universal.GoInto),
Description: self.c.Tr.ViewCommits,
},
}
@ -59,7 +68,3 @@ func (self *SwitchToSubCommitsController) viewCommits() error {
ShowBranchHeads: self.context.ShowBranchHeadsInSubCommits(),
})
}
func (self *SwitchToSubCommitsController) Context() types.Context {
return self.context
}

View File

@ -10,37 +10,46 @@ import (
type TagsController struct {
baseController
*ListControllerTrait[*models.Tag]
c *ControllerCommon
}
var _ types.IController = &TagsController{}
func NewTagsController(
common *ControllerCommon,
c *ControllerCommon,
) *TagsController {
return &TagsController{
baseController: baseController{},
c: common,
ListControllerTrait: NewListControllerTrait[*models.Tag](
c,
c.Contexts().Tags,
c.Contexts().Tags.GetSelected,
),
c: c,
}
}
func (self *TagsController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
bindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.withSelectedTag(self.checkout),
Description: self.c.Tr.Checkout,
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.withItem(self.checkout),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Checkout,
},
{
Key: opts.GetKey(opts.Config.Universal.Remove),
Handler: self.withSelectedTag(self.delete),
Description: self.c.Tr.ViewDeleteOptions,
OpensMenu: true,
Key: opts.GetKey(opts.Config.Universal.Remove),
Handler: self.withItem(self.delete),
Description: self.c.Tr.ViewDeleteOptions,
GetDisabledReason: self.require(self.singleItemSelected()),
OpensMenu: true,
},
{
Key: opts.GetKey(opts.Config.Branches.PushTag),
Handler: self.withSelectedTag(self.push),
Description: self.c.Tr.PushTag,
Key: opts.GetKey(opts.Config.Branches.PushTag),
Handler: self.withItem(self.push),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.PushTag,
},
{
Key: opts.GetKey(opts.Config.Universal.New),
@ -48,10 +57,11 @@ func (self *TagsController) GetKeybindings(opts types.KeybindingsOpts) []*types.
Description: self.c.Tr.CreateTag,
},
{
Key: opts.GetKey(opts.Config.Commits.ViewResetOptions),
Handler: self.withSelectedTag(self.createResetMenu),
Description: self.c.Tr.ViewResetOptions,
OpensMenu: true,
Key: opts.GetKey(opts.Config.Commits.ViewResetOptions),
Handler: self.withItem(self.createResetMenu),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.ViewResetOptions,
OpensMenu: true,
},
}
@ -215,21 +225,6 @@ func (self *TagsController) create() error {
})
}
func (self *TagsController) withSelectedTag(f func(tag *models.Tag) error) func() error {
return func() error {
tag := self.context().GetSelected()
if tag == nil {
return nil
}
return f(tag)
}
}
func (self *TagsController) Context() types.Context {
return self.context()
}
func (self *TagsController) context() *context.TagsContext {
return self.c.Contexts().Tags
}

View File

@ -27,11 +27,11 @@ type UndoController struct {
var _ types.IController = &UndoController{}
func NewUndoController(
common *ControllerCommon,
c *ControllerCommon,
) *UndoController {
return &UndoController{
baseController: baseController{},
c: common,
c: c,
}
}

View File

@ -14,15 +14,21 @@ type CanViewWorktreeOptions interface {
type WorktreeOptionsController struct {
baseController
*ListControllerTrait[string]
c *ControllerCommon
context CanViewWorktreeOptions
}
func NewWorktreeOptionsController(controllerCommon *ControllerCommon, context CanViewWorktreeOptions) *WorktreeOptionsController {
func NewWorktreeOptionsController(c *ControllerCommon, context CanViewWorktreeOptions) *WorktreeOptionsController {
return &WorktreeOptionsController{
baseController: baseController{},
c: controllerCommon,
context: context,
ListControllerTrait: NewListControllerTrait[string](
c,
context,
context.GetSelectedItemId,
),
c: c,
context: context,
}
}
@ -30,7 +36,7 @@ func (self *WorktreeOptionsController) GetKeybindings(opts types.KeybindingsOpts
bindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.Worktrees.ViewWorktreeOptions),
Handler: self.checkSelected(self.viewWorktreeOptions),
Handler: self.withItem(self.viewWorktreeOptions),
Description: self.c.Tr.ViewWorktreeOptions,
OpensMenu: true,
},
@ -39,21 +45,6 @@ func (self *WorktreeOptionsController) GetKeybindings(opts types.KeybindingsOpts
return bindings
}
func (self *WorktreeOptionsController) checkSelected(callback func(string) error) func() error {
return func() error {
ref := self.context.GetSelectedItemId()
if ref == "" {
return nil
}
return callback(ref)
}
}
func (self *WorktreeOptionsController) Context() types.Context {
return self.context
}
func (self *WorktreeOptionsController) viewWorktreeOptions(ref string) error {
return self.c.Helpers().Worktree.ViewWorktreeOptions(self.context, ref)
}

View File

@ -13,17 +13,23 @@ import (
type WorktreesController struct {
baseController
*ListControllerTrait[*models.Worktree]
c *ControllerCommon
}
var _ types.IController = &WorktreesController{}
func NewWorktreesController(
common *ControllerCommon,
c *ControllerCommon,
) *WorktreesController {
return &WorktreesController{
baseController: baseController{},
c: common,
ListControllerTrait: NewListControllerTrait[*models.Worktree](
c,
c.Contexts().Worktrees,
c.Contexts().Worktrees.GetSelected,
),
c: c,
}
}
@ -35,24 +41,28 @@ func (self *WorktreesController) GetKeybindings(opts types.KeybindingsOpts) []*t
Description: self.c.Tr.CreateWorktree,
},
{
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.checkSelected(self.enter),
Description: self.c.Tr.SwitchToWorktree,
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.withItem(self.enter),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.SwitchToWorktree,
},
{
Key: opts.GetKey(opts.Config.Universal.Confirm),
Handler: self.checkSelected(self.enter),
Description: self.c.Tr.SwitchToWorktree,
Key: opts.GetKey(opts.Config.Universal.Confirm),
Handler: self.withItem(self.enter),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.SwitchToWorktree,
},
{
Key: opts.GetKey(opts.Config.Universal.OpenFile),
Handler: self.checkSelected(self.open),
Description: self.c.Tr.OpenInEditor,
Key: opts.GetKey(opts.Config.Universal.OpenFile),
Handler: self.withItem(self.open),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.OpenInEditor,
},
{
Key: opts.GetKey(opts.Config.Universal.Remove),
Handler: self.checkSelected(self.remove),
Description: self.c.Tr.RemoveWorktree,
Key: opts.GetKey(opts.Config.Universal.Remove),
Handler: self.withItem(self.remove),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.RemoveWorktree,
},
}
@ -113,7 +123,7 @@ func (self *WorktreesController) remove(worktree *models.Worktree) error {
}
func (self *WorktreesController) GetOnClick() func() error {
return self.checkSelected(self.enter)
return self.withItemGraceful(self.enter)
}
func (self *WorktreesController) enter(worktree *models.Worktree) error {
@ -124,21 +134,6 @@ func (self *WorktreesController) open(worktree *models.Worktree) error {
return self.c.Helpers().Files.OpenDirInEditor(worktree.Path)
}
func (self *WorktreesController) checkSelected(callback func(worktree *models.Worktree) error) func() error {
return func() error {
worktree := self.context().GetSelected()
if worktree == nil {
return nil
}
return callback(worktree)
}
}
func (self *WorktreesController) Context() types.Context {
return self.context()
}
func (self *WorktreesController) context() *context.WorktreesContext {
return self.c.Contexts().Worktrees
}