mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-05-27 23:08:02 +02:00
Show mode-specific keybinding suggestions
As part of making lazygit more discoverable, there are certain keys which you almost certainly need to press when you're in a given mode e.g. 'v' to paste commits when cherry-picking. This commit prominently shows these keybinding suggestions alongside the others in the option view. I'm using the same colours for these keybindings as is associated with the mode elsewhere e.g. yellow for rebasing and cyan for cherry-picking. The cherry-picking one is a bit weird because we also use cyan text to show loaders and app status at the bottom left so it may be confusing, but I haven't personally found it awkward from having tested it out myself. Previously we would render these options whenever a new context was activated, but now that we need to re-render options whenever a mode changes, I'm instead rendering them on each screen re-render (i.e. in the layout function). Given how cheap it is to render this text, I think it's fine performance-wise.
This commit is contained in:
parent
c07b3fad64
commit
0f9d9e13d1
@ -12,3 +12,11 @@ const (
|
|||||||
REBASE_MODE_REBASING
|
REBASE_MODE_REBASING
|
||||||
REBASE_MODE_MERGING
|
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
|
||||||
|
}
|
||||||
|
@ -245,8 +245,6 @@ func (self *ContextMgr) ActivateContext(c types.Context, opts types.OnFocusOpts)
|
|||||||
|
|
||||||
self.gui.c.GocuiGui().Cursor = v.Editable
|
self.gui.c.GocuiGui().Cursor = v.Editable
|
||||||
|
|
||||||
self.gui.renderContextOptionsMap(c)
|
|
||||||
|
|
||||||
if err := c.HandleFocus(opts); err != nil {
|
if err := c.HandleFocus(opts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -24,16 +24,16 @@ func NewConfirmationController(
|
|||||||
func (self *ConfirmationController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
func (self *ConfirmationController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
||||||
bindings := []*types.Binding{
|
bindings := []*types.Binding{
|
||||||
{
|
{
|
||||||
Key: opts.GetKey(opts.Config.Universal.Confirm),
|
Key: opts.GetKey(opts.Config.Universal.Confirm),
|
||||||
Handler: func() error { return self.context().State.OnConfirm() },
|
Handler: func() error { return self.context().State.OnConfirm() },
|
||||||
Description: self.c.Tr.Confirm,
|
Description: self.c.Tr.Confirm,
|
||||||
Display: true,
|
DisplayOnScreen: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Key: opts.GetKey(opts.Config.Universal.Return),
|
Key: opts.GetKey(opts.Config.Universal.Return),
|
||||||
Handler: func() error { return self.context().State.OnClose() },
|
Handler: func() error { return self.context().State.OnClose() },
|
||||||
Description: self.c.Tr.CloseCancel,
|
Description: self.c.Tr.CloseCancel,
|
||||||
Display: true,
|
DisplayOnScreen: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
|
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
|
||||||
|
@ -72,6 +72,7 @@ func (self *GlobalController) GetKeybindings(opts types.KeybindingsOpts) []*type
|
|||||||
Description: self.c.Tr.OpenKeybindingsMenu,
|
Description: self.c.Tr.OpenKeybindingsMenu,
|
||||||
Handler: self.createOptionsMenu,
|
Handler: self.createOptionsMenu,
|
||||||
ShortDescription: self.c.Tr.Keybindings,
|
ShortDescription: self.c.Tr.Keybindings,
|
||||||
|
DisplayOnScreen: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "",
|
ViewName: "",
|
||||||
@ -112,10 +113,11 @@ func (self *GlobalController) GetKeybindings(opts types.KeybindingsOpts) []*type
|
|||||||
Handler: self.quitWithoutChangingDirectory,
|
Handler: self.quitWithoutChangingDirectory,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Key: opts.GetKey(opts.Config.Universal.Return),
|
Key: opts.GetKey(opts.Config.Universal.Return),
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: self.escape,
|
Handler: self.escape,
|
||||||
Description: self.c.Tr.Cancel,
|
Description: self.c.Tr.Cancel,
|
||||||
|
DisplayOnScreen: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Key: opts.GetKey(opts.Config.Universal.ToggleWhitespaceInDiffView),
|
Key: opts.GetKey(opts.Config.Universal.ToggleWhitespaceInDiffView),
|
||||||
|
@ -42,13 +42,13 @@ func (self *MenuController) GetKeybindings(opts types.KeybindingsOpts) []*types.
|
|||||||
Handler: self.withItem(self.press),
|
Handler: self.withItem(self.press),
|
||||||
GetDisabledReason: self.require(self.singleItemSelected()),
|
GetDisabledReason: self.require(self.singleItemSelected()),
|
||||||
Description: self.c.Tr.Execute,
|
Description: self.c.Tr.Execute,
|
||||||
Display: true,
|
DisplayOnScreen: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Key: opts.GetKey(opts.Config.Universal.Return),
|
Key: opts.GetKey(opts.Config.Universal.Return),
|
||||||
Handler: self.close,
|
Handler: self.close,
|
||||||
Description: self.c.Tr.Close,
|
Description: self.c.Tr.Close,
|
||||||
Display: true,
|
DisplayOnScreen: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,30 +48,30 @@ func (self *MergeConflictsController) GetKeybindings(opts types.KeybindingsOpts)
|
|||||||
Description: self.c.Tr.SelectNextHunk,
|
Description: self.c.Tr.SelectNextHunk,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Key: opts.GetKey(opts.Config.Universal.PrevBlock),
|
Key: opts.GetKey(opts.Config.Universal.PrevBlock),
|
||||||
Handler: self.withRenderAndFocus(self.PrevConflict),
|
Handler: self.withRenderAndFocus(self.PrevConflict),
|
||||||
Description: self.c.Tr.PrevConflict,
|
Description: self.c.Tr.PrevConflict,
|
||||||
Display: true,
|
DisplayOnScreen: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Key: opts.GetKey(opts.Config.Universal.NextBlock),
|
Key: opts.GetKey(opts.Config.Universal.NextBlock),
|
||||||
Handler: self.withRenderAndFocus(self.NextConflict),
|
Handler: self.withRenderAndFocus(self.NextConflict),
|
||||||
Description: self.c.Tr.NextConflict,
|
Description: self.c.Tr.NextConflict,
|
||||||
Display: true,
|
DisplayOnScreen: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Key: opts.GetKey(opts.Config.Universal.Undo),
|
Key: opts.GetKey(opts.Config.Universal.Undo),
|
||||||
Handler: self.withRenderAndFocus(self.HandleUndo),
|
Handler: self.withRenderAndFocus(self.HandleUndo),
|
||||||
Description: self.c.Tr.Undo,
|
Description: self.c.Tr.Undo,
|
||||||
Tooltip: self.c.Tr.UndoMergeResolveTooltip,
|
Tooltip: self.c.Tr.UndoMergeResolveTooltip,
|
||||||
Display: true,
|
DisplayOnScreen: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Key: opts.GetKey(opts.Config.Universal.Edit),
|
Key: opts.GetKey(opts.Config.Universal.Edit),
|
||||||
Handler: self.HandleEditFile,
|
Handler: self.HandleEditFile,
|
||||||
Description: self.c.Tr.EditFile,
|
Description: self.c.Tr.EditFile,
|
||||||
Tooltip: self.c.Tr.EditFileTooltip,
|
Tooltip: self.c.Tr.EditFileTooltip,
|
||||||
Display: true,
|
DisplayOnScreen: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Key: opts.GetKey(opts.Config.Universal.OpenFile),
|
Key: opts.GetKey(opts.Config.Universal.OpenFile),
|
||||||
@ -108,11 +108,11 @@ func (self *MergeConflictsController) GetKeybindings(opts types.KeybindingsOpts)
|
|||||||
Tag: "navigation",
|
Tag: "navigation",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Key: opts.GetKey(opts.Config.Files.OpenMergeTool),
|
Key: opts.GetKey(opts.Config.Files.OpenMergeTool),
|
||||||
Handler: self.c.Helpers().WorkingTree.OpenMergeTool,
|
Handler: self.c.Helpers().WorkingTree.OpenMergeTool,
|
||||||
Description: self.c.Tr.OpenMergeTool,
|
Description: self.c.Tr.OpenMergeTool,
|
||||||
Tooltip: self.c.Tr.OpenMergeToolTooltip,
|
Tooltip: self.c.Tr.OpenMergeToolTooltip,
|
||||||
Display: true,
|
DisplayOnScreen: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Key: opts.GetKey(opts.Config.Universal.Return),
|
Key: opts.GetKey(opts.Config.Universal.Return),
|
||||||
|
@ -165,6 +165,8 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gui.renderContextOptionsMap()
|
||||||
|
|
||||||
outer:
|
outer:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
@ -4,9 +4,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
"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/gui/types"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -14,30 +18,89 @@ type OptionsMapMgr struct {
|
|||||||
c *helpers.HelperCommon
|
c *helpers.HelperCommon
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) renderContextOptionsMap(c types.Context) {
|
func (gui *Gui) renderContextOptionsMap() {
|
||||||
// In demos, we render our own content to this view
|
// In demos, we render our own content to this view
|
||||||
if gui.integrationTest != nil && gui.integrationTest.IsDemo() {
|
if gui.integrationTest != nil && gui.integrationTest.IsDemo() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
mgr := OptionsMapMgr{c: gui.c}
|
mgr := OptionsMapMgr{c: gui.c}
|
||||||
mgr.renderContextOptionsMap(c)
|
mgr.renderContextOptionsMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
// render the options available for the current context at the bottom of the screen
|
// Render the options available for the current context at the bottom of the screen
|
||||||
func (self *OptionsMapMgr) renderContextOptionsMap(c types.Context) {
|
// STYLE GUIDE: we use the default options fg color for most keybindings. We can
|
||||||
bindingsToDisplay := lo.Filter(c.GetKeybindings(self.c.KeybindingsOpts()), func(binding *types.Binding, _ int) bool {
|
// only use a different color if we're in a specific mode where the user is likely
|
||||||
return binding.Display
|
// 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
|
optionsMap := lo.Map(bindingsToDisplay, func(binding *types.Binding, _ int) bindingInfo {
|
||||||
if len(bindingsToDisplay) == 0 {
|
displayStyle := theme.OptionsFgColor
|
||||||
optionsMap = self.globalOptions()
|
if binding.DisplayStyle != nil {
|
||||||
} else {
|
displayStyle = *binding.DisplayStyle
|
||||||
optionsMap = lo.Map(bindingsToDisplay, func(binding *types.Binding, _ int) bindingInfo {
|
}
|
||||||
return bindingInfo{
|
|
||||||
key: keybindings.LabelFromKey(binding.Key),
|
description := binding.Description
|
||||||
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 {
|
func (self *OptionsMapMgr) formatBindingInfos(bindingInfos []bindingInfo) string {
|
||||||
return strings.Join(
|
width := self.c.Views().Options.Width() - 4 // -4 for the padding
|
||||||
lo.Map(bindingInfos, func(bindingInfo bindingInfo, _ int) string {
|
var builder strings.Builder
|
||||||
return fmt.Sprintf("%s: %s", bindingInfo.key, bindingInfo.description)
|
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) {
|
func (self *OptionsMapMgr) renderOptions(options string) {
|
||||||
self.c.SetViewContent(self.c.Views().Options, options)
|
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 {
|
type bindingInfo struct {
|
||||||
key string
|
key string
|
||||||
description string
|
description string
|
||||||
|
style style.TextStyle
|
||||||
}
|
}
|
||||||
|
@ -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 only handled if the given view has focus, or handled globally if the view
|
||||||
// is ""
|
// is ""
|
||||||
type Binding struct {
|
type Binding struct {
|
||||||
ViewName string
|
ViewName string
|
||||||
Handler func() error
|
Handler func() error
|
||||||
Key Key
|
Key Key
|
||||||
Modifier gocui.Modifier
|
Modifier gocui.Modifier
|
||||||
Description string
|
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
|
ShortDescription string
|
||||||
Alternative string
|
Alternative string
|
||||||
Tag string // e.g. 'navigation'. Used for grouping things in the cheatsheet
|
Tag string // e.g. 'navigation'. Used for grouping things in the cheatsheet
|
||||||
OpensMenu bool
|
OpensMenu bool
|
||||||
|
|
||||||
// If true, the keybinding will appear at the bottom of the screen. If
|
// If true, the keybinding will appear at the bottom of the screen.
|
||||||
// the given view has no bindings with Display: true, the default keybindings
|
// Even if set to true, the keybinding will not be displayed if it is currently
|
||||||
// will be displayed instead.
|
// disabled. We could instead display it with a strikethrough, but there's
|
||||||
// TODO: implement this
|
// limited realestate to show all the keybindings we want, so we're hiding it instead.
|
||||||
Display bool
|
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
|
DisplayStyle *style.TextStyle
|
||||||
|
|
||||||
// to be displayed if the keybinding is highlighted from within a menu
|
// to be displayed if the keybinding is highlighted from within a menu
|
||||||
@ -39,6 +43,10 @@ type Binding struct {
|
|||||||
GetDisabledReason func() *DisabledReason
|
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
|
// A guard is a decorator which checks something before executing a handler
|
||||||
// and potentially early-exits if some precondition hasn't been met.
|
// and potentially early-exits if some precondition hasn't been met.
|
||||||
type Guard func(func() error) func() error
|
type Guard func(func() error) func() error
|
||||||
|
@ -94,7 +94,6 @@ func (gui *Gui) createAllViews() error {
|
|||||||
(*mapping.viewPtr).SelBgColor = theme.GocuiSelectedLineBgColor
|
(*mapping.viewPtr).SelBgColor = theme.GocuiSelectedLineBgColor
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.Views.Options.FgColor = theme.OptionsColor
|
|
||||||
gui.Views.Options.Frame = false
|
gui.Views.Options.Frame = false
|
||||||
|
|
||||||
gui.Views.SearchPrefix.BgColor = gocui.ColorDefault
|
gui.Views.SearchPrefix.BgColor = gocui.ColorDefault
|
||||||
|
@ -62,3 +62,29 @@ func (self *Common) SelectPatchOption(matcher *TextMatcher) {
|
|||||||
|
|
||||||
self.t.ExpectPopup().Menu().Title(Equals("Patch options")).Select(matcher).Confirm()
|
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()
|
||||||
|
}
|
||||||
|
@ -147,3 +147,7 @@ func (self *Views) Search() *ViewDriver {
|
|||||||
func (self *Views) Tooltip() *ViewDriver {
|
func (self *Views) Tooltip() *ViewDriver {
|
||||||
return self.regularView("tooltip")
|
return self.regularView("tooltip")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *Views) Options() *ViewDriver {
|
||||||
|
return self.regularView("options")
|
||||||
|
}
|
||||||
|
@ -269,6 +269,7 @@ var tests = []*components.IntegrationTest{
|
|||||||
ui.Accordion,
|
ui.Accordion,
|
||||||
ui.DoublePopup,
|
ui.DoublePopup,
|
||||||
ui.EmptyMenu,
|
ui.EmptyMenu,
|
||||||
|
ui.ModeSpecificKeybindingSuggestions,
|
||||||
ui.OpenLinkFailure,
|
ui.OpenLinkFailure,
|
||||||
ui.RangeSelect,
|
ui.RangeSelect,
|
||||||
ui.SwitchTabFromMenu,
|
ui.SwitchTabFromMenu,
|
||||||
|
118
pkg/integration/tests/ui/mode_specific_keybinding_suggestions.go
Normal file
118
pkg/integration/tests/ui/mode_specific_keybinding_suggestions.go
Normal 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))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
Loading…
x
Reference in New Issue
Block a user