mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-07-17 01:42:45 +02:00
Truncate branch names to make branch status always visible (#3075)
This commit is contained in:
@ -97,6 +97,10 @@ To run gofumpt from your terminal go:
|
|||||||
go install mvdan.cc/gofumpt@latest && gofumpt -l -w .
|
go install mvdan.cc/gofumpt@latest && gofumpt -l -w .
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Programming Font
|
||||||
|
|
||||||
|
Lazygit supports [Nerd Fonts](https://www.nerdfonts.com) to render certain icons. Sometimes we use some of these icons verbatim in string literals in the code (mainly in tests), so you need to set your development environment to use a nerd font to see these.
|
||||||
|
|
||||||
## Internationalisation
|
## Internationalisation
|
||||||
|
|
||||||
Boy that's a hard word to spell. Anyway, lazygit is translated into several languages within the pkg/i18n package. If you need to render text to the user, you should add a new field to the TranslationSet struct in `pkg/i18n/english.go` and add the actual content within the `EnglishTranslationSet()` method in the same file. Then you can access via `gui.Tr.YourNewText` (or `self.c.Tr.YourNewText`, etc). Although it is appreciated if you translate the text into other languages, it's not expected of you (google translate will likely do a bad job anyway!).
|
Boy that's a hard word to spell. Anyway, lazygit is translated into several languages within the pkg/i18n package. If you need to render text to the user, you should add a new field to the TranslationSet struct in `pkg/i18n/english.go` and add the actual content within the `EnglishTranslationSet()` method in the same file. Then you can access via `gui.Tr.YourNewText` (or `self.c.Tr.YourNewText`, etc). Although it is appreciated if you translate the text into other languages, it's not expected of you (google translate will likely do a bad job anyway!).
|
||||||
|
@ -23,6 +23,7 @@ type BaseContext struct {
|
|||||||
focusable bool
|
focusable bool
|
||||||
transient bool
|
transient bool
|
||||||
hasControlledBounds bool
|
hasControlledBounds bool
|
||||||
|
needsRerenderOnWidthChange bool
|
||||||
highlightOnFocus bool
|
highlightOnFocus bool
|
||||||
|
|
||||||
*ParentContextMgr
|
*ParentContextMgr
|
||||||
@ -44,6 +45,7 @@ type NewBaseContextOpts struct {
|
|||||||
Transient bool
|
Transient bool
|
||||||
HasUncontrolledBounds bool // negating for the sake of making false the default
|
HasUncontrolledBounds bool // negating for the sake of making false the default
|
||||||
HighlightOnFocus bool
|
HighlightOnFocus bool
|
||||||
|
NeedsRerenderOnWidthChange bool
|
||||||
|
|
||||||
OnGetOptionsMap func() map[string]string
|
OnGetOptionsMap func() map[string]string
|
||||||
}
|
}
|
||||||
@ -63,6 +65,7 @@ func NewBaseContext(opts NewBaseContextOpts) *BaseContext {
|
|||||||
transient: opts.Transient,
|
transient: opts.Transient,
|
||||||
hasControlledBounds: hasControlledBounds,
|
hasControlledBounds: hasControlledBounds,
|
||||||
highlightOnFocus: opts.HighlightOnFocus,
|
highlightOnFocus: opts.HighlightOnFocus,
|
||||||
|
needsRerenderOnWidthChange: opts.NeedsRerenderOnWidthChange,
|
||||||
ParentContextMgr: &ParentContextMgr{},
|
ParentContextMgr: &ParentContextMgr{},
|
||||||
viewTrait: viewTrait,
|
viewTrait: viewTrait,
|
||||||
}
|
}
|
||||||
@ -190,6 +193,10 @@ func (self *BaseContext) HasControlledBounds() bool {
|
|||||||
return self.hasControlledBounds
|
return self.hasControlledBounds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *BaseContext) NeedsRerenderOnWidthChange() bool {
|
||||||
|
return self.needsRerenderOnWidthChange
|
||||||
|
}
|
||||||
|
|
||||||
func (self *BaseContext) Title() string {
|
func (self *BaseContext) Title() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
|
|||||||
c.State().GetItemOperation,
|
c.State().GetItemOperation,
|
||||||
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
|
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
|
||||||
c.Modes().Diffing.Ref,
|
c.Modes().Diffing.Ref,
|
||||||
|
c.Views().Branches.Width(),
|
||||||
c.Tr,
|
c.Tr,
|
||||||
c.UserConfig,
|
c.UserConfig,
|
||||||
c.Model().Worktrees,
|
c.Model().Worktrees,
|
||||||
@ -45,6 +46,7 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
|
|||||||
Key: LOCAL_BRANCHES_CONTEXT_KEY,
|
Key: LOCAL_BRANCHES_CONTEXT_KEY,
|
||||||
Kind: types.SIDE_CONTEXT,
|
Kind: types.SIDE_CONTEXT,
|
||||||
Focusable: true,
|
Focusable: true,
|
||||||
|
NeedsRerenderOnWidthChange: true,
|
||||||
})),
|
})),
|
||||||
ListRenderer: ListRenderer{
|
ListRenderer: ListRenderer{
|
||||||
list: viewModel,
|
list: viewModel,
|
||||||
|
@ -73,6 +73,7 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
|
|||||||
Key: LOCAL_COMMITS_CONTEXT_KEY,
|
Key: LOCAL_COMMITS_CONTEXT_KEY,
|
||||||
Kind: types.SIDE_CONTEXT,
|
Kind: types.SIDE_CONTEXT,
|
||||||
Focusable: true,
|
Focusable: true,
|
||||||
|
NeedsRerenderOnWidthChange: true,
|
||||||
})),
|
})),
|
||||||
ListRenderer: ListRenderer{
|
ListRenderer: ListRenderer{
|
||||||
list: viewModel,
|
list: viewModel,
|
||||||
|
@ -48,6 +48,7 @@ func NewReflogCommitsContext(c *ContextCommon) *ReflogCommitsContext {
|
|||||||
Key: REFLOG_COMMITS_CONTEXT_KEY,
|
Key: REFLOG_COMMITS_CONTEXT_KEY,
|
||||||
Kind: types.SIDE_CONTEXT,
|
Kind: types.SIDE_CONTEXT,
|
||||||
Focusable: true,
|
Focusable: true,
|
||||||
|
NeedsRerenderOnWidthChange: true,
|
||||||
})),
|
})),
|
||||||
ListRenderer: ListRenderer{
|
ListRenderer: ListRenderer{
|
||||||
list: viewModel,
|
list: viewModel,
|
||||||
|
@ -42,6 +42,7 @@ func NewRemoteBranchesContext(
|
|||||||
Kind: types.SIDE_CONTEXT,
|
Kind: types.SIDE_CONTEXT,
|
||||||
Focusable: true,
|
Focusable: true,
|
||||||
Transient: true,
|
Transient: true,
|
||||||
|
NeedsRerenderOnWidthChange: true,
|
||||||
})),
|
})),
|
||||||
ListRenderer: ListRenderer{
|
ListRenderer: ListRenderer{
|
||||||
list: viewModel,
|
list: viewModel,
|
||||||
|
@ -121,6 +121,7 @@ func NewSubCommitsContext(
|
|||||||
Kind: types.SIDE_CONTEXT,
|
Kind: types.SIDE_CONTEXT,
|
||||||
Focusable: true,
|
Focusable: true,
|
||||||
Transient: true,
|
Transient: true,
|
||||||
|
NeedsRerenderOnWidthChange: true,
|
||||||
})),
|
})),
|
||||||
ListRenderer: ListRenderer{
|
ListRenderer: ListRenderer{
|
||||||
list: viewModel,
|
list: viewModel,
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jesseduffield/gocui"
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,7 +16,7 @@ func (self *ScreenModeActions) Next() error {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.rerenderViewsWithScreenModeDependentContent()
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ScreenModeActions) Prev() error {
|
func (self *ScreenModeActions) Prev() error {
|
||||||
@ -28,32 +27,9 @@ func (self *ScreenModeActions) Prev() error {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.rerenderViewsWithScreenModeDependentContent()
|
|
||||||
}
|
|
||||||
|
|
||||||
// these views need to be re-rendered when the screen mode changes. The commits view,
|
|
||||||
// for example, will show authorship information in half and full screen mode.
|
|
||||||
func (self *ScreenModeActions) rerenderViewsWithScreenModeDependentContent() error {
|
|
||||||
// for now we re-render all list views.
|
|
||||||
for _, context := range self.c.Context().AllList() {
|
|
||||||
if err := self.rerenderView(context.GetView()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ScreenModeActions) rerenderView(view *gocui.View) error {
|
|
||||||
context, ok := self.c.Helpers().View.ContextForView(view.Name())
|
|
||||||
if !ok {
|
|
||||||
self.c.Log.Errorf("no context found for view %s", view.Name())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return context.HandleRender()
|
|
||||||
}
|
|
||||||
|
|
||||||
func nextIntInCycle(sl []types.WindowMaximisation, current types.WindowMaximisation) types.WindowMaximisation {
|
func nextIntInCycle(sl []types.WindowMaximisation, current types.WindowMaximisation) types.WindowMaximisation {
|
||||||
for i, val := range sl {
|
for i, val := range sl {
|
||||||
if val == current {
|
if val == current {
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||||
"github.com/jesseduffield/lazygit/pkg/constants"
|
"github.com/jesseduffield/lazygit/pkg/constants"
|
||||||
@ -106,7 +107,7 @@ func (self *StatusController) onClick() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cx, _ := self.c.Views().Status.Cursor()
|
cx, _ := self.c.Views().Status.Cursor()
|
||||||
upstreamStatus := presentation.BranchStatus(currentBranch, types.ItemOperationNone, self.c.Tr)
|
upstreamStatus := presentation.BranchStatus(currentBranch, types.ItemOperationNone, self.c.Tr, time.Now())
|
||||||
repoName := self.c.Git().RepoPaths.RepoName()
|
repoName := self.c.Git().RepoPaths.RepoName()
|
||||||
workingTreeState := self.c.Git().Status.WorkingTreeState()
|
workingTreeState := self.c.Git().Status.WorkingTreeState()
|
||||||
switch workingTreeState {
|
switch workingTreeState {
|
||||||
|
@ -44,8 +44,13 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contextsToRerender := []types.Context{}
|
||||||
|
|
||||||
// we assume that the view has already been created.
|
// we assume that the view has already been created.
|
||||||
setViewFromDimensions := func(viewName string, windowName string) (*gocui.View, error) {
|
setViewFromDimensions := func(context types.Context) (*gocui.View, error) {
|
||||||
|
viewName := context.GetViewName()
|
||||||
|
windowName := context.GetWindowName()
|
||||||
|
|
||||||
dimensionsObj, ok := viewDimensions[windowName]
|
dimensionsObj, ok := viewDimensions[windowName]
|
||||||
|
|
||||||
view, err := g.View(viewName)
|
view, err := g.View(viewName)
|
||||||
@ -67,6 +72,16 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
if view.Frame {
|
if view.Frame {
|
||||||
frameOffset = 0
|
frameOffset = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if context.NeedsRerenderOnWidthChange() {
|
||||||
|
// view.Width() returns the width -1 for some reason
|
||||||
|
oldWidth := view.Width() + 1
|
||||||
|
newWidth := dimensionsObj.X1 - dimensionsObj.X0 + 2*frameOffset
|
||||||
|
if oldWidth != newWidth {
|
||||||
|
contextsToRerender = append(contextsToRerender, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_, err = g.SetView(
|
_, err = g.SetView(
|
||||||
viewName,
|
viewName,
|
||||||
dimensionsObj.X0-frameOffset,
|
dimensionsObj.X0-frameOffset,
|
||||||
@ -85,7 +100,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := setViewFromDimensions(context.GetViewName(), context.GetWindowName())
|
_, err := setViewFromDimensions(context)
|
||||||
if err != nil && !gocui.IsUnknownView(err) {
|
if err != nil && !gocui.IsUnknownView(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -146,6 +161,12 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, context := range contextsToRerender {
|
||||||
|
if err := context.HandleRender(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// here is a good place log some stuff
|
// here is a good place log some stuff
|
||||||
// if you run `lazygit --logs`
|
// if you run `lazygit --logs`
|
||||||
// this will let you see these branches as prettified json
|
// this will let you see these branches as prettified json
|
||||||
|
@ -3,6 +3,7 @@ package presentation
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
@ -13,6 +14,7 @@ import (
|
|||||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
|
"github.com/mattn/go-runewidth"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -23,13 +25,14 @@ func GetBranchListDisplayStrings(
|
|||||||
getItemOperation func(item types.HasUrn) types.ItemOperation,
|
getItemOperation func(item types.HasUrn) types.ItemOperation,
|
||||||
fullDescription bool,
|
fullDescription bool,
|
||||||
diffName string,
|
diffName string,
|
||||||
|
viewWidth int,
|
||||||
tr *i18n.TranslationSet,
|
tr *i18n.TranslationSet,
|
||||||
userConfig *config.UserConfig,
|
userConfig *config.UserConfig,
|
||||||
worktrees []*models.Worktree,
|
worktrees []*models.Worktree,
|
||||||
) [][]string {
|
) [][]string {
|
||||||
return lo.Map(branches, func(branch *models.Branch, _ int) []string {
|
return lo.Map(branches, func(branch *models.Branch, _ int) []string {
|
||||||
diffed := branch.Name == diffName
|
diffed := branch.Name == diffName
|
||||||
return getBranchDisplayStrings(branch, getItemOperation(branch), fullDescription, diffed, tr, userConfig, worktrees)
|
return getBranchDisplayStrings(branch, getItemOperation(branch), fullDescription, diffed, viewWidth, tr, userConfig, worktrees, time.Now())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,10 +42,32 @@ func getBranchDisplayStrings(
|
|||||||
itemOperation types.ItemOperation,
|
itemOperation types.ItemOperation,
|
||||||
fullDescription bool,
|
fullDescription bool,
|
||||||
diffed bool,
|
diffed bool,
|
||||||
|
viewWidth int,
|
||||||
tr *i18n.TranslationSet,
|
tr *i18n.TranslationSet,
|
||||||
userConfig *config.UserConfig,
|
userConfig *config.UserConfig,
|
||||||
worktrees []*models.Worktree,
|
worktrees []*models.Worktree,
|
||||||
|
now time.Time,
|
||||||
) []string {
|
) []string {
|
||||||
|
checkedOutByWorkTree := git_commands.CheckedOutByOtherWorktree(b, worktrees)
|
||||||
|
showCommitHash := fullDescription || userConfig.Gui.ShowBranchCommitHash
|
||||||
|
branchStatus := BranchStatus(b, itemOperation, tr, now)
|
||||||
|
worktreeIcon := lo.Ternary(icons.IsIconEnabled(), icons.LINKED_WORKTREE_ICON, fmt.Sprintf("(%s)", tr.LcWorktree))
|
||||||
|
|
||||||
|
// Recency is always three characters, plus one for the space
|
||||||
|
availableWidth := viewWidth - 4
|
||||||
|
if len(branchStatus) > 0 {
|
||||||
|
availableWidth -= runewidth.StringWidth(branchStatus) + 1
|
||||||
|
}
|
||||||
|
if icons.IsIconEnabled() {
|
||||||
|
availableWidth -= 2 // one for the icon, one for the space
|
||||||
|
}
|
||||||
|
if showCommitHash {
|
||||||
|
availableWidth -= utils.COMMIT_HASH_SHORT_SIZE + 1
|
||||||
|
}
|
||||||
|
if checkedOutByWorkTree {
|
||||||
|
availableWidth -= runewidth.StringWidth(worktreeIcon) + 1
|
||||||
|
}
|
||||||
|
|
||||||
displayName := b.Name
|
displayName := b.Name
|
||||||
if b.DisplayName != "" {
|
if b.DisplayName != "" {
|
||||||
displayName = b.DisplayName
|
displayName = b.DisplayName
|
||||||
@ -53,13 +78,19 @@ func getBranchDisplayStrings(
|
|||||||
nameTextStyle = theme.DiffTerminalColor
|
nameTextStyle = theme.DiffTerminalColor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(displayName) > availableWidth {
|
||||||
|
// Never shorten the branch name to less then 3 characters
|
||||||
|
len := utils.Max(availableWidth, 4)
|
||||||
|
displayName = displayName[:len-1] + "…"
|
||||||
|
}
|
||||||
coloredName := nameTextStyle.Sprint(displayName)
|
coloredName := nameTextStyle.Sprint(displayName)
|
||||||
branchStatus := utils.WithPadding(ColoredBranchStatus(b, itemOperation, tr), 2, utils.AlignLeft)
|
if checkedOutByWorkTree {
|
||||||
if git_commands.CheckedOutByOtherWorktree(b, worktrees) {
|
|
||||||
worktreeIcon := lo.Ternary(icons.IsIconEnabled(), icons.LINKED_WORKTREE_ICON, fmt.Sprintf("(%s)", tr.LcWorktree))
|
|
||||||
coloredName = fmt.Sprintf("%s %s", coloredName, style.FgDefault.Sprint(worktreeIcon))
|
coloredName = fmt.Sprintf("%s %s", coloredName, style.FgDefault.Sprint(worktreeIcon))
|
||||||
}
|
}
|
||||||
coloredName = fmt.Sprintf("%s %s", coloredName, branchStatus)
|
if len(branchStatus) > 0 {
|
||||||
|
coloredStatus := branchStatusColor(b, itemOperation).Sprint(branchStatus)
|
||||||
|
coloredName = fmt.Sprintf("%s %s", coloredName, coloredStatus)
|
||||||
|
}
|
||||||
|
|
||||||
recencyColor := style.FgCyan
|
recencyColor := style.FgCyan
|
||||||
if b.Recency == " *" {
|
if b.Recency == " *" {
|
||||||
@ -73,7 +104,7 @@ func getBranchDisplayStrings(
|
|||||||
res = append(res, nameTextStyle.Sprint(icons.IconForBranch(b)))
|
res = append(res, nameTextStyle.Sprint(icons.IconForBranch(b)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if fullDescription || userConfig.Gui.ShowBranchCommitHash {
|
if showCommitHash {
|
||||||
res = append(res, utils.ShortSha(b.CommitHash))
|
res = append(res, utils.ShortSha(b.CommitHash))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +143,7 @@ func GetBranchTextStyle(name string) style.TextStyle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ColoredBranchStatus(branch *models.Branch, itemOperation types.ItemOperation, tr *i18n.TranslationSet) string {
|
func branchStatusColor(branch *models.Branch, itemOperation types.ItemOperation) style.TextStyle {
|
||||||
colour := style.FgYellow
|
colour := style.FgYellow
|
||||||
if itemOperation != types.ItemOperationNone {
|
if itemOperation != types.ItemOperationNone {
|
||||||
colour = style.FgCyan
|
colour = style.FgCyan
|
||||||
@ -124,13 +155,17 @@ func ColoredBranchStatus(branch *models.Branch, itemOperation types.ItemOperatio
|
|||||||
colour = style.FgMagenta
|
colour = style.FgMagenta
|
||||||
}
|
}
|
||||||
|
|
||||||
return colour.Sprint(BranchStatus(branch, itemOperation, tr))
|
return colour
|
||||||
}
|
}
|
||||||
|
|
||||||
func BranchStatus(branch *models.Branch, itemOperation types.ItemOperation, tr *i18n.TranslationSet) string {
|
func ColoredBranchStatus(branch *models.Branch, itemOperation types.ItemOperation, tr *i18n.TranslationSet) string {
|
||||||
|
return branchStatusColor(branch, itemOperation).Sprint(BranchStatus(branch, itemOperation, tr, time.Now()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func BranchStatus(branch *models.Branch, itemOperation types.ItemOperation, tr *i18n.TranslationSet, now time.Time) string {
|
||||||
itemOperationStr := itemOperationToString(itemOperation, tr)
|
itemOperationStr := itemOperationToString(itemOperation, tr)
|
||||||
if itemOperationStr != "" {
|
if itemOperationStr != "" {
|
||||||
return itemOperationStr + " " + utils.Loader()
|
return itemOperationStr + " " + utils.Loader(now)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !branch.IsTrackingRemote() {
|
if !branch.IsTrackingRemote() {
|
||||||
|
214
pkg/gui/presentation/branches_test.go
Normal file
214
pkg/gui/presentation/branches_test.go
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
package presentation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
|
"github.com/samber/lo"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_getBranchDisplayStrings(t *testing.T) {
|
||||||
|
scenarios := []struct {
|
||||||
|
branch *models.Branch
|
||||||
|
itemOperation types.ItemOperation
|
||||||
|
fullDescription bool
|
||||||
|
viewWidth int
|
||||||
|
useIcons bool
|
||||||
|
checkedOutByWorktree bool
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
// First some tests for when the view is wide enough so that everything fits:
|
||||||
|
{
|
||||||
|
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
|
||||||
|
itemOperation: types.ItemOperationNone,
|
||||||
|
fullDescription: false,
|
||||||
|
viewWidth: 100,
|
||||||
|
useIcons: false,
|
||||||
|
checkedOutByWorktree: false,
|
||||||
|
expected: []string{"1m", "branch_name"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
|
||||||
|
itemOperation: types.ItemOperationNone,
|
||||||
|
fullDescription: false,
|
||||||
|
viewWidth: 100,
|
||||||
|
useIcons: false,
|
||||||
|
checkedOutByWorktree: true,
|
||||||
|
expected: []string{"1m", "branch_name (worktree)"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
|
||||||
|
itemOperation: types.ItemOperationNone,
|
||||||
|
fullDescription: false,
|
||||||
|
viewWidth: 100,
|
||||||
|
useIcons: true,
|
||||||
|
checkedOutByWorktree: true,
|
||||||
|
expected: []string{"1m", "", "branch_name "},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
branch: &models.Branch{
|
||||||
|
Name: "branch_name",
|
||||||
|
Recency: "1m",
|
||||||
|
UpstreamRemote: "origin",
|
||||||
|
Pushables: "0",
|
||||||
|
Pullables: "0",
|
||||||
|
},
|
||||||
|
itemOperation: types.ItemOperationNone,
|
||||||
|
fullDescription: false,
|
||||||
|
viewWidth: 100,
|
||||||
|
useIcons: false,
|
||||||
|
checkedOutByWorktree: false,
|
||||||
|
expected: []string{"1m", "branch_name ✓"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
branch: &models.Branch{
|
||||||
|
Name: "branch_name",
|
||||||
|
Recency: "1m",
|
||||||
|
UpstreamRemote: "origin",
|
||||||
|
Pushables: "3",
|
||||||
|
Pullables: "5",
|
||||||
|
},
|
||||||
|
itemOperation: types.ItemOperationNone,
|
||||||
|
fullDescription: false,
|
||||||
|
viewWidth: 100,
|
||||||
|
useIcons: false,
|
||||||
|
checkedOutByWorktree: true,
|
||||||
|
expected: []string{"1m", "branch_name (worktree) ↑3↓5"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
|
||||||
|
itemOperation: types.ItemOperationPushing,
|
||||||
|
fullDescription: false,
|
||||||
|
viewWidth: 100,
|
||||||
|
useIcons: false,
|
||||||
|
checkedOutByWorktree: false,
|
||||||
|
expected: []string{"1m", "branch_name Pushing |"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
branch: &models.Branch{
|
||||||
|
Name: "branch_name",
|
||||||
|
Recency: "1m",
|
||||||
|
CommitHash: "1234567890",
|
||||||
|
UpstreamRemote: "origin",
|
||||||
|
UpstreamBranch: "branch_name",
|
||||||
|
Pushables: "0",
|
||||||
|
Pullables: "0",
|
||||||
|
Subject: "commit title",
|
||||||
|
},
|
||||||
|
itemOperation: types.ItemOperationNone,
|
||||||
|
fullDescription: true,
|
||||||
|
viewWidth: 100,
|
||||||
|
useIcons: false,
|
||||||
|
checkedOutByWorktree: false,
|
||||||
|
expected: []string{"1m", "12345678", "branch_name ✓", "origin branch_name", "commit title"},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Now tests for how we truncate the branch name when there's not enough room:
|
||||||
|
{
|
||||||
|
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
|
||||||
|
itemOperation: types.ItemOperationNone,
|
||||||
|
fullDescription: false,
|
||||||
|
viewWidth: 14,
|
||||||
|
useIcons: false,
|
||||||
|
checkedOutByWorktree: false,
|
||||||
|
expected: []string{"1m", "branch_na…"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
|
||||||
|
itemOperation: types.ItemOperationNone,
|
||||||
|
fullDescription: false,
|
||||||
|
viewWidth: 14,
|
||||||
|
useIcons: false,
|
||||||
|
checkedOutByWorktree: true,
|
||||||
|
expected: []string{"1m", "bra… (worktree)"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
|
||||||
|
itemOperation: types.ItemOperationNone,
|
||||||
|
fullDescription: false,
|
||||||
|
viewWidth: 14,
|
||||||
|
useIcons: true,
|
||||||
|
checkedOutByWorktree: true,
|
||||||
|
expected: []string{"1m", "", "branc… "},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
branch: &models.Branch{
|
||||||
|
Name: "branch_name",
|
||||||
|
Recency: "1m",
|
||||||
|
UpstreamRemote: "origin",
|
||||||
|
Pushables: "0",
|
||||||
|
Pullables: "0",
|
||||||
|
},
|
||||||
|
itemOperation: types.ItemOperationNone,
|
||||||
|
fullDescription: false,
|
||||||
|
viewWidth: 14,
|
||||||
|
useIcons: false,
|
||||||
|
checkedOutByWorktree: false,
|
||||||
|
expected: []string{"1m", "branch_… ✓"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
branch: &models.Branch{
|
||||||
|
Name: "branch_name",
|
||||||
|
Recency: "1m",
|
||||||
|
UpstreamRemote: "origin",
|
||||||
|
Pushables: "3",
|
||||||
|
Pullables: "5",
|
||||||
|
},
|
||||||
|
itemOperation: types.ItemOperationNone,
|
||||||
|
fullDescription: false,
|
||||||
|
viewWidth: 30,
|
||||||
|
useIcons: false,
|
||||||
|
checkedOutByWorktree: true,
|
||||||
|
expected: []string{"1m", "branch_na… (worktree) ↑3↓5"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
|
||||||
|
itemOperation: types.ItemOperationPushing,
|
||||||
|
fullDescription: false,
|
||||||
|
viewWidth: 20,
|
||||||
|
useIcons: false,
|
||||||
|
checkedOutByWorktree: false,
|
||||||
|
expected: []string{"1m", "branc… Pushing |"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
branch: &models.Branch{
|
||||||
|
Name: "branch_name",
|
||||||
|
Recency: "1m",
|
||||||
|
CommitHash: "1234567890",
|
||||||
|
UpstreamRemote: "origin",
|
||||||
|
UpstreamBranch: "branch_name",
|
||||||
|
Pushables: "0",
|
||||||
|
Pullables: "0",
|
||||||
|
Subject: "commit title",
|
||||||
|
},
|
||||||
|
itemOperation: types.ItemOperationNone,
|
||||||
|
fullDescription: true,
|
||||||
|
viewWidth: 20,
|
||||||
|
useIcons: false,
|
||||||
|
checkedOutByWorktree: false,
|
||||||
|
expected: []string{"1m", "12345678", "bran… ✓", "origin branch_name", "commit title"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
c := utils.NewDummyCommon()
|
||||||
|
|
||||||
|
for i, s := range scenarios {
|
||||||
|
icons.SetNerdFontsVersion(lo.Ternary(s.useIcons, "3", ""))
|
||||||
|
|
||||||
|
worktrees := []*models.Worktree{}
|
||||||
|
if s.checkedOutByWorktree {
|
||||||
|
worktrees = append(worktrees, &models.Worktree{Branch: s.branch.Name})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(fmt.Sprintf("getBranchDisplayStrings_%d", i), func(t *testing.T) {
|
||||||
|
strings := getBranchDisplayStrings(s.branch, s.itemOperation, s.fullDescription, false, s.viewWidth, c.Tr, c.UserConfig, worktrees, time.Time{})
|
||||||
|
assert.Equal(t, s.expected, strings)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,9 @@ func IsIconEnabled() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func SetNerdFontsVersion(version string) {
|
func SetNerdFontsVersion(version string) {
|
||||||
|
if version == "" {
|
||||||
|
isIconEnabled = false
|
||||||
|
} else {
|
||||||
if !lo.Contains([]string{"2", "3"}, version) {
|
if !lo.Contains([]string{"2", "3"}, version) {
|
||||||
log.Fatalf("Unsupported nerdFontVersion %s", version)
|
log.Fatalf("Unsupported nerdFontVersion %s", version)
|
||||||
}
|
}
|
||||||
@ -24,3 +27,4 @@ func SetNerdFontsVersion(version string) {
|
|||||||
|
|
||||||
isIconEnabled = true
|
isIconEnabled = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package presentation
|
package presentation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
|
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||||
@ -37,7 +39,7 @@ func getTagDisplayStrings(t *models.Tag, itemOperation types.ItemOperation, diff
|
|||||||
descriptionStr := descriptionColor.Sprint(t.Description())
|
descriptionStr := descriptionColor.Sprint(t.Description())
|
||||||
itemOperationStr := itemOperationToString(itemOperation, tr)
|
itemOperationStr := itemOperationToString(itemOperation, tr)
|
||||||
if itemOperationStr != "" {
|
if itemOperationStr != "" {
|
||||||
descriptionStr = style.FgCyan.Sprint(itemOperationStr+" "+utils.Loader()) + " " + descriptionStr
|
descriptionStr = style.FgCyan.Sprint(itemOperationStr+" "+utils.Loader(time.Now())) + " " + descriptionStr
|
||||||
}
|
}
|
||||||
res = append(res, textStyle.Sprint(t.Name), descriptionStr)
|
res = append(res, textStyle.Sprint(t.Name), descriptionStr)
|
||||||
return res
|
return res
|
||||||
|
@ -71,7 +71,7 @@ func (self *StatusManager) GetStatusString() string {
|
|||||||
}
|
}
|
||||||
topStatus := self.statuses[0]
|
topStatus := self.statuses[0]
|
||||||
if topStatus.statusType == "waiting" {
|
if topStatus.statusType == "waiting" {
|
||||||
return topStatus.message + " " + utils.Loader()
|
return topStatus.message + " " + utils.Loader(time.Now())
|
||||||
}
|
}
|
||||||
return topStatus.message
|
return topStatus.message
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,9 @@ type IBaseContext interface {
|
|||||||
// determined independently.
|
// determined independently.
|
||||||
HasControlledBounds() bool
|
HasControlledBounds() bool
|
||||||
|
|
||||||
|
// true if the view needs to be rerendered when its width changes
|
||||||
|
NeedsRerenderOnWidthChange() bool
|
||||||
|
|
||||||
// returns the desired title for the view upon activation. If there is no desired title (returns empty string), then
|
// returns the desired title for the view upon activation. If there is no desired title (returns empty string), then
|
||||||
// no title will be set
|
// no title will be set
|
||||||
Title() string
|
Title() string
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
const unitTestDescription = "test test"
|
const unitTestDescription = "test test"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultWidth = 100
|
defaultWidth = 150
|
||||||
defaultHeight = 100
|
defaultHeight = 100
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -28,9 +28,8 @@ func GetProjectRoot() string {
|
|||||||
const LoaderAnimationInterval = 50
|
const LoaderAnimationInterval = 50
|
||||||
|
|
||||||
// Loader dumps a string to be displayed as a loader
|
// Loader dumps a string to be displayed as a loader
|
||||||
func Loader() string {
|
func Loader(now time.Time) string {
|
||||||
characters := "|/-\\"
|
characters := "|/-\\"
|
||||||
now := time.Now()
|
|
||||||
milliseconds := now.UnixMilli()
|
milliseconds := now.UnixMilli()
|
||||||
index := milliseconds / LoaderAnimationInterval % int64(len(characters))
|
index := milliseconds / LoaderAnimationInterval % int64(len(characters))
|
||||||
return characters[index : index+1]
|
return characters[index : index+1]
|
||||||
|
Reference in New Issue
Block a user