Show PR icons in branches list

Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
This commit is contained in:
Jesse Duffield
2026-04-01 09:13:55 +02:00
committed by Stefan Haller
co-authored by Stefan Haller
parent d33fa5bb05
commit ca9eeebea3
4 changed files with 69 additions and 25 deletions
+1
View File
@@ -28,6 +28,7 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
return presentation.GetBranchListDisplayStrings(
viewModel.GetItems(),
c.State().GetItemOperation,
c.Model().PullRequestsMap,
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
c.Modes().Diffing.Ref,
c.Views().Branches.InnerWidth()+c.Views().Branches.OriginX(),
+35 -1
View File
@@ -28,6 +28,7 @@ var colorPatterns *colorMatcher
func GetBranchListDisplayStrings(
branches []*models.Branch,
getItemOperation func(item types.HasUrn) types.ItemOperation,
prs map[string]*models.GithubPullRequest,
fullDescription bool,
diffName string,
viewWidth int,
@@ -37,7 +38,7 @@ func GetBranchListDisplayStrings(
) [][]string {
return lo.Map(branches, func(branch *models.Branch, _ int) []string {
diffed := branch.Name == diffName
return getBranchDisplayStrings(branch, getItemOperation(branch), fullDescription, diffed, viewWidth, tr, userConfig, worktrees, time.Now())
return getBranchDisplayStrings(branch, getItemOperation(branch), fullDescription, diffed, viewWidth, tr, userConfig, worktrees, time.Now(), prs)
})
}
@@ -52,6 +53,7 @@ func getBranchDisplayStrings(
userConfig *config.UserConfig,
worktrees []*models.Worktree,
now time.Time,
prs map[string]*models.GithubPullRequest,
) []string {
checkedOutByWorkTree := git_commands.CheckedOutByOtherWorktree(b, worktrees)
showCommitHash := fullDescription || userConfig.Gui.ShowBranchCommitHash
@@ -66,6 +68,10 @@ func getBranchDisplayStrings(
if showCommitHash {
availableWidth -= utils.COMMIT_HASH_SHORT_SIZE + 1
}
if len(prs) > 0 {
// if we have PRs then we assume that at least one branch in the list has one
availableWidth -= 2
}
paddingNeededForDivergence := availableWidth
displayName := b.Name
@@ -133,6 +139,19 @@ func getBranchDisplayStrings(
res := make([]string, 0, 6)
res = append(res, recencyColor.Sprint(b.Recency))
var coloredPrIcon string
pr, hasPr := prs[b.Name]
if hasPr {
var prIcon string
if icons.IsIconEnabled() {
prIcon = icons.IconForRemoteUrl(pr.Url)
} else {
prIcon = "●"
}
coloredPrIcon = prColor(pr.State).Sprint(prIcon)
}
res = append(res, coloredPrIcon)
if showCommitHash {
res = append(res, utils.ShortHash(b.CommitHash))
}
@@ -251,3 +270,18 @@ func SetCustomBranches(customBranchColors map[string]string, isRegex bool) {
isRegex: isRegex,
}
}
func prColor(state string) style.TextStyle {
switch state {
case "OPEN":
return style.FgGreen
case "CLOSED":
return style.FgRed
case "MERGED":
return style.FgMagenta
case "DRAFT":
return style.FgBlackLighter
default:
return style.FgDefault
}
}
+24 -24
View File
@@ -42,7 +42,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
useIcons: false,
checkedOutByWorktree: false,
showDivergenceCfg: "none",
expected: []string{"1m", "branch_name"},
expected: []string{"1m", "", "branch_name"},
},
{
branch: &models.Branch{Name: "🍉_special_char", Recency: "1m"},
@@ -52,7 +52,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
useIcons: false,
checkedOutByWorktree: false,
showDivergenceCfg: "none",
expected: []string{"1m", "🍉_special_char"},
expected: []string{"1m", "", "🍉_special_char"},
},
{
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
@@ -62,7 +62,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
useIcons: false,
checkedOutByWorktree: true,
showDivergenceCfg: "none",
expected: []string{"1m", "branch_name (worktree other-worktree)"},
expected: []string{"1m", "", "branch_name (worktree other-worktree)"},
},
{
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
@@ -72,7 +72,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
useIcons: true,
checkedOutByWorktree: true,
showDivergenceCfg: "none",
expected: []string{"1m", "branch_name (󰌹 other-worktree)"},
expected: []string{"1m", "", "branch_name (󰌹 other-worktree)"},
},
{
branch: &models.Branch{
@@ -88,7 +88,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
useIcons: false,
checkedOutByWorktree: false,
showDivergenceCfg: "none",
expected: []string{"1m", "branch_name ✓"},
expected: []string{"1m", "", "branch_name ✓"},
},
{
branch: &models.Branch{
@@ -104,7 +104,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
useIcons: false,
checkedOutByWorktree: true,
showDivergenceCfg: "none",
expected: []string{"1m", "branch_name (worktree other-worktree) ↓5↑3"},
expected: []string{"1m", "", "branch_name (worktree other-worktree) ↓5↑3"},
},
{
branch: &models.Branch{
@@ -118,7 +118,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
useIcons: false,
checkedOutByWorktree: false,
showDivergenceCfg: "onlyArrow",
expected: []string{"1m", "branch_name ↓"},
expected: []string{"1m", "", "branch_name ↓"},
},
{
branch: &models.Branch{
@@ -135,7 +135,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
useIcons: false,
checkedOutByWorktree: false,
showDivergenceCfg: "arrowAndNumber",
expected: []string{"1m", "branch_name ✓ ↓2"},
expected: []string{"1m", "", "branch_name ✓ ↓2"},
},
{
branch: &models.Branch{
@@ -152,7 +152,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
useIcons: false,
checkedOutByWorktree: false,
showDivergenceCfg: "arrowAndNumber",
expected: []string{"1m", "branch_name ↓5↑3 ↓2"},
expected: []string{"1m", "", "branch_name ↓5↑3 ↓2"},
},
{
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
@@ -162,7 +162,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
useIcons: false,
checkedOutByWorktree: false,
showDivergenceCfg: "none",
expected: []string{"1m", "branch_name Pushing |"},
expected: []string{"1m", "", "branch_name Pushing |"},
},
{
branch: &models.Branch{
@@ -181,7 +181,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
useIcons: false,
checkedOutByWorktree: false,
showDivergenceCfg: "none",
expected: []string{"1m", "12345678", "branch_name ✓", "origin branch_name", "commit title"},
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:
@@ -193,7 +193,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
useIcons: false,
checkedOutByWorktree: false,
showDivergenceCfg: "none",
expected: []string{"1m", "branch_na…"},
expected: []string{"1m", "", "branch_na…"},
},
{
branch: &models.Branch{Name: "🍉_special_char", Recency: "1m"},
@@ -203,7 +203,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
useIcons: false,
checkedOutByWorktree: false,
showDivergenceCfg: "none",
expected: []string{"1m", "🍉_special_ch…"},
expected: []string{"1m", "", "🍉_special_ch…"},
},
{
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
@@ -213,7 +213,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
useIcons: false,
checkedOutByWorktree: true,
showDivergenceCfg: "none",
expected: []string{"1m", "bra… (worktree)"},
expected: []string{"1m", "", "bra… (worktree)"},
},
{
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
@@ -223,7 +223,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
useIcons: true,
checkedOutByWorktree: true,
showDivergenceCfg: "none",
expected: []string{"1m", "branc… 󰌹"},
expected: []string{"1m", "", "branc… 󰌹"},
},
{
branch: &models.Branch{
@@ -239,7 +239,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
useIcons: false,
checkedOutByWorktree: false,
showDivergenceCfg: "none",
expected: []string{"1m", "branch_… ✓"},
expected: []string{"1m", "", "branch_… ✓"},
},
{
branch: &models.Branch{
@@ -256,7 +256,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
useIcons: false,
checkedOutByWorktree: false,
showDivergenceCfg: "arrowAndNumber",
expected: []string{"1m", "branch_n… ↓5↑3 ↓4"},
expected: []string{"1m", "", "branch_n… ↓5↑3 ↓4"},
},
{
branch: &models.Branch{
@@ -272,7 +272,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
useIcons: false,
checkedOutByWorktree: true,
showDivergenceCfg: "none",
expected: []string{"1m", "branch_na… (worktree) ↓5↑3"},
expected: []string{"1m", "", "branch_na… (worktree) ↓5↑3"},
},
{
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
@@ -282,7 +282,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
useIcons: false,
checkedOutByWorktree: false,
showDivergenceCfg: "none",
expected: []string{"1m", "branc… Pushing |"},
expected: []string{"1m", "", "branc… Pushing |"},
},
{
branch: &models.Branch{Name: "abc", Recency: "1m"},
@@ -292,7 +292,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
useIcons: false,
checkedOutByWorktree: false,
showDivergenceCfg: "none",
expected: []string{"1m", "abc Pushing |"},
expected: []string{"1m", "", "abc Pushing |"},
},
{
branch: &models.Branch{Name: "ab", Recency: "1m"},
@@ -302,7 +302,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
useIcons: false,
checkedOutByWorktree: false,
showDivergenceCfg: "none",
expected: []string{"1m", "ab Pushing |"},
expected: []string{"1m", "", "ab Pushing |"},
},
{
branch: &models.Branch{Name: "a", Recency: "1m"},
@@ -312,7 +312,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
useIcons: false,
checkedOutByWorktree: false,
showDivergenceCfg: "none",
expected: []string{"1m", "a Pushing |"},
expected: []string{"1m", "", "a Pushing |"},
},
{
branch: &models.Branch{
@@ -331,7 +331,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
useIcons: false,
checkedOutByWorktree: false,
showDivergenceCfg: "none",
expected: []string{"1m", "12345678", "bran… ✓", "origin branch_name", "commit title"},
expected: []string{"1m", "", "12345678", "bran… ✓", "origin branch_name", "commit title"},
},
}
@@ -351,7 +351,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
}
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{})
strings := getBranchDisplayStrings(s.branch, s.itemOperation, s.fullDescription, false, s.viewWidth, c.Tr, c.UserConfig(), worktrees, time.Time{}, map[string]*models.GithubPullRequest{})
assert.Equal(t, s.expected, strings)
})
}
+9
View File
@@ -79,6 +79,15 @@ func IconForRemote(remote *models.Remote) string {
return DEFAULT_REMOTE_ICON
}
func IconForRemoteUrl(url string) string {
for domain, icon := range remoteIcons {
if strings.Contains(url, domain) {
return icon
}
}
return DEFAULT_REMOTE_ICON
}
func IconForStash(stash *models.StashEntry) string {
return STASH_ICON
}