1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-09-12 03:51:51 +03:00
Files
lazygit/pkg/gui/options_map.go
Stefan Haller 4961c4b678 Avoid duplicate key bindings in options map
Sometimes there is a local and a global keybinding for the same key; if both are
configured to be shown in the options map, we should only show the local one
because it takes precedence. This happens for example for <esc> in a popup, or
for <esc> in the focused main view.

Note that this is also a problem in the keybindings menu, and we don't solve
that here.
2025-08-14 17:59:38 +02:00

147 lines
4.6 KiB
Go

package gui
import (
"fmt"
"strings"
"github.com/jesseduffield/generics/set"
"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"
)
type OptionsMapMgr struct {
c *helpers.HelperCommon
}
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()
}
// 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.Context().Current()
currentContextBindings := currentContext.GetKeybindings(self.c.KeybindingsOpts())
globalBindings := self.c.Contexts().Global.GetKeybindings(self.c.KeybindingsOpts())
currentContextKeys := set.NewFromSlice(
lo.Map(currentContextBindings, func(binding *types.Binding, _ int) types.Key {
return binding.Key
}))
allBindings := append(currentContextBindings, lo.Filter(globalBindings, func(b *types.Binding, _ int) bool {
return !currentContextKeys.Includes(b.Key)
})...)
bindingsToDisplay := lo.Filter(allBindings, func(binding *types.Binding, _ int) bool {
return binding.DisplayOnScreen && !binding.IsDisabled()
})
optionsMap := lo.Map(bindingsToDisplay, func(binding *types.Binding, _ int) bindingInfo {
displayStyle := theme.OptionsFgColor
if binding.DisplayStyle != nil {
displayStyle = *binding.DisplayStyle
}
return bindingInfo{
key: keybindings.LabelFromKey(binding.Key),
description: binding.GetShortDescription(),
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 state := self.c.Model().WorkingTreeStateAtLastCommitRefresh; state.Any() {
optionsMap = utils.Prepend(optionsMap, bindingInfo{
key: keybindings.Label(self.c.KeybindingsOpts().Config.Universal.CreateRebaseOptionsMenu),
description: state.OptionsMapTitle(self.c.Tr),
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,
})
}
self.renderOptions(self.formatBindingInfos(optionsMap))
}
func (self *OptionsMapMgr) formatBindingInfos(bindingInfos []bindingInfo) string {
width := self.c.Views().Options.InnerWidth() - 2 // -2 for some 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
textLen := utils.StringWidth(plainText)
if i > 0 && length+len(separator)+textLen > 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 += textLen
}
return builder.String()
}
func (self *OptionsMapMgr) renderOptions(options string) {
self.c.SetViewContent(self.c.Views().Options, options)
}
type bindingInfo struct {
key string
description string
style style.TextStyle
}