diff --git a/pkg/gui/context/branches_context.go b/pkg/gui/context/branches_context.go index d73961275..d83538131 100644 --- a/pkg/gui/context/branches_context.go +++ b/pkg/gui/context/branches_context.go @@ -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(), diff --git a/pkg/gui/presentation/branches.go b/pkg/gui/presentation/branches.go index 02cc5bb8b..f406547bc 100644 --- a/pkg/gui/presentation/branches.go +++ b/pkg/gui/presentation/branches.go @@ -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 + } +} diff --git a/pkg/gui/presentation/branches_test.go b/pkg/gui/presentation/branches_test.go index 5a67d57f2..b2c19a9ea 100644 --- a/pkg/gui/presentation/branches_test.go +++ b/pkg/gui/presentation/branches_test.go @@ -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) }) } diff --git a/pkg/gui/presentation/icons/git_icons.go b/pkg/gui/presentation/icons/git_icons.go index 23b7d8787..af292b380 100644 --- a/pkg/gui/presentation/icons/git_icons.go +++ b/pkg/gui/presentation/icons/git_icons.go @@ -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 }