mirror of
				https://github.com/jesseduffield/lazygit.git
				synced 2025-10-30 23:57:43 +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:
		| @@ -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) | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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, | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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, | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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, | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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, | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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, | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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:]..., | ||||
| 					)..., | ||||
|   | ||||
| @@ -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{ | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -11,11 +11,11 @@ type GlobalController struct { | ||||
| } | ||||
|  | ||||
| func NewGlobalController( | ||||
| 	common *ControllerCommon, | ||||
| 	c *ControllerCommon, | ||||
| ) *GlobalController { | ||||
| 	return &GlobalController{ | ||||
| 		baseController: baseController{}, | ||||
| 		c:              common, | ||||
| 		c:              c, | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -14,11 +14,11 @@ type JumpToSideWindowController struct { | ||||
| } | ||||
|  | ||||
| func NewJumpToSideWindowController( | ||||
| 	common *ControllerCommon, | ||||
| 	c *ControllerCommon, | ||||
| ) *JumpToSideWindowController { | ||||
| 	return &JumpToSideWindowController{ | ||||
| 		baseController: baseController{}, | ||||
| 		c:              common, | ||||
| 		c:              c, | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										95
									
								
								pkg/gui/controllers/list_controller_trait.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								pkg/gui/controllers/list_controller_trait.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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, | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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, | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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, | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -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, | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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, | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -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, | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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, | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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, | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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, | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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", | ||||
|   | ||||
| @@ -231,6 +231,7 @@ type IListCursor interface { | ||||
| 	GetRangeStartIdx() (int, bool) | ||||
| 	GetSelectionRange() (int, int) | ||||
| 	IsSelectingRange() bool | ||||
| 	AreMultipleItemsSelected() bool | ||||
| 	ToggleStickyRange() | ||||
| 	ExpandNonStickyRange(int) | ||||
| } | ||||
|   | ||||
| @@ -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", | ||||
|   | ||||
| @@ -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() | ||||
| 			}) | ||||
|   | ||||
| @@ -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(). | ||||
|   | ||||
		Reference in New Issue
	
	Block a user