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 +}