mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-05-12 21:05:48 +02:00
better commit lines in fullscreen mode
This commit is contained in:
parent
b8717d750a
commit
9fd9fd6816
7 changed files with 202 additions and 102 deletions
|
@ -1,13 +1,5 @@
|
||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/fatih/color"
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Commit : A git commit
|
// Commit : A git commit
|
||||||
type Commit struct {
|
type Commit struct {
|
||||||
Sha string
|
Sha string
|
||||||
|
@ -17,53 +9,7 @@ type Commit struct {
|
||||||
Action string // one of "", "pick", "edit", "squash", "reword", "drop", "fixup"
|
Action string // one of "", "pick", "edit", "squash", "reword", "drop", "fixup"
|
||||||
Copied bool // to know if this commit is ready to be cherry-picked somewhere
|
Copied bool // to know if this commit is ready to be cherry-picked somewhere
|
||||||
Tags []string
|
Tags []string
|
||||||
}
|
ExtraInfo string // something like 'HEAD -> master, tag: v0.15.2'
|
||||||
|
Author string
|
||||||
// GetDisplayStrings is a function.
|
Date string
|
||||||
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)}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@ import (
|
||||||
// if we find out we need to use one of these functions in the git.go file, we
|
// 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
|
// 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
|
// CommitListBuilder returns a list of Branch objects for the current repo
|
||||||
type CommitListBuilder struct {
|
type CommitListBuilder struct {
|
||||||
Log *logrus.Entry
|
Log *logrus.Entry
|
||||||
|
@ -45,42 +47,37 @@ func NewCommitListBuilder(log *logrus.Entry, gitCommand *GitCommand, osCommand *
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// nameAndTag takes a line from a git log and extracts the sha, message and tag (if present)
|
// extractCommitFromLine takes a line from a git log and extracts the sha, message, date, and tag if present
|
||||||
// example inputs:
|
// then puts them into a commit object
|
||||||
// 66e6369c284e96ed5af5 (tag: v0.14.4) allow fastforwarding the current branch
|
// example input:
|
||||||
// 32e650e0bb3f4327749f (HEAD -> show-tags) this is my commit
|
// 8ad01fe32fcc20f07bc6693f87aa4977c327f1e1|10 hours ago|Jesse Duffield| (HEAD -> master, tag: v0.15.2)|refresh commits when adding a tag
|
||||||
// 32e650e0bb3f4327749e this is my other commit
|
func (c *CommitListBuilder) extractCommitFromLine(line string) *Commit {
|
||||||
func (c *CommitListBuilder) commitLineParts(line string) (string, string, []string) {
|
split := strings.Split(line, SEPARATION_CHAR)
|
||||||
re := regexp.MustCompile(`(\w+) (.*)`)
|
|
||||||
shaMatch := re.FindStringSubmatch(line)
|
|
||||||
|
|
||||||
if len(shaMatch) <= 1 {
|
sha := split[0]
|
||||||
return line, "", nil
|
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])
|
||||||
}
|
}
|
||||||
sha := shaMatch[1]
|
|
||||||
rest := shaMatch[2]
|
|
||||||
|
|
||||||
if !strings.HasPrefix(rest, "(") {
|
|
||||||
return sha, rest, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
re = regexp.MustCompile(`\((.*)\) (.*)`)
|
return &Commit{
|
||||||
|
Sha: sha,
|
||||||
parensMatch := re.FindStringSubmatch(rest)
|
Name: message,
|
||||||
if len(parensMatch) <= 1 {
|
DisplayString: line,
|
||||||
return sha, rest, nil
|
Tags: tags,
|
||||||
|
ExtraInfo: extraInfo,
|
||||||
|
Date: date,
|
||||||
|
Author: author,
|
||||||
}
|
}
|
||||||
|
|
||||||
notes := parensMatch[1]
|
|
||||||
message := parensMatch[2]
|
|
||||||
re = regexp.MustCompile(`tag: ([^,]+)`)
|
|
||||||
tagMatch := re.FindStringSubmatch(notes)
|
|
||||||
if len(tagMatch) <= 1 {
|
|
||||||
return sha, message, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
tag := tagMatch[1]
|
|
||||||
return sha, message, []string{tag}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCommits obtains the commits of the current branch
|
// 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
|
// now we can split it up and turn it into commits
|
||||||
for _, line := range utils.SplitLines(log) {
|
for _, line := range utils.SplitLines(log) {
|
||||||
sha, name, tags := c.commitLineParts(line)
|
commit := c.extractCommitFromLine(line)
|
||||||
_, unpushed := unpushedCommits[sha]
|
_, unpushed := unpushedCommits[commit.Sha]
|
||||||
status := map[bool]string{true: "unpushed", false: "pushed"}[unpushed]
|
commit.Status = map[bool]string{true: "unpushed", false: "pushed"}[unpushed]
|
||||||
commits = append(commits, &Commit{
|
commits = append(commits, commit)
|
||||||
Sha: sha,
|
|
||||||
Name: name,
|
|
||||||
Status: status,
|
|
||||||
DisplayString: line,
|
|
||||||
Tags: tags,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if rebaseMode != "" {
|
if rebaseMode != "" {
|
||||||
currentCommit := commits[len(rebasingCommits)]
|
currentCommit := commits[len(rebasingCommits)]
|
||||||
|
@ -325,7 +316,8 @@ func (c *CommitListBuilder) getLog(limit bool) string {
|
||||||
limitFlag = "-30"
|
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 {
|
if err != nil {
|
||||||
// assume if there is an error there are no commits yet for this branch
|
// assume if there is an error there are no commits yet for this branch
|
||||||
return ""
|
return ""
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -600,9 +601,8 @@ func (gui *Gui) renderBranchCommitsWithSelection() error {
|
||||||
commitsView := gui.getCommitsView()
|
commitsView := gui.getCommitsView()
|
||||||
|
|
||||||
gui.refreshSelectedLine(&gui.State.Panels.Commits.SelectedLine, len(gui.State.Commits))
|
gui.refreshSelectedLine(&gui.State.Panels.Commits.SelectedLine, len(gui.State.Commits))
|
||||||
if err := gui.renderListPanel(commitsView, gui.State.Commits); err != nil {
|
displayStrings := presentation.GetCommitListDisplayStrings(gui.State.Commits, gui.State.ScreenMode != SCREEN_NORMAL)
|
||||||
return err
|
gui.renderDisplayStrings(commitsView, displayStrings)
|
||||||
}
|
|
||||||
if gui.g.CurrentView() == commitsView && commitsView.Context == "branch-commits" {
|
if gui.g.CurrentView() == commitsView && commitsView.Context == "branch-commits" {
|
||||||
if err := gui.handleCommitSelect(gui.g, commitsView); err != nil {
|
if err := gui.handleCommitSelect(gui.g, commitsView); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -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 {
|
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)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) prevScreenMode(g *gocui.Gui, v *gocui.View) error {
|
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)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
126
pkg/gui/presentation/commits.go
Normal file
126
pkg/gui/presentation/commits.go
Normal file
|
@ -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)}
|
||||||
|
}
|
|
@ -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 {
|
func (gui *Gui) renderListPanel(v *gocui.View, items interface{}) error {
|
||||||
gui.g.Update(func(g *gocui.Gui) error {
|
gui.g.Update(func(g *gocui.Gui) error {
|
||||||
isFocused := gui.g.CurrentView().Name() == v.Name()
|
isFocused := gui.g.CurrentView().Name() == v.Name()
|
||||||
|
|
|
@ -348,3 +348,22 @@ func PrevIntInCycle(sl []int, current int) int {
|
||||||
}
|
}
|
||||||
return sl[len(sl)-1]
|
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] + "..."
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue