From 517e9445df11c160fded2b3b924048e6f1e92c57 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sun, 8 May 2022 11:41:13 +1000 Subject: [PATCH 1/3] refactor view definitions --- pkg/gui/arrangement.go | 142 +++++++++++++++-------------- pkg/gui/gui.go | 139 ----------------------------- pkg/gui/layout.go | 76 ++-------------- pkg/gui/views.go | 196 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 282 insertions(+), 271 deletions(-) create mode 100644 pkg/gui/views.go 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/gui.go b/pkg/gui/gui.go index f2dd1c3f4..286b72beb 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 @@ -596,121 +572,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..ea408822b 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,17 @@ 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 + for _, context := range gui.TransientContexts() { view, err := gui.g.View(context.GetViewName()) if err != nil && err.Error() != UNKNOWN_VIEW_ERROR_MSG { @@ -205,40 +178,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/views.go b/pkg/gui/views.go new file mode 100644 index 000000000..fa1c2f224 --- /dev/null +++ b/pkg/gui/views.go @@ -0,0 +1,196 @@ +package gui + +import ( + "github.com/jesseduffield/generics/slices" + "github.com/jesseduffield/gocui" + "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 + Description *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.Description, name: "description"}, + + // this guy will cover everything else when it appears + {viewPtr: &gui.Views.Limit, name: "limit"}, + } +} + +type controlledView struct { + viewName string + windowName string + frame bool +} + +// controlled views have their size and position determined in arrangement.go. +// Some views, like the confirmation panel, are currently sized at the time of +// displaying the view, based on the view's contents. +func (gui *Gui) controlledViews() []controlledView { + return []controlledView{ + {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}, + {viewName: "description", windowName: "description", frame: true}, + {viewName: "limit", windowName: "limit", frame: true}, + } +} + +func (gui *Gui) createAllViews() error { + var err error + for _, mapping := range gui.orderedViewNameMappings() { + *mapping.viewPtr, err = gui.prepareView(mapping.name) + if err != nil && err.Error() != UNKNOWN_VIEW_ERROR_MSG { + return err + } + } + + gui.Views.Options.FgColor = theme.OptionsColor + + gui.Views.SearchPrefix.BgColor = gocui.ColorDefault + gui.Views.SearchPrefix.FgColor = gocui.ColorGreen + 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.Editable = true + + gui.Views.AppStatus.BgColor = gocui.ColorDefault + gui.Views.AppStatus.FgColor = gocui.ColorCyan + 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.Description.FgColor = theme.GocuiDefaultTextColor + + gui.Views.Menu.Visible = false + + gui.Views.Information.BgColor = gocui.ColorDefault + gui.Views.Information.FgColor = gocui.ColorGreen + + 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 +} From f257740ea7ec52f14da2207b3a261363971cfcbb Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sun, 8 May 2022 12:46:48 +1000 Subject: [PATCH 2/3] add tooltip view for showing menu item descriptions --- pkg/gui/confirmation_panel.go | 3 +- pkg/gui/context/menu_context.go | 15 +++++----- pkg/gui/controllers/undo_controller.go | 2 ++ .../controllers/workspace_reset_controller.go | 3 +- pkg/gui/gui.go | 19 ------------ pkg/gui/layout.go | 2 ++ pkg/gui/list_context_config.go | 6 ++-- pkg/gui/menu_panel.go | 29 +++++++++++++----- pkg/gui/options_menu_panel.go | 3 +- pkg/gui/types/common.go | 3 ++ pkg/gui/types/keybindings.go | 3 ++ pkg/gui/view_helpers.go | 6 +++- pkg/gui/views.go | 30 ++++++++++++++++--- pkg/i18n/english.go | 6 ++++ 14 files changed, 85 insertions(+), 45 deletions(-) diff --git a/pkg/gui/confirmation_panel.go b/pkg/gui/confirmation_panel.go index b08fcba44..8ddb9b260 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) } 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 286b72beb..a75ef897c 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -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 // NewGui builds a new gui handler func NewGui( diff --git a/pkg/gui/layout.go b/pkg/gui/layout.go index ea408822b..bd36174bd 100644 --- a/pkg/gui/layout.go +++ b/pkg/gui/layout.go @@ -88,6 +88,8 @@ func (gui *Gui) layout(g *gocui.Gui) error { 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 { 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..dcc9831e1 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 { @@ -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 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) +} 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..52bc6fefa 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -47,9 +47,13 @@ func (gui *Gui) resizeCurrentPopupPanel() error { if v == 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 nil } diff --git a/pkg/gui/views.go b/pkg/gui/views.go index fa1c2f224..2da245507 100644 --- a/pkg/gui/views.go +++ b/pkg/gui/views.go @@ -3,6 +3,8 @@ 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" ) @@ -27,7 +29,7 @@ type Views struct { SearchPrefix *gocui.View Limit *gocui.View Suggestions *gocui.View - Description *gocui.View + Tooltip *gocui.View Extras *gocui.View } @@ -71,7 +73,7 @@ func (gui *Gui) orderedViewNameMappings() []viewNameMapping { {viewPtr: &gui.Views.Menu, name: "menu"}, {viewPtr: &gui.Views.Suggestions, name: "suggestions"}, {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 {viewPtr: &gui.Views.Limit, name: "limit"}, @@ -105,7 +107,6 @@ func (gui *Gui) controlledViews() []controlledView { {viewName: "appStatus", windowName: "appStatus", frame: false}, {viewName: "information", windowName: "information", frame: false}, {viewName: "extras", windowName: "extras", frame: true}, - {viewName: "description", windowName: "description", frame: true}, {viewName: "limit", windowName: "limit", frame: true}, } } @@ -180,10 +181,12 @@ func (gui *Gui) createAllViews() error { gui.Views.Suggestions.Visible = false - gui.Views.Description.FgColor = theme.GocuiDefaultTextColor + gui.Views.Tooltip.FgColor = theme.GocuiDefaultTextColor gui.Views.Menu.Visible = false + gui.Views.Tooltip.Visible = false + gui.Views.Information.BgColor = gocui.ColorDefault gui.Views.Information.FgColor = gocui.ColorGreen @@ -194,3 +197,22 @@ func (gui *Gui) createAllViews() error { 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, + } +} diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 8466a6571..59619d90f 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -108,6 +108,8 @@ type TranslationSet struct { LcUndo string LcUndoReflog string LcRedoReflog string + UndoTooltip string + RedoTooltip string LcPop string LcDrop string LcApply string @@ -486,6 +488,7 @@ type TranslationSet struct { CheckoutPrompt string HardResetAutostashPrompt string UpstreamGone string + NukeDescription string Actions Actions Bisect Bisect } @@ -719,6 +722,8 @@ func EnglishTranslationSet() TranslationSet { LcUndo: "undo", LcUndoReflog: "undo (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", LcDrop: "drop", 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.", CheckoutPrompt: "Are you sure you want to checkout '%s'?", 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{ // TODO: combine this with the original keybinding descriptions (those are all in lowercase atm) CheckoutCommit: "Checkout commit", From 22d98249fe2256d7c11b77ad926ba8bb8b67be04 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sun, 8 May 2022 13:14:24 +1000 Subject: [PATCH 3/3] better popup resizing logic --- pkg/gui/confirmation_panel.go | 13 +----------- pkg/gui/menu_panel.go | 13 ------------ pkg/gui/view_helpers.go | 37 +++++++++++++++++++++++++++++------ 3 files changed, 32 insertions(+), 31 deletions(-) diff --git a/pkg/gui/confirmation_panel.go b/pkg/gui/confirmation_panel.go index 8ddb9b260..5d8a13bb7 100644 --- a/pkg/gui/confirmation_panel.go +++ b/pkg/gui/confirmation_panel.go @@ -130,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() @@ -149,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/menu_panel.go b/pkg/gui/menu_panel.go index dcc9831e1..459fd60ce 100644 --- a/pkg/gui/menu_panel.go +++ b/pkg/gui/menu_panel.go @@ -59,16 +59,3 @@ 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 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) -} diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index 52bc6fefa..023a1b41c 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -50,6 +50,8 @@ func (gui *Gui) resizeCurrentPopupPanel() error { 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()) } @@ -58,17 +60,40 @@ func (gui *Gui) resizeCurrentPopupPanel() error { } 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