mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-04-23 12:18:51 +02:00
It would crash when some keybindings are set to null, and the filter string is such that only those keybindings remain visible. The reason for the crash is that when inserting non-model items (menu section headers in this case) you specify a column to align them to. This works on the assumption that the number of columns is always the same. It can cope with the case that columns are removed because they are empty for all items; but it can't cope with the case that the getDisplayStrings function returns a lower number of columns. And this is what happened here: MenuViewModel.GetDisplayStrings would omit the keybinding column when none of the entries have a keybinding. This logic is unnecessary, the generic list rendering mechanism takes care of this, so removing this logic fixes the crash. We do have to make sure though that the column is really empty when there's no keybinding, so change the logic to use FgCyan only when there's a keybinding.
177 lines
4.9 KiB
Go
177 lines
4.9 KiB
Go
package context
|
|
|
|
import (
|
|
"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/utils"
|
|
"github.com/samber/lo"
|
|
)
|
|
|
|
type MenuContext struct {
|
|
c *ContextCommon
|
|
|
|
*MenuViewModel
|
|
*ListContextTrait
|
|
}
|
|
|
|
var _ types.IListContext = (*MenuContext)(nil)
|
|
|
|
func NewMenuContext(
|
|
c *ContextCommon,
|
|
) *MenuContext {
|
|
viewModel := NewMenuViewModel(c)
|
|
|
|
return &MenuContext{
|
|
c: c,
|
|
MenuViewModel: viewModel,
|
|
ListContextTrait: &ListContextTrait{
|
|
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
|
View: c.Views().Menu,
|
|
WindowName: "menu",
|
|
Key: "menu",
|
|
Kind: types.TEMPORARY_POPUP,
|
|
Focusable: true,
|
|
HasUncontrolledBounds: true,
|
|
})),
|
|
ListRenderer: ListRenderer{
|
|
list: viewModel,
|
|
getDisplayStrings: viewModel.GetDisplayStrings,
|
|
getColumnAlignments: func() []utils.Alignment { return viewModel.columnAlignment },
|
|
getNonModelItems: viewModel.GetNonModelItems,
|
|
},
|
|
c: c,
|
|
},
|
|
}
|
|
}
|
|
|
|
type MenuViewModel struct {
|
|
c *ContextCommon
|
|
menuItems []*types.MenuItem
|
|
columnAlignment []utils.Alignment
|
|
*FilteredListViewModel[*types.MenuItem]
|
|
}
|
|
|
|
func NewMenuViewModel(c *ContextCommon) *MenuViewModel {
|
|
self := &MenuViewModel{
|
|
menuItems: nil,
|
|
c: c,
|
|
}
|
|
|
|
self.FilteredListViewModel = NewFilteredListViewModel(
|
|
func() []*types.MenuItem { return self.menuItems },
|
|
func(item *types.MenuItem) []string { return item.LabelColumns },
|
|
)
|
|
|
|
return self
|
|
}
|
|
|
|
func (self *MenuViewModel) SetMenuItems(items []*types.MenuItem, columnAlignment []utils.Alignment) {
|
|
self.menuItems = items
|
|
self.columnAlignment = columnAlignment
|
|
}
|
|
|
|
// TODO: move into presentation package
|
|
func (self *MenuViewModel) GetDisplayStrings(_ int, _ int) [][]string {
|
|
menuItems := self.FilteredListViewModel.GetItems()
|
|
|
|
return lo.Map(menuItems, func(item *types.MenuItem, _ int) []string {
|
|
displayStrings := item.LabelColumns
|
|
if item.DisabledReason != nil {
|
|
displayStrings[0] = style.FgDefault.SetStrikethrough().Sprint(displayStrings[0])
|
|
}
|
|
|
|
keyLabel := ""
|
|
if item.Key != nil {
|
|
keyLabel = style.FgCyan.Sprint(keybindings.LabelFromKey(item.Key))
|
|
}
|
|
|
|
displayStrings = utils.Prepend(displayStrings, keyLabel)
|
|
return displayStrings
|
|
})
|
|
}
|
|
|
|
func (self *MenuViewModel) GetNonModelItems() []*NonModelItem {
|
|
// Don't display section headers when we are filtering, and the filter mode
|
|
// is fuzzy. The reason is that filtering changes the order of the items
|
|
// (they are sorted by best match), so all the sections would be messed up.
|
|
if self.FilteredListViewModel.IsFiltering() && self.c.UserConfig.Gui.UseFuzzySearch() {
|
|
return []*NonModelItem{}
|
|
}
|
|
|
|
result := []*NonModelItem{}
|
|
menuItems := self.FilteredListViewModel.GetItems()
|
|
var prevSection *types.MenuSection = nil
|
|
for i, menuItem := range menuItems {
|
|
menuItem := menuItem
|
|
if menuItem.Section != nil && menuItem.Section != prevSection {
|
|
if prevSection != nil {
|
|
result = append(result, &NonModelItem{
|
|
Index: i,
|
|
Column: 1,
|
|
Content: "",
|
|
})
|
|
}
|
|
|
|
result = append(result, &NonModelItem{
|
|
Index: i,
|
|
Column: 1,
|
|
Content: style.FgGreen.SetBold().Sprintf("--- %s ---", menuItem.Section.Title),
|
|
})
|
|
prevSection = menuItem.Section
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (self *MenuContext) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
|
basicBindings := self.ListContextTrait.GetKeybindings(opts)
|
|
menuItemsWithKeys := lo.Filter(self.menuItems, func(item *types.MenuItem, _ int) bool {
|
|
return item.Key != nil
|
|
})
|
|
|
|
menuItemBindings := lo.Map(menuItemsWithKeys, func(item *types.MenuItem, _ int) *types.Binding {
|
|
return &types.Binding{
|
|
Key: item.Key,
|
|
Handler: func() error { return self.OnMenuPress(item) },
|
|
}
|
|
})
|
|
|
|
// appending because that means the menu item bindings have lower precedence.
|
|
// So if a basic binding is to escape from the menu, we want that to still be
|
|
// what happens when you press escape. This matters when we're showing the menu
|
|
// for all keybindings of say the files context.
|
|
return append(basicBindings, menuItemBindings...)
|
|
}
|
|
|
|
func (self *MenuContext) OnMenuPress(selectedItem *types.MenuItem) error {
|
|
if selectedItem != nil && selectedItem.DisabledReason != nil {
|
|
if selectedItem.DisabledReason.ShowErrorInPanel {
|
|
return self.c.ErrorMsg(selectedItem.DisabledReason.Text)
|
|
}
|
|
|
|
self.c.ErrorToast(self.c.Tr.DisabledMenuItemPrefix + selectedItem.DisabledReason.Text)
|
|
return nil
|
|
}
|
|
|
|
if err := self.c.PopContext(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if selectedItem == nil {
|
|
return nil
|
|
}
|
|
|
|
if err := selectedItem.OnPress(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// There is currently no need to use range-select in a menu so we're disabling it.
|
|
func (self *MenuContext) RangeSelectEnabled() bool {
|
|
return false
|
|
}
|