2023-03-23 09:55:41 +02:00
|
|
|
package helpers
|
2020-05-17 13:32:17 +02:00
|
|
|
|
2020-05-18 14:21:36 +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"
|
2022-01-16 05:46:53 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
2020-05-18 14:21:36 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
2022-05-07 10:10:38 +02:00
|
|
|
"github.com/mattn/go-runewidth"
|
2020-05-18 14:21:36 +02:00
|
|
|
)
|
|
|
|
|
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,
|
|
|
|
}
|
2022-12-30 14:24:24 +02:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
2022-12-30 14:24:24 +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
|
2022-12-30 14:24:24 +02:00
|
|
|
if self.splitMainPanelSideBySide() {
|
2022-05-08 03:41:13 +02:00
|
|
|
mainPanelsDirection = boxlayout.COLUMN
|
|
|
|
}
|
|
|
|
|
2022-12-30 14:24:24 +02:00
|
|
|
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 ||
|
2023-05-27 06:14:43 +02:00
|
|
|
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,
|
2022-12-30 14:24:24 +02:00
|
|
|
ConditionalChildren: self.sidePanelChildren,
|
2022-05-08 03:41:13 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
Direction: boxlayout.ROW,
|
|
|
|
Weight: mainSectionWeight,
|
|
|
|
Children: []*boxlayout.Box{
|
|
|
|
{
|
|
|
|
Direction: mainPanelsDirection,
|
2022-12-30 14:24:24 +02:00
|
|
|
Children: self.mainSectionChildren(),
|
2022-05-08 03:41:13 +02:00
|
|
|
Weight: 1,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Window: "extras",
|
|
|
|
Size: extrasWindowSize,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Direction: boxlayout.COLUMN,
|
|
|
|
Size: infoSectionSize,
|
2022-12-30 14:24:24 +02:00
|
|
|
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") {
|
2020-08-15 00:28:02 +02:00
|
|
|
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
|
|
|
|
2020-08-15 00:28:02 +02:00
|
|
|
return []*boxlayout.Box{
|
2020-05-17 13:32:17 +02:00
|
|
|
{
|
2022-06-13 03:01:26 +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
|
|
|
{
|
2022-06-13 03:01:26 +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
|
|
|
|
|
2022-12-30 14:24:24 +02:00
|
|
|
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 {
|
2023-05-27 06:14:43 +02:00
|
|
|
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
|
|
|
|
}
|
2020-08-15 00:28:02 +02:00
|
|
|
return []*boxlayout.Box{
|
2020-05-18 14:21:36 +02:00
|
|
|
{
|
2020-08-21 11:53:45 +02:00
|
|
|
Window: "searchPrefix",
|
2023-05-27 06:14:43 +02:00
|
|
|
Size: runewidth.StringWidth(prefix),
|
2020-05-18 14:21:36 +02:00
|
|
|
},
|
|
|
|
{
|
2020-08-21 11:53:45 +02:00
|
|
|
Window: "search",
|
|
|
|
Weight: 1,
|
2020-05-18 14:21:36 +02:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-14 00:04:56 +02:00
|
|
|
appStatusBox := &boxlayout.Box{Window: "appStatus"}
|
|
|
|
optionsBox := &boxlayout.Box{Window: "options"}
|
2020-05-18 14:21:36 +02:00
|
|
|
|
2023-03-23 09:47:29 +02:00
|
|
|
if !self.c.UserConfig.Gui.ShowBottomLine {
|
2022-11-14 00:04:56 +02:00
|
|
|
optionsBox.Weight = 0
|
|
|
|
appStatusBox.Weight = 1
|
|
|
|
} else {
|
|
|
|
optionsBox.Weight = 1
|
2023-07-31 10:32:38 +02:00
|
|
|
if self.c.InDemo() {
|
|
|
|
// app status appears very briefly in demos and dislodges the caption,
|
|
|
|
// so better not to show it at all
|
|
|
|
appStatusBox.Size = 0
|
|
|
|
} else {
|
|
|
|
appStatusBox.Size = runewidth.StringWidth(INFO_SECTION_PADDING) + runewidth.StringWidth(appStatus)
|
|
|
|
}
|
2020-05-18 14:21:36 +02:00
|
|
|
}
|
|
|
|
|
2022-11-14 00:04:56 +02:00
|
|
|
result := []*boxlayout.Box{appStatusBox, optionsBox}
|
|
|
|
|
2023-07-31 10:32:38 +02:00
|
|
|
if (!self.c.InDemo() && self.c.UserConfig.Gui.ShowBottomLine) || self.modeHelper.IsAnyModeActive() {
|
2022-11-14 00:04:56 +02:00
|
|
|
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)),
|
|
|
|
})
|
|
|
|
}
|
2020-05-18 14:21:36 +02:00
|
|
|
|
|
|
|
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() {
|
2020-10-12 22:25:56 +02:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-03-23 09:47:29 +02:00
|
|
|
mainPanelSplitMode := self.c.UserConfig.Gui.MainPanelSplitMode
|
|
|
|
width, height := self.c.GocuiGui().Size()
|
2020-10-12 22:25:56 +02:00
|
|
|
|
|
|
|
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 {
|
2020-08-15 00:28:02 +02:00
|
|
|
return &boxlayout.Box{
|
2020-08-21 11:53:45 +02:00
|
|
|
Window: window,
|
|
|
|
Weight: 1,
|
2020-05-17 13:32:17 +02:00
|
|
|
}
|
|
|
|
} else {
|
2020-08-15 00:28:02 +02:00
|
|
|
return &boxlayout.Box{
|
2020-08-21 11:53:45 +02:00
|
|
|
Window: window,
|
|
|
|
Size: 0,
|
2020-05-17 13:32:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-15 00:28:02 +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 {
|
2020-08-15 00:28:02 +02:00
|
|
|
return &boxlayout.Box{
|
2020-08-21 11:53:45 +02:00
|
|
|
Window: defaultBox.Window,
|
|
|
|
Weight: 2,
|
2020-08-12 14:06:37 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return defaultBox
|
|
|
|
}
|
|
|
|
|
2020-08-15 00:28:02 +02:00
|
|
|
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}),
|
2022-12-30 14:24:24 +02:00
|
|
|
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 {
|
2020-08-15 00:28:02 +02:00
|
|
|
return &boxlayout.Box{
|
2020-08-21 11:53:45 +02:00
|
|
|
Window: window,
|
|
|
|
Weight: 1,
|
2020-05-17 13:32:17 +02:00
|
|
|
}
|
|
|
|
} else {
|
2020-08-15 00:28:02 +02:00
|
|
|
return &boxlayout.Box{
|
2020-08-21 11:53:45 +02:00
|
|
|
Window: window,
|
|
|
|
Size: squashedHeight,
|
2020-05-17 13:32:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-15 00:28:02 +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
|
|
|
}
|