1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-01-20 05:19:24 +02: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

@ -114,10 +114,18 @@ func (self *ListCursor) CancelRangeSelect() {
self.rangeSelectMode = RangeSelectModeNone
}
// Returns true if we are in range select mode. Note that we may be in range select
// mode and still only selecting a single item. See AreMultipleItemsSelected below.
func (self *ListCursor) IsSelectingRange() bool {
return self.rangeSelectMode != RangeSelectModeNone
}
// Returns true if we are in range select mode and selecting multiple items
func (self *ListCursor) AreMultipleItemsSelected() bool {
startIdx, endIdx := self.GetSelectionRange()
return startIdx != endIdx
}
func (self *ListCursor) GetSelectionRange() (int, int) {
if self.IsSelectingRange() {
return utils.MinMax(self.selectedIdx, self.rangeStartIdx)

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
}

View File

@ -139,6 +139,28 @@ func (gui *Gui) handleCopySelectedSideContextItemToClipboard() error {
return nil
}
func (gui *Gui) getCopySelectedSideContextItemToClipboardDisabledReason() *types.DisabledReason {
// important to note that this assumes we've selected an item in a side context
currentSideContext := gui.c.CurrentSideContext()
if currentSideContext == nil {
// This should never happen but if it does we'll just ignore the keypress
return nil
}
listContext, ok := currentSideContext.(types.IListContext)
if !ok {
// This should never happen but if it does we'll just ignore the keypress
return nil
}
startIdx, endIdx := listContext.GetList().GetSelectionRange()
if startIdx != endIdx {
return &types.DisabledReason{Text: gui.Tr.RangeSelectNotSupported}
}
return nil
}
func (gui *Gui) setCaption(caption string) {
gui.Views.Options.FgColor = gocui.ColorWhite
gui.Views.Options.FgColor |= gocui.AttrBold

View File

@ -123,28 +123,32 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
Handler: self.scrollDownMain,
},
{
ViewName: "files",
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard,
Description: self.c.Tr.CopyFileNameToClipboard,
ViewName: "files",
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard,
GetDisabledReason: self.getCopySelectedSideContextItemToClipboardDisabledReason,
Description: self.c.Tr.CopyFileNameToClipboard,
},
{
ViewName: "localBranches",
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard,
Description: self.c.Tr.CopyBranchNameToClipboard,
ViewName: "localBranches",
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard,
GetDisabledReason: self.getCopySelectedSideContextItemToClipboardDisabledReason,
Description: self.c.Tr.CopyBranchNameToClipboard,
},
{
ViewName: "remoteBranches",
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard,
Description: self.c.Tr.CopyBranchNameToClipboard,
ViewName: "remoteBranches",
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard,
GetDisabledReason: self.getCopySelectedSideContextItemToClipboardDisabledReason,
Description: self.c.Tr.CopyBranchNameToClipboard,
},
{
ViewName: "commits",
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard,
Description: self.c.Tr.CopyCommitShaToClipboard,
ViewName: "commits",
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard,
GetDisabledReason: self.getCopySelectedSideContextItemToClipboardDisabledReason,
Description: self.c.Tr.CopyCommitShaToClipboard,
},
{
ViewName: "commits",
@ -153,16 +157,18 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
Description: self.c.Tr.ResetCherryPick,
},
{
ViewName: "reflogCommits",
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard,
Description: self.c.Tr.CopyCommitShaToClipboard,
ViewName: "reflogCommits",
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard,
GetDisabledReason: self.getCopySelectedSideContextItemToClipboardDisabledReason,
Description: self.c.Tr.CopyCommitShaToClipboard,
},
{
ViewName: "subCommits",
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard,
Description: self.c.Tr.CopyCommitShaToClipboard,
ViewName: "subCommits",
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard,
GetDisabledReason: self.getCopySelectedSideContextItemToClipboardDisabledReason,
Description: self.c.Tr.CopyCommitShaToClipboard,
},
{
ViewName: "information",
@ -171,10 +177,11 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
Handler: self.handleInfoClick,
},
{
ViewName: "commitFiles",
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard,
Description: self.c.Tr.CopyCommitFileNameToClipboard,
ViewName: "commitFiles",
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard,
GetDisabledReason: self.getCopySelectedSideContextItemToClipboardDisabledReason,
Description: self.c.Tr.CopyCommitFileNameToClipboard,
},
{
ViewName: "",
@ -240,10 +247,11 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
Handler: self.scrollDownConfirmationPanel,
},
{
ViewName: "submodules",
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard,
Description: self.c.Tr.CopySubmoduleNameToClipboard,
ViewName: "submodules",
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard,
GetDisabledReason: self.getCopySelectedSideContextItemToClipboardDisabledReason,
Description: self.c.Tr.CopySubmoduleNameToClipboard,
},
{
ViewName: "extras",

View File

@ -231,6 +231,7 @@ type IListCursor interface {
GetRangeStartIdx() (int, bool)
GetSelectionRange() (int, int)
IsSelectingRange() bool
AreMultipleItemsSelected() bool
ToggleStickyRange()
ExpandNonStickyRange(int)
}

View File

@ -644,7 +644,6 @@ type TranslationSet struct {
MarkedCommitMarker string
PleaseGoToURL string
DisabledMenuItemPrefix string
NoCommitSelected string
NoCopiedCommits string
QuickStartInteractiveRebase string
QuickStartInteractiveRebaseTooltip string
@ -652,6 +651,9 @@ type TranslationSet struct {
ToggleRangeSelect string
RangeSelectUp string
RangeSelectDown string
RangeSelectNotSupported string
NoItemSelected string
SelectedItemIsNotABranch string
Actions Actions
Bisect Bisect
Log Log
@ -1478,13 +1480,15 @@ func EnglishTranslationSet() TranslationSet {
MarkedCommitMarker: "↑↑↑ Will rebase from here ↑↑↑",
PleaseGoToURL: "Please go to {{.url}}",
DisabledMenuItemPrefix: "Disabled: ",
NoCommitSelected: "No commit selected",
NoCopiedCommits: "No copied commits",
QuickStartInteractiveRebase: "Start interactive rebase",
QuickStartInteractiveRebaseTooltip: "Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit.\nIf you would instead like to start an interactive rebase from the selected commit, press `{{.editKey}}`.",
CannotQuickStartInteractiveRebase: "Cannot start interactive rebase: the HEAD commit is a merge commit or is present on the main branch, so there is no appropriate base commit to start the rebase from. You can start an interactive rebase from a specific commit by selecting the commit and pressing `{{.editKey}}`.",
RangeSelectUp: "Range select up",
RangeSelectDown: "Range select down",
RangeSelectNotSupported: "Action does not support range selection, please select a single item",
NoItemSelected: "No item selected",
SelectedItemIsNotABranch: "Selected item is not a branch",
Actions: Actions{
// TODO: combine this with the original keybinding descriptions (those are all in lowercase atm)
CheckoutCommit: "Checkout commit",

View File

@ -29,10 +29,10 @@ var CopyMenu = NewIntegrationTest(NewIntegrationTestArgs{
t.ExpectPopup().Menu().
Title(Equals("Copy to clipboard")).
Select(Contains("File name")).
Tooltip(Equals("Disabled: Nothing to copy")).
Tooltip(Equals("Disabled: No item selected")).
Confirm().
Tap(func() {
t.ExpectToast(Equals("Disabled: Nothing to copy"))
t.ExpectToast(Equals("Disabled: No item selected"))
}).
Cancel()
})

View File

@ -22,7 +22,14 @@ var EmptyMenu = NewIntegrationTest(NewIntegrationTestArgs{
// a string that filters everything out
FilterOrSearch("ljasldkjaslkdjalskdjalsdjaslkd").
IsEmpty().
Press(keys.Universal.Select)
Press(keys.Universal.Select).
Tap(func() {
t.ExpectToast(Equals("Disabled: No item selected"))
}).
// escape the search
PressEscape().
// escape the view
PressEscape()
// back in the files view, selecting the non-existing menu item was a no-op
t.Views().Files().