1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2024-12-14 11:23:09 +02:00
lazygit/pkg/gui/controllers/helpers/window_arrangement_helper.go

361 lines
9.4 KiB
Go
Raw Normal View History

2023-03-23 09:55:41 +02:00
package helpers
2020-05-17 13:32:17 +02:00
import (
2022-10-09 17:31:14 +02:00
"github.com/jesseduffield/lazycore/pkg/boxlayout"
2022-01-29 10:15:46 +02:00
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/mattn/go-runewidth"
)
2022-05-07 07:42:36 +02:00
// In this file we use the boxlayout package, along with knowledge about the app's state,
// to arrange the windows (i.e. panels) on the screen.
2023-03-23 09:55:41 +02:00
type WindowArrangementHelper struct {
c *HelperCommon
windowHelper *WindowHelper
modeHelper *ModeHelper
appStatusHelper *AppStatusHelper
2023-03-23 09:47:29 +02:00
}
2023-03-23 09:55:41 +02:00
func NewWindowArrangementHelper(
c *HelperCommon,
windowHelper *WindowHelper,
modeHelper *ModeHelper,
appStatusHelper *AppStatusHelper,
) *WindowArrangementHelper {
return &WindowArrangementHelper{
2023-03-23 09:47:29 +02:00
c: c,
windowHelper: windowHelper,
modeHelper: modeHelper,
appStatusHelper: appStatusHelper,
}
}
2023-03-23 09:55:41 +02:00
const INFO_SECTION_PADDING = " "
func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string, appStatus string) map[string]boxlayout.Dimensions {
2023-03-23 09:47:29 +02:00
width, height := self.c.GocuiGui().Size()
2022-05-08 03:41:13 +02:00
sideSectionWeight, mainSectionWeight := self.getMidSectionWeights()
2022-05-08 03:41:13 +02:00
sidePanelsDirection := boxlayout.COLUMN
portraitMode := width <= 84 && height > 45
if portraitMode {
sidePanelsDirection = boxlayout.ROW
}
mainPanelsDirection := boxlayout.ROW
if self.splitMainPanelSideBySide() {
2022-05-08 03:41:13 +02:00
mainPanelsDirection = boxlayout.COLUMN
}
extrasWindowSize := self.getExtrasWindowSize(height)
2022-05-08 03:41:13 +02:00
2023-03-23 09:47:29 +02:00
self.c.Modes().Filtering.Active()
showInfoSection := self.c.UserConfig.Gui.ShowBottomLine ||
self.c.State().GetRepoState().InSearchPrompt() ||
2023-03-23 09:47:29 +02:00
self.modeHelper.IsAnyModeActive() ||
self.appStatusHelper.HasStatus()
2022-05-08 03:41:13 +02:00
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: self.sidePanelChildren,
2022-05-08 03:41:13 +02:00
},
{
Direction: boxlayout.ROW,
Weight: mainSectionWeight,
Children: []*boxlayout.Box{
{
Direction: mainPanelsDirection,
Children: self.mainSectionChildren(),
2022-05-08 03:41:13 +02:00
Weight: 1,
},
{
Window: "extras",
Size: extrasWindowSize,
},
},
},
},
},
{
Direction: boxlayout.COLUMN,
Size: infoSectionSize,
Children: self.infoSectionChildren(informationStr, appStatus),
2022-05-08 03:41:13 +02:00
},
},
}
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
}
2023-03-23 09:55:41 +02:00
func (self *WindowArrangementHelper) mainSectionChildren() []*boxlayout.Box {
2023-03-23 09:47:29 +02:00
currentWindow := self.windowHelper.CurrentWindow()
2020-05-18 14:00:07 +02:00
// if we're not in split mode we can just show the one main panel. Likewise if
// the main panel is focused and we're in full-screen mode
2023-03-23 09:47:29 +02:00
if !self.c.State().GetRepoState().GetSplitMainPanel() || (self.c.State().GetRepoState().GetScreenMode() == types.SCREEN_FULL && currentWindow == "main") {
return []*boxlayout.Box{
2020-05-18 14:00:07 +02:00
{
2020-08-21 11:53:45 +02:00
Window: "main",
Weight: 1,
2020-05-18 14:00:07 +02:00
},
}
}
2020-05-17 13:32:17 +02:00
return []*boxlayout.Box{
2020-05-17 13:32:17 +02:00
{
Window: "main",
2020-08-21 11:53:45 +02:00
Weight: 1,
2020-05-17 13:32:17 +02:00
},
2020-05-18 14:00:07 +02:00
{
Window: "secondary",
2020-08-21 11:53:45 +02:00
Weight: 1,
2020-05-18 14:00:07 +02:00
},
2020-05-17 13:32:17 +02:00
}
2020-05-18 14:00:07 +02:00
}
2023-03-23 09:55:41 +02:00
func (self *WindowArrangementHelper) getMidSectionWeights() (int, int) {
2023-03-23 09:47:29 +02:00
currentWindow := self.windowHelper.CurrentWindow()
2020-05-17 13:32:17 +02:00
2020-05-17 13:44:59 +02:00
// we originally specified this as a ratio i.e. .20 would correspond to a weight of 1 against 4
2023-03-23 09:47:29 +02:00
sidePanelWidthRatio := self.c.UserConfig.Gui.SidePanelWidth
2020-05-17 13:44:59 +02:00
// we could make this better by creating ratios like 2:3 rather than always 1:something
mainSectionWeight := int(1/sidePanelWidthRatio) - 1
sideSectionWeight := 1
if self.splitMainPanelSideBySide() {
2020-05-17 13:44:59 +02:00
mainSectionWeight = 5 // need to shrink side panel to make way for main panels if side-by-side
}
2020-05-18 14:00:07 +02:00
2023-03-23 09:47:29 +02:00
screenMode := self.c.State().GetRepoState().GetScreenMode()
2020-08-21 11:53:45 +02:00
if currentWindow == "main" {
2023-03-23 09:47:29 +02:00
if screenMode == types.SCREEN_HALF || screenMode == types.SCREEN_FULL {
2020-05-17 13:44:59 +02:00
sideSectionWeight = 0
}
} else {
2023-03-23 09:47:29 +02:00
if screenMode == types.SCREEN_HALF {
2020-05-17 13:44:59 +02:00
mainSectionWeight = 1
2023-03-23 09:47:29 +02:00
} else if screenMode == types.SCREEN_FULL {
2020-05-17 13:44:59 +02:00
mainSectionWeight = 0
}
}
2020-05-18 14:00:07 +02:00
return sideSectionWeight, mainSectionWeight
}
2023-03-23 09:55:41 +02:00
func (self *WindowArrangementHelper) infoSectionChildren(informationStr string, appStatus string) []*boxlayout.Box {
if self.c.State().GetRepoState().InSearchPrompt() {
var prefix string
if self.c.State().GetRepoState().GetSearchState().SearchType() == types.SearchTypeSearch {
prefix = self.c.Tr.SearchPrefix
} else {
prefix = self.c.Tr.FilterPrefix
}
return []*boxlayout.Box{
{
2020-08-21 11:53:45 +02:00
Window: "searchPrefix",
Size: runewidth.StringWidth(prefix),
},
{
2020-08-21 11:53:45 +02:00
Window: "search",
Weight: 1,
},
}
}
appStatusBox := &boxlayout.Box{Window: "appStatus"}
optionsBox := &boxlayout.Box{Window: "options"}
2023-03-23 09:47:29 +02:00
if !self.c.UserConfig.Gui.ShowBottomLine {
optionsBox.Weight = 0
appStatusBox.Weight = 1
} else {
optionsBox.Weight = 1
appStatusBox.Size = runewidth.StringWidth(INFO_SECTION_PADDING) + runewidth.StringWidth(appStatus)
}
result := []*boxlayout.Box{appStatusBox, optionsBox}
2023-03-23 09:47:29 +02:00
if self.c.UserConfig.Gui.ShowBottomLine || self.modeHelper.IsAnyModeActive() {
result = append(result, &boxlayout.Box{
Window: "information",
// unlike appStatus, informationStr has various colors so we need to decolorise before taking the length
Size: runewidth.StringWidth(INFO_SECTION_PADDING) + runewidth.StringWidth(utils.Decolorise(informationStr)),
})
}
return result
}
2023-03-23 09:55:41 +02:00
func (self *WindowArrangementHelper) splitMainPanelSideBySide() bool {
2023-03-23 09:47:29 +02:00
if !self.c.State().GetRepoState().GetSplitMainPanel() {
return false
}
2023-03-23 09:47:29 +02:00
mainPanelSplitMode := self.c.UserConfig.Gui.MainPanelSplitMode
width, height := self.c.GocuiGui().Size()
switch mainPanelSplitMode {
case "vertical":
return false
case "horizontal":
return true
default:
if width < 200 && height > 30 { // 2 80 character width panels + 40 width for side panel
return false
} else {
return true
}
}
}
2023-03-23 09:55:41 +02:00
func (self *WindowArrangementHelper) getExtrasWindowSize(screenHeight int) int {
2023-03-23 09:47:29 +02:00
if !self.c.State().GetShowExtrasWindow() {
2021-04-19 10:04:00 +02:00
return 0
}
var baseSize int
2023-03-23 09:47:29 +02:00
if self.c.CurrentStaticContext().GetKey() == context.COMMAND_LOG_CONTEXT_KEY {
2021-04-19 10:04:00 +02:00
baseSize = 1000 // my way of saying 'fill the available space'
} else if screenHeight < 40 {
baseSize = 1
} else {
2023-03-23 09:47:29 +02:00
baseSize = self.c.UserConfig.Gui.CommandLogSize
2021-04-19 10:04:00 +02:00
}
frameSize := 2
return baseSize + frameSize
}
2020-08-23 07:01:02 +02:00
// 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
2021-09-01 22:51:24 +02:00
// the default behaviour when accordion mode is NOT in effect. If it is in effect
2020-08-23 07:01:02 +02:00
// then when it's accessed it will have weight 2, not 1.
2023-03-23 09:55:41 +02:00
func (self *WindowArrangementHelper) getDefaultStashWindowBox() *boxlayout.Box {
2020-08-23 07:01:02 +02:00
stashWindowAccessed := false
2023-03-23 09:47:29 +02:00
self.c.Context().ForEach(func(context types.Context) {
2020-08-23 07:01:02 +02:00
if context.GetWindowName() == "stash" {
stashWindowAccessed = true
}
2023-03-23 09:47:29 +02:00
})
box := &boxlayout.Box{Window: "stash"}
2020-08-23 07:01:02 +02:00
// if the stash window is anywhere in our stack we should enlargen it
if stashWindowAccessed {
box.Weight = 1
} else {
box.Size = 3
}
return box
}
2023-03-23 09:55:41 +02:00
func (self *WindowArrangementHelper) sidePanelChildren(width int, height int) []*boxlayout.Box {
2023-03-23 09:47:29 +02:00
currentWindow := self.c.CurrentSideContext().GetWindowName()
2020-05-17 13:32:17 +02:00
2023-03-23 09:47:29 +02:00
screenMode := self.c.State().GetRepoState().GetScreenMode()
if screenMode == types.SCREEN_FULL || screenMode == types.SCREEN_HALF {
2020-08-21 11:53:45 +02:00
fullHeightBox := func(window string) *boxlayout.Box {
if window == currentWindow {
return &boxlayout.Box{
2020-08-21 11:53:45 +02:00
Window: window,
Weight: 1,
2020-05-17 13:32:17 +02:00
}
} else {
return &boxlayout.Box{
2020-08-21 11:53:45 +02:00
Window: window,
Size: 0,
2020-05-17 13:32:17 +02:00
}
}
}
return []*boxlayout.Box{
2020-05-17 13:32:17 +02:00
fullHeightBox("status"),
fullHeightBox("files"),
fullHeightBox("branches"),
fullHeightBox("commits"),
fullHeightBox("stash"),
}
2020-05-17 13:44:59 +02:00
} else if height >= 28 {
2023-03-23 09:47:29 +02:00
accordionMode := self.c.UserConfig.Gui.ExpandFocusedSidePanel
2021-09-01 22:51:24 +02:00
accordionBox := func(defaultBox *boxlayout.Box) *boxlayout.Box {
if accordionMode && defaultBox.Window == currentWindow {
return &boxlayout.Box{
2020-08-21 11:53:45 +02:00
Window: defaultBox.Window,
Weight: 2,
}
}
return defaultBox
}
return []*boxlayout.Box{
2020-05-17 13:32:17 +02:00
{
2020-08-21 11:53:45 +02:00
Window: "status",
Size: 3,
2020-05-17 13:32:17 +02:00
},
2021-09-01 22:51:24 +02:00
accordionBox(&boxlayout.Box{Window: "files", Weight: 1}),
accordionBox(&boxlayout.Box{Window: "branches", Weight: 1}),
accordionBox(&boxlayout.Box{Window: "commits", Weight: 1}),
accordionBox(self.getDefaultStashWindowBox()),
2020-05-17 13:32:17 +02:00
}
} else {
squashedHeight := 1
if height >= 21 {
squashedHeight = 3
}
2020-08-21 11:53:45 +02:00
squashedSidePanelBox := func(window string) *boxlayout.Box {
if window == currentWindow {
return &boxlayout.Box{
2020-08-21 11:53:45 +02:00
Window: window,
Weight: 1,
2020-05-17 13:32:17 +02:00
}
} else {
return &boxlayout.Box{
2020-08-21 11:53:45 +02:00
Window: window,
Size: squashedHeight,
2020-05-17 13:32:17 +02:00
}
}
}
return []*boxlayout.Box{
2020-05-17 13:32:17 +02:00
squashedSidePanelBox("status"),
squashedSidePanelBox("files"),
squashedSidePanelBox("branches"),
squashedSidePanelBox("commits"),
squashedSidePanelBox("stash"),
}
}
2020-05-17 13:44:59 +02:00
}