diff --git a/pkg/commands/commit.go b/pkg/commands/commit.go index f60e98abd7b..4d440d5e3ee 100644 --- a/pkg/commands/commit.go +++ b/pkg/commands/commit.go @@ -1,13 +1,5 @@ package commands -import ( - "strings" - - "github.com/fatih/color" - "github.com/jesseduffield/lazygit/pkg/theme" - "github.com/jesseduffield/lazygit/pkg/utils" -) - // Commit : A git commit type Commit struct { Sha string @@ -17,53 +9,7 @@ type Commit struct { Action string // one of "", "pick", "edit", "squash", "reword", "drop", "fixup" Copied bool // to know if this commit is ready to be cherry-picked somewhere Tags []string -} - -// GetDisplayStrings is a function. -func (c *Commit) GetDisplayStrings(isFocused bool) []string { - red := color.New(color.FgRed) - yellow := color.New(color.FgYellow) - green := color.New(color.FgGreen) - blue := color.New(color.FgBlue) - cyan := color.New(color.FgCyan) - defaultColor := color.New(theme.DefaultTextColor) - magenta := color.New(color.FgMagenta) - - // for some reason, setting the background to blue pads out the other commits - // horizontally. For the sake of accessibility I'm considering this a feature, - // not a bug - copied := color.New(color.FgCyan, color.BgBlue) - - var shaColor *color.Color - switch c.Status { - case "unpushed": - shaColor = red - case "pushed": - shaColor = yellow - case "merged": - shaColor = green - case "rebasing": - shaColor = blue - case "reflog": - shaColor = blue - case "selected": - shaColor = magenta - default: - shaColor = defaultColor - } - - if c.Copied { - shaColor = copied - } - - actionString := "" - tagString := "" - if c.Action != "" { - actionString = cyan.Sprint(utils.WithPadding(c.Action, 7)) + " " - } else if len(c.Tags) > 0 { - tagColor := color.New(color.FgMagenta, color.Bold) - tagString = utils.ColoredStringDirect(strings.Join(c.Tags, " "), tagColor) + " " - } - - return []string{shaColor.Sprint(c.Sha[:8]), actionString + tagString + defaultColor.Sprint(c.Name)} + ExtraInfo string // something like 'HEAD -> master, tag: v0.15.2' + Author string + Date string } diff --git a/pkg/commands/commit_list_builder.go b/pkg/commands/commit_list_builder.go index cf33fcac1c2..1e621f4e411 100644 --- a/pkg/commands/commit_list_builder.go +++ b/pkg/commands/commit_list_builder.go @@ -23,6 +23,8 @@ import ( // if we find out we need to use one of these functions in the git.go file, we // can just pull them out of here and put them there and then call them from in here +const SEPARATION_CHAR = "|" + // CommitListBuilder returns a list of Branch objects for the current repo type CommitListBuilder struct { Log *logrus.Entry @@ -45,42 +47,37 @@ func NewCommitListBuilder(log *logrus.Entry, gitCommand *GitCommand, osCommand * }, nil } -// nameAndTag takes a line from a git log and extracts the sha, message and tag (if present) -// example inputs: -// 66e6369c284e96ed5af5 (tag: v0.14.4) allow fastforwarding the current branch -// 32e650e0bb3f4327749f (HEAD -> show-tags) this is my commit -// 32e650e0bb3f4327749e this is my other commit -func (c *CommitListBuilder) commitLineParts(line string) (string, string, []string) { - re := regexp.MustCompile(`(\w+) (.*)`) - shaMatch := re.FindStringSubmatch(line) - - if len(shaMatch) <= 1 { - return line, "", nil - } - sha := shaMatch[1] - rest := shaMatch[2] - - if !strings.HasPrefix(rest, "(") { - return sha, rest, nil - } - - re = regexp.MustCompile(`\((.*)\) (.*)`) - - parensMatch := re.FindStringSubmatch(rest) - if len(parensMatch) <= 1 { - return sha, rest, nil +// extractCommitFromLine takes a line from a git log and extracts the sha, message, date, and tag if present +// then puts them into a commit object +// example input: +// 8ad01fe32fcc20f07bc6693f87aa4977c327f1e1|10 hours ago|Jesse Duffield| (HEAD -> master, tag: v0.15.2)|refresh commits when adding a tag +func (c *CommitListBuilder) extractCommitFromLine(line string) *Commit { + split := strings.Split(line, SEPARATION_CHAR) + + sha := split[0] + date := split[1] + author := split[2] + extraInfo := strings.TrimSpace(split[3]) + message := strings.Join(split[4:len(split)], SEPARATION_CHAR) + tags := []string{} + + if extraInfo != "" { + re := regexp.MustCompile(`tag: ([^,\)]+)`) + tagMatch := re.FindStringSubmatch(extraInfo) + if len(tagMatch) > 1 { + tags = append(tags, tagMatch[1]) + } } - notes := parensMatch[1] - message := parensMatch[2] - re = regexp.MustCompile(`tag: ([^,]+)`) - tagMatch := re.FindStringSubmatch(notes) - if len(tagMatch) <= 1 { - return sha, message, nil + return &Commit{ + Sha: sha, + Name: message, + DisplayString: line, + Tags: tags, + ExtraInfo: extraInfo, + Date: date, + Author: author, } - - tag := tagMatch[1] - return sha, message, []string{tag} } // GetCommits obtains the commits of the current branch @@ -107,16 +104,10 @@ func (c *CommitListBuilder) GetCommits(limit bool) ([]*Commit, error) { // now we can split it up and turn it into commits for _, line := range utils.SplitLines(log) { - sha, name, tags := c.commitLineParts(line) - _, unpushed := unpushedCommits[sha] - status := map[bool]string{true: "unpushed", false: "pushed"}[unpushed] - commits = append(commits, &Commit{ - Sha: sha, - Name: name, - Status: status, - DisplayString: line, - Tags: tags, - }) + commit := c.extractCommitFromLine(line) + _, unpushed := unpushedCommits[commit.Sha] + commit.Status = map[bool]string{true: "unpushed", false: "pushed"}[unpushed] + commits = append(commits, commit) } if rebaseMode != "" { currentCommit := commits[len(rebasingCommits)] @@ -325,7 +316,8 @@ func (c *CommitListBuilder) getLog(limit bool) string { limitFlag = "-30" } - result, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git log --decorate --oneline %s --abbrev=%d", limitFlag, 20)) + result, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git log --oneline --pretty=format:\"%%H%s%%ar%s%%aN%s%%d%s%%s\" %s --abbrev=%d", SEPARATION_CHAR, SEPARATION_CHAR, SEPARATION_CHAR, SEPARATION_CHAR, limitFlag, 20)) + if err != nil { // assume if there is an error there are no commits yet for this branch return "" diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index b84401e797a..e42aabfd037 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -7,6 +7,7 @@ import ( "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/gui/presentation" "github.com/jesseduffield/lazygit/pkg/utils" ) @@ -600,9 +601,8 @@ func (gui *Gui) renderBranchCommitsWithSelection() error { commitsView := gui.getCommitsView() gui.refreshSelectedLine(&gui.State.Panels.Commits.SelectedLine, len(gui.State.Commits)) - if err := gui.renderListPanel(commitsView, gui.State.Commits); err != nil { - return err - } + displayStrings := presentation.GetCommitListDisplayStrings(gui.State.Commits, gui.State.ScreenMode != SCREEN_NORMAL) + gui.renderDisplayStrings(commitsView, displayStrings) if gui.g.CurrentView() == commitsView && commitsView.Context == "branch-commits" { if err := gui.handleCommitSelect(gui.g, commitsView); err != nil { return err diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index f04032fb571..4cf435b8f06 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -269,12 +269,20 @@ func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *comma func (gui *Gui) nextScreenMode(g *gocui.Gui, v *gocui.View) error { gui.State.ScreenMode = utils.NextIntInCycle([]int{SCREEN_NORMAL, SCREEN_HALF, SCREEN_FULL}, gui.State.ScreenMode) + // commits render differently depending on whether we're in fullscreen more or not + if err := gui.renderBranchCommitsWithSelection(); err != nil { + return err + } return nil } func (gui *Gui) prevScreenMode(g *gocui.Gui, v *gocui.View) error { gui.State.ScreenMode = utils.PrevIntInCycle([]int{SCREEN_NORMAL, SCREEN_HALF, SCREEN_FULL}, gui.State.ScreenMode) + // commits render differently depending on whether we're in fullscreen more or not + if err := gui.renderBranchCommitsWithSelection(); err != nil { + return err + } return nil } diff --git a/pkg/gui/presentation/commits.go b/pkg/gui/presentation/commits.go new file mode 100644 index 00000000000..04dbb0780ed --- /dev/null +++ b/pkg/gui/presentation/commits.go @@ -0,0 +1,126 @@ +package presentation + +import ( + "strings" + + "github.com/fatih/color" + "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/theme" + "github.com/jesseduffield/lazygit/pkg/utils" +) + +func GetCommitListDisplayStrings(commits []*commands.Commit, fullDescription bool) [][]string { + lines := make([][]string, len(commits)) + + var displayFunc func(*commands.Commit) []string + if fullDescription { + displayFunc = getFullDescriptionDisplayStringsForCommit + } else { + displayFunc = getDisplayStringsForCommit + } + + for i := range commits { + lines[i] = displayFunc(commits[i]) + } + + return lines +} + +func getFullDescriptionDisplayStringsForCommit(c *commands.Commit) []string { + red := color.New(color.FgRed) + yellow := color.New(color.FgYellow) + green := color.New(color.FgGreen) + blue := color.New(color.FgBlue) + cyan := color.New(color.FgCyan) + defaultColor := color.New(theme.DefaultTextColor) + magenta := color.New(color.FgMagenta) + + // for some reason, setting the background to blue pads out the other commits + // horizontally. For the sake of accessibility I'm considering this a feature, + // not a bug + copied := color.New(color.FgCyan, color.BgBlue) + + var shaColor *color.Color + switch c.Status { + case "unpushed": + shaColor = red + case "pushed": + shaColor = yellow + case "merged": + shaColor = green + case "rebasing": + shaColor = blue + case "reflog": + shaColor = blue + case "selected": + shaColor = magenta + default: + shaColor = defaultColor + } + + if c.Copied { + shaColor = copied + } + + tagString := "" + truncatedDate := utils.TruncateWithEllipsis(c.Date, 15) + secondColumnString := blue.Sprint(truncatedDate) + if c.Action != "" { + secondColumnString = cyan.Sprint(c.Action) + } else if len(c.Tags) > 0 { + tagColor := color.New(color.FgMagenta, color.Bold) + tagString = utils.ColoredStringDirect(c.ExtraInfo, tagColor) + " " + } + + truncatedAuthor := utils.TruncateWithEllipsis(c.Author, 17) + + return []string{shaColor.Sprint(c.Sha[:8]), secondColumnString, yellow.Sprint(truncatedAuthor), tagString + defaultColor.Sprint(c.Name)} +} + +func getDisplayStringsForCommit(c *commands.Commit) []string { + red := color.New(color.FgRed) + yellow := color.New(color.FgYellow) + green := color.New(color.FgGreen) + blue := color.New(color.FgBlue) + cyan := color.New(color.FgCyan) + defaultColor := color.New(theme.DefaultTextColor) + magenta := color.New(color.FgMagenta) + + // for some reason, setting the background to blue pads out the other commits + // horizontally. For the sake of accessibility I'm considering this a feature, + // not a bug + copied := color.New(color.FgCyan, color.BgBlue) + + var shaColor *color.Color + switch c.Status { + case "unpushed": + shaColor = red + case "pushed": + shaColor = yellow + case "merged": + shaColor = green + case "rebasing": + shaColor = blue + case "reflog": + shaColor = blue + case "selected": + shaColor = magenta + default: + shaColor = defaultColor + } + + if c.Copied { + shaColor = copied + } + + actionString := "" + tagString := "" + if c.Action != "" { + actionString = cyan.Sprint(utils.WithPadding(c.Action, 7)) + " " + } else if len(c.Tags) > 0 { + tagColor := color.New(color.FgMagenta, color.Bold) + tagString = utils.ColoredStringDirect(strings.Join(c.Tags, " "), tagColor) + " " + } + + return []string{shaColor.Sprint(c.Sha[:8]), actionString + tagString + defaultColor.Sprint(c.Name)} +} diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index 5452f17549d..119014802bf 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -387,6 +387,15 @@ func (gui *Gui) refreshSelectedLine(line *int, total int) { } } +func (gui *Gui) renderDisplayStrings(v *gocui.View, displayStrings [][]string) { + gui.g.Update(func(g *gocui.Gui) error { + list := utils.RenderDisplayStrings(displayStrings) + v.Clear() + fmt.Fprint(v, list) + return nil + }) +} + func (gui *Gui) renderListPanel(v *gocui.View, items interface{}) error { gui.g.Update(func(g *gocui.Gui) error { isFocused := gui.g.CurrentView().Name() == v.Name() diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 42529776fd8..7fa33e76916 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -348,3 +348,22 @@ func PrevIntInCycle(sl []int, current int) int { } return sl[len(sl)-1] } + +// TruncateWithEllipsis returns a string, truncated to a certain length, with an ellipsis +func TruncateWithEllipsis(str string, limit int) string { + if limit == 1 && len(str) > 1 { + return "." + } + + if limit == 2 && len(str) > 2 { + return ".." + } + + ellipsis := "..." + if len(str) <= limit { + return str + } + + remainingLength := limit - len(ellipsis) + return str[0:remainingLength] + "..." +}