Truncate the path column of the recent repos menu too

The path column is the last one, so nothing pushes it aside; instead it
runs off the right edge of the menu itself, and the reader has no way of
telling that there is more to it. Give it a maximum width as well, sized
so that the three columns and the spaces between them fill the menu at
its widest.

A path loses its middle rather than its end, because the directory that
immediately contains the repo says more about where it is than the root
of the tree does. The whole path, with the home directory still
abbreviated, joins the names in the tooltip when it doesn't fit.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Haller 2026-09-07 19:08:19 +02:00
parent e7d0ccc0c1
commit 95fc3dbe24
3 changed files with 39 additions and 14 deletions

View file

@ -103,29 +103,48 @@ func (self *ReposHelper) getCurrentBranch(path string) string {
return self.c.Tr.BranchUnknown
}
// The maximum width of the repo name and branch name columns of the recent
// repos menu. Without a limit, one long name pushes the path column off the
// right edge of the menu for every entry, because each column is padded to the
// width of its widest entry.
const recentReposColumnMaxWidth = 30
// The maximum widths of the three columns of the recent repos menu. Each
// column is padded to the width of its widest entry, so without a limit one
// long entry pushes the columns after it off the right edge of the menu for
// every entry. The three widths plus the two spaces between the columns add up
// to the content width of a menu that is as wide as it gets, so that a whole
// row fits into it.
const (
recentReposNameMaxWidth = 30
recentReposBranchMaxWidth = 30
// The menu's frame takes up two columns, and two more separate the three
// columns from each other.
recentReposPathMaxWidth = menuMaxWidth - 2 - 2 - recentReposNameMaxWidth - recentReposBranchMaxWidth
)
func (self *ReposHelper) recentRepoMenuItem(path string, branchName string) *types.MenuItem {
repoName := filepath.Base(path)
displayedRepoName := utils.TruncateWithEllipsis(repoName, recentReposColumnMaxWidth)
displayedRepoName := utils.TruncateWithEllipsis(repoName, recentReposNameMaxWidth)
// The icon is part of the column, so it counts towards the maximum width.
branchColumn := branchName
if icons.IsIconEnabled() {
branchColumn = icons.BRANCH_ICON + " " + branchName
}
displayedBranchColumn := utils.TruncateWithEllipsis(branchColumn, recentReposColumnMaxWidth)
displayedBranchColumn := utils.TruncateWithEllipsis(branchColumn, recentReposBranchMaxWidth)
// The last segment of the path is already in the first column, so showing
// the directory that contains the repo is enough to tell repos with the
// same name apart. Its beginning and its end are both worth seeing, so it
// loses its middle rather than its end when it doesn't fit.
dir := utils.ContractTilde(filepath.Dir(path))
displayedDir := utils.TruncateWithEllipsisInMiddle(dir, recentReposPathMaxWidth)
// Spell out whatever the columns show in truncated form. The labels are
// padded to the same width no matter which of them appear, so that the
// tooltip doesn't shift around as the selection moves.
tooltipLines := []string{}
labelWidth := utils.MaxFn(
[]string{self.c.Tr.RecentReposRepoLabel, self.c.Tr.RecentReposBranchLabel},
[]string{
self.c.Tr.RecentReposRepoLabel,
self.c.Tr.RecentReposBranchLabel,
self.c.Tr.RecentReposPathLabel,
},
utils.StringWidth)
addTooltipLine := func(label string, value string) {
tooltipLines = append(tooltipLines,
@ -138,15 +157,15 @@ func (self *ReposHelper) recentRepoMenuItem(path string, branchName string) *typ
if displayedBranchColumn != branchColumn {
addTooltipLine(self.c.Tr.RecentReposBranchLabel, branchName)
}
if displayedDir != dir {
addTooltipLine(self.c.Tr.RecentReposPathLabel, utils.ContractTilde(path))
}
return &types.MenuItem{
LabelColumns: []string{
displayedRepoName,
style.FgCyan.Sprint(displayedBranchColumn),
// The last segment of the path is already in the first column, so
// showing the directory that contains the repo is enough to tell
// repos with the same name apart.
style.FgMagenta.Sprint(utils.ContractTilde(filepath.Dir(path))),
style.FgMagenta.Sprint(displayedDir),
},
// Filtering matches the full text, including the parts that the columns
// above truncate or leave out.

View file

@ -334,6 +334,7 @@ type TranslationSet struct {
RecentRepos string
RecentReposRepoLabel string
RecentReposBranchLabel string
RecentReposPathLabel string
MergeOptionsTitle string
RebaseOptionsTitle string
CherryPickOptionsTitle string
@ -1494,6 +1495,7 @@ func EnglishTranslationSet() *TranslationSet {
RecentRepos: "Recent repositories",
RecentReposRepoLabel: "Repo:",
RecentReposBranchLabel: "Branch:",
RecentReposPathLabel: "Path:",
MergeOptionsTitle: "Merge options",
RebaseOptionsTitle: "Rebase options",
CherryPickOptionsTitle: "Cherry-pick options",

View file

@ -29,10 +29,14 @@ var RecentReposWithLongNames = NewIntegrationTest(NewIntegrationTestArgs{
t.ExpectPopup().Menu().
Title(Equals("Recent repositories")).
Lines(
Contains("repo-with-a-name-that-is-far-… branch-with-a-name-that-is-to…").IsSelected(),
// The repo lives in the test's own directory, so its path is
// long enough to lose its middle
Contains("repo-with-a-name-that-is-far-… branch-with-a-name-that-is-to… ~/_results/mi…names/actual").IsSelected(),
Contains("Cancel"),
).
Tooltip(Equals("Repo: repo-with-a-name-that-is-far-too-long\nBranch: branch-with-a-name-that-is-too-long")).
Tooltip(Contains("Repo: repo-with-a-name-that-is-far-too-long\n" +
"Branch: branch-with-a-name-that-is-too-long\n" +
"Path: ~/")).
// Filtering matches the full names, including the part that the
// menu truncates
Filter("too-long").