1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-02-03 13:21:56 +02:00

Show better keybinding suggestions (#3203)

- **PR Description**

This PR's goal is to improve discoverability of keybindings in lazygit.
I've have a couple people in real life mention to me that it wasn't
obvious what key to press for viewing rebase options, for example.


This PR:
* shows more keybindings in the options view
* shows certain keybindings prominently in certain modes e.g. 'view
rebase options: m' when mid-rebase.

Before:

![image](https://github.com/jesseduffield/lazygit/assets/8456633/483477ef-93e1-4fd1-af86-3ffa84167f62)


After:

![image](https://github.com/jesseduffield/lazygit/assets/8456633/4c93dd19-f072-45ec-afa6-810727211f66)




- **Please check if the PR fulfills these requirements**

* [x] Cheatsheets are up-to-date (run `go generate ./...`)
* [x] Code has been formatted (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [x] Tests have been added/updated (see
[here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
for the integration test guide)
* [x] Text is internationalised (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
* [x] Docs (specifically `docs/Config.md`) have been updated if
necessary
* [x] You've read through your own file changes for silly mistakes etc

<!--
Be sure to name your PR with an imperative e.g. 'Add worktrees view'
see https://github.com/jesseduffield/lazygit/releases/tag/v0.40.0 for
examples
-->
This commit is contained in:
Jesse Duffield 2024-01-28 09:27:01 +11:00 committed by GitHub
commit cf5d4d4f8e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 502 additions and 204 deletions

View File

@ -12,3 +12,11 @@ const (
REBASE_MODE_REBASING
REBASE_MODE_MERGING
)
func (self RebaseMode) IsMerging() bool {
return self == REBASE_MODE_MERGING
}
func (self RebaseMode) IsRebasing() bool {
return self == REBASE_MODE_INTERACTIVE || self == REBASE_MODE_NORMAL || self == REBASE_MODE_REBASING
}

View File

@ -245,8 +245,6 @@ func (self *ContextMgr) ActivateContext(c types.Context, opts types.OnFocusOpts)
self.gui.c.GocuiGui().Cursor = v.Editable
self.gui.renderContextOptionsMap(c)
if err := c.HandleFocus(opts); err != nil {
return err
}

View File

@ -20,15 +20,11 @@ func NewListViewModel[T HasID](getModel func() []T) *ListViewModel[T] {
getModel: getModel,
}
self.ListCursor = traits.NewListCursor(self)
self.ListCursor = traits.NewListCursor(func() int { return len(getModel()) })
return self
}
func (self *ListViewModel[T]) Len() int {
return len(self.getModel())
}
func (self *ListViewModel[T]) GetSelected() T {
if self.Len() == 0 {
return Zero[T]()

View File

@ -5,10 +5,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/utils"
)
type HasLength interface {
Len() int
}
type RangeSelectMode int
const (
@ -27,15 +23,17 @@ type ListCursor struct {
rangeSelectMode RangeSelectMode
// value is ignored when rangeSelectMode is RangeSelectModeNone
rangeStartIdx int
list HasLength
// Get the length of the list. We use this to clamp the selection so that
// the selected index is always valid
getLength func() int
}
func NewListCursor(list HasLength) *ListCursor {
func NewListCursor(getLength func() int) *ListCursor {
return &ListCursor{
selectedIdx: 0,
rangeStartIdx: 0,
rangeSelectMode: RangeSelectModeNone,
list: list,
getLength: getLength,
}
}
@ -81,8 +79,9 @@ func (self *ListCursor) GetSelectionRangeAndMode() (int, int, RangeSelectMode) {
func (self *ListCursor) clampValue(value int) int {
clampedValue := -1
if self.list.Len() > 0 {
clampedValue = utils.Clamp(value, 0, self.list.Len()-1)
length := self.getLength()
if length > 0 {
clampedValue = utils.Clamp(value, 0, length-1)
}
return clampedValue
@ -114,7 +113,12 @@ func (self *ListCursor) ClampSelection() {
}
func (self *ListCursor) Len() int {
return self.list.Len()
// The length of the model slice can change at any time, so the selection may
// become out of bounds. To reduce the likelihood of this, we clamp the selection
// whenever we obtain the length of the model.
self.ClampSelection()
return self.getLength()
}
func (self *ListCursor) GetRangeStartIdx() (int, bool) {

View File

@ -52,6 +52,7 @@ func (self *BasicCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Checkout,
Tooltip: self.c.Tr.CheckoutCommitTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Commits.CopyCommitAttributeToClipboard),
@ -80,6 +81,7 @@ func (self *BasicCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
Description: self.c.Tr.ViewResetOptions,
Tooltip: self.c.Tr.ResetTooltip,
OpensMenu: true,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Commits.CherryPickCopy),
@ -91,6 +93,7 @@ func (self *BasicCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
"escape": keybindings.Label(opts.Config.Universal.Return),
},
),
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Commits.ResetCherryPick),

View File

@ -47,14 +47,16 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
self.singleItemSelected(),
self.notPulling,
),
Description: self.c.Tr.Checkout,
Tooltip: self.c.Tr.CheckoutTooltip,
Description: self.c.Tr.Checkout,
Tooltip: self.c.Tr.CheckoutTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.New),
Handler: self.withItem(self.newBranch),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.NewBranch,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Branches.CreatePullRequest),
@ -95,6 +97,7 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
Description: self.c.Tr.Delete,
Tooltip: self.c.Tr.BranchDeleteTooltip,
OpensMenu: true,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Branches.RebaseBranch),
@ -102,8 +105,9 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
GetDisabledReason: self.require(
self.singleItemSelected(self.notRebasingOntoSelf),
),
Description: self.c.Tr.RebaseBranch,
Tooltip: self.c.Tr.RebaseBranchTooltip,
Description: self.c.Tr.RebaseBranch,
Tooltip: self.c.Tr.RebaseBranchTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Branches.MergeIntoCurrentBranch),
@ -111,6 +115,7 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Merge,
Tooltip: self.c.Tr.MergeBranchTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Branches.FastForward),
@ -136,6 +141,7 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.ViewResetOptions,
OpensMenu: true,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Branches.RenameBranch),
@ -151,6 +157,7 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
Tooltip: self.c.Tr.ViewBranchUpstreamOptionsTooltip,
ShortDescription: self.c.Tr.Upstream,
OpensMenu: true,
DisplayOnScreen: true,
},
}
}

View File

@ -43,6 +43,7 @@ func (self *CommitFilesController) GetKeybindings(opts types.KeybindingsOpts) []
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Checkout,
Tooltip: self.c.Tr.CheckoutCommitFileTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.Remove),
@ -50,6 +51,7 @@ func (self *CommitFilesController) GetKeybindings(opts types.KeybindingsOpts) []
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Remove,
Tooltip: self.c.Tr.DiscardOldFileChangeTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.OpenFile),
@ -64,6 +66,7 @@ func (self *CommitFilesController) GetKeybindings(opts types.KeybindingsOpts) []
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Edit,
Tooltip: self.c.Tr.EditFileTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.OpenDiffTool),
@ -79,6 +82,7 @@ func (self *CommitFilesController) GetKeybindings(opts types.KeybindingsOpts) []
Tooltip: utils.ResolvePlaceholderString(self.c.Tr.ToggleAddToPatchTooltip,
map[string]string{"doc": constants.Links.Docs.CustomPatchDemo},
),
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Files.ToggleStagedAll),

View File

@ -24,16 +24,16 @@ func NewConfirmationController(
func (self *ConfirmationController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
bindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.Confirm),
Handler: func() error { return self.context().State.OnConfirm() },
Description: self.c.Tr.Confirm,
Display: true,
Key: opts.GetKey(opts.Config.Universal.Confirm),
Handler: func() error { return self.context().State.OnConfirm() },
Description: self.c.Tr.Confirm,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.Return),
Handler: func() error { return self.context().State.OnClose() },
Description: self.c.Tr.CloseCancel,
Display: true,
Key: opts.GetKey(opts.Config.Universal.Return),
Handler: func() error { return self.context().State.OnClose() },
Description: self.c.Tr.CloseCancel,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.TogglePanel),

View File

@ -43,6 +43,7 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
GetDisabledReason: self.require(self.itemsSelected()),
Description: self.c.Tr.Stage,
Tooltip: self.c.Tr.StageTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Files.OpenStatusFilter),
@ -56,10 +57,11 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
OpensMenu: true,
},
{
Key: opts.GetKey(opts.Config.Files.CommitChanges),
Handler: self.c.Helpers().WorkingTree.HandleCommitPress,
Description: self.c.Tr.Commit,
Tooltip: self.c.Tr.CommitTooltip,
Key: opts.GetKey(opts.Config.Files.CommitChanges),
Handler: self.c.Helpers().WorkingTree.HandleCommitPress,
Description: self.c.Tr.Commit,
Tooltip: self.c.Tr.CommitTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Files.CommitChangesWithoutHook),
@ -88,6 +90,7 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Edit,
Tooltip: self.c.Tr.EditFileTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.OpenFile),
@ -109,10 +112,11 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
Description: self.c.Tr.RefreshFiles,
},
{
Key: opts.GetKey(opts.Config.Files.StashAllChanges),
Handler: self.stash,
Description: self.c.Tr.Stash,
Tooltip: self.c.Tr.StashTooltip,
Key: opts.GetKey(opts.Config.Files.StashAllChanges),
Handler: self.stash,
Description: self.c.Tr.Stash,
Tooltip: self.c.Tr.StashTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Files.ViewStashOptions),
@ -141,6 +145,7 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
Description: self.c.Tr.Discard,
Tooltip: self.c.Tr.DiscardFileChangesTooltip,
OpensMenu: true,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Commits.ViewResetOptions),
@ -149,11 +154,12 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
OpensMenu: true,
},
{
Key: opts.GetKey(opts.Config.Files.ViewResetOptions),
Handler: self.createResetMenu,
Description: self.c.Tr.Reset,
Tooltip: self.c.Tr.FileResetOptionsTooltip,
OpensMenu: true,
Key: opts.GetKey(opts.Config.Files.ViewResetOptions),
Handler: self.createResetMenu,
Description: self.c.Tr.Reset,
Tooltip: self.c.Tr.FileResetOptionsTooltip,
OpensMenu: true,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Files.ToggleTreeView),

View File

@ -72,6 +72,7 @@ func (self *GlobalController) GetKeybindings(opts types.KeybindingsOpts) []*type
Description: self.c.Tr.OpenKeybindingsMenu,
Handler: self.createOptionsMenu,
ShortDescription: self.c.Tr.Keybindings,
DisplayOnScreen: true,
},
{
ViewName: "",
@ -112,10 +113,11 @@ func (self *GlobalController) GetKeybindings(opts types.KeybindingsOpts) []*type
Handler: self.quitWithoutChangingDirectory,
},
{
Key: opts.GetKey(opts.Config.Universal.Return),
Modifier: gocui.ModNone,
Handler: self.escape,
Description: self.c.Tr.Cancel,
Key: opts.GetKey(opts.Config.Universal.Return),
Modifier: gocui.ModNone,
Handler: self.escape,
Description: self.c.Tr.Cancel,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.ToggleWhitespaceInDiffView),

View File

@ -64,8 +64,9 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
self.canSquashOrFixup,
),
),
Description: self.c.Tr.Squash,
Tooltip: self.c.Tr.SquashTooltip,
Description: self.c.Tr.Squash,
Tooltip: self.c.Tr.SquashTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Commits.MarkCommitAsFixup),
@ -76,8 +77,9 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
self.canSquashOrFixup,
),
),
Description: self.c.Tr.Fixup,
Tooltip: self.c.Tr.FixupTooltip,
Description: self.c.Tr.Fixup,
Tooltip: self.c.Tr.FixupTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Commits.RenameCommit),
@ -85,9 +87,10 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
GetDisabledReason: self.require(
self.singleItemSelected(self.rewordEnabled),
),
Description: self.c.Tr.Reword,
Tooltip: self.c.Tr.CommitRewordTooltip,
OpensMenu: true,
Description: self.c.Tr.Reword,
Tooltip: self.c.Tr.CommitRewordTooltip,
DisplayOnScreen: true,
OpensMenu: true,
},
{
Key: opts.GetKey(opts.Config.Commits.RenameCommitWithEditor),
@ -105,8 +108,9 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
self.midRebaseCommandEnabled,
),
),
Description: self.c.Tr.DropCommit,
Tooltip: self.c.Tr.DropCommitTooltip,
Description: self.c.Tr.DropCommit,
Tooltip: self.c.Tr.DropCommitTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(editCommitKey),
@ -118,6 +122,7 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
Description: self.c.Tr.EditCommit,
ShortDescription: self.c.Tr.Edit,
Tooltip: self.c.Tr.EditCommitTooltip,
DisplayOnScreen: true,
},
{
// The user-facing description here is 'Start interactive rebase' but internally
@ -139,6 +144,14 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
),
Description: self.c.Tr.Pick,
Tooltip: self.c.Tr.PickCommitTooltip,
// Not displaying this because we only want to display it when a TODO commit
// is selected. A keybinding is displayed in the options view if Display is true,
// and if it's not disabled, but if we disable it whenever a non-TODO commit is
// selected, we'll be preventing pulls from happening within the commits view
// (given they both use the 'p' key). Some approaches that come to mind:
// * Allow a disabled keybinding to conditionally fallback to a global keybinding
// * Allow a separate way of deciding whether a keybinding is displayed in the options view
DisplayOnScreen: false,
},
{
Key: opts.GetKey(opts.Config.Commits.CreateFixupCommit),
@ -221,6 +234,7 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
GetDisabledReason: self.require(self.singleItemSelected(self.canAmend)),
Description: self.c.Tr.Amend,
Tooltip: self.c.Tr.AmendCommitTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Commits.ResetCommitAuthor),

View File

@ -42,13 +42,13 @@ func (self *MenuController) GetKeybindings(opts types.KeybindingsOpts) []*types.
Handler: self.withItem(self.press),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Execute,
Display: true,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.Return),
Handler: self.close,
Description: self.c.Tr.Close,
Display: true,
Key: opts.GetKey(opts.Config.Universal.Return),
Handler: self.close,
Description: self.c.Tr.Close,
DisplayOnScreen: true,
},
}

View File

@ -28,50 +28,54 @@ func NewMergeConflictsController(
func (self *MergeConflictsController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
bindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.withRenderAndFocus(self.HandlePickHunk),
Description: self.c.Tr.PickHunk,
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.withRenderAndFocus(self.HandlePickHunk),
Description: self.c.Tr.PickHunk,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Main.PickBothHunks),
Handler: self.withRenderAndFocus(self.HandlePickAllHunks),
Description: self.c.Tr.PickAllHunks,
Key: opts.GetKey(opts.Config.Main.PickBothHunks),
Handler: self.withRenderAndFocus(self.HandlePickAllHunks),
Description: self.c.Tr.PickAllHunks,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.PrevItem),
Handler: self.withRenderAndFocus(self.PrevConflictHunk),
Description: self.c.Tr.SelectPrevHunk,
Key: opts.GetKey(opts.Config.Universal.PrevItem),
Handler: self.withRenderAndFocus(self.PrevConflictHunk),
Description: self.c.Tr.SelectPrevHunk,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.NextItem),
Handler: self.withRenderAndFocus(self.NextConflictHunk),
Description: self.c.Tr.SelectNextHunk,
Key: opts.GetKey(opts.Config.Universal.NextItem),
Handler: self.withRenderAndFocus(self.NextConflictHunk),
Description: self.c.Tr.SelectNextHunk,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.PrevBlock),
Handler: self.withRenderAndFocus(self.PrevConflict),
Description: self.c.Tr.PrevConflict,
Display: true,
Key: opts.GetKey(opts.Config.Universal.PrevBlock),
Handler: self.withRenderAndFocus(self.PrevConflict),
Description: self.c.Tr.PrevConflict,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.NextBlock),
Handler: self.withRenderAndFocus(self.NextConflict),
Description: self.c.Tr.NextConflict,
Display: true,
Key: opts.GetKey(opts.Config.Universal.NextBlock),
Handler: self.withRenderAndFocus(self.NextConflict),
Description: self.c.Tr.NextConflict,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.Undo),
Handler: self.withRenderAndFocus(self.HandleUndo),
Description: self.c.Tr.Undo,
Tooltip: self.c.Tr.UndoMergeResolveTooltip,
Display: true,
Key: opts.GetKey(opts.Config.Universal.Undo),
Handler: self.withRenderAndFocus(self.HandleUndo),
Description: self.c.Tr.Undo,
Tooltip: self.c.Tr.UndoMergeResolveTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.Edit),
Handler: self.HandleEditFile,
Description: self.c.Tr.EditFile,
Tooltip: self.c.Tr.EditFileTooltip,
Display: true,
Key: opts.GetKey(opts.Config.Universal.Edit),
Handler: self.HandleEditFile,
Description: self.c.Tr.EditFile,
Tooltip: self.c.Tr.EditFileTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.OpenFile),
@ -108,11 +112,11 @@ func (self *MergeConflictsController) GetKeybindings(opts types.KeybindingsOpts)
Tag: "navigation",
},
{
Key: opts.GetKey(opts.Config.Files.OpenMergeTool),
Handler: self.c.Helpers().WorkingTree.OpenMergeTool,
Description: self.c.Tr.OpenMergeTool,
Tooltip: self.c.Tr.OpenMergeToolTooltip,
Display: true,
Key: opts.GetKey(opts.Config.Files.OpenMergeTool),
Handler: self.c.Helpers().WorkingTree.OpenMergeTool,
Description: self.c.Tr.OpenMergeTool,
Tooltip: self.c.Tr.OpenMergeToolTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.Return),

View File

@ -37,9 +37,10 @@ func (self *PatchBuildingController) GetKeybindings(opts types.KeybindingsOpts)
Tooltip: self.c.Tr.EditFileTooltip,
},
{
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.ToggleSelectionAndRefresh,
Description: self.c.Tr.ToggleSelectionForPatch,
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.ToggleSelectionAndRefresh,
Description: self.c.Tr.ToggleSelectionForPatch,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.Return),

View File

@ -92,10 +92,11 @@ func (self *PatchExplorerController) GetKeybindings(opts types.KeybindingsOpts)
Description: self.c.Tr.ToggleRangeSelect,
},
{
Key: opts.GetKey(opts.Config.Main.ToggleSelectHunk),
Handler: self.withRenderAndFocus(self.HandleToggleSelectHunk),
Description: self.c.Tr.ToggleSelectHunk,
Tooltip: self.c.Tr.ToggleSelectHunkTooltip,
Key: opts.GetKey(opts.Config.Main.ToggleSelectHunk),
Handler: self.withRenderAndFocus(self.HandleToggleSelectHunk),
Description: self.c.Tr.ToggleSelectHunk,
Tooltip: self.c.Tr.ToggleSelectHunkTooltip,
DisplayOnScreen: true,
},
{
Tag: "navigation",

View File

@ -41,6 +41,7 @@ func (self *RemoteBranchesController) GetKeybindings(opts types.KeybindingsOpts)
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Checkout,
Tooltip: self.c.Tr.RemoteBranchCheckoutTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.New),
@ -54,6 +55,7 @@ func (self *RemoteBranchesController) GetKeybindings(opts types.KeybindingsOpts)
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Merge,
Tooltip: self.c.Tr.MergeBranchTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Branches.RebaseBranch),
@ -61,6 +63,7 @@ func (self *RemoteBranchesController) GetKeybindings(opts types.KeybindingsOpts)
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.RebaseBranch,
Tooltip: self.c.Tr.RebaseBranchTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.Remove),
@ -68,6 +71,7 @@ func (self *RemoteBranchesController) GetKeybindings(opts types.KeybindingsOpts)
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Delete,
Tooltip: self.c.Tr.DeleteRemoteBranchTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Branches.SetUpstream),
@ -75,6 +79,7 @@ func (self *RemoteBranchesController) GetKeybindings(opts types.KeybindingsOpts)
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.SetAsUpstream,
Tooltip: self.c.Tr.SetAsUpstreamTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Branches.SortOrder),

View File

@ -46,11 +46,13 @@ func (self *RemotesController) GetKeybindings(opts types.KeybindingsOpts) []*typ
Handler: self.withItem(self.enter),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.ViewBranches,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.New),
Handler: self.add,
Description: self.c.Tr.NewRemote,
Key: opts.GetKey(opts.Config.Universal.New),
Handler: self.add,
Description: self.c.Tr.NewRemote,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.Remove),
@ -58,6 +60,7 @@ func (self *RemotesController) GetKeybindings(opts types.KeybindingsOpts) []*typ
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Remove,
Tooltip: self.c.Tr.RemoveRemoteTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.Edit),
@ -65,6 +68,7 @@ func (self *RemotesController) GetKeybindings(opts types.KeybindingsOpts) []*typ
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Edit,
Tooltip: self.c.Tr.EditRemoteTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Branches.FetchRemote),
@ -72,6 +76,7 @@ func (self *RemotesController) GetKeybindings(opts types.KeybindingsOpts) []*typ
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Fetch,
Tooltip: self.c.Tr.FetchRemoteTooltip,
DisplayOnScreen: true,
},
}

View File

@ -40,16 +40,18 @@ func NewStagingController(
func (self *StagingController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
return []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.ToggleStaged,
Description: self.c.Tr.Stage,
Tooltip: self.c.Tr.StageSelectionTooltip,
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.ToggleStaged,
Description: self.c.Tr.Stage,
Tooltip: self.c.Tr.StageSelectionTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.Remove),
Handler: self.DiscardSelection,
Description: self.c.Tr.DiscardSelection,
Tooltip: self.c.Tr.DiscardSelectionTooltip,
Key: opts.GetKey(opts.Config.Universal.Remove),
Handler: self.DiscardSelection,
Description: self.c.Tr.DiscardSelection,
Tooltip: self.c.Tr.DiscardSelectionTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.OpenFile),
@ -69,10 +71,11 @@ func (self *StagingController) GetKeybindings(opts types.KeybindingsOpts) []*typ
Description: self.c.Tr.ReturnToFilesPanel,
},
{
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
Handler: self.TogglePanel,
Description: self.c.Tr.ToggleStagingView,
Tooltip: self.c.Tr.ToggleStagingViewTooltip,
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
Handler: self.TogglePanel,
Description: self.c.Tr.ToggleStagingView,
Tooltip: self.c.Tr.ToggleStagingViewTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Main.EditSelectHunk),

View File

@ -38,6 +38,7 @@ func (self *StashController) GetKeybindings(opts types.KeybindingsOpts) []*types
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Apply,
Tooltip: self.c.Tr.StashApplyTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Stash.PopStash),
@ -45,6 +46,7 @@ func (self *StashController) GetKeybindings(opts types.KeybindingsOpts) []*types
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Pop,
Tooltip: self.c.Tr.StashPopTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.Remove),
@ -52,6 +54,7 @@ func (self *StashController) GetKeybindings(opts types.KeybindingsOpts) []*types
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Drop,
Tooltip: self.c.Tr.StashDropTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.New),

View File

@ -39,20 +39,23 @@ func (self *StatusController) GetKeybindings(opts types.KeybindingsOpts) []*type
Tooltip: self.c.Tr.OpenFileTooltip,
},
{
Key: opts.GetKey(opts.Config.Universal.Edit),
Handler: self.editConfig,
Description: self.c.Tr.EditConfig,
Tooltip: self.c.Tr.EditFileTooltip,
Key: opts.GetKey(opts.Config.Universal.Edit),
Handler: self.editConfig,
Description: self.c.Tr.EditConfig,
Tooltip: self.c.Tr.EditFileTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Status.CheckForUpdate),
Handler: self.handleCheckForUpdate,
Description: self.c.Tr.CheckForUpdate,
Key: opts.GetKey(opts.Config.Status.CheckForUpdate),
Handler: self.handleCheckForUpdate,
Description: self.c.Tr.CheckForUpdate,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Status.RecentRepos),
Handler: self.c.Helpers().Repos.CreateRecentReposMenu,
Description: self.c.Tr.SwitchRepo,
Key: opts.GetKey(opts.Config.Status.RecentRepos),
Handler: self.c.Helpers().Repos.CreateRecentReposMenu,
Description: self.c.Tr.SwitchRepo,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Status.AllBranchesLogGraph),

View File

@ -46,6 +46,7 @@ func (self *SubmodulesController) GetKeybindings(opts types.KeybindingsOpts) []*
Description: self.c.Tr.Enter,
Tooltip: utils.ResolvePlaceholderString(self.c.Tr.EnterSubmoduleTooltip,
map[string]string{"escape": keybindings.Label(opts.Config.Universal.Return)}),
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.Select),
@ -58,6 +59,7 @@ func (self *SubmodulesController) GetKeybindings(opts types.KeybindingsOpts) []*
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Remove,
Tooltip: self.c.Tr.RemoveSubmoduleTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Submodules.Update),
@ -65,11 +67,13 @@ func (self *SubmodulesController) GetKeybindings(opts types.KeybindingsOpts) []*
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Update,
Tooltip: self.c.Tr.SubmoduleUpdateTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.New),
Handler: self.add,
Description: self.c.Tr.NewSubmodule,
Key: opts.GetKey(opts.Config.Universal.New),
Handler: self.add,
Description: self.c.Tr.NewSubmodule,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.Edit),

View File

@ -39,12 +39,14 @@ func (self *TagsController) GetKeybindings(opts types.KeybindingsOpts) []*types.
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Checkout,
Tooltip: self.c.Tr.TagCheckoutTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.New),
Handler: self.create,
Description: self.c.Tr.NewTag,
Tooltip: self.c.Tr.NewTagTooltip,
Key: opts.GetKey(opts.Config.Universal.New),
Handler: self.create,
Description: self.c.Tr.NewTag,
Tooltip: self.c.Tr.NewTagTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.Remove),
@ -53,6 +55,7 @@ func (self *TagsController) GetKeybindings(opts types.KeybindingsOpts) []*types.
GetDisabledReason: self.require(self.singleItemSelected()),
Tooltip: self.c.Tr.TagDeleteTooltip,
OpensMenu: true,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Branches.PushTag),
@ -60,6 +63,7 @@ func (self *TagsController) GetKeybindings(opts types.KeybindingsOpts) []*types.
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.PushTag,
Tooltip: self.c.Tr.PushTagTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Commits.ViewResetOptions),
@ -67,6 +71,7 @@ func (self *TagsController) GetKeybindings(opts types.KeybindingsOpts) []*types.
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Reset,
Tooltip: self.c.Tr.ResetTooltip,
DisplayOnScreen: true,
OpensMenu: true,
},
}

View File

@ -37,9 +37,10 @@ func NewWorktreesController(
func (self *WorktreesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
bindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.New),
Handler: self.add,
Description: self.c.Tr.NewWorktree,
Key: opts.GetKey(opts.Config.Universal.New),
Handler: self.add,
Description: self.c.Tr.NewWorktree,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.Select),
@ -47,6 +48,7 @@ func (self *WorktreesController) GetKeybindings(opts types.KeybindingsOpts) []*t
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Switch,
Tooltip: self.c.Tr.SwitchToWorktreeTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.Confirm),
@ -65,6 +67,7 @@ func (self *WorktreesController) GetKeybindings(opts types.KeybindingsOpts) []*t
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Remove,
Tooltip: self.c.Tr.RemoveWorktreeTooltip,
DisplayOnScreen: true,
},
}

View File

@ -22,8 +22,8 @@ type ICommitFileTreeViewModel interface {
type CommitFileTreeViewModel struct {
sync.RWMutex
ICommitFileTree
types.IListCursor
ICommitFileTree
// this is e.g. the commit for which we're viewing the files
ref types.Ref
@ -37,7 +37,7 @@ var _ ICommitFileTreeViewModel = &CommitFileTreeViewModel{}
func NewCommitFileTreeViewModel(getFiles func() []*models.CommitFile, log *logrus.Entry, showTree bool) *CommitFileTreeViewModel {
fileTree := NewCommitFileTree(getFiles, log, showTree)
listCursor := traits.NewListCursor(fileTree)
listCursor := traits.NewListCursor(fileTree.Len)
return &CommitFileTreeViewModel{
ICommitFileTree: fileTree,
IListCursor: listCursor,

View File

@ -5,6 +5,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
"github.com/sirupsen/logrus"
)
@ -138,7 +139,8 @@ func (self *FileTree) GetAllItems() []*FileNode {
}
func (self *FileTree) Len() int {
return self.tree.Size(self.collapsedPaths) - 1 // ignoring root
// -1 because we're ignoring the root
return utils.Max(self.tree.Size(self.collapsedPaths)-1, 0)
}
func (self *FileTree) GetItem(index int) types.HasUrn {

View File

@ -21,15 +21,15 @@ type IFileTreeViewModel interface {
// after the files are refreshed
type FileTreeViewModel struct {
sync.RWMutex
IFileTree
types.IListCursor
IFileTree
}
var _ IFileTreeViewModel = &FileTreeViewModel{}
func NewFileTreeViewModel(getFiles func() []*models.File, log *logrus.Entry, showTree bool) *FileTreeViewModel {
fileTree := NewFileTree(getFiles, log, showTree)
listCursor := traits.NewListCursor(fileTree)
listCursor := traits.NewListCursor(fileTree.Len)
return &FileTreeViewModel{
IFileTree: fileTree,
IListCursor: listCursor,
@ -54,6 +54,10 @@ func (self *FileTreeViewModel) GetSelectedItemId() string {
}
func (self *FileTreeViewModel) GetSelectedItems() ([]*FileNode, int, int) {
if self.Len() == 0 {
return nil, 0, 0
}
startIdx, endIdx := self.GetSelectionRange()
nodes := []*FileNode{}

View File

@ -165,6 +165,8 @@ func (gui *Gui) layout(g *gocui.Gui) error {
return err
}
gui.renderContextOptionsMap()
outer:
for {
select {

View File

@ -4,9 +4,13 @@ import (
"fmt"
"strings"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/theme"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
@ -14,30 +18,89 @@ type OptionsMapMgr struct {
c *helpers.HelperCommon
}
func (gui *Gui) renderContextOptionsMap(c types.Context) {
func (gui *Gui) renderContextOptionsMap() {
// In demos, we render our own content to this view
if gui.integrationTest != nil && gui.integrationTest.IsDemo() {
return
}
mgr := OptionsMapMgr{c: gui.c}
mgr.renderContextOptionsMap(c)
mgr.renderContextOptionsMap()
}
// render the options available for the current context at the bottom of the screen
func (self *OptionsMapMgr) renderContextOptionsMap(c types.Context) {
bindingsToDisplay := lo.Filter(c.GetKeybindings(self.c.KeybindingsOpts()), func(binding *types.Binding, _ int) bool {
return binding.Display
// Render the options available for the current context at the bottom of the screen
// STYLE GUIDE: we use the default options fg color for most keybindings. We can
// only use a different color if we're in a specific mode where the user is likely
// to want to press that key. For example, when in cherry-picking mode, we
// want to prominently show the keybinding for pasting commits.
func (self *OptionsMapMgr) renderContextOptionsMap() {
currentContext := self.c.CurrentContext()
currentContextBindings := currentContext.GetKeybindings(self.c.KeybindingsOpts())
globalBindings := self.c.Contexts().Global.GetKeybindings(self.c.KeybindingsOpts())
allBindings := append(currentContextBindings, globalBindings...)
bindingsToDisplay := lo.Filter(allBindings, func(binding *types.Binding, _ int) bool {
return binding.DisplayOnScreen && !binding.IsDisabled()
})
var optionsMap []bindingInfo
if len(bindingsToDisplay) == 0 {
optionsMap = self.globalOptions()
} else {
optionsMap = lo.Map(bindingsToDisplay, func(binding *types.Binding, _ int) bindingInfo {
return bindingInfo{
key: keybindings.LabelFromKey(binding.Key),
description: binding.Description,
}
optionsMap := lo.Map(bindingsToDisplay, func(binding *types.Binding, _ int) bindingInfo {
displayStyle := theme.OptionsFgColor
if binding.DisplayStyle != nil {
displayStyle = *binding.DisplayStyle
}
description := binding.Description
if binding.ShortDescription != "" {
description = binding.ShortDescription
}
return bindingInfo{
key: keybindings.LabelFromKey(binding.Key),
description: description,
style: displayStyle,
}
})
// Mode-specific local keybindings
if currentContext.GetKey() == context.LOCAL_COMMITS_CONTEXT_KEY {
if self.c.Modes().CherryPicking.Active() {
optionsMap = utils.Prepend(optionsMap, bindingInfo{
key: keybindings.Label(self.c.KeybindingsOpts().Config.Commits.PasteCommits),
description: self.c.Tr.PasteCommits,
style: style.FgCyan,
})
}
if self.c.Model().BisectInfo.Started() {
optionsMap = utils.Prepend(optionsMap, bindingInfo{
key: keybindings.Label(self.c.KeybindingsOpts().Config.Commits.ViewBisectOptions),
description: self.c.Tr.ViewBisectOptions,
style: style.FgGreen,
})
}
}
// Mode-specific global keybindings
if self.c.Model().WorkingTreeStateAtLastCommitRefresh.IsRebasing() {
optionsMap = utils.Prepend(optionsMap, bindingInfo{
key: keybindings.Label(self.c.KeybindingsOpts().Config.Universal.CreateRebaseOptionsMenu),
description: self.c.Tr.ViewRebaseOptions,
style: style.FgYellow,
})
} else if self.c.Model().WorkingTreeStateAtLastCommitRefresh.IsMerging() {
optionsMap = utils.Prepend(optionsMap, bindingInfo{
key: keybindings.Label(self.c.KeybindingsOpts().Config.Universal.CreateRebaseOptionsMenu),
description: self.c.Tr.ViewMergeOptions,
style: style.FgYellow,
})
}
if self.c.Git().Patch.PatchBuilder.Active() {
optionsMap = utils.Prepend(optionsMap, bindingInfo{
key: keybindings.Label(self.c.KeybindingsOpts().Config.Universal.CreatePatchOptionsMenu),
description: self.c.Tr.ViewPatchOptions,
style: style.FgYellow,
})
}
@ -45,49 +108,41 @@ func (self *OptionsMapMgr) renderContextOptionsMap(c types.Context) {
}
func (self *OptionsMapMgr) formatBindingInfos(bindingInfos []bindingInfo) string {
return strings.Join(
lo.Map(bindingInfos, func(bindingInfo bindingInfo, _ int) string {
return fmt.Sprintf("%s: %s", bindingInfo.key, bindingInfo.description)
}),
", ")
width := self.c.Views().Options.Width() - 4 // -4 for the padding
var builder strings.Builder
ellipsis := "…"
separator := " | "
length := 0
for i, info := range bindingInfos {
plainText := fmt.Sprintf("%s: %s", info.description, info.key)
// Check if adding the next formatted string exceeds the available width
if i > 0 && length+len(separator)+len(plainText) > width {
builder.WriteString(theme.OptionsFgColor.Sprint(separator + ellipsis))
break
}
formatted := info.style.Sprintf(plainText)
if i > 0 {
builder.WriteString(theme.OptionsFgColor.Sprint(separator))
length += len(separator)
}
builder.WriteString(formatted)
length += len(plainText)
}
return builder.String()
}
func (self *OptionsMapMgr) renderOptions(options string) {
self.c.SetViewContent(self.c.Views().Options, options)
}
func (self *OptionsMapMgr) globalOptions() []bindingInfo {
keybindingConfig := self.c.UserConfig.Keybinding
return []bindingInfo{
{
key: fmt.Sprintf("%s/%s", keybindings.Label(keybindingConfig.Universal.ScrollUpMain), keybindings.Label(keybindingConfig.Universal.ScrollDownMain)),
description: self.c.Tr.Scroll,
},
{
key: keybindings.Label(keybindingConfig.Universal.Return),
description: self.c.Tr.Cancel,
},
{
key: keybindings.Label(keybindingConfig.Universal.Quit),
description: self.c.Tr.Quit,
},
{
key: keybindings.Label(keybindingConfig.Universal.OptionMenuAlt1),
description: self.c.Tr.Keybindings,
},
{
key: fmt.Sprintf("%s-%s", keybindings.Label(keybindingConfig.Universal.JumpToBlock[0]), keybindings.Label(keybindingConfig.Universal.JumpToBlock[len(keybindingConfig.Universal.JumpToBlock)-1])),
description: self.c.Tr.Jump,
},
{
key: fmt.Sprintf("%s/%s", keybindings.Label(keybindingConfig.Universal.ScrollLeft), keybindings.Label(keybindingConfig.Universal.ScrollRight)),
description: self.c.Tr.ScrollLeftRight,
},
}
}
type bindingInfo struct {
key string
description string
style style.TextStyle
}

View File

@ -11,21 +11,25 @@ type Key interface{} // FIXME: find out how to get `gocui.Key | rune`
// is only handled if the given view has focus, or handled globally if the view
// is ""
type Binding struct {
ViewName string
Handler func() error
Key Key
Modifier gocui.Modifier
Description string
ViewName string
Handler func() error
Key Key
Modifier gocui.Modifier
Description string
// If defined, this is used in place of Description when showing the keybinding
// in the options view at the bottom left of the screen.
ShortDescription string
Alternative string
Tag string // e.g. 'navigation'. Used for grouping things in the cheatsheet
OpensMenu bool
// If true, the keybinding will appear at the bottom of the screen. If
// the given view has no bindings with Display: true, the default keybindings
// will be displayed instead.
// TODO: implement this
Display bool
// If true, the keybinding will appear at the bottom of the screen.
// Even if set to true, the keybinding will not be displayed if it is currently
// disabled. We could instead display it with a strikethrough, but there's
// limited realestate to show all the keybindings we want, so we're hiding it instead.
DisplayOnScreen bool
// if unset, the binding will be displayed in the default color. Only applies to the keybinding
// on-screen, not in the keybindings menu.
DisplayStyle *style.TextStyle
// to be displayed if the keybinding is highlighted from within a menu
@ -39,6 +43,10 @@ type Binding struct {
GetDisabledReason func() *DisabledReason
}
func (Binding *Binding) IsDisabled() bool {
return Binding.GetDisabledReason != nil && Binding.GetDisabledReason() != nil
}
// A guard is a decorator which checks something before executing a handler
// and potentially early-exits if some precondition hasn't been met.
type Guard func(func() error) func() error

View File

@ -94,7 +94,6 @@ func (gui *Gui) createAllViews() error {
(*mapping.viewPtr).SelBgColor = theme.GocuiSelectedLineBgColor
}
gui.Views.Options.FgColor = theme.OptionsColor
gui.Views.Options.Frame = false
gui.Views.SearchPrefix.BgColor = gocui.ColorDefault

View File

@ -62,3 +62,29 @@ func (self *Common) SelectPatchOption(matcher *TextMatcher) {
self.t.ExpectPopup().Menu().Title(Equals("Patch options")).Select(matcher).Confirm()
}
func (self *Common) ResetBisect() {
self.t.Views().Commits().
Focus().
Press(self.t.keys.Commits.ViewBisectOptions).
Tap(func() {
self.t.ExpectPopup().Menu().
Title(Equals("Bisect")).
Select(Contains("Reset bisect")).
Confirm()
self.t.ExpectPopup().Confirmation().
Title(Equals("Reset 'git bisect'")).
Content(Contains("Are you sure you want to reset 'git bisect'?")).
Confirm()
})
}
func (self *Common) ResetCustomPatch() {
self.t.GlobalPress(self.t.keys.Universal.CreatePatchOptionsMenu)
self.t.ExpectPopup().Menu().
Title(Equals("Patch options")).
Select(Contains("Reset patch")).
Confirm()
}

View File

@ -147,3 +147,7 @@ func (self *Views) Search() *ViewDriver {
func (self *Views) Tooltip() *ViewDriver {
return self.regularView("tooltip")
}
func (self *Views) Options() *ViewDriver {
return self.regularView("options")
}

View File

@ -269,6 +269,7 @@ var tests = []*components.IntegrationTest{
ui.Accordion,
ui.DoublePopup,
ui.EmptyMenu,
ui.ModeSpecificKeybindingSuggestions,
ui.OpenLinkFailure,
ui.RangeSelect,
ui.SwitchTabFromMenu,

View File

@ -0,0 +1,118 @@
package ui
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
"github.com/jesseduffield/lazygit/pkg/integration/tests/shared"
)
var ModeSpecificKeybindingSuggestions = NewIntegrationTest(NewIntegrationTestArgs{
Description: "When in various modes, we should corresponding keybinding suggestions onscreen",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
shell.CreateNCommits(2)
shell.NewBranch("base-branch")
shared.MergeConflictsSetup(shell)
shell.Checkout("base-branch")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
rebaseSuggestion := "View rebase options: m"
cherryPickSuggestion := "Paste (cherry-pick): V"
bisectSuggestion := "View bisect options: b"
customPatchSuggestion := "View custom patch options: <c-p>"
mergeSuggestion := "View merge options: m"
t.Views().Commits().
Focus().
Lines(
Contains("commit 02").IsSelected(),
Contains("commit 01"),
).
Tap(func() {
// These suggestions are mode-specific so are not shown by default
t.Views().Options().Content(
DoesNotContain(rebaseSuggestion).
DoesNotContain(mergeSuggestion).
DoesNotContain(cherryPickSuggestion).
DoesNotContain(bisectSuggestion).
DoesNotContain(customPatchSuggestion),
)
}).
// Start an interactive rebase
Press(keys.Universal.Edit).
Tap(func() {
// Confirm the rebase suggestion now appears
t.Views().Options().Content(Contains(rebaseSuggestion))
}).
Press(keys.Commits.CherryPickCopy).
Tap(func() {
// Confirm the cherry pick suggestion now appears
t.Views().Options().Content(Contains(cherryPickSuggestion))
// Importantly, we show multiple of these suggestions at once
t.Views().Options().Content(Contains(rebaseSuggestion))
}).
// Cancel the cherry pick
PressEscape().
Tap(func() {
t.Views().Options().Content(DoesNotContain(cherryPickSuggestion))
}).
// Cancel the rebase
Tap(func() {
t.Common().AbortRebase()
t.Views().Options().Content(DoesNotContain(rebaseSuggestion))
}).
Press(keys.Commits.ViewBisectOptions).
Tap(func() {
t.ExpectPopup().Menu().
Title(Equals("Bisect")).
Select(MatchesRegexp("Mark.* as bad")).
Confirm()
t.Views().Options().Content(Contains(bisectSuggestion))
// Cancel bisect
t.Common().ResetBisect()
t.Views().Options().Content(DoesNotContain(bisectSuggestion))
}).
// Enter commit files view
PressEnter()
t.Views().CommitFiles().
IsFocused().
// Add a commit file to the patch
Press(keys.Universal.Select).
Tap(func() {
t.Views().Options().Content(Contains(customPatchSuggestion))
t.Common().ResetCustomPatch()
t.Views().Options().Content(DoesNotContain(customPatchSuggestion))
})
// Test merge options suggestion
t.Views().Branches().
Focus().
NavigateToLine(Contains("first-change-branch")).
Press(keys.Universal.Select).
NavigateToLine(Contains("second-change-branch")).
Press(keys.Branches.MergeIntoCurrentBranch).
Tap(func() {
t.ExpectPopup().Confirmation().
Title(Equals("Merge")).
Content(Contains("Are you sure you want to merge")).
Confirm()
t.Common().AcknowledgeConflicts()
t.Views().Options().Content(Contains(mergeSuggestion))
t.Common().AbortMerge()
t.Views().Options().Content(DoesNotContain(mergeSuggestion))
})
},
})