mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-05-15 22:26:40 +02:00
Refactor window arrangement helper to use pure function
This will make it easier to test the file
This commit is contained in:
parent
b96befa250
commit
8a08abcd35
@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazycore/pkg/boxlayout"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/mattn/go-runewidth"
|
||||
@ -36,40 +36,106 @@ func NewWindowArrangementHelper(
|
||||
}
|
||||
}
|
||||
|
||||
func (self *WindowArrangementHelper) shouldUsePortraitMode(width, height int) bool {
|
||||
switch self.c.UserConfig.Gui.PortraitMode {
|
||||
type WindowArrangementArgs struct {
|
||||
// Width of the screen (in characters)
|
||||
Width int
|
||||
// Height of the screen (in characters)
|
||||
Height int
|
||||
// User config
|
||||
UserConfig *config.UserConfig
|
||||
// Name of the currently focused window
|
||||
CurrentWindow string
|
||||
// Name of the current static window (meaning popups are ignored)
|
||||
CurrentStaticWindow string
|
||||
// Name of the current side window (i.e. the current window in the left
|
||||
// section of the UI)
|
||||
CurrentSideWindow string
|
||||
// Whether the main panel is split (as is the case e.g. when a file has both
|
||||
// staged and unstaged changes)
|
||||
SplitMainPanel bool
|
||||
// The current screen mode (normal, half, full)
|
||||
ScreenMode types.WindowMaximisation
|
||||
// The content shown on the bottom left of the screen when showing a loader
|
||||
// or toast e.g. 'Rebasing /'
|
||||
AppStatus string
|
||||
// The content shown on the bottom right of the screen (e.g. the 'donate',
|
||||
// 'ask question' links or a message about the current mode e.g. rebase mode)
|
||||
InformationStr string
|
||||
// Whether to show the extras window which contains the command log context
|
||||
ShowExtrasWindow bool
|
||||
// Whether we are in a demo (which is used for generating demo gifs for the
|
||||
// repo's readme)
|
||||
InDemo bool
|
||||
// Whether any mode is active (e.g. rebasing, cherry picking, etc)
|
||||
IsAnyModeActive bool
|
||||
// Whether the search prompt is shown in the bottom left
|
||||
InSearchPrompt bool
|
||||
// One of '' (not searching), 'Search: ', and 'Filter: '
|
||||
SearchPrefix string
|
||||
}
|
||||
|
||||
func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string, appStatus string) map[string]boxlayout.Dimensions {
|
||||
width, height := self.c.GocuiGui().Size()
|
||||
repoState := self.c.State().GetRepoState()
|
||||
|
||||
var searchPrefix string
|
||||
if repoState.GetSearchState().SearchType() == types.SearchTypeSearch {
|
||||
searchPrefix = self.c.Tr.SearchPrefix
|
||||
} else {
|
||||
searchPrefix = self.c.Tr.FilterPrefix
|
||||
}
|
||||
|
||||
args := WindowArrangementArgs{
|
||||
Width: width,
|
||||
Height: height,
|
||||
UserConfig: self.c.UserConfig,
|
||||
CurrentWindow: self.windowHelper.CurrentWindow(),
|
||||
CurrentSideWindow: self.c.CurrentSideContext().GetWindowName(),
|
||||
CurrentStaticWindow: self.c.CurrentStaticContext().GetWindowName(),
|
||||
SplitMainPanel: repoState.GetSplitMainPanel(),
|
||||
ScreenMode: repoState.GetScreenMode(),
|
||||
AppStatus: appStatus,
|
||||
InformationStr: informationStr,
|
||||
ShowExtrasWindow: self.c.State().GetShowExtrasWindow(),
|
||||
InDemo: self.c.InDemo(),
|
||||
IsAnyModeActive: self.modeHelper.IsAnyModeActive(),
|
||||
InSearchPrompt: repoState.InSearchPrompt(),
|
||||
SearchPrefix: searchPrefix,
|
||||
}
|
||||
|
||||
return GetWindowDimensions(args)
|
||||
}
|
||||
|
||||
func shouldUsePortraitMode(args WindowArrangementArgs) bool {
|
||||
switch args.UserConfig.Gui.PortraitMode {
|
||||
case "never":
|
||||
return false
|
||||
case "always":
|
||||
return true
|
||||
default: // "auto" or any garbage values in PortraitMode value
|
||||
return width <= 84 && height > 45
|
||||
return args.Width <= 84 && args.Height > 45
|
||||
}
|
||||
}
|
||||
|
||||
func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string, appStatus string) map[string]boxlayout.Dimensions {
|
||||
width, height := self.c.GocuiGui().Size()
|
||||
|
||||
sideSectionWeight, mainSectionWeight := self.getMidSectionWeights()
|
||||
func GetWindowDimensions(args WindowArrangementArgs) map[string]boxlayout.Dimensions {
|
||||
sideSectionWeight, mainSectionWeight := getMidSectionWeights(args)
|
||||
|
||||
sidePanelsDirection := boxlayout.COLUMN
|
||||
if self.shouldUsePortraitMode(width, height) {
|
||||
if shouldUsePortraitMode(args) {
|
||||
sidePanelsDirection = boxlayout.ROW
|
||||
}
|
||||
|
||||
mainPanelsDirection := boxlayout.ROW
|
||||
if self.splitMainPanelSideBySide() {
|
||||
if splitMainPanelSideBySide(args) {
|
||||
mainPanelsDirection = boxlayout.COLUMN
|
||||
}
|
||||
|
||||
extrasWindowSize := self.getExtrasWindowSize(height)
|
||||
extrasWindowSize := getExtrasWindowSize(args)
|
||||
|
||||
self.c.Modes().Filtering.Active()
|
||||
|
||||
showInfoSection := self.c.UserConfig.Gui.ShowBottomLine ||
|
||||
self.c.State().GetRepoState().InSearchPrompt() ||
|
||||
self.modeHelper.IsAnyModeActive() ||
|
||||
self.appStatusHelper.HasStatus()
|
||||
showInfoSection := args.UserConfig.Gui.ShowBottomLine ||
|
||||
args.InSearchPrompt ||
|
||||
args.IsAnyModeActive ||
|
||||
args.AppStatus != ""
|
||||
infoSectionSize := 0
|
||||
if showInfoSection {
|
||||
infoSectionSize = 1
|
||||
@ -85,7 +151,7 @@ func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string,
|
||||
{
|
||||
Direction: boxlayout.ROW,
|
||||
Weight: sideSectionWeight,
|
||||
ConditionalChildren: self.sidePanelChildren,
|
||||
ConditionalChildren: sidePanelChildren(args),
|
||||
},
|
||||
{
|
||||
Direction: boxlayout.ROW,
|
||||
@ -93,7 +159,7 @@ func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string,
|
||||
Children: []*boxlayout.Box{
|
||||
{
|
||||
Direction: mainPanelsDirection,
|
||||
Children: self.mainSectionChildren(),
|
||||
Children: mainSectionChildren(args),
|
||||
Weight: 1,
|
||||
},
|
||||
{
|
||||
@ -107,13 +173,13 @@ func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string,
|
||||
{
|
||||
Direction: boxlayout.COLUMN,
|
||||
Size: infoSectionSize,
|
||||
Children: self.infoSectionChildren(informationStr, appStatus),
|
||||
Children: infoSectionChildren(args),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
layerOneWindows := boxlayout.ArrangeWindows(root, 0, 0, width, height)
|
||||
limitWindows := boxlayout.ArrangeWindows(&boxlayout.Box{Window: "limit"}, 0, 0, width, height)
|
||||
layerOneWindows := boxlayout.ArrangeWindows(root, 0, 0, args.Width, args.Height)
|
||||
limitWindows := boxlayout.ArrangeWindows(&boxlayout.Box{Window: "limit"}, 0, 0, args.Width, args.Height)
|
||||
|
||||
return MergeMaps(layerOneWindows, limitWindows)
|
||||
}
|
||||
@ -129,12 +195,10 @@ func MergeMaps[K comparable, V any](maps ...map[K]V) map[K]V {
|
||||
return result
|
||||
}
|
||||
|
||||
func (self *WindowArrangementHelper) mainSectionChildren() []*boxlayout.Box {
|
||||
currentWindow := self.windowHelper.CurrentWindow()
|
||||
|
||||
func mainSectionChildren(args WindowArrangementArgs) []*boxlayout.Box {
|
||||
// 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
|
||||
if !self.c.State().GetRepoState().GetSplitMainPanel() || (self.c.State().GetRepoState().GetScreenMode() == types.SCREEN_FULL && currentWindow == "main") {
|
||||
if !args.SplitMainPanel || (args.ScreenMode == types.SCREEN_FULL && args.CurrentWindow == "main") {
|
||||
return []*boxlayout.Box{
|
||||
{
|
||||
Window: "main",
|
||||
@ -155,29 +219,25 @@ func (self *WindowArrangementHelper) mainSectionChildren() []*boxlayout.Box {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *WindowArrangementHelper) getMidSectionWeights() (int, int) {
|
||||
currentWindow := self.windowHelper.CurrentWindow()
|
||||
|
||||
func getMidSectionWeights(args WindowArrangementArgs) (int, int) {
|
||||
// we originally specified this as a ratio i.e. .20 would correspond to a weight of 1 against 4
|
||||
sidePanelWidthRatio := self.c.UserConfig.Gui.SidePanelWidth
|
||||
sidePanelWidthRatio := args.UserConfig.Gui.SidePanelWidth
|
||||
// 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() {
|
||||
if splitMainPanelSideBySide(args) {
|
||||
mainSectionWeight = 5 // need to shrink side panel to make way for main panels if side-by-side
|
||||
}
|
||||
|
||||
screenMode := self.c.State().GetRepoState().GetScreenMode()
|
||||
|
||||
if currentWindow == "main" {
|
||||
if screenMode == types.SCREEN_HALF || screenMode == types.SCREEN_FULL {
|
||||
if args.CurrentWindow == "main" {
|
||||
if args.ScreenMode == types.SCREEN_HALF || args.ScreenMode == types.SCREEN_FULL {
|
||||
sideSectionWeight = 0
|
||||
}
|
||||
} else {
|
||||
if screenMode == types.SCREEN_HALF {
|
||||
if args.ScreenMode == types.SCREEN_HALF {
|
||||
mainSectionWeight = 1
|
||||
} else if screenMode == types.SCREEN_FULL {
|
||||
} else if args.ScreenMode == types.SCREEN_FULL {
|
||||
mainSectionWeight = 0
|
||||
}
|
||||
}
|
||||
@ -185,18 +245,12 @@ func (self *WindowArrangementHelper) getMidSectionWeights() (int, int) {
|
||||
return sideSectionWeight, mainSectionWeight
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
func infoSectionChildren(args WindowArrangementArgs) []*boxlayout.Box {
|
||||
if args.InSearchPrompt {
|
||||
return []*boxlayout.Box{
|
||||
{
|
||||
Window: "searchPrefix",
|
||||
Size: runewidth.StringWidth(prefix),
|
||||
Size: runewidth.StringWidth(args.SearchPrefix),
|
||||
},
|
||||
{
|
||||
Window: "search",
|
||||
@ -245,24 +299,24 @@ func (self *WindowArrangementHelper) infoSectionChildren(informationStr string,
|
||||
// between at the end
|
||||
var result []*boxlayout.Box
|
||||
|
||||
if !self.c.InDemo() {
|
||||
if !args.InDemo {
|
||||
// app status appears very briefly in demos and dislodges the caption,
|
||||
// so better not to show it at all
|
||||
if appStatus != "" {
|
||||
result = append(result, &boxlayout.Box{Window: "appStatus", Size: runewidth.StringWidth(appStatus)})
|
||||
if args.AppStatus != "" {
|
||||
result = append(result, &boxlayout.Box{Window: "appStatus", Size: runewidth.StringWidth(args.AppStatus)})
|
||||
}
|
||||
}
|
||||
|
||||
if self.c.UserConfig.Gui.ShowBottomLine {
|
||||
if args.UserConfig.Gui.ShowBottomLine {
|
||||
result = append(result, &boxlayout.Box{Window: "options", Weight: 1})
|
||||
}
|
||||
|
||||
if (!self.c.InDemo() && self.c.UserConfig.Gui.ShowBottomLine) || self.modeHelper.IsAnyModeActive() {
|
||||
if (!args.InDemo && args.UserConfig.Gui.ShowBottomLine) || args.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(utils.Decolorise(informationStr)),
|
||||
Size: runewidth.StringWidth(utils.Decolorise(args.InformationStr)),
|
||||
})
|
||||
}
|
||||
|
||||
@ -295,21 +349,19 @@ func (self *WindowArrangementHelper) infoSectionChildren(informationStr string,
|
||||
return result
|
||||
}
|
||||
|
||||
func (self *WindowArrangementHelper) splitMainPanelSideBySide() bool {
|
||||
if !self.c.State().GetRepoState().GetSplitMainPanel() {
|
||||
func splitMainPanelSideBySide(args WindowArrangementArgs) bool {
|
||||
if !args.SplitMainPanel {
|
||||
return false
|
||||
}
|
||||
|
||||
mainPanelSplitMode := self.c.UserConfig.Gui.MainPanelSplitMode
|
||||
width, height := self.c.GocuiGui().Size()
|
||||
|
||||
mainPanelSplitMode := args.UserConfig.Gui.MainPanelSplitMode
|
||||
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
|
||||
if args.Width < 200 && args.Height > 30 { // 2 80 character width panels + 40 width for side panel
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
@ -317,18 +369,19 @@ func (self *WindowArrangementHelper) splitMainPanelSideBySide() bool {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *WindowArrangementHelper) getExtrasWindowSize(screenHeight int) int {
|
||||
if !self.c.State().GetShowExtrasWindow() {
|
||||
func getExtrasWindowSize(args WindowArrangementArgs) int {
|
||||
if !args.ShowExtrasWindow {
|
||||
return 0
|
||||
}
|
||||
|
||||
var baseSize int
|
||||
if self.c.CurrentStaticContext().GetKey() == context.COMMAND_LOG_CONTEXT_KEY {
|
||||
// The 'extras' window contains the command log context
|
||||
if args.CurrentStaticWindow == "extras" {
|
||||
baseSize = 1000 // my way of saying 'fill the available space'
|
||||
} else if screenHeight < 40 {
|
||||
} else if args.Height < 40 {
|
||||
baseSize = 1
|
||||
} else {
|
||||
baseSize = self.c.UserConfig.Gui.CommandLogSize
|
||||
baseSize = args.UserConfig.Gui.CommandLogSize
|
||||
}
|
||||
|
||||
frameSize := 2
|
||||
@ -339,17 +392,10 @@ func (self *WindowArrangementHelper) getExtrasWindowSize(screenHeight int) int {
|
||||
// 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
|
||||
// then when it's accessed it will have weight 2, not 1.
|
||||
func (self *WindowArrangementHelper) getDefaultStashWindowBox() *boxlayout.Box {
|
||||
stashWindowAccessed := false
|
||||
self.c.Context().ForEach(func(context types.Context) {
|
||||
if context.GetWindowName() == "stash" {
|
||||
stashWindowAccessed = true
|
||||
}
|
||||
})
|
||||
|
||||
func getDefaultStashWindowBox(args WindowArrangementArgs) *boxlayout.Box {
|
||||
box := &boxlayout.Box{Window: "stash"}
|
||||
// if the stash window is anywhere in our stack we should enlargen it
|
||||
if stashWindowAccessed {
|
||||
if args.CurrentSideWindow == "stash" {
|
||||
box.Weight = 1
|
||||
} else {
|
||||
box.Size = 3
|
||||
@ -358,81 +404,80 @@ func (self *WindowArrangementHelper) getDefaultStashWindowBox() *boxlayout.Box {
|
||||
return box
|
||||
}
|
||||
|
||||
func (self *WindowArrangementHelper) sidePanelChildren(width int, height int) []*boxlayout.Box {
|
||||
currentWindow := self.c.CurrentSideContext().GetWindowName()
|
||||
|
||||
screenMode := self.c.State().GetRepoState().GetScreenMode()
|
||||
if screenMode == types.SCREEN_FULL || screenMode == types.SCREEN_HALF {
|
||||
fullHeightBox := func(window string) *boxlayout.Box {
|
||||
if window == currentWindow {
|
||||
return &boxlayout.Box{
|
||||
Window: window,
|
||||
Weight: 1,
|
||||
}
|
||||
} else {
|
||||
return &boxlayout.Box{
|
||||
Window: window,
|
||||
Size: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return []*boxlayout.Box{
|
||||
fullHeightBox("status"),
|
||||
fullHeightBox("files"),
|
||||
fullHeightBox("branches"),
|
||||
fullHeightBox("commits"),
|
||||
fullHeightBox("stash"),
|
||||
}
|
||||
} else if height >= 28 {
|
||||
accordionMode := self.c.UserConfig.Gui.ExpandFocusedSidePanel
|
||||
accordionBox := func(defaultBox *boxlayout.Box) *boxlayout.Box {
|
||||
if accordionMode && defaultBox.Window == currentWindow {
|
||||
return &boxlayout.Box{
|
||||
Window: defaultBox.Window,
|
||||
Weight: 2,
|
||||
func sidePanelChildren(args WindowArrangementArgs) func(width int, height int) []*boxlayout.Box {
|
||||
return func(width int, height int) []*boxlayout.Box {
|
||||
if args.ScreenMode == types.SCREEN_FULL || args.ScreenMode == types.SCREEN_HALF {
|
||||
fullHeightBox := func(window string) *boxlayout.Box {
|
||||
if window == args.CurrentSideWindow {
|
||||
return &boxlayout.Box{
|
||||
Window: window,
|
||||
Weight: 1,
|
||||
}
|
||||
} else {
|
||||
return &boxlayout.Box{
|
||||
Window: window,
|
||||
Size: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return defaultBox
|
||||
}
|
||||
|
||||
return []*boxlayout.Box{
|
||||
{
|
||||
Window: "status",
|
||||
Size: 3,
|
||||
},
|
||||
accordionBox(&boxlayout.Box{Window: "files", Weight: 1}),
|
||||
accordionBox(&boxlayout.Box{Window: "branches", Weight: 1}),
|
||||
accordionBox(&boxlayout.Box{Window: "commits", Weight: 1}),
|
||||
accordionBox(self.getDefaultStashWindowBox()),
|
||||
}
|
||||
} else {
|
||||
squashedHeight := 1
|
||||
if height >= 21 {
|
||||
squashedHeight = 3
|
||||
}
|
||||
|
||||
squashedSidePanelBox := func(window string) *boxlayout.Box {
|
||||
if window == currentWindow {
|
||||
return &boxlayout.Box{
|
||||
Window: window,
|
||||
Weight: 1,
|
||||
return []*boxlayout.Box{
|
||||
fullHeightBox("status"),
|
||||
fullHeightBox("files"),
|
||||
fullHeightBox("branches"),
|
||||
fullHeightBox("commits"),
|
||||
fullHeightBox("stash"),
|
||||
}
|
||||
} else if height >= 28 {
|
||||
accordionMode := args.UserConfig.Gui.ExpandFocusedSidePanel
|
||||
accordionBox := func(defaultBox *boxlayout.Box) *boxlayout.Box {
|
||||
if accordionMode && defaultBox.Window == args.CurrentSideWindow {
|
||||
return &boxlayout.Box{
|
||||
Window: defaultBox.Window,
|
||||
Weight: 2,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return &boxlayout.Box{
|
||||
Window: window,
|
||||
Size: squashedHeight,
|
||||
|
||||
return defaultBox
|
||||
}
|
||||
|
||||
return []*boxlayout.Box{
|
||||
{
|
||||
Window: "status",
|
||||
Size: 3,
|
||||
},
|
||||
accordionBox(&boxlayout.Box{Window: "files", Weight: 1}),
|
||||
accordionBox(&boxlayout.Box{Window: "branches", Weight: 1}),
|
||||
accordionBox(&boxlayout.Box{Window: "commits", Weight: 1}),
|
||||
accordionBox(getDefaultStashWindowBox(args)),
|
||||
}
|
||||
} else {
|
||||
squashedHeight := 1
|
||||
if height >= 21 {
|
||||
squashedHeight = 3
|
||||
}
|
||||
|
||||
squashedSidePanelBox := func(window string) *boxlayout.Box {
|
||||
if window == args.CurrentSideWindow {
|
||||
return &boxlayout.Box{
|
||||
Window: window,
|
||||
Weight: 1,
|
||||
}
|
||||
} else {
|
||||
return &boxlayout.Box{
|
||||
Window: window,
|
||||
Size: squashedHeight,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return []*boxlayout.Box{
|
||||
squashedSidePanelBox("status"),
|
||||
squashedSidePanelBox("files"),
|
||||
squashedSidePanelBox("branches"),
|
||||
squashedSidePanelBox("commits"),
|
||||
squashedSidePanelBox("stash"),
|
||||
return []*boxlayout.Box{
|
||||
squashedSidePanelBox("status"),
|
||||
squashedSidePanelBox("files"),
|
||||
squashedSidePanelBox("branches"),
|
||||
squashedSidePanelBox("commits"),
|
||||
squashedSidePanelBox("stash"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user