1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-02-09 13:47:11 +02:00

Layout the bottom line view using spacer views

We are also removing the single-character padding on the left/right edges of the bottom
line because it's unnecessary

Unfortunately we need to create views for each spacer: it's not enough to just
layout the existing views with padding inbetween because gocui only renders
views meaning if there is no view in a given position, that position will just
render whatever was there previously (at least that's what I recall from talking
this through with Stefan: I could be way off).

Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
This commit is contained in:
Jesse Duffield 2023-12-02 12:45:15 +11:00 committed by Stefan Haller
parent 8cc820668a
commit b96befa250
5 changed files with 115 additions and 37 deletions

View File

@ -31,11 +31,13 @@ const (
MERGE_CONFLICTS_CONTEXT_KEY types.ContextKey = "mergeConflicts"
// these shouldn't really be needed for anything but I'm giving them unique keys nonetheless
OPTIONS_CONTEXT_KEY types.ContextKey = "options"
APP_STATUS_CONTEXT_KEY types.ContextKey = "appStatus"
SEARCH_PREFIX_CONTEXT_KEY types.ContextKey = "searchPrefix"
INFORMATION_CONTEXT_KEY types.ContextKey = "information"
LIMIT_CONTEXT_KEY types.ContextKey = "limit"
OPTIONS_CONTEXT_KEY types.ContextKey = "options"
APP_STATUS_CONTEXT_KEY types.ContextKey = "appStatus"
SEARCH_PREFIX_CONTEXT_KEY types.ContextKey = "searchPrefix"
INFORMATION_CONTEXT_KEY types.ContextKey = "information"
LIMIT_CONTEXT_KEY types.ContextKey = "limit"
STATUS_SPACER1_CONTEXT_KEY types.ContextKey = "statusSpacer1"
STATUS_SPACER2_CONTEXT_KEY types.ContextKey = "statusSpacer2"
MENU_CONTEXT_KEY types.ContextKey = "menu"
CONFIRMATION_CONTEXT_KEY types.ContextKey = "confirmation"
@ -109,12 +111,14 @@ type ContextTree struct {
CommandLog types.Context
// display contexts
AppStatus types.Context
Options types.Context
SearchPrefix types.Context
Search types.Context
Information types.Context
Limit types.Context
AppStatus types.Context
Options types.Context
SearchPrefix types.Context
Search types.Context
Information types.Context
Limit types.Context
StatusSpacer1 types.Context
StatusSpacer2 types.Context
}
// the order of this decides which context is initially at the top of its window
@ -156,6 +160,8 @@ func (self *ContextTree) Flatten() []types.Context {
self.Search,
self.Information,
self.Limit,
self.StatusSpacer1,
self.StatusSpacer2,
}
}

View File

@ -138,10 +138,12 @@ func NewContextTree(c *ContextCommon) *ContextTree {
Focusable: true,
}),
),
Options: NewDisplayContext(OPTIONS_CONTEXT_KEY, c.Views().Options, "options"),
AppStatus: NewDisplayContext(APP_STATUS_CONTEXT_KEY, c.Views().AppStatus, "appStatus"),
SearchPrefix: NewDisplayContext(SEARCH_PREFIX_CONTEXT_KEY, c.Views().SearchPrefix, "searchPrefix"),
Information: NewDisplayContext(INFORMATION_CONTEXT_KEY, c.Views().Information, "information"),
Limit: NewDisplayContext(LIMIT_CONTEXT_KEY, c.Views().Limit, "limit"),
Options: NewDisplayContext(OPTIONS_CONTEXT_KEY, c.Views().Options, "options"),
AppStatus: NewDisplayContext(APP_STATUS_CONTEXT_KEY, c.Views().AppStatus, "appStatus"),
SearchPrefix: NewDisplayContext(SEARCH_PREFIX_CONTEXT_KEY, c.Views().SearchPrefix, "searchPrefix"),
Information: NewDisplayContext(INFORMATION_CONTEXT_KEY, c.Views().Information, "information"),
Limit: NewDisplayContext(LIMIT_CONTEXT_KEY, c.Views().Limit, "limit"),
StatusSpacer1: NewDisplayContext(STATUS_SPACER1_CONTEXT_KEY, c.Views().StatusSpacer1, "statusSpacer1"),
StatusSpacer2: NewDisplayContext(STATUS_SPACER2_CONTEXT_KEY, c.Views().StatusSpacer2, "statusSpacer2"),
}
}

View File

@ -1,11 +1,15 @@
package helpers
import (
"fmt"
"strings"
"github.com/jesseduffield/lazycore/pkg/boxlayout"
"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"
"golang.org/x/exp/slices"
)
// In this file we use the boxlayout package, along with knowledge about the app's state,
@ -32,8 +36,6 @@ func NewWindowArrangementHelper(
}
}
const INFO_SECTION_PADDING = " "
func (self *WindowArrangementHelper) shouldUsePortraitMode(width, height int) bool {
switch self.c.UserConfig.Gui.PortraitMode {
case "never":
@ -203,31 +205,91 @@ func (self *WindowArrangementHelper) infoSectionChildren(informationStr string,
}
}
appStatusBox := &boxlayout.Box{Window: "appStatus"}
optionsBox := &boxlayout.Box{Window: "options"}
statusSpacerPrefix := "statusSpacer"
spacerBoxIndex := 0
maxSpacerBoxIndex := 2 // See pkg/gui/types/views.go
// Returns a box with size 1 to be used as padding between views
spacerBox := func() *boxlayout.Box {
spacerBoxIndex++
if !self.c.UserConfig.Gui.ShowBottomLine {
optionsBox.Weight = 0
appStatusBox.Weight = 1
} else {
optionsBox.Weight = 1
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)
if spacerBoxIndex > maxSpacerBoxIndex {
panic("Too many spacer boxes")
}
return &boxlayout.Box{Window: fmt.Sprintf("%s%d", statusSpacerPrefix, spacerBoxIndex), Size: 1}
}
// Returns a box with weight 1 to be used as flexible padding between views
flexibleSpacerBox := func() *boxlayout.Box {
spacerBoxIndex++
if spacerBoxIndex > maxSpacerBoxIndex {
panic("Too many spacer boxes")
}
return &boxlayout.Box{Window: fmt.Sprintf("%s%d", statusSpacerPrefix, spacerBoxIndex), Weight: 1}
}
// Adds spacer boxes inbetween given boxes
insertSpacerBoxes := func(boxes []*boxlayout.Box) []*boxlayout.Box {
for i := len(boxes) - 1; i >= 1; i-- {
// ignore existing spacer boxes
if !strings.HasPrefix(boxes[i].Window, statusSpacerPrefix) {
boxes = slices.Insert(boxes, i, spacerBox())
}
}
return boxes
}
// First collect the real views that we want to show, we'll add spacers in
// between at the end
var result []*boxlayout.Box
if !self.c.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)})
}
}
result := []*boxlayout.Box{appStatusBox, optionsBox}
if self.c.UserConfig.Gui.ShowBottomLine {
result = append(result, &boxlayout.Box{Window: "options", Weight: 1})
}
if (!self.c.InDemo() && 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)),
})
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)),
})
}
if len(result) == 2 && result[0].Window == "appStatus" {
// Only status and information are showing; need to insert a flexible
// spacer between the two, so that information is right-aligned. Note
// that the call to insertSpacerBoxes below will still insert a 1-char
// spacer in addition (right after the flexible one); this is needed for
// the case that there's not enough room, to ensure there's always at
// least one space.
result = slices.Insert(result, 1, flexibleSpacerBox())
} else if len(result) == 1 {
if result[0].Window == "information" {
// Only information is showing; need to add a flexible spacer so
// that information is right-aligned
result = slices.Insert(result, 0, flexibleSpacerBox())
} else {
// Only status is showing; need to make it flexible so that it
// extends over the whole width
result[0].Size = 0
result[0].Weight = 1
}
}
if len(result) > 0 {
// If we have at least one view, insert 1-char wide spacer boxes between them.
result = insertSpacerBoxes(result)
}
return result

View File

@ -34,6 +34,8 @@ type Views struct {
AppStatus *gocui.View
Search *gocui.View
SearchPrefix *gocui.View
StatusSpacer1 *gocui.View
StatusSpacer2 *gocui.View
Limit *gocui.View
Suggestions *gocui.View
Tooltip *gocui.View

View File

@ -55,6 +55,9 @@ func (gui *Gui) orderedViewNameMappings() []viewNameMapping {
{viewPtr: &gui.Views.Search, name: "search"},
// this view shows either the "Search:" prompt when searching, or the "Filter:" prompt when filtering
{viewPtr: &gui.Views.SearchPrefix, name: "searchPrefix"},
// these views contain one space, and are used as spacers between the various views in the bottom line
{viewPtr: &gui.Views.StatusSpacer1, name: "statusSpacer1"},
{viewPtr: &gui.Views.StatusSpacer2, name: "statusSpacer2"},
// popups.
{viewPtr: &gui.Views.CommitMessage, name: "commitMessage"},
@ -98,6 +101,9 @@ func (gui *Gui) createAllViews() error {
gui.Views.SearchPrefix.Frame = false
gui.c.SetViewContent(gui.Views.SearchPrefix, gui.Tr.SearchPrefix)
gui.Views.StatusSpacer1.Frame = false
gui.Views.StatusSpacer2.Frame = false
gui.Views.Search.BgColor = gocui.ColorDefault
gui.Views.Search.FgColor = gocui.ColorCyan
gui.Views.Search.Editable = true