mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-07-31 14:24:25 +03:00
Truncate long branch names to make branch status visible
This commit is contained in:
@ -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,
|
||||||
|
@ -14,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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -24,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, time.Now())
|
return getBranchDisplayStrings(branch, getItemOperation(branch), fullDescription, diffed, viewWidth, tr, userConfig, worktrees, time.Now())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,11 +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,
|
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
|
||||||
@ -55,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 == " *" {
|
||||||
@ -75,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))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,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
|
||||||
@ -126,7 +155,11 @@ func ColoredBranchStatus(branch *models.Branch, itemOperation types.ItemOperatio
|
|||||||
colour = style.FgMagenta
|
colour = style.FgMagenta
|
||||||
}
|
}
|
||||||
|
|
||||||
return colour.Sprint(BranchStatus(branch, itemOperation, tr, time.Now()))
|
return colour
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
func BranchStatus(branch *models.Branch, itemOperation types.ItemOperation, tr *i18n.TranslationSet, now time.Time) string {
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user