diff --git a/pkg/gui/presentation/commits.go b/pkg/gui/presentation/commits.go index 4bad77ce9..ace3b1b3c 100644 --- a/pkg/gui/presentation/commits.go +++ b/pkg/gui/presentation/commits.go @@ -177,6 +177,10 @@ func GetCommitListDisplayStrings( (hasRebaseUpdateRefsConfig || b.CommitHash != commits[0].Hash()) })) + reservedWidths := getReservedColumnWidths( + commits, common.UserConfig().Gui.CommitHashLength, fullDescription, + timeFormat, shortTimeFormat, now, bisectInfo, bisectBounds) + lines := make([][]string, 0, len(filteredCommits)) var bisectStatus BisectStatus willBeRebased := markedBaseCommit == "" @@ -204,11 +208,64 @@ func GetCommitListDisplayStrings( fullDescription, bisectStatus, bisectInfo, + reservedWidths, )) } return lines } +// The width of a column is the width of the widest string in it, and a column +// whose strings are all empty is left out entirely. The panels that show a +// commit list hand over only the lines that are on screen, so several columns +// would change their width, or come and go, as the user scrolls. These are the +// widths those columns need for all the commits in the list. +type reservedColumnWidths struct { + hash int + bisect int + description int + action int +} + +// precondition: commits is not empty +func getReservedColumnWidths( + commits []*models.Commit, + hashLength int, + fullDescription bool, + timeFormat string, + shortTimeFormat string, + now time.Time, + bisectInfo *git_commands.BisectInfo, + bisectBounds *bisectBounds, +) reservedColumnWidths { + result := reservedColumnWidths{} + + for i, commit := range commits { + result.hash = max(result.hash, utils.StringWidth(getHashText(commit, hashLength))) + if commit.IsTODO() { + result.action = max(result.action, utils.StringWidth(getActionText(commit))) + } + bisectStatus := getBisectStatus(i, commit.Hash(), bisectInfo, bisectBounds) + result.bisect = max(result.bisect, + utils.StringWidth(getBisectStatusText(bisectStatus, bisectInfo))) + } + + if fullDescription { + // Formatting the date of every commit on every render would be too + // expensive, so measure the oldest one only. It is the one least likely + // to be from today, and so the one most likely to be shown in the long + // time format; with a conventional time format that one is both wider + // than the short format and the same width for every date, which makes + // it the width the whole column needs. An unconventional format can + // break either of those assumptions, and then some of the column's + // width still comes and goes; it can never reserve more width than one + // of the commits asks for, though. + result.description = utils.StringWidth(utils.UnixToDateSmart( + now, commits[len(commits)-1].UnixTimestamp, timeFormat, shortTimeFormat)) + } + + return result +} + func getbisectBounds(commits []*models.Commit, bisectInfo *git_commands.BisectInfo) *bisectBounds { if !bisectInfo.Bisecting() { return nil @@ -376,6 +433,7 @@ func displayCommit( fullDescription bool, bisectStatus BisectStatus, bisectInfo *git_commands.BisectInfo, + reservedWidths reservedColumnWidths, ) []string { bisectString := "" if bisectText := getBisectStatusText(bisectStatus, bisectInfo); bisectText != "" { @@ -457,10 +515,10 @@ func displayCommit( cols = append( cols, divergenceString, - hashString, - bisectString, - descriptionString, - actionString, + utils.WithPadding(hashString, reservedWidths.hash, utils.AlignLeft), + utils.WithPadding(bisectString, reservedWidths.bisect, utils.AlignLeft), + utils.WithPadding(descriptionString, reservedWidths.description, utils.AlignLeft), + utils.WithPadding(actionString, reservedWidths.action, utils.AlignLeft), author, graphLine+mark+tagString+theme.DefaultTextColor.Sprint(name), ) diff --git a/pkg/gui/presentation/commits_test.go b/pkg/gui/presentation/commits_test.go index 7efb5c5ae..3c3ce0044 100644 --- a/pkg/gui/presentation/commits_test.go +++ b/pkg/gui/presentation/commits_test.go @@ -274,16 +274,10 @@ func TestGetCommitListDisplayStrings(t *testing.T) { bisectInfo: git_commands.NewNullBisectInfo(), cherryPickedCommitHashSet: set.New[string](), now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), - /* EXPECTED: expected: formatExpected(` hash4 ○ commit4 hash5 ○ commit5 `), - ACTUAL: */ - expected: formatExpected(` - hash4 ○ commit4 - hash5 ○ commit5 - `), }, { testName: "only showing TODO commits", @@ -358,16 +352,10 @@ func TestGetCommitListDisplayStrings(t *testing.T) { bisectInfo: git_commands.NewNullBisectInfo(), cherryPickedCommitHashSet: set.New[string](), now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), - /* EXPECTED: expected: formatExpected(` update-ref branch1 update-ref branch2 `), - ACTUAL: */ - expected: formatExpected(` - update-ref branch1 - update-ref branch2 - `), }, { testName: "graph in divergence view - all commits visible", @@ -576,14 +564,9 @@ func TestGetCommitListDisplayStrings(t *testing.T) { bisectInfo: git_commands.NewNullBisectInfo(), cherryPickedCommitHashSet: set.New[string](), now: time.Date(2020, 1, 1, 5, 3, 4, 0, time.UTC), - /* EXPECTED: expected: formatExpected(` hash1 2:03AM Jesse Duffield commit1 `), - ACTUAL: */ - expected: formatExpected(` - hash1 2:03AM Jesse Duffield commit1 - `), }, }