diff options
author | Jesse Duffield <jessedduffield@gmail.com> | 2022-05-08 13:24:51 +1000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-05-08 13:24:51 +1000 |
commit | 8247089e53e851a76147205d72766a264bb00a97 (patch) | |
tree | 3aaf91fcecb99742af1aaec54fa6dbf8419bcc64 /pkg/gui | |
parent | 549409a6f35d86aadc4ee6dd976890dd5e5fc9f1 (diff) | |
parent | 22d98249fe2256d7c11b77ad926ba8bb8b67be04 (diff) |
Merge pull request #1925 from jesseduffield/details-view
Diffstat (limited to 'pkg/gui')
-rw-r--r-- | pkg/gui/arrangement.go | 142 | ||||
-rw-r--r-- | pkg/gui/confirmation_panel.go | 16 | ||||
-rw-r--r-- | pkg/gui/context/menu_context.go | 15 | ||||
-rw-r--r-- | pkg/gui/controllers/undo_controller.go | 2 | ||||
-rw-r--r-- | pkg/gui/controllers/workspace_reset_controller.go | 3 | ||||
-rw-r--r-- | pkg/gui/gui.go | 158 | ||||
-rw-r--r-- | pkg/gui/layout.go | 78 | ||||
-rw-r--r-- | pkg/gui/list_context_config.go | 6 | ||||
-rw-r--r-- | pkg/gui/menu_panel.go | 16 | ||||
-rw-r--r-- | pkg/gui/options_menu_panel.go | 3 | ||||
-rw-r--r-- | pkg/gui/types/common.go | 3 | ||||
-rw-r--r-- | pkg/gui/types/keybindings.go | 3 | ||||
-rw-r--r-- | pkg/gui/view_helpers.go | 43 | ||||
-rw-r--r-- | pkg/gui/views.go | 218 |
14 files changed, 376 insertions, 330 deletions
diff --git a/pkg/gui/arrangement.go b/pkg/gui/arrangement.go index d0e4c7fd0..5ab8ab5dd 100644 --- a/pkg/gui/arrangement.go +++ b/pkg/gui/arrangement.go @@ -13,6 +13,84 @@ import ( const INFO_SECTION_PADDING = " " +func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map[string]boxlayout.Dimensions { + width, height := gui.g.Size() + + sideSectionWeight, mainSectionWeight := gui.getMidSectionWeights() + + sidePanelsDirection := boxlayout.COLUMN + portraitMode := width <= 84 && height > 45 + if portraitMode { + sidePanelsDirection = boxlayout.ROW + } + + mainPanelsDirection := boxlayout.ROW + if gui.splitMainPanelSideBySide() { + mainPanelsDirection = boxlayout.COLUMN + } + + extrasWindowSize := gui.getExtrasWindowSize(height) + + showInfoSection := gui.c.UserConfig.Gui.ShowBottomLine || (gui.State.Searching.isSearching || gui.isAnyModeActive()) + infoSectionSize := 0 + if showInfoSection { + infoSectionSize = 1 + } + + root := &boxlayout.Box{ + Direction: boxlayout.ROW, + Children: []*boxlayout.Box{ + { + Direction: sidePanelsDirection, + Weight: 1, + Children: []*boxlayout.Box{ + { + Direction: boxlayout.ROW, + Weight: sideSectionWeight, + ConditionalChildren: gui.sidePanelChildren, + }, + { + Direction: boxlayout.ROW, + Weight: mainSectionWeight, + Children: []*boxlayout.Box{ + { + Direction: mainPanelsDirection, + Children: gui.mainSectionChildren(), + Weight: 1, + }, + { + Window: "extras", + Size: extrasWindowSize, + }, + }, + }, + }, + }, + { + Direction: boxlayout.COLUMN, + Size: infoSectionSize, + Children: gui.infoSectionChildren(informationStr, appStatus), + }, + }, + } + + layerOneWindows := boxlayout.ArrangeWindows(root, 0, 0, width, height) + limitWindows := boxlayout.ArrangeWindows(&boxlayout.Box{Window: "limit"}, 0, 0, width, height) + + return MergeMaps(layerOneWindows, limitWindows) +} + +func MergeMaps[K comparable, V any](maps ...map[K]V) map[K]V { + result := map[K]V{} + for _, currMap := range maps { + for key, value := range currMap { + result[key] = value + } + } + + return result +} + func (gui *Gui) mainSectionChildren() []*boxlayout.Box { currentWindow := gui.currentWindow() @@ -156,70 +234,6 @@ func (gui *Gui) getExtrasWindowSize(screenHeight int) int { return baseSize + frameSize } -func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map[string]boxlayout.Dimensions { - width, height := gui.g.Size() - - sideSectionWeight, mainSectionWeight := gui.getMidSectionWeights() - - sidePanelsDirection := boxlayout.COLUMN - portraitMode := width <= 84 && height > 45 - if portraitMode { - sidePanelsDirection = boxlayout.ROW - } - - mainPanelsDirection := boxlayout.ROW - if gui.splitMainPanelSideBySide() { - mainPanelsDirection = boxlayout.COLUMN - } - - extrasWindowSize := gui.getExtrasWindowSize(height) - - showInfoSection := gui.c.UserConfig.Gui.ShowBottomLine || (gui.State.Searching.isSearching || gui.isAnyModeActive()) - infoSectionSize := 0 - if showInfoSection { - infoSectionSize = 1 - } - - root := &boxlayout.Box{ - Direction: boxlayout.ROW, - Children: []*boxlayout.Box{ - { - Direction: sidePanelsDirection, - Weight: 1, - Children: []*boxlayout.Box{ - { - Direction: boxlayout.ROW, - Weight: sideSectionWeight, - ConditionalChildren: gui.sidePanelChildren, - }, - { - Direction: boxlayout.ROW, - Weight: mainSectionWeight, - Children: []*boxlayout.Box{ - { - Direction: mainPanelsDirection, - Children: gui.mainSectionChildren(), - Weight: 1, - }, - { - Window: "extras", - Size: extrasWindowSize, - }, - }, - }, - }, - }, - { - Direction: boxlayout.COLUMN, - Size: infoSectionSize, - Children: gui.infoSectionChildren(informationStr, appStatus), - }, - }, - } - - return boxlayout.ArrangeWindows(root, 0, 0, width, height) -} - // The stash window by default only contains one line so that it's not hogging // too much space, but if you access it it should take up some space. This is // the default behaviour when accordion mode is NOT in effect. If it is in effect diff --git a/pkg/gui/confirmation_panel.go b/pkg/gui/confirmation_panel.go index b08fcba44..5d8a13bb7 100644 --- a/pkg/gui/confirmation_panel.go +++ b/pkg/gui/confirmation_panel.go @@ -91,8 +91,7 @@ func (gui *Gui) getConfirmationPanelDimensions(wrap bool, prompt string) (int, i return gui.getConfirmationPanelDimensionsAux(panelWidth, panelHeight) } -func (gui *Gui) getConfirmationPanelDimensionsForContentHeight(contentHeight int) (int, int, int, int) { - panelWidth := gui.getConfirmationPanelWidth() +func (gui *Gui) getConfirmationPanelDimensionsForContentHeight(panelWidth, contentHeight int) (int, int, int, int) { return gui.getConfirmationPanelDimensionsAux(panelWidth, contentHeight) } @@ -131,13 +130,6 @@ func (gui *Gui) prepareConfirmationPanel( editable bool, mask bool, ) error { - x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(true, prompt) - // calling SetView on an existing view returns the same view, so I'm not bothering - // to reassign to gui.Views.Confirmation - _, err := gui.g.SetView("confirmation", x0, y0, x1, y1, 0) - if err != nil { - return err - } gui.Views.Confirmation.HasLoader = hasLoader if hasLoader { gui.g.StartTicking() @@ -150,11 +142,7 @@ func (gui *Gui) prepareConfirmationPanel( gui.findSuggestions = findSuggestionsFunc if findSuggestionsFunc != nil { - suggestionsViewHeight := 11 - suggestionsView, err := gui.g.SetView("suggestions", x0, y1+1, x1, y1+suggestionsViewHeight, 0) - if err != nil { - return err - } + suggestionsView := gui.Views.Suggestions suggestionsView.Wrap = false suggestionsView.FgColor = theme.GocuiDefaultTextColor gui.setSuggestions(findSuggestionsFunc("")) diff --git a/pkg/gui/context/menu_context.go b/pkg/gui/context/menu_context.go index 376a3ae4b..be748eb70 100644 --- a/pkg/gui/context/menu_context.go +++ b/pkg/gui/context/menu_context.go @@ -19,15 +19,18 @@ var _ types.IListContext = (*MenuContext)(nil) func NewMenuContext( view *gocui.View, - onFocus func(...types.OnFocusOpts) error, - onRenderToMain func(...types.OnFocusOpts) error, - onFocusLost func() error, - c *types.HelperCommon, getOptionsMap func() map[string]string, + renderToDescriptionView func(string), ) *MenuContext { viewModel := NewMenuViewModel() + onFocus := func(...types.OnFocusOpts) error { + selectedMenuItem := viewModel.GetSelected() + renderToDescriptionView(selectedMenuItem.Tooltip) + return nil + } + return &MenuContext{ MenuViewModel: viewModel, ListContextTrait: &ListContextTrait{ @@ -38,9 +41,7 @@ func NewMenuContext( OnGetOptionsMap: getOptionsMap, Focusable: true, }), ContextCallbackOpts{ - OnFocus: onFocus, - OnFocusLost: onFocusLost, - OnRenderToMain: onRenderToMain, + OnFocus: onFocus, }), getDisplayStrings: viewModel.GetDisplayStrings, list: viewModel, diff --git a/pkg/gui/controllers/undo_controller.go b/pkg/gui/controllers/undo_controller.go index 5e4c2043b..5e0bf5730 100644 --- a/pkg/gui/controllers/undo_controller.go +++ b/pkg/gui/controllers/undo_controller.go @@ -55,11 +55,13 @@ func (self *UndoController) GetKeybindings(opts types.KeybindingsOpts) []*types. Key: opts.GetKey(opts.Config.Universal.Undo), Handler: self.reflogUndo, Description: self.c.Tr.LcUndoReflog, + Tooltip: self.c.Tr.UndoTooltip, }, { Key: opts.GetKey(opts.Config.Universal.Redo), Handler: self.reflogRedo, Description: self.c.Tr.LcRedoReflog, + Tooltip: self.c.Tr.RedoTooltip, }, } diff --git a/pkg/gui/controllers/workspace_reset_controller.go b/pkg/gui/controllers/workspace_reset_controller.go index 32dd959df..bd5f47195 100644 --- a/pkg/gui/controllers/workspace_reset_controller.go +++ b/pkg/gui/controllers/workspace_reset_controller.go @@ -31,7 +31,8 @@ func (self *FilesController) createResetMenu() error { 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{ diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index f2dd1c3f4..a75ef897c 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -223,30 +223,6 @@ type panelStates struct { Merging *MergingPanelState } -type Views struct { - Status *gocui.View - Files *gocui.View - Branches *gocui.View - RemoteBranches *gocui.View - Commits *gocui.View - Stash *gocui.View - Main *gocui.View - Secondary *gocui.View - Options *gocui.View - Confirmation *gocui.View - Menu *gocui.View - CommitMessage *gocui.View - CommitFiles *gocui.View - SubCommits *gocui.View - Information *gocui.View - AppStatus *gocui.View - Search *gocui.View - SearchPrefix *gocui.View - Limit *gocui.View - Suggestions *gocui.View - Extras *gocui.View -} - type searchingState struct { view *gocui.View isSearching bool @@ -389,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 // NewGui builds a new gui handler func NewGui( @@ -596,121 +553,6 @@ func (gui *Gui) Run(filterPath string) error { return gui.g.MainLoop() } -func (gui *Gui) createAllViews() error { - viewNameMappings := []struct { - viewPtr **gocui.View - name string - }{ - {viewPtr: &gui.Views.Status, name: "status"}, - {viewPtr: &gui.Views.Files, name: "files"}, - {viewPtr: &gui.Views.Branches, name: "branches"}, - {viewPtr: &gui.Views.RemoteBranches, name: "remoteBranches"}, - {viewPtr: &gui.Views.Commits, name: "commits"}, - {viewPtr: &gui.Views.Stash, name: "stash"}, - {viewPtr: &gui.Views.CommitFiles, name: "commitFiles"}, - {viewPtr: &gui.Views.SubCommits, name: "subCommits"}, - {viewPtr: &gui.Views.Main, name: "main"}, - {viewPtr: &gui.Views.Secondary, name: "secondary"}, - {viewPtr: &gui.Views.Options, name: "options"}, - {viewPtr: &gui.Views.AppStatus, name: "appStatus"}, - {viewPtr: &gui.Views.Information, name: "information"}, - {viewPtr: &gui.Views.Search, name: "search"}, - {viewPtr: &gui.Views.SearchPrefix, name: "searchPrefix"}, - {viewPtr: &gui.Views.CommitMessage, name: "commitMessage"}, - {viewPtr: &gui.Views.Menu, name: "menu"}, - {viewPtr: &gui.Views.Suggestions, name: "suggestions"}, - {viewPtr: &gui.Views.Confirmation, name: "confirmation"}, - {viewPtr: &gui.Views.Limit, name: "limit"}, - {viewPtr: &gui.Views.Extras, name: "extras"}, - } - - var err error - for _, mapping := range viewNameMappings { - *mapping.viewPtr, err = gui.prepareView(mapping.name) - if err != nil && err.Error() != UNKNOWN_VIEW_ERROR_MSG { - return err - } - } - - gui.Views.Options.Frame = false - gui.Views.Options.FgColor = theme.OptionsColor - - gui.Views.SearchPrefix.BgColor = gocui.ColorDefault - gui.Views.SearchPrefix.FgColor = gocui.ColorGreen - gui.Views.SearchPrefix.Frame = false - gui.setViewContent(gui.Views.SearchPrefix, SEARCH_PREFIX) - - gui.Views.Stash.Title = gui.c.Tr.StashTitle - gui.Views.Stash.FgColor = theme.GocuiDefaultTextColor - - gui.Views.Commits.Title = gui.c.Tr.CommitsTitle - gui.Views.Commits.FgColor = theme.GocuiDefaultTextColor - - gui.Views.CommitFiles.Title = gui.c.Tr.CommitFiles - gui.Views.CommitFiles.FgColor = theme.GocuiDefaultTextColor - - gui.Views.SubCommits.FgColor = theme.GocuiDefaultTextColor - - gui.Views.Branches.Title = gui.c.Tr.BranchesTitle - gui.Views.Branches.FgColor = theme.GocuiDefaultTextColor - - gui.Views.RemoteBranches.FgColor = theme.GocuiDefaultTextColor - - gui.Views.Files.Title = gui.c.Tr.FilesTitle - gui.Views.Files.FgColor = theme.GocuiDefaultTextColor - - gui.Views.Secondary.Title = gui.c.Tr.DiffTitle - gui.Views.Secondary.Wrap = true - gui.Views.Secondary.FgColor = theme.GocuiDefaultTextColor - gui.Views.Secondary.IgnoreCarriageReturns = true - gui.Views.Secondary.CanScrollPastBottom = gui.c.UserConfig.Gui.ScrollPastBottom - - gui.Views.Main.Title = gui.c.Tr.DiffTitle - gui.Views.Main.Wrap = true - gui.Views.Main.FgColor = theme.GocuiDefaultTextColor - gui.Views.Main.IgnoreCarriageReturns = true - gui.Views.Main.CanScrollPastBottom = gui.c.UserConfig.Gui.ScrollPastBottom - - gui.Views.Limit.Title = gui.c.Tr.NotEnoughSpace - gui.Views.Limit.Wrap = true - - gui.Views.Status.Title = gui.c.Tr.StatusTitle - gui.Views.Status.FgColor = theme.GocuiDefaultTextColor - - gui.Views.Search.BgColor = gocui.ColorDefault - gui.Views.Search.FgColor = gocui.ColorGreen - gui.Views.Search.Frame = false - gui.Views.Search.Editable = true - - gui.Views.AppStatus.BgColor = gocui.ColorDefault - gui.Views.AppStatus.FgColor = gocui.ColorCyan - gui.Views.AppStatus.Frame = false - gui.Views.AppStatus.Visible = false - - gui.Views.CommitMessage.Visible = false - gui.Views.CommitMessage.Title = gui.c.Tr.CommitMessage - gui.Views.CommitMessage.FgColor = theme.GocuiDefaultTextColor - gui.Views.CommitMessage.Editable = true - gui.Views.CommitMessage.Editor = gocui.EditorFunc(gui.commitMessageEditor) - - gui.Views.Confirmation.Visible = false - - gui.Views.Suggestions.Visible = false - - gui.Views.Menu.Visible = false - - gui.Views.Information.BgColor = gocui.ColorDefault - gui.Views.Information.FgColor = gocui.ColorGreen - gui.Views.Information.Frame = false - - gui.Views.Extras.Title = gui.c.Tr.CommandLog - gui.Views.Extras.FgColor = theme.GocuiDefaultTextColor - gui.Views.Extras.Autoscroll = true - gui.Views.Extras.Wrap = true - - return nil -} - func (gui *Gui) RunAndHandleError(filterPath string) error { gui.stopChan = make(chan struct{}) return utils.SafeWithError(func() error { diff --git a/pkg/gui/layout.go b/pkg/gui/layout.go index 2fb165ff4..bd36174bd 100644 --- a/pkg/gui/layout.go +++ b/pkg/gui/layout.go @@ -20,15 +20,6 @@ func (gui *Gui) layout(g *gocui.Gui) error { g.Highlight = true width, height := g.Size() - minimumHeight := 9 - minimumWidth := 10 - var err error - _, err = g.SetView("limit", 0, 0, width-1, height-1, 0) - if err != nil && err.Error() != UNKNOWN_VIEW_ERROR_MSG { - return err - } - gui.Views.Limit.Visible = height < minimumHeight || width < minimumWidth - informationStr := gui.informationStr() appStatus := gui.statusManager.getStatusString() @@ -77,6 +68,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { dimensionsObj.Y1+frameOffset, 0, ) + view.Frame = frame if view != nil { view.Visible = true @@ -85,36 +77,19 @@ func (gui *Gui) layout(g *gocui.Gui) error { return view, err } - args := []struct { - viewName string - windowName string - frame bool - }{ - {viewName: "main", windowName: "main", frame: true}, - {viewName: "secondary", windowName: "secondary", frame: true}, - {viewName: "status", windowName: "status", frame: true}, - {viewName: "files", windowName: "files", frame: true}, - {viewName: "branches", windowName: "branches", frame: true}, - {viewName: "remoteBranches", windowName: "branches", frame: true}, - {viewName: "commitFiles", windowName: gui.State.Contexts.CommitFiles.GetWindowName(), frame: true}, - {viewName: "subCommits", windowName: gui.State.Contexts.SubCommits.GetWindowName(), frame: true}, - {viewName: "commits", windowName: "commits", frame: true}, - {viewName: "stash", windowName: "stash", frame: true}, - {viewName: "options", windowName: "options", frame: false}, - {viewName: "searchPrefix", windowName: "searchPrefix", frame: false}, - {viewName: "search", windowName: "search", frame: false}, - {viewName: "appStatus", windowName: "appStatus", frame: false}, - {viewName: "information", windowName: "information", frame: false}, - {viewName: "extras", windowName: "extras", frame: true}, - } - - for _, arg := range args { - _, err = setViewFromDimensions(arg.viewName, arg.windowName, arg.frame) + for _, arg := range gui.controlledViews() { + _, err := setViewFromDimensions(arg.viewName, arg.windowName, arg.frame) if err != nil && err.Error() != UNKNOWN_VIEW_ERROR_MSG { return err } } + minimumHeight := 9 + minimumWidth := 10 + 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() { view, err := gui.g.View(context.GetViewName()) if err != nil && err.Error() != UNKNOWN_VIEW_ERROR_MSG { @@ -205,40 +180,7 @@ func (gui *Gui) onInitialViewsCreationForRepo() error { func (gui *Gui) onInitialViewsCreation() error { // now we order the views (in order of bottom first) - layerOneViews := []*gocui.View{ - // first layer. Ordering within this layer does not matter because there are - // no overlapping views - gui.Views.Status, - gui.Views.Files, - gui.Views.Branches, - gui.Views.RemoteBranches, - gui.Views.Commits, - gui.Views.Stash, - gui.Views.SubCommits, - gui.Views.CommitFiles, - gui.Views.Main, - gui.Views.Secondary, - gui.Views.Extras, - - // bottom line - gui.Views.Options, - gui.Views.AppStatus, - gui.Views.Information, - gui.Views.Search, - gui.Views.SearchPrefix, // this view takes up one character. Its only purpose is to show the slash when searching - - // popups. Ordering within this layer does not matter because there should - // only be one popup shown at a time - gui.Views.CommitMessage, - gui.Views.Menu, - gui.Views.Suggestions, - gui.Views.Confirmation, - - // this guy will cover everything else when it appears - gui.Views.Limit, - } - - for _, view := range layerOneViews { + for _, view := range gui.orderedViews() { if _, err := gui.g.SetViewOnTop(view.Name()); err != nil { return err } diff --git a/pkg/gui/list_context_config.go b/pkg/gui/list_context_config.go index e8a65d6da..5fe710055 100644 --- a/pkg/gui/list_context_config.go +++ b/pkg/gui/list_context_config.go @@ -15,11 +15,11 @@ import ( func (gui *Gui) menuListContext() *context.MenuContext { return context.NewMenuContext( gui.Views.Menu, - nil, - nil, - nil, gui.c, gui.getMenuOptions, + func(content string) { + gui.Views.Tooltip.SetContent(content) + }, ) } diff --git a/pkg/gui/menu_panel.go b/pkg/gui/menu_panel.go index 8bce02011..459fd60ce 100644 --- a/pkg/gui/menu_panel.go +++ b/pkg/gui/menu_panel.go @@ -36,16 +36,18 @@ func (gui *Gui) createMenu(opts types.CreateMenuOptions) error { } } - x0, y0, x1, y1 := gui.getConfirmationPanelDimensionsForContentHeight(len(opts.Items)) - menuView, _ := gui.g.SetView("menu", x0, y0, x1, y1, 0) - menuView.Title = opts.Title - menuView.FgColor = theme.GocuiDefaultTextColor - menuView.SetOnSelectItem(gui.onSelectItemWrapper(func(selectedLine int) error { + gui.State.Contexts.Menu.SetMenuItems(opts.Items) + gui.State.Contexts.Menu.SetSelectedLineIdx(0) + + gui.Views.Menu.Title = opts.Title + gui.Views.Menu.FgColor = theme.GocuiDefaultTextColor + gui.Views.Menu.SetOnSelectItem(gui.onSelectItemWrapper(func(selectedLine int) error { return nil })) - gui.State.Contexts.Menu.SetMenuItems(opts.Items) - gui.State.Contexts.Menu.SetSelectedLineIdx(0) + gui.Views.Tooltip.Wrap = true + gui.Views.Tooltip.FgColor = theme.GocuiDefaultTextColor + gui.Views.Tooltip.Visible = true // resetting keybindings so that the menu-specific keybindings are registered if err := gui.resetKeybindings(); err != nil { diff --git a/pkg/gui/options_menu_panel.go b/pkg/gui/options_menu_panel.go index 846d1de16..f7baf10ad 100644 --- a/pkg/gui/options_menu_panel.go +++ b/pkg/gui/options_menu_panel.go @@ -73,7 +73,8 @@ func (gui *Gui) handleCreateOptionsMenu() error { return binding.Handler() }, - Key: binding.Key, + Key: binding.Key, + Tooltip: binding.Tooltip, } }) diff --git a/pkg/gui/types/common.go b/pkg/gui/types/common.go index 277732a34..fc2eebf59 100644 --- a/pkg/gui/types/common.go +++ b/pkg/gui/types/common.go @@ -118,6 +118,9 @@ type MenuItem struct { // 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 Key Key + + // the tooltip will be displayed upon highlighting the menu item + Tooltip string } type Model struct { diff --git a/pkg/gui/types/keybindings.go b/pkg/gui/types/keybindings.go index b4db46336..a257fcfc2 100644 --- a/pkg/gui/types/keybindings.go +++ b/pkg/gui/types/keybindings.go @@ -17,6 +17,9 @@ type Binding struct { Alternative string Tag string // e.g. 'navigation'. Used for grouping things in the cheatsheet 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 diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index dfb77e1e2..023a1b41c 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -47,24 +47,53 @@ func (gui *Gui) resizeCurrentPopupPanel() error { if v == nil { return nil } - if gui.isPopupPanel(v.Name()) { + + if v == gui.Views.Menu { + gui.resizeMenu() + } else if v == gui.Views.Confirmation || v == gui.Views.Suggestions { + gui.resizeConfirmationPanel() + } else if gui.isPopupPanel(v.Name()) { return gui.resizePopupPanel(v, v.Buffer()) } + return nil } func (gui *Gui) resizePopupPanel(v *gocui.View, content string) error { - // If the confirmation panel is already displayed, just resize the width, - // otherwise continue x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(v.Wrap, content) - vx0, vy0, vx1, vy1 := v.Dimensions() - if vx0 == x0 && vy0 == y0 && vx1 == x1 && vy1 == y1 { - return nil - } _, err := gui.g.SetView(v.Name(), x0, y0, x1, y1, 0) return err } +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(gui.Views.Menu.Name(), 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(gui.Views.Tooltip.Name(), x0, tooltipTop, x1, tooltipTop+tooltipHeight-1, 0) +} + +func (gui *Gui) resizeConfirmationPanel() { + suggestionsViewHeight := 0 + if gui.Views.Suggestions.Visible { + suggestionsViewHeight = 11 + } + panelWidth := gui.getConfirmationPanelWidth() + prompt := gui.Views.Confirmation.Buffer() + panelHeight := gui.getMessageHeight(true, prompt, panelWidth) + suggestionsViewHeight + x0, y0, x1, y1 := gui.getConfirmationPanelDimensionsAux(panelWidth, panelHeight) + confirmationViewBottom := y1 - suggestionsViewHeight + _, _ = gui.g.SetView(gui.Views.Confirmation.Name(), x0, y0, x1, confirmationViewBottom, 0) + + suggestionsViewTop := confirmationViewBottom + 1 + _, _ = gui.g.SetView(gui.Views.Suggestions.Name(), x0, suggestionsViewTop, x1, suggestionsViewTop+suggestionsViewHeight, 0) +} + func (gui *Gui) globalOptionsMap() map[string]string { keybindingConfig := gui.c.UserConfig.Keybinding diff --git a/pkg/gui/views.go b/pkg/gui/views.go new file mode 100644 index 000000000..2da245507 --- /dev/null +++ b/pkg/gui/views.go @@ -0,0 +1,218 @@ +package gui + +import ( + "github.com/jesseduffield/generics/slices" + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/gui/context" + "github.com/jesseduffield/lazygit/pkg/gui/types" + "github.com/jesseduffield/lazygit/pkg/theme" +) + +type Views struct { + Status *gocui.View + Files *gocui.View + Branches *gocui.View + RemoteBranches *gocui.View + Commits *gocui.View + Stash *gocui.View + Main *gocui.View + Secondary *gocui.View + Options *gocui.View + Confirmation *gocui.View + Menu *gocui.View + CommitMessage *gocui.View + CommitFiles *gocui.View + SubCommits *gocui.View + Information *gocui.View + AppStatus *gocui.View + Search *gocui.View + SearchPrefix *gocui.View + Limit *gocui.View + Suggestions *gocui.View + Tooltip *gocui.View + Extras *gocui.View +} + +type viewNameMapping struct { + viewPtr **gocui.View + name string +} + +func (gui *Gui) orderedViews() []*gocui.View { + return slices.Map(gui.orderedViewNameMappings(), func(v viewNameMapping) *gocui.View { + return *v.viewPtr + }) +} + +func (gui *Gui) orderedViewNameMappings() []viewNameMapping { + return []viewNameMapping{ + // first layer. Ordering within this layer does not matter because there are + // no overlapping views + {viewPtr: &gui.Views.Status, name: "status"}, + {viewPtr: &gui.Views.Files, name: "files"}, + {viewPtr: &gui.Views.Branches, name: "branches"}, + {viewPtr: &gui.Views.RemoteBranches, name: "remoteBranches"}, + {viewPtr: &gui.Views.Commits, name: "commits"}, + {viewPtr: &gui.Views.Stash, name: "stash"}, + {viewPtr: &gui.Views.SubCommits, name: "subCommits"}, + {viewPtr: &gui.Views.CommitFiles, name: "commitFiles"}, + {viewPtr: &gui.Views.Main, name: "main"}, + {viewPtr: &gui.Views.Secondary, name: "secondary"}, + {viewPtr: &gui.Views.Extras, name: "extras"}, + + // bottom line + {viewPtr: &gui.Views.Options, name: "options"}, + {viewPtr: &gui.Views.AppStatus, name: "appStatus"}, + {viewPtr: &gui.Views.Information, name: "information"}, + {viewPtr: &gui.Views.Search, name: "search"}, + // this view takes up one character. Its only purpose is to show the slash when searching + {viewPtr: &gui.Views.SearchPrefix, name: "searchPrefix"}, + + // popups. + {viewPtr: &gui.Views.CommitMessage, name: "commitMessage"}, + {viewPtr: &gui.Views.Menu, name: "menu"}, + {viewPtr: &gui.Views.Suggestions, name: "suggestions"}, + {viewPtr: &gui.Views.Confirmation, name: "confirmation"}, + {viewPtr: &gui.Views.Tooltip, name: "tooltip"}, + + // this guy will cover everything else when it appears + {viewPtr: &gui.Views.Limit, name: "limit"}, |