Skip to content

Commit 8a8072a

Browse files
committed
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.
1 parent 0017ff0 commit 8a8072a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+849
-748
lines changed

pkg/gui/context/traits/list_cursor.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,18 @@ func (self *ListCursor) CancelRangeSelect() {
114114
self.rangeSelectMode = RangeSelectModeNone
115115
}
116116

117+
// Returns true if we are in range select mode. Note that we may be in range select
118+
// mode and still only selecting a single item. See AreMultipleItemsSelected below.
117119
func (self *ListCursor) IsSelectingRange() bool {
118120
return self.rangeSelectMode != RangeSelectModeNone
119121
}
120122

123+
// Returns true if we are in range select mode and selecting multiple items
124+
func (self *ListCursor) AreMultipleItemsSelected() bool {
125+
startIdx, endIdx := self.GetSelectionRange()
126+
return startIdx != endIdx
127+
}
128+
121129
func (self *ListCursor) GetSelectionRange() (int, int) {
122130
if self.IsSelectingRange() {
123131
return utils.MinMax(self.selectedIdx, self.rangeStartIdx)

pkg/gui/controllers/basic_commits_controller.go

Lines changed: 35 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -22,50 +22,61 @@ type ContainsCommits interface {
2222

2323
type BasicCommitsController struct {
2424
baseController
25+
*ListControllerTrait[*models.Commit]
2526
c *ControllerCommon
2627
context ContainsCommits
2728
}
2829

29-
func NewBasicCommitsController(controllerCommon *ControllerCommon, context ContainsCommits) *BasicCommitsController {
30+
func NewBasicCommitsController(c *ControllerCommon, context ContainsCommits) *BasicCommitsController {
3031
return &BasicCommitsController{
3132
baseController: baseController{},
32-
c: controllerCommon,
33+
c: c,
3334
context: context,
35+
ListControllerTrait: NewListControllerTrait[*models.Commit](
36+
c,
37+
context,
38+
context.GetSelected,
39+
),
3440
}
3541
}
3642

3743
func (self *BasicCommitsController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
3844
bindings := []*types.Binding{
3945
{
40-
Key: opts.GetKey(opts.Config.Commits.CheckoutCommit),
41-
Handler: self.checkSelected(self.checkout),
42-
Description: self.c.Tr.CheckoutCommit,
46+
Key: opts.GetKey(opts.Config.Commits.CheckoutCommit),
47+
Handler: self.withItem(self.checkout),
48+
GetDisabledReason: self.require(self.singleItemSelected()),
49+
Description: self.c.Tr.CheckoutCommit,
4350
},
4451
{
45-
Key: opts.GetKey(opts.Config.Commits.CopyCommitAttributeToClipboard),
46-
Handler: self.checkSelected(self.copyCommitAttribute),
47-
Description: self.c.Tr.CopyCommitAttributeToClipboard,
48-
OpensMenu: true,
52+
Key: opts.GetKey(opts.Config.Commits.CopyCommitAttributeToClipboard),
53+
Handler: self.withItem(self.copyCommitAttribute),
54+
GetDisabledReason: self.require(self.singleItemSelected()),
55+
Description: self.c.Tr.CopyCommitAttributeToClipboard,
56+
OpensMenu: true,
4957
},
5058
{
51-
Key: opts.GetKey(opts.Config.Commits.OpenInBrowser),
52-
Handler: self.checkSelected(self.openInBrowser),
53-
Description: self.c.Tr.OpenCommitInBrowser,
59+
Key: opts.GetKey(opts.Config.Commits.OpenInBrowser),
60+
Handler: self.withItem(self.openInBrowser),
61+
GetDisabledReason: self.require(self.singleItemSelected()),
62+
Description: self.c.Tr.OpenCommitInBrowser,
5463
},
5564
{
56-
Key: opts.GetKey(opts.Config.Universal.New),
57-
Handler: self.checkSelected(self.newBranch),
58-
Description: self.c.Tr.CreateNewBranchFromCommit,
65+
Key: opts.GetKey(opts.Config.Universal.New),
66+
Handler: self.withItem(self.newBranch),
67+
GetDisabledReason: self.require(self.singleItemSelected()),
68+
Description: self.c.Tr.CreateNewBranchFromCommit,
5969
},
6070
{
61-
Key: opts.GetKey(opts.Config.Commits.ViewResetOptions),
62-
Handler: self.checkSelected(self.createResetMenu),
63-
Description: self.c.Tr.ViewResetOptions,
64-
OpensMenu: true,
71+
Key: opts.GetKey(opts.Config.Commits.ViewResetOptions),
72+
Handler: self.withItem(self.createResetMenu),
73+
GetDisabledReason: self.require(self.singleItemSelected()),
74+
Description: self.c.Tr.ViewResetOptions,
75+
OpensMenu: true,
6576
},
6677
{
6778
Key: opts.GetKey(opts.Config.Commits.CherryPickCopy),
68-
Handler: self.checkSelected(self.copyRange),
79+
Handler: self.withItem(self.copyRange),
6980
Description: self.c.Tr.CherryPickCopy,
7081
},
7182
{
@@ -74,30 +85,16 @@ func (self *BasicCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
7485
Description: self.c.Tr.ResetCherryPick,
7586
},
7687
{
77-
Key: opts.GetKey(opts.Config.Universal.OpenDiffTool),
78-
Handler: self.checkSelected(self.openDiffTool),
79-
Description: self.c.Tr.OpenDiffTool,
88+
Key: opts.GetKey(opts.Config.Universal.OpenDiffTool),
89+
Handler: self.withItem(self.openDiffTool),
90+
GetDisabledReason: self.require(self.singleItemSelected()),
91+
Description: self.c.Tr.OpenDiffTool,
8092
},
8193
}
8294

8395
return bindings
8496
}
8597

86-
func (self *BasicCommitsController) checkSelected(callback func(*models.Commit) error) func() error {
87-
return func() error {
88-
commit := self.context.GetSelected()
89-
if commit == nil {
90-
return nil
91-
}
92-
93-
return callback(commit)
94-
}
95-
}
96-
97-
func (self *BasicCommitsController) Context() types.Context {
98-
return self.context
99-
}
100-
10198
func (self *BasicCommitsController) copyCommitAttribute(commit *models.Commit) error {
10299
return self.c.Menu(types.CreateMenuOptions{
103100
Title: self.c.Tr.Actions.CopyCommitAttributeToClipboard,

pkg/gui/controllers/bisect_controller.go

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,31 @@ import (
1414

1515
type BisectController struct {
1616
baseController
17+
*ListControllerTrait[*models.Commit]
1718
c *ControllerCommon
1819
}
1920

2021
var _ types.IController = &BisectController{}
2122

2223
func NewBisectController(
23-
common *ControllerCommon,
24+
c *ControllerCommon,
2425
) *BisectController {
2526
return &BisectController{
2627
baseController: baseController{},
27-
c: common,
28+
c: c,
29+
ListControllerTrait: NewListControllerTrait[*models.Commit](
30+
c,
31+
c.Contexts().LocalCommits,
32+
c.Contexts().LocalCommits.GetSelected,
33+
),
2834
}
2935
}
3036

3137
func (self *BisectController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
3238
bindings := []*types.Binding{
3339
{
3440
Key: opts.GetKey(opts.Config.Commits.ViewBisectOptions),
35-
Handler: opts.Guards.OutsideFilterMode(self.checkSelected(self.openMenu)),
41+
Handler: opts.Guards.OutsideFilterMode(self.withItem(self.openMenu)),
3642
Description: self.c.Tr.ViewBisectOptions,
3743
OpensMenu: true,
3844
},
@@ -70,9 +76,19 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
7076
// If we have a current sha already, then we always want to use that one. If
7177
// not, we're still picking the initial commits before we really start, so
7278
// use the selected commit in that case.
73-
shaToMark := lo.Ternary(info.GetCurrentSha() != "", info.GetCurrentSha(), commit.Sha)
79+
80+
bisecting := info.GetCurrentSha() != ""
81+
shaToMark := lo.Ternary(bisecting, info.GetCurrentSha(), commit.Sha)
7482
shortShaToMark := utils.ShortSha(shaToMark)
7583

84+
// For marking a commit as bad, when we're not already bisecting, we require
85+
// a single item selected, but once we are bisecting, it doesn't matter because
86+
// the action applies to the HEAD commit rather than the selected commit.
87+
var singleItemIfNotBisecting *types.DisabledReason
88+
if !bisecting {
89+
singleItemIfNotBisecting = self.require(self.singleItemSelected())()
90+
}
91+
7692
menuItems := []*types.MenuItem{
7793
{
7894
Label: fmt.Sprintf(self.c.Tr.Bisect.Mark, shortShaToMark, info.NewTerm()),
@@ -84,7 +100,8 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
84100

85101
return self.afterMark(selectCurrentAfter, waitToReselect)
86102
},
87-
Key: 'b',
103+
DisabledReason: singleItemIfNotBisecting,
104+
Key: 'b',
88105
},
89106
{
90107
Label: fmt.Sprintf(self.c.Tr.Bisect.Mark, shortShaToMark, info.OldTerm()),
@@ -96,7 +113,8 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
96113

97114
return self.afterMark(selectCurrentAfter, waitToReselect)
98115
},
99-
Key: 'g',
116+
DisabledReason: singleItemIfNotBisecting,
117+
Key: 'g',
100118
},
101119
{
102120
Label: fmt.Sprintf(self.c.Tr.Bisect.SkipCurrent, shortShaToMark),
@@ -108,7 +126,8 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
108126

109127
return self.afterMark(selectCurrentAfter, waitToReselect)
110128
},
111-
Key: 's',
129+
DisabledReason: singleItemIfNotBisecting,
130+
Key: 's',
112131
},
113132
}
114133
if info.GetCurrentSha() != "" && info.GetCurrentSha() != commit.Sha {
@@ -122,7 +141,8 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
122141

123142
return self.afterMark(selectCurrentAfter, waitToReselect)
124143
},
125-
Key: 'S',
144+
DisabledReason: self.require(self.singleItemSelected())(),
145+
Key: 'S',
126146
}))
127147
}
128148
menuItems = append(menuItems, lo.ToPtr(types.MenuItem{
@@ -157,7 +177,8 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
157177

158178
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
159179
},
160-
Key: 'b',
180+
DisabledReason: self.require(self.singleItemSelected())(),
181+
Key: 'b',
161182
},
162183
{
163184
Label: fmt.Sprintf(self.c.Tr.Bisect.MarkStart, commit.ShortSha(), info.OldTerm()),
@@ -173,7 +194,8 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
173194

174195
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
175196
},
176-
Key: 'g',
197+
DisabledReason: self.require(self.singleItemSelected())(),
198+
Key: 'g',
177199
},
178200
{
179201
Label: self.c.Tr.Bisect.ChooseTerms,
@@ -273,21 +295,6 @@ func (self *BisectController) selectCurrentBisectCommit() {
273295
}
274296
}
275297

276-
func (self *BisectController) checkSelected(callback func(*models.Commit) error) func() error {
277-
return func() error {
278-
commit := self.context().GetSelected()
279-
if commit == nil {
280-
return nil
281-
}
282-
283-
return callback(commit)
284-
}
285-
}
286-
287-
func (self *BisectController) Context() types.Context {
288-
return self.context()
289-
}
290-
291298
func (self *BisectController) context() *context.LocalCommitsContext {
292299
return self.c.Contexts().LocalCommits
293300
}

0 commit comments

Comments
 (0)