1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-05-13 22:17:05 +02:00

add tooltip view for showing menu item descriptions

This commit is contained in:
Jesse Duffield 2022-05-08 12:46:48 +10:00
parent 517e9445df
commit f257740ea7
14 changed files with 85 additions and 45 deletions

View File

@ -91,8 +91,7 @@ func (gui *Gui) getConfirmationPanelDimensions(wrap bool, prompt string) (int, i
return gui.getConfirmationPanelDimensionsAux(panelWidth, panelHeight) return gui.getConfirmationPanelDimensionsAux(panelWidth, panelHeight)
} }
func (gui *Gui) getConfirmationPanelDimensionsForContentHeight(contentHeight int) (int, int, int, int) { func (gui *Gui) getConfirmationPanelDimensionsForContentHeight(panelWidth, contentHeight int) (int, int, int, int) {
panelWidth := gui.getConfirmationPanelWidth()
return gui.getConfirmationPanelDimensionsAux(panelWidth, contentHeight) return gui.getConfirmationPanelDimensionsAux(panelWidth, contentHeight)
} }

View File

@ -19,15 +19,18 @@ var _ types.IListContext = (*MenuContext)(nil)
func NewMenuContext( func NewMenuContext(
view *gocui.View, view *gocui.View,
onFocus func(...types.OnFocusOpts) error,
onRenderToMain func(...types.OnFocusOpts) error,
onFocusLost func() error,
c *types.HelperCommon, c *types.HelperCommon,
getOptionsMap func() map[string]string, getOptionsMap func() map[string]string,
renderToDescriptionView func(string),
) *MenuContext { ) *MenuContext {
viewModel := NewMenuViewModel() viewModel := NewMenuViewModel()
onFocus := func(...types.OnFocusOpts) error {
selectedMenuItem := viewModel.GetSelected()
renderToDescriptionView(selectedMenuItem.Tooltip)
return nil
}
return &MenuContext{ return &MenuContext{
MenuViewModel: viewModel, MenuViewModel: viewModel,
ListContextTrait: &ListContextTrait{ ListContextTrait: &ListContextTrait{
@ -38,9 +41,7 @@ func NewMenuContext(
OnGetOptionsMap: getOptionsMap, OnGetOptionsMap: getOptionsMap,
Focusable: true, Focusable: true,
}), ContextCallbackOpts{ }), ContextCallbackOpts{
OnFocus: onFocus, OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}), }),
getDisplayStrings: viewModel.GetDisplayStrings, getDisplayStrings: viewModel.GetDisplayStrings,
list: viewModel, list: viewModel,

View File

@ -55,11 +55,13 @@ func (self *UndoController) GetKeybindings(opts types.KeybindingsOpts) []*types.
Key: opts.GetKey(opts.Config.Universal.Undo), Key: opts.GetKey(opts.Config.Universal.Undo),
Handler: self.reflogUndo, Handler: self.reflogUndo,
Description: self.c.Tr.LcUndoReflog, Description: self.c.Tr.LcUndoReflog,
Tooltip: self.c.Tr.UndoTooltip,
}, },
{ {
Key: opts.GetKey(opts.Config.Universal.Redo), Key: opts.GetKey(opts.Config.Universal.Redo),
Handler: self.reflogRedo, Handler: self.reflogRedo,
Description: self.c.Tr.LcRedoReflog, Description: self.c.Tr.LcRedoReflog,
Tooltip: self.c.Tr.RedoTooltip,
}, },
} }

View File

@ -31,7 +31,8 @@ func (self *FilesController) createResetMenu() error {
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}}) return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
}, },
Key: 'D', Key: 'D',
Tooltip: self.c.Tr.NukeDescription,
}, },
{ {
DisplayStrings: []string{ DisplayStrings: []string{

View File

@ -365,25 +365,6 @@ func (gui *Gui) syncViewContexts() {
} }
} }
func initialViewContextMapping(contextTree *context.ContextTree) map[string]types.Context {
return map[string]types.Context{
"status": contextTree.Status,
"files": contextTree.Files,
"branches": contextTree.Branches,
"remoteBranches": contextTree.RemoteBranches,
"commits": contextTree.LocalCommits,
"commitFiles": contextTree.CommitFiles,
"subCommits": contextTree.SubCommits,
"stash": contextTree.Stash,
"menu": contextTree.Menu,
"confirmation": contextTree.Confirmation,
"commitMessage": contextTree.CommitMessage,
"main": contextTree.Normal,
"secondary": contextTree.Normal,
"extras": contextTree.CommandLog,
}
}
// for now the split view will always be on // for now the split view will always be on
// NewGui builds a new gui handler // NewGui builds a new gui handler
func NewGui( func NewGui(

View File

@ -88,6 +88,8 @@ func (gui *Gui) layout(g *gocui.Gui) error {
minimumWidth := 10 minimumWidth := 10
gui.Views.Limit.Visible = height < minimumHeight || width < minimumWidth gui.Views.Limit.Visible = height < minimumHeight || width < minimumWidth
gui.Views.Tooltip.Visible = gui.Views.Menu.Visible && gui.Views.Tooltip.Buffer() != ""
for _, context := range gui.TransientContexts() { for _, context := range gui.TransientContexts() {
view, err := gui.g.View(context.GetViewName()) view, err := gui.g.View(context.GetViewName())
if err != nil && err.Error() != UNKNOWN_VIEW_ERROR_MSG { if err != nil && err.Error() != UNKNOWN_VIEW_ERROR_MSG {

View File

@ -15,11 +15,11 @@ import (
func (gui *Gui) menuListContext() *context.MenuContext { func (gui *Gui) menuListContext() *context.MenuContext {
return context.NewMenuContext( return context.NewMenuContext(
gui.Views.Menu, gui.Views.Menu,
nil,
nil,
nil,
gui.c, gui.c,
gui.getMenuOptions, gui.getMenuOptions,
func(content string) {
gui.Views.Tooltip.SetContent(content)
},
) )
} }

View File

@ -36,16 +36,18 @@ func (gui *Gui) createMenu(opts types.CreateMenuOptions) error {
} }
} }
x0, y0, x1, y1 := gui.getConfirmationPanelDimensionsForContentHeight(len(opts.Items)) gui.State.Contexts.Menu.SetMenuItems(opts.Items)
menuView, _ := gui.g.SetView("menu", x0, y0, x1, y1, 0) gui.State.Contexts.Menu.SetSelectedLineIdx(0)
menuView.Title = opts.Title
menuView.FgColor = theme.GocuiDefaultTextColor gui.Views.Menu.Title = opts.Title
menuView.SetOnSelectItem(gui.onSelectItemWrapper(func(selectedLine int) error { gui.Views.Menu.FgColor = theme.GocuiDefaultTextColor
gui.Views.Menu.SetOnSelectItem(gui.onSelectItemWrapper(func(selectedLine int) error {
return nil return nil
})) }))
gui.State.Contexts.Menu.SetMenuItems(opts.Items) gui.Views.Tooltip.Wrap = true
gui.State.Contexts.Menu.SetSelectedLineIdx(0) gui.Views.Tooltip.FgColor = theme.GocuiDefaultTextColor
gui.Views.Tooltip.Visible = true
// resetting keybindings so that the menu-specific keybindings are registered // resetting keybindings so that the menu-specific keybindings are registered
if err := gui.resetKeybindings(); err != nil { if err := gui.resetKeybindings(); err != nil {
@ -57,3 +59,16 @@ func (gui *Gui) createMenu(opts types.CreateMenuOptions) error {
// TODO: ensure that if we're opened a menu from within a menu that it renders correctly // TODO: ensure that if we're opened a menu from within a menu that it renders correctly
return gui.c.PushContext(gui.State.Contexts.Menu) return gui.c.PushContext(gui.State.Contexts.Menu)
} }
func (gui *Gui) resizeMenu() {
itemCount := gui.State.Contexts.Menu.GetList().Len()
offset := 3
panelWidth := gui.getConfirmationPanelWidth()
x0, y0, x1, y1 := gui.getConfirmationPanelDimensionsForContentHeight(panelWidth, itemCount+offset)
menuBottom := y1 - offset
_, _ = gui.g.SetView("menu", x0, y0, x1, menuBottom, 0)
tooltipTop := menuBottom + 1
tooltipHeight := gui.getMessageHeight(true, gui.State.Contexts.Menu.GetSelected().Tooltip, panelWidth) + 2 // plus 2 for the frame
_, _ = gui.g.SetView("tooltip", x0, tooltipTop, x1, tooltipTop+tooltipHeight-1, 0)
}

View File

@ -73,7 +73,8 @@ func (gui *Gui) handleCreateOptionsMenu() error {
return binding.Handler() return binding.Handler()
}, },
Key: binding.Key, Key: binding.Key,
Tooltip: binding.Tooltip,
} }
}) })

View File

@ -118,6 +118,9 @@ type MenuItem struct {
// if Key is defined it allows the user to press the key to invoke the menu // if Key is defined it allows the user to press the key to invoke the menu
// item, as opposed to having to navigate to it // item, as opposed to having to navigate to it
Key Key Key Key
// the tooltip will be displayed upon highlighting the menu item
Tooltip string
} }
type Model struct { type Model struct {

View File

@ -17,6 +17,9 @@ type Binding struct {
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
// to be displayed if the keybinding is highlighted from within a menu
Tooltip string
} }
// A guard is a decorator which checks something before executing a handler // A guard is a decorator which checks something before executing a handler

View File

@ -47,9 +47,13 @@ func (gui *Gui) resizeCurrentPopupPanel() error {
if v == nil { if v == nil {
return nil return nil
} }
if gui.isPopupPanel(v.Name()) {
if v == gui.Views.Menu {
gui.resizeMenu()
} else if gui.isPopupPanel(v.Name()) {
return gui.resizePopupPanel(v, v.Buffer()) return gui.resizePopupPanel(v, v.Buffer())
} }
return nil return nil
} }

View File

@ -3,6 +3,8 @@ package gui
import ( import (
"github.com/jesseduffield/generics/slices" "github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/theme" "github.com/jesseduffield/lazygit/pkg/theme"
) )
@ -27,7 +29,7 @@ type Views struct {
SearchPrefix *gocui.View SearchPrefix *gocui.View
Limit *gocui.View Limit *gocui.View
Suggestions *gocui.View Suggestions *gocui.View
Description *gocui.View Tooltip *gocui.View
Extras *gocui.View Extras *gocui.View
} }
@ -71,7 +73,7 @@ func (gui *Gui) orderedViewNameMappings() []viewNameMapping {
{viewPtr: &gui.Views.Menu, name: "menu"}, {viewPtr: &gui.Views.Menu, name: "menu"},
{viewPtr: &gui.Views.Suggestions, name: "suggestions"}, {viewPtr: &gui.Views.Suggestions, name: "suggestions"},
{viewPtr: &gui.Views.Confirmation, name: "confirmation"}, {viewPtr: &gui.Views.Confirmation, name: "confirmation"},
{viewPtr: &gui.Views.Description, name: "description"}, {viewPtr: &gui.Views.Tooltip, name: "tooltip"},
// this guy will cover everything else when it appears // this guy will cover everything else when it appears
{viewPtr: &gui.Views.Limit, name: "limit"}, {viewPtr: &gui.Views.Limit, name: "limit"},
@ -105,7 +107,6 @@ func (gui *Gui) controlledViews() []controlledView {
{viewName: "appStatus", windowName: "appStatus", frame: false}, {viewName: "appStatus", windowName: "appStatus", frame: false},
{viewName: "information", windowName: "information", frame: false}, {viewName: "information", windowName: "information", frame: false},
{viewName: "extras", windowName: "extras", frame: true}, {viewName: "extras", windowName: "extras", frame: true},
{viewName: "description", windowName: "description", frame: true},
{viewName: "limit", windowName: "limit", frame: true}, {viewName: "limit", windowName: "limit", frame: true},
} }
} }
@ -180,10 +181,12 @@ func (gui *Gui) createAllViews() error {
gui.Views.Suggestions.Visible = false gui.Views.Suggestions.Visible = false
gui.Views.Description.FgColor = theme.GocuiDefaultTextColor gui.Views.Tooltip.FgColor = theme.GocuiDefaultTextColor
gui.Views.Menu.Visible = false gui.Views.Menu.Visible = false
gui.Views.Tooltip.Visible = false
gui.Views.Information.BgColor = gocui.ColorDefault gui.Views.Information.BgColor = gocui.ColorDefault
gui.Views.Information.FgColor = gocui.ColorGreen gui.Views.Information.FgColor = gocui.ColorGreen
@ -194,3 +197,22 @@ func (gui *Gui) createAllViews() error {
return nil return nil
} }
func initialViewContextMapping(contextTree *context.ContextTree) map[string]types.Context {
return map[string]types.Context{
"status": contextTree.Status,
"files": contextTree.Files,
"branches": contextTree.Branches,
"remoteBranches": contextTree.RemoteBranches,
"commits": contextTree.LocalCommits,
"commitFiles": contextTree.CommitFiles,
"subCommits": contextTree.SubCommits,
"stash": contextTree.Stash,
"menu": contextTree.Menu,
"confirmation": contextTree.Confirmation,
"commitMessage": contextTree.CommitMessage,
"main": contextTree.Normal,
"secondary": contextTree.Normal,
"extras": contextTree.CommandLog,
}
}

View File

@ -108,6 +108,8 @@ type TranslationSet struct {
LcUndo string LcUndo string
LcUndoReflog string LcUndoReflog string
LcRedoReflog string LcRedoReflog string
UndoTooltip string
RedoTooltip string
LcPop string LcPop string
LcDrop string LcDrop string
LcApply string LcApply string
@ -486,6 +488,7 @@ type TranslationSet struct {
CheckoutPrompt string CheckoutPrompt string
HardResetAutostashPrompt string HardResetAutostashPrompt string
UpstreamGone string UpstreamGone string
NukeDescription string
Actions Actions Actions Actions
Bisect Bisect Bisect Bisect
} }
@ -719,6 +722,8 @@ func EnglishTranslationSet() TranslationSet {
LcUndo: "undo", LcUndo: "undo",
LcUndoReflog: "undo (via reflog) (experimental)", LcUndoReflog: "undo (via reflog) (experimental)",
LcRedoReflog: "redo (via reflog) (experimental)", LcRedoReflog: "redo (via reflog) (experimental)",
UndoTooltip: "The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration.",
RedoTooltip: "The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration.",
LcPop: "pop", LcPop: "pop",
LcDrop: "drop", LcDrop: "drop",
LcApply: "apply", LcApply: "apply",
@ -1098,6 +1103,7 @@ func EnglishTranslationSet() TranslationSet {
HardResetAutostashPrompt: "Are you sure you want to hard reset to '%s'? An auto-stash will be performed if necessary.", HardResetAutostashPrompt: "Are you sure you want to hard reset to '%s'? An auto-stash will be performed if necessary.",
CheckoutPrompt: "Are you sure you want to checkout '%s'?", CheckoutPrompt: "Are you sure you want to checkout '%s'?",
UpstreamGone: "(upstream gone)", UpstreamGone: "(upstream gone)",
NukeDescription: "If you want to make all the changes in the worktree go away, this is the way to do it. If there are dirty submodule changes this will stash those changes in the submodule(s).",
Actions: Actions{ Actions: Actions{
// TODO: combine this with the original keybinding descriptions (those are all in lowercase atm) // TODO: combine this with the original keybinding descriptions (those are all in lowercase atm)
CheckoutCommit: "Checkout commit", CheckoutCommit: "Checkout commit",