From 52b132fe01156d8c7654180d03e40726e18692cd Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Mon, 10 Sep 2018 20:17:39 +1000 Subject: [PATCH 01/14] better handling of cursor and origin positionings --- pkg/gui/files_panel.go | 25 ++++++++++++++++--------- pkg/gui/menu_panel.go | 10 ++++++++-- pkg/gui/view_helpers.go | 39 ++++++++++++++++++++++++++++----------- pkg/utils/utils.go | 8 ++++++++ 4 files changed, 60 insertions(+), 22 deletions(-) diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index bfcc938bb..8fd2fb30c 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -275,22 +275,23 @@ func (gui *Gui) updateHasMergeConflictStatus() error { return nil } -func (gui *Gui) renderFile(file commands.File, filesView *gocui.View) { +func (gui *Gui) renderFile(file commands.File) string { // potentially inefficient to be instantiating these color // objects with each render red := color.New(color.FgRed) green := color.New(color.FgGreen) if !file.Tracked && !file.HasStagedChanges { - red.Fprintln(filesView, file.DisplayString) - return + return red.Sprint(file.DisplayString) } - green.Fprint(filesView, file.DisplayString[0:1]) - red.Fprint(filesView, file.DisplayString[1:3]) + + output := green.Sprint(file.DisplayString[0:1]) + output += red.Sprint(file.DisplayString[1:3]) if file.HasUnstagedChanges { - red.Fprintln(filesView, file.Name) + output += red.Sprint(file.Name) } else { - green.Fprintln(filesView, file.Name) + output += green.Sprint(file.Name) } + return output } func (gui *Gui) catSelectedFile(g *gocui.Gui) (string, error) { @@ -319,8 +320,14 @@ func (gui *Gui) refreshFiles(g *gocui.Gui) error { } gui.refreshStateFiles() filesView.Clear() - for _, file := range gui.State.Files { - gui.renderFile(file, filesView) + for i, file := range gui.State.Files { + str := gui.renderFile(file) + if i < len(gui.State.Files)-1 { + str += "\n" + } + if _, err := filesView.Write([]byte(str)); err != nil { + return err + } } gui.correctCursor(filesView) if filesView == g.CurrentView() { diff --git a/pkg/gui/menu_panel.go b/pkg/gui/menu_panel.go index 68041390d..f52ceda07 100644 --- a/pkg/gui/menu_panel.go +++ b/pkg/gui/menu_panel.go @@ -8,6 +8,12 @@ import ( "github.com/jesseduffield/lazygit/pkg/utils" ) +// I need to store the handler function in state and it will take an interface and do something with it +// I need to have another function describing how to display one of the structs +// perhaps this calls for an interface where the struct is Binding and the interface has the methods Display and Execute +// but this means that for the one struct I can only have one possible display/execute function, but I want to use whatever I want. +// Would I ever need to use different handlers for different things? Maybe I should assume not given that I can cross that bridge when I come to it + func (gui *Gui) handleMenuPress(g *gocui.Gui, v *gocui.View) error { lineNumber := gui.getItemPosition(v) if gui.State.Keys[lineNumber].Key == nil { @@ -106,11 +112,11 @@ func (gui *Gui) handleMenu(g *gocui.Gui, v *gocui.View) error { content := append(contentPanel, contentGlobal...) gui.State.Keys = append(bindingsPanel, bindingsGlobal...) // append newline at the end so the last line would be selectable - contentJoined := strings.Join(content, "\n") + "\n" + contentJoined := strings.Join(content, "\n") // y1-1 so there will not be an extra space at the end of panel x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, contentJoined) - menuView, _ := g.SetView("menu", x0, y0, x1, y1-1, 0) + menuView, _ := g.SetView("menu", x0, y0, x1, y1, 0) menuView.Title = strings.Title(gui.Tr.SLocalize("menu")) menuView.FgColor = gocui.ColorWhite diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index 7a5bd8874..5178bd4d9 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -160,7 +160,6 @@ func (gui *Gui) getItemPosition(v *gocui.View) int { func (gui *Gui) cursorUp(g *gocui.Gui, v *gocui.View) error { // swallowing cursor movements in main - // TODO: pull this out if v == nil || v.Name() == "main" { return nil } @@ -179,19 +178,28 @@ func (gui *Gui) cursorUp(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) cursorDown(g *gocui.Gui, v *gocui.View) error { // swallowing cursor movements in main - // TODO: pull this out if v == nil || v.Name() == "main" { return nil } cx, cy := v.Cursor() ox, oy := v.Origin() - if cy+oy >= len(v.BufferLines())-2 { + ly := len(v.BufferLines()) - 1 + _, height := v.Size() + maxY := height - 1 + + // if we are at the end we just return + if cy+oy == ly { return nil } - if err := v.SetCursor(cx, cy+1); err != nil { - if err := v.SetOrigin(ox, oy+1); err != nil { - return err - } + + var err error + if cy < maxY { + err = v.SetCursor(cx, cy+1) + } else { + err = v.SetOrigin(ox, oy+1) + } + if err != nil { + return err } gui.newLineFocused(g, v) @@ -208,10 +216,19 @@ func (gui *Gui) resetOrigin(v *gocui.View) error { // if the cursor down past the last item, move it to the last line func (gui *Gui) correctCursor(v *gocui.View) error { cx, cy := v.Cursor() - _, oy := v.Origin() - lineCount := len(v.BufferLines()) - 2 - if cy >= lineCount-oy { - return v.SetCursor(cx, lineCount-oy) + ox, oy := v.Origin() + _, height := v.Size() + maxY := height - 1 + ly := len(v.BufferLines()) - 1 + if oy+cy <= ly { + return nil + } + newCy := utils.Min(ly, maxY) + if err := v.SetCursor(cx, newCy); err != nil { + return err + } + if err := v.SetOrigin(ox, ly-newCy); err != nil { + return err } return nil } diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index e28ab1824..cb23eb75e 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -99,3 +99,11 @@ func ResolvePlaceholderString(str string, arguments map[string]string) string { } return str } + +// Min returns the minimum of two integers +func Min(x, y int) int { + if x < y { + return x + } + return y +} From f8b484f638e813537b9b968cf65d378b900fbcee Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 12 Sep 2018 18:23:25 +1000 Subject: [PATCH 02/14] don't use newlines at the end of panel buffers --- pkg/gui/branches_panel.go | 4 +++- pkg/gui/commits_panel.go | 31 +++++++++++++++++++------------ pkg/gui/files_panel.go | 16 ++++++++-------- pkg/gui/stash_panel.go | 12 +++++++++--- 4 files changed, 39 insertions(+), 24 deletions(-) diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index 26c3c5618..d164003e5 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -152,9 +152,11 @@ func (gui *Gui) refreshBranches(g *gocui.Gui) error { } gui.State.Branches = builder.Build() v.Clear() + displayStrings := []string{} for _, branch := range gui.State.Branches { - fmt.Fprintln(v, branch.GetDisplayString()) + displayStrings = append(displayStrings, branch.GetDisplayString()) } + fmt.Fprint(v, strings.Join(displayStrings, "\n")) gui.resetOrigin(v) return gui.refreshStatus(g) }) diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index 1c1662475..dd33369a5 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -2,12 +2,27 @@ package gui import ( "errors" + "fmt" + "strings" "github.com/fatih/color" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" ) +func (gui *Gui) renderCommit(commit commands.Commit) string { + red := color.New(color.FgRed) + yellow := color.New(color.FgYellow) + white := color.New(color.FgWhite) + + shaColor := yellow + if commit.Pushed { + shaColor = red + } + + return shaColor.Sprint(commit.Sha) + " " + white.Sprint(commit.Name) +} + func (gui *Gui) refreshCommits(g *gocui.Gui) error { g.Update(func(*gocui.Gui) error { gui.State.Commits = gui.GitCommand.GetCommits() @@ -16,19 +31,11 @@ func (gui *Gui) refreshCommits(g *gocui.Gui) error { panic(err) } v.Clear() - red := color.New(color.FgRed) - yellow := color.New(color.FgYellow) - white := color.New(color.FgWhite) - shaColor := white - for _, commit := range gui.State.Commits { - if commit.Pushed { - shaColor = red - } else { - shaColor = yellow - } - shaColor.Fprint(v, commit.Sha+" ") - white.Fprintln(v, commit.Name) + displayStrings := make([]string, len(gui.State.Commits)) + for i, commit := range gui.State.Commits { + displayStrings[i] = gui.renderCommit(commit) } + fmt.Fprint(v, strings.Join(displayStrings, "\n")) gui.refreshStatus(g) if g.CurrentView().Name() == "commits" { gui.handleCommitSelect(g, v) diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 8fd2fb30c..6ad2aed5b 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -7,6 +7,7 @@ import ( // "strings" + "fmt" "strings" "github.com/fatih/color" @@ -319,16 +320,15 @@ func (gui *Gui) refreshFiles(g *gocui.Gui) error { return err } gui.refreshStateFiles() - filesView.Clear() + + displayStrings := make([]string, len(gui.State.Files)) for i, file := range gui.State.Files { - str := gui.renderFile(file) - if i < len(gui.State.Files)-1 { - str += "\n" - } - if _, err := filesView.Write([]byte(str)); err != nil { - return err - } + displayStrings[i] = gui.renderFile(file) } + + filesView.Clear() + fmt.Fprint(filesView, strings.Join(displayStrings, "\n")) + gui.correctCursor(filesView) if filesView == g.CurrentView() { gui.handleFileSelect(g, filesView) diff --git a/pkg/gui/stash_panel.go b/pkg/gui/stash_panel.go index 9ca07717b..3a9fac8d4 100644 --- a/pkg/gui/stash_panel.go +++ b/pkg/gui/stash_panel.go @@ -2,6 +2,7 @@ package gui import ( "fmt" + "strings" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" @@ -14,10 +15,15 @@ func (gui *Gui) refreshStashEntries(g *gocui.Gui) error { panic(err) } gui.State.StashEntries = gui.GitCommand.GetStashEntries() - v.Clear() - for _, stashEntry := range gui.State.StashEntries { - fmt.Fprintln(v, stashEntry.DisplayString) + + displayStrings := make([]string, len(gui.State.StashEntries)) + for i, stashEntry := range gui.State.StashEntries { + displayStrings[i] = stashEntry.DisplayString } + + v.Clear() + fmt.Fprint(v, strings.Join(displayStrings, "\n")) + return gui.resetOrigin(v) }) return nil From 31c33dfdcb4f8e27a8b50493876b17825c25c0ec Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 12 Sep 2018 18:47:37 +1000 Subject: [PATCH 03/14] remove redundant comments --- pkg/gui/menu_panel.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pkg/gui/menu_panel.go b/pkg/gui/menu_panel.go index f52ceda07..983543c94 100644 --- a/pkg/gui/menu_panel.go +++ b/pkg/gui/menu_panel.go @@ -8,12 +8,6 @@ import ( "github.com/jesseduffield/lazygit/pkg/utils" ) -// I need to store the handler function in state and it will take an interface and do something with it -// I need to have another function describing how to display one of the structs -// perhaps this calls for an interface where the struct is Binding and the interface has the methods Display and Execute -// but this means that for the one struct I can only have one possible display/execute function, but I want to use whatever I want. -// Would I ever need to use different handlers for different things? Maybe I should assume not given that I can cross that bridge when I come to it - func (gui *Gui) handleMenuPress(g *gocui.Gui, v *gocui.View) error { lineNumber := gui.getItemPosition(v) if gui.State.Keys[lineNumber].Key == nil { @@ -111,10 +105,8 @@ func (gui *Gui) handleMenu(g *gocui.Gui, v *gocui.View) error { content := append(contentPanel, contentGlobal...) gui.State.Keys = append(bindingsPanel, bindingsGlobal...) - // append newline at the end so the last line would be selectable contentJoined := strings.Join(content, "\n") - // y1-1 so there will not be an extra space at the end of panel x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, contentJoined) menuView, _ := g.SetView("menu", x0, y0, x1, y1, 0) menuView.Title = strings.Title(gui.Tr.SLocalize("menu")) From 35cae80de962ba17a489c3a432c9f0db5149158c Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 12 Sep 2018 18:49:09 +1000 Subject: [PATCH 04/14] more efficient building of branch displaystrings --- pkg/gui/branches_panel.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index d164003e5..659725ef2 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -152,9 +152,9 @@ func (gui *Gui) refreshBranches(g *gocui.Gui) error { } gui.State.Branches = builder.Build() v.Clear() - displayStrings := []string{} - for _, branch := range gui.State.Branches { - displayStrings = append(displayStrings, branch.GetDisplayString()) + displayStrings := make([]string, len(gui.State.Branches)) + for i, branch := range gui.State.Branches { + displayStrings[i] = branch.GetDisplayString() } fmt.Fprint(v, strings.Join(displayStrings, "\n")) gui.resetOrigin(v) From 3b765e5417501a39bca5c2f0038488dbbeb6b200 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 12 Sep 2018 19:39:36 +1000 Subject: [PATCH 05/14] add test for min method --- pkg/utils/utils_test.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index 0b2d35959..a088ed849 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -167,3 +167,33 @@ func TestResolvePlaceholderString(t *testing.T) { assert.EqualValues(t, string(s.expected), ResolvePlaceholderString(s.templateString, s.arguments)) } } + +func TestMin(t *testing.T) { + type scenario struct { + a int + b int + expected int + } + + scenarios := []scenario{ + { + 2, + 4, + 2, + }, + { + 2, + 1, + 1, + }, + { + 1, + 1, + 1, + }, + } + + for _, s := range scenarios { + assert.EqualValues(t, s.expected, Min(s.a, s.b)) + } +} From c00c834b359bc0ebcd6e940e5cb5ef6f7247a6c7 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Mon, 17 Sep 2018 21:02:30 +1000 Subject: [PATCH 06/14] standardise rendering of lists in panels --- pkg/commands/branch.go | 6 ++-- pkg/commands/commit.go | 26 ++++++++++++++ pkg/commands/file.go | 36 +++++++++++++++++++ pkg/commands/git.go | 28 +++++++-------- pkg/commands/git_structs.go | 27 -------------- pkg/commands/stash_entry.go | 13 +++++++ pkg/git/branch_list_builder.go | 32 ++++++++--------- pkg/gui/branches_panel.go | 14 +++++--- pkg/gui/commits_panel.go | 32 ++++++----------- pkg/gui/files_panel.go | 46 +++++++----------------- pkg/gui/gui.go | 16 ++++----- pkg/gui/keybindings.go | 28 ++++++++++++--- pkg/gui/menu_panel.go | 51 ++++++-------------------- pkg/gui/stash_panel.go | 15 ++++---- pkg/utils/utils.go | 66 ++++++++++++++++++++++++++++++++++ 15 files changed, 256 insertions(+), 180 deletions(-) create mode 100644 pkg/commands/commit.go create mode 100644 pkg/commands/file.go create mode 100644 pkg/commands/stash_entry.go diff --git a/pkg/commands/branch.go b/pkg/commands/branch.go index 13c26e766..19553a26b 100644 --- a/pkg/commands/branch.go +++ b/pkg/commands/branch.go @@ -14,9 +14,9 @@ type Branch struct { Recency string } -// GetDisplayString returns the dispaly string of branch -func (b *Branch) GetDisplayString() string { - return utils.WithPadding(b.Recency, 4) + utils.ColoredString(b.Name, b.GetColor()) +// GetDisplayStrings returns the dispaly string of branch +func (b *Branch) GetDisplayStrings() []string { + return []string{b.Recency, utils.ColoredString(b.Name, b.GetColor())} } // GetColor branch color diff --git a/pkg/commands/commit.go b/pkg/commands/commit.go new file mode 100644 index 000000000..37c3e9525 --- /dev/null +++ b/pkg/commands/commit.go @@ -0,0 +1,26 @@ +package commands + +import ( + "github.com/fatih/color" +) + +// Commit : A git commit +type Commit struct { + Sha string + Name string + Pushed bool + DisplayString string +} + +func (c *Commit) GetDisplayStrings() []string { + red := color.New(color.FgRed) + yellow := color.New(color.FgYellow) + white := color.New(color.FgWhite) + + shaColor := yellow + if c.Pushed { + shaColor = red + } + + return []string{shaColor.Sprint(c.Sha), white.Sprint(c.Name)} +} diff --git a/pkg/commands/file.go b/pkg/commands/file.go new file mode 100644 index 000000000..8fcd9aff9 --- /dev/null +++ b/pkg/commands/file.go @@ -0,0 +1,36 @@ +package commands + +import "github.com/fatih/color" + +// File : A file from git status +// duplicating this for now +type File struct { + Name string + HasStagedChanges bool + HasUnstagedChanges bool + Tracked bool + Deleted bool + HasMergeConflicts bool + DisplayString string + Type string // one of 'file', 'directory', and 'other' +} + +// GetDisplayStrings returns the display string of a file +func (f *File) GetDisplayStrings() []string { + // potentially inefficient to be instantiating these color + // objects with each render + red := color.New(color.FgRed) + green := color.New(color.FgGreen) + if !f.Tracked && !f.HasStagedChanges { + return []string{red.Sprint(f.DisplayString)} + } + + output := green.Sprint(f.DisplayString[0:1]) + output += red.Sprint(f.DisplayString[1:3]) + if f.HasUnstagedChanges { + output += red.Sprint(f.Name) + } else { + output += green.Sprint(f.Name) + } + return []string{output} +} diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 5744fa6aa..3fb46fff7 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -104,17 +104,17 @@ func NewGitCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localizer) } // GetStashEntries stash entryies -func (c *GitCommand) GetStashEntries() []StashEntry { +func (c *GitCommand) GetStashEntries() []*StashEntry { rawString, _ := c.OSCommand.RunCommandWithOutput("git stash list --pretty='%gs'") - stashEntries := []StashEntry{} + stashEntries := []*StashEntry{} for i, line := range utils.SplitLines(rawString) { stashEntries = append(stashEntries, stashEntryFromLine(line, i)) } return stashEntries } -func stashEntryFromLine(line string, index int) StashEntry { - return StashEntry{ +func stashEntryFromLine(line string, index int) *StashEntry { + return &StashEntry{ Name: line, Index: index, DisplayString: line, @@ -127,10 +127,10 @@ func (c *GitCommand) GetStashEntryDiff(index int) (string, error) { } // GetStatusFiles git status files -func (c *GitCommand) GetStatusFiles() []File { +func (c *GitCommand) GetStatusFiles() []*File { statusOutput, _ := c.GitStatus() statusStrings := utils.SplitLines(statusOutput) - files := []File{} + files := []*File{} for _, statusString := range statusStrings { change := statusString[0:2] @@ -140,7 +140,7 @@ func (c *GitCommand) GetStatusFiles() []File { _, untracked := map[string]bool{"??": true, "A ": true, "AM": true}[change] _, hasNoStagedChanges := map[string]bool{" ": true, "U": true, "?": true}[stagedChange] - file := File{ + file := &File{ Name: filename, DisplayString: statusString, HasStagedChanges: !hasNoStagedChanges, @@ -168,7 +168,7 @@ func (c *GitCommand) StashSave(message string) error { } // MergeStatusFiles merge status files -func (c *GitCommand) MergeStatusFiles(oldFiles, newFiles []File) []File { +func (c *GitCommand) MergeStatusFiles(oldFiles, newFiles []*File) []*File { if len(oldFiles) == 0 { return newFiles } @@ -176,7 +176,7 @@ func (c *GitCommand) MergeStatusFiles(oldFiles, newFiles []File) []File { appendedIndexes := []int{} // retain position of files we already could see - result := []File{} + result := []*File{} for _, oldFile := range oldFiles { for newIndex, newFile := range newFiles { if oldFile.Name == newFile.Name { @@ -415,7 +415,7 @@ func (c *GitCommand) IsInMergeState() (bool, error) { } // RemoveFile directly -func (c *GitCommand) RemoveFile(file File) error { +func (c *GitCommand) RemoveFile(file *File) error { // if the file isn't tracked, we assume you want to delete it if file.HasStagedChanges { if err := c.OSCommand.RunCommand("git reset -- " + file.Name); err != nil { @@ -471,17 +471,17 @@ func includesString(list []string, a string) bool { } // GetCommits obtains the commits of the current branch -func (c *GitCommand) GetCommits() []Commit { +func (c *GitCommand) GetCommits() []*Commit { pushables := c.GetCommitsToPush() log := c.GetLog() - commits := []Commit{} + commits := []*Commit{} // now we can split it up and turn it into commits lines := utils.SplitLines(log) for _, line := range lines { splitLine := strings.Split(line, " ") sha := splitLine[0] pushed := includesString(pushables, sha) - commits = append(commits, Commit{ + commits = append(commits, &Commit{ Sha: sha, Name: strings.Join(splitLine[1:], " "), Pushed: pushed, @@ -519,7 +519,7 @@ func (c *GitCommand) Show(sha string) string { } // Diff returns the diff of a file -func (c *GitCommand) Diff(file File) string { +func (c *GitCommand) Diff(file *File) string { cachedArg := "" fileName := c.OSCommand.Quote(file.Name) if file.HasStagedChanges && !file.HasUnstagedChanges { diff --git a/pkg/commands/git_structs.go b/pkg/commands/git_structs.go index 2b3b3f2db..1590ce32e 100644 --- a/pkg/commands/git_structs.go +++ b/pkg/commands/git_structs.go @@ -1,32 +1,5 @@ package commands -// File : A staged/unstaged file -type File struct { - Name string - HasStagedChanges bool - HasUnstagedChanges bool - Tracked bool - Deleted bool - HasMergeConflicts bool - DisplayString string - Type string // one of 'file', 'directory', and 'other' -} - -// Commit : A git commit -type Commit struct { - Sha string - Name string - Pushed bool - DisplayString string -} - -// StashEntry : A git stash entry -type StashEntry struct { - Index int - Name string - DisplayString string -} - // Conflict : A git conflict with a start middle and end corresponding to line // numbers in the file where the conflict bars appear type Conflict struct { diff --git a/pkg/commands/stash_entry.go b/pkg/commands/stash_entry.go new file mode 100644 index 000000000..3886fd4c9 --- /dev/null +++ b/pkg/commands/stash_entry.go @@ -0,0 +1,13 @@ +package commands + +// StashEntry : A git stash entry +type StashEntry struct { + Index int + Name string + DisplayString string +} + +// GetDisplayStrings returns the dispaly string of branch +func (s *StashEntry) GetDisplayStrings() []string { + return []string{s.DisplayString} +} diff --git a/pkg/git/branch_list_builder.go b/pkg/git/branch_list_builder.go index 651b684f5..151e5b0b4 100644 --- a/pkg/git/branch_list_builder.go +++ b/pkg/git/branch_list_builder.go @@ -34,7 +34,7 @@ func NewBranchListBuilder(log *logrus.Entry, gitCommand *commands.GitCommand) (* }, nil } -func (b *BranchListBuilder) obtainCurrentBranch() commands.Branch { +func (b *BranchListBuilder) obtainCurrentBranch() *commands.Branch { // I used go-git for this, but that breaks if you've just done a git init, // even though you're on 'master' branchName, err := b.GitCommand.OSCommand.RunCommandWithOutput("git symbolic-ref --short HEAD") @@ -44,11 +44,11 @@ func (b *BranchListBuilder) obtainCurrentBranch() commands.Branch { panic(err.Error()) } } - return commands.Branch{Name: strings.TrimSpace(branchName), Recency: " *"} + return &commands.Branch{Name: strings.TrimSpace(branchName), Recency: " *"} } -func (b *BranchListBuilder) obtainReflogBranches() []commands.Branch { - branches := make([]commands.Branch, 0) +func (b *BranchListBuilder) obtainReflogBranches() []*commands.Branch { + branches := make([]*commands.Branch, 0) rawString, err := b.GitCommand.OSCommand.RunCommandWithOutput("git reflog -n100 --pretty='%cr|%gs' --grep-reflog='checkout: moving' HEAD") if err != nil { return branches @@ -58,14 +58,14 @@ func (b *BranchListBuilder) obtainReflogBranches() []commands.Branch { for _, line := range branchLines { timeNumber, timeUnit, branchName := branchInfoFromLine(line) timeUnit = abbreviatedTimeUnit(timeUnit) - branch := commands.Branch{Name: branchName, Recency: timeNumber + timeUnit} + branch := &commands.Branch{Name: branchName, Recency: timeNumber + timeUnit} branches = append(branches, branch) } return branches } -func (b *BranchListBuilder) obtainSafeBranches() []commands.Branch { - branches := make([]commands.Branch, 0) +func (b *BranchListBuilder) obtainSafeBranches() []*commands.Branch { + branches := make([]*commands.Branch, 0) bIter, err := b.GitCommand.Repo.Branches() if err != nil { @@ -73,14 +73,14 @@ func (b *BranchListBuilder) obtainSafeBranches() []commands.Branch { } err = bIter.ForEach(func(b *plumbing.Reference) error { name := b.Name().Short() - branches = append(branches, commands.Branch{Name: name}) + branches = append(branches, &commands.Branch{Name: name}) return nil }) return branches } -func (b *BranchListBuilder) appendNewBranches(finalBranches, newBranches, existingBranches []commands.Branch, included bool) []commands.Branch { +func (b *BranchListBuilder) appendNewBranches(finalBranches, newBranches, existingBranches []*commands.Branch, included bool) []*commands.Branch { for _, newBranch := range newBranches { if included == branchIncluded(newBranch.Name, existingBranches) { finalBranches = append(finalBranches, newBranch) @@ -89,7 +89,7 @@ func (b *BranchListBuilder) appendNewBranches(finalBranches, newBranches, existi return finalBranches } -func sanitisedReflogName(reflogBranch commands.Branch, safeBranches []commands.Branch) string { +func sanitisedReflogName(reflogBranch *commands.Branch, safeBranches []*commands.Branch) string { for _, safeBranch := range safeBranches { if strings.ToLower(safeBranch.Name) == strings.ToLower(reflogBranch.Name) { return safeBranch.Name @@ -99,15 +99,15 @@ func sanitisedReflogName(reflogBranch commands.Branch, safeBranches []commands.B } // Build the list of branches for the current repo -func (b *BranchListBuilder) Build() []commands.Branch { - branches := make([]commands.Branch, 0) +func (b *BranchListBuilder) Build() []*commands.Branch { + branches := make([]*commands.Branch, 0) head := b.obtainCurrentBranch() safeBranches := b.obtainSafeBranches() if len(safeBranches) == 0 { return append(branches, head) } reflogBranches := b.obtainReflogBranches() - reflogBranches = uniqueByName(append([]commands.Branch{head}, reflogBranches...)) + reflogBranches = uniqueByName(append([]*commands.Branch{head}, reflogBranches...)) for i, reflogBranch := range reflogBranches { reflogBranches[i].Name = sanitisedReflogName(reflogBranch, safeBranches) } @@ -118,7 +118,7 @@ func (b *BranchListBuilder) Build() []commands.Branch { return branches } -func branchIncluded(branchName string, branches []commands.Branch) bool { +func branchIncluded(branchName string, branches []*commands.Branch) bool { for _, existingBranch := range branches { if strings.ToLower(existingBranch.Name) == strings.ToLower(branchName) { return true @@ -127,8 +127,8 @@ func branchIncluded(branchName string, branches []commands.Branch) bool { return false } -func uniqueByName(branches []commands.Branch) []commands.Branch { - finalBranches := make([]commands.Branch, 0) +func uniqueByName(branches []*commands.Branch) []*commands.Branch { + finalBranches := make([]*commands.Branch, 0) for _, branch := range branches { if branchIncluded(branch.Name, finalBranches) { continue diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index 659725ef2..0f66533b1 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -7,6 +7,7 @@ import ( "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/git" + "github.com/jesseduffield/lazygit/pkg/utils" ) func (gui *Gui) handleBranchPress(g *gocui.Gui, v *gocui.View) error { @@ -109,7 +110,7 @@ func (gui *Gui) handleMerge(g *gocui.Gui, v *gocui.View) error { return nil } -func (gui *Gui) getSelectedBranch(v *gocui.View) commands.Branch { +func (gui *Gui) getSelectedBranch(v *gocui.View) *commands.Branch { lineNumber := gui.getItemPosition(v) return gui.State.Branches[lineNumber] } @@ -151,12 +152,15 @@ func (gui *Gui) refreshBranches(g *gocui.Gui) error { return err } gui.State.Branches = builder.Build() + v.Clear() - displayStrings := make([]string, len(gui.State.Branches)) - for i, branch := range gui.State.Branches { - displayStrings[i] = branch.GetDisplayString() + list, err := utils.RenderList(gui.State.Branches) + if err != nil { + return err } - fmt.Fprint(v, strings.Join(displayStrings, "\n")) + + fmt.Fprint(v, list) + gui.resetOrigin(v) return gui.refreshStatus(g) }) diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index dd33369a5..f0b96586a 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -3,26 +3,12 @@ package gui import ( "errors" "fmt" - "strings" - "github.com/fatih/color" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/utils" ) -func (gui *Gui) renderCommit(commit commands.Commit) string { - red := color.New(color.FgRed) - yellow := color.New(color.FgYellow) - white := color.New(color.FgWhite) - - shaColor := yellow - if commit.Pushed { - shaColor = red - } - - return shaColor.Sprint(commit.Sha) + " " + white.Sprint(commit.Name) -} - func (gui *Gui) refreshCommits(g *gocui.Gui) error { g.Update(func(*gocui.Gui) error { gui.State.Commits = gui.GitCommand.GetCommits() @@ -30,12 +16,14 @@ func (gui *Gui) refreshCommits(g *gocui.Gui) error { if err != nil { panic(err) } + v.Clear() - displayStrings := make([]string, len(gui.State.Commits)) - for i, commit := range gui.State.Commits { - displayStrings[i] = gui.renderCommit(commit) + list, err := utils.RenderList(gui.State.Commits) + if err != nil { + return err } - fmt.Fprint(v, strings.Join(displayStrings, "\n")) + fmt.Fprint(v, list) + gui.refreshStatus(g) if g.CurrentView().Name() == "commits" { gui.handleCommitSelect(g, v) @@ -106,7 +94,7 @@ func (gui *Gui) handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error { } // TODO: move to files panel -func (gui *Gui) anyUnStagedChanges(files []commands.File) bool { +func (gui *Gui) anyUnStagedChanges(files []*commands.File) bool { for _, file := range files { if file.Tracked && file.HasUnstagedChanges { return true @@ -169,13 +157,13 @@ func (gui *Gui) handleRenameCommitEditor(g *gocui.Gui, v *gocui.View) error { return nil } -func (gui *Gui) getSelectedCommit(g *gocui.Gui) (commands.Commit, error) { +func (gui *Gui) getSelectedCommit(g *gocui.Gui) (*commands.Commit, error) { v, err := g.View("commits") if err != nil { panic(err) } if len(gui.State.Commits) == 0 { - return commands.Commit{}, errors.New(gui.Tr.SLocalize("NoCommitsThisBranch")) + return &commands.Commit{}, errors.New(gui.Tr.SLocalize("NoCommitsThisBranch")) } lineNumber := gui.getItemPosition(v) if lineNumber > len(gui.State.Commits)-1 { diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 6ad2aed5b..6fd05552f 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -10,14 +10,14 @@ import ( "fmt" "strings" - "github.com/fatih/color" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/utils" ) -func (gui *Gui) stagedFiles() []commands.File { +func (gui *Gui) stagedFiles() []*commands.File { files := gui.State.Files - result := make([]commands.File, 0) + result := make([]*commands.File, 0) for _, file := range files { if file.HasStagedChanges { result = append(result, file) @@ -26,9 +26,9 @@ func (gui *Gui) stagedFiles() []commands.File { return result } -func (gui *Gui) trackedFiles() []commands.File { +func (gui *Gui) trackedFiles() []*commands.File { files := gui.State.Files - result := make([]commands.File, 0) + result := make([]*commands.File, 0) for _, file := range files { if file.Tracked { result = append(result, file) @@ -117,9 +117,9 @@ func (gui *Gui) handleAddPatch(g *gocui.Gui, v *gocui.View) error { return gui.Errors.ErrSubProcess } -func (gui *Gui) getSelectedFile(g *gocui.Gui) (commands.File, error) { +func (gui *Gui) getSelectedFile(g *gocui.Gui) (*commands.File, error) { if len(gui.State.Files) == 0 { - return commands.File{}, gui.Errors.ErrNoFiles + return &commands.File{}, gui.Errors.ErrNoFiles } filesView, err := g.View("files") if err != nil { @@ -185,7 +185,7 @@ func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View) error { gui.renderString(g, "main", gui.Tr.SLocalize("NoChangedFiles")) return gui.renderfilesOptions(g, nil) } - gui.renderfilesOptions(g, &file) + gui.renderfilesOptions(g, file) var content string if file.HasMergeConflicts { return gui.refreshMergePanel(g) @@ -276,25 +276,6 @@ func (gui *Gui) updateHasMergeConflictStatus() error { return nil } -func (gui *Gui) renderFile(file commands.File) string { - // potentially inefficient to be instantiating these color - // objects with each render - red := color.New(color.FgRed) - green := color.New(color.FgGreen) - if !file.Tracked && !file.HasStagedChanges { - return red.Sprint(file.DisplayString) - } - - output := green.Sprint(file.DisplayString[0:1]) - output += red.Sprint(file.DisplayString[1:3]) - if file.HasUnstagedChanges { - output += red.Sprint(file.Name) - } else { - output += green.Sprint(file.Name) - } - return output -} - func (gui *Gui) catSelectedFile(g *gocui.Gui) (string, error) { item, err := gui.getSelectedFile(g) if err != nil { @@ -321,13 +302,12 @@ func (gui *Gui) refreshFiles(g *gocui.Gui) error { } gui.refreshStateFiles() - displayStrings := make([]string, len(gui.State.Files)) - for i, file := range gui.State.Files { - displayStrings[i] = gui.renderFile(file) - } - filesView.Clear() - fmt.Fprint(filesView, strings.Join(displayStrings, "\n")) + list, err := utils.RenderList(gui.State.Files) + if err != nil { + return err + } + fmt.Fprint(filesView, list) gui.correctCursor(filesView) if filesView == g.CurrentView() { diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index e3cc61529..f527e8c5d 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -71,10 +71,10 @@ type Gui struct { } type guiState struct { - Files []commands.File - Branches []commands.Branch - Commits []commands.Commit - StashEntries []commands.StashEntry + Files []*commands.File + Branches []*commands.Branch + Commits []*commands.Commit + StashEntries []*commands.StashEntry PreviousView string HasMergeConflicts bool ConflictIndex int @@ -83,17 +83,17 @@ type guiState struct { EditHistory *stack.Stack Platform commands.Platform Updating bool - Keys []Binding + Keys []*Binding } // NewGui builds a new gui handler func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *commands.OSCommand, tr *i18n.Localizer, config config.AppConfigurer, updater *updates.Updater) (*Gui, error) { initialState := guiState{ - Files: make([]commands.File, 0), + Files: make([]*commands.File, 0), PreviousView: "files", - Commits: make([]commands.Commit, 0), - StashEntries: make([]commands.StashEntry, 0), + Commits: make([]*commands.Commit, 0), + StashEntries: make([]*commands.StashEntry, 0), ConflictIndex: 0, ConflictTop: true, Conflicts: make([]commands.Conflict, 0), diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index c8ed7d3c8..e05caaec7 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -1,6 +1,8 @@ package gui -import "github.com/jesseduffield/gocui" +import ( + "github.com/jesseduffield/gocui" +) // Binding - a keybinding mapping a key and modifier to a handler. The keypress // is only handled if the given view has focus, or handled globally if the view @@ -14,8 +16,26 @@ type Binding struct { Description string } -func (gui *Gui) GetKeybindings() []Binding { - bindings := []Binding{ +// GetDisplayStrings returns the display string of a file +func (b *Binding) GetDisplayStrings() []string { + return []string{b.GetKey(), b.Description} +} + +func (b *Binding) GetKey() string { + r, ok := b.Key.(rune) + key := "" + + if ok { + key = string(r) + } else if b.KeyReadable != "" { + key = b.KeyReadable + } + + return key +} + +func (gui *Gui) GetKeybindings() []*Binding { + bindings := []*Binding{ { ViewName: "", Key: 'q', @@ -360,7 +380,7 @@ func (gui *Gui) GetKeybindings() []Binding { // Would make these keybindings global but that interferes with editing // input in the confirmation panel for _, viewName := range []string{"status", "files", "branches", "commits", "stash", "menu"} { - bindings = append(bindings, []Binding{ + bindings = append(bindings, []*Binding{ {ViewName: viewName, Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: gui.nextView}, {ViewName: viewName, Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: gui.previousView}, {ViewName: viewName, Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: gui.nextView}, diff --git a/pkg/gui/menu_panel.go b/pkg/gui/menu_panel.go index 983543c94..ee0773a51 100644 --- a/pkg/gui/menu_panel.go +++ b/pkg/gui/menu_panel.go @@ -49,50 +49,20 @@ func (gui *Gui) handleMenuClose(g *gocui.Gui, v *gocui.View) error { return gui.returnFocus(g, v) } -func (gui *Gui) GetKey(binding Binding) string { - r, ok := binding.Key.(rune) - key := "" - - if ok { - key = string(r) - } else if binding.KeyReadable != "" { - key = binding.KeyReadable - } - - return key -} - -func (gui *Gui) GetMaxKeyLength(bindings []Binding) int { - max := 0 - for _, binding := range bindings { - keyLength := len(gui.GetKey(binding)) - if keyLength > max { - max = keyLength - } - } - return max -} - func (gui *Gui) handleMenu(g *gocui.Gui, v *gocui.View) error { var ( - contentGlobal, contentPanel []string - bindingsGlobal, bindingsPanel []Binding + bindingsGlobal, bindingsPanel []*Binding ) // clear keys slice, so we don't have ghost elements gui.State.Keys = gui.State.Keys[:0] bindings := gui.GetKeybindings() - padWidth := gui.GetMaxKeyLength(bindings) for _, binding := range bindings { - key := gui.GetKey(binding) - if key != "" && binding.Description != "" { - content := fmt.Sprintf("%s %s", utils.WithPadding(key, padWidth), binding.Description) + if binding.GetKey() != "" && binding.Description != "" { switch binding.ViewName { case "": - contentGlobal = append(contentGlobal, content) bindingsGlobal = append(bindingsGlobal, binding) case v.Name(): - contentPanel = append(contentPanel, content) bindingsPanel = append(bindingsPanel, binding) } } @@ -100,24 +70,25 @@ func (gui *Gui) handleMenu(g *gocui.Gui, v *gocui.View) error { // append dummy element to have a separator between // panel and global keybindings - contentPanel = append(contentPanel, "") - bindingsPanel = append(bindingsPanel, Binding{}) - - content := append(contentPanel, contentGlobal...) + bindingsPanel = append(bindingsPanel, &Binding{}) gui.State.Keys = append(bindingsPanel, bindingsGlobal...) - contentJoined := strings.Join(content, "\n") - x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, contentJoined) + list, err := utils.RenderList(gui.State.Keys) + if err != nil { + return err + } + + x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, list) menuView, _ := g.SetView("menu", x0, y0, x1, y1, 0) menuView.Title = strings.Title(gui.Tr.SLocalize("menu")) menuView.FgColor = gocui.ColorWhite + menuView.Clear() + fmt.Fprint(menuView, list) if err := gui.renderMenuOptions(g); err != nil { return err } - fmt.Fprint(menuView, contentJoined) - g.Update(func(g *gocui.Gui) error { _, err := g.SetViewOnTop("menu") if err != nil { diff --git a/pkg/gui/stash_panel.go b/pkg/gui/stash_panel.go index 3a9fac8d4..62b4efda7 100644 --- a/pkg/gui/stash_panel.go +++ b/pkg/gui/stash_panel.go @@ -2,10 +2,10 @@ package gui import ( "fmt" - "strings" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/utils" ) func (gui *Gui) refreshStashEntries(g *gocui.Gui) error { @@ -16,13 +16,12 @@ func (gui *Gui) refreshStashEntries(g *gocui.Gui) error { } gui.State.StashEntries = gui.GitCommand.GetStashEntries() - displayStrings := make([]string, len(gui.State.StashEntries)) - for i, stashEntry := range gui.State.StashEntries { - displayStrings[i] = stashEntry.DisplayString - } - v.Clear() - fmt.Fprint(v, strings.Join(displayStrings, "\n")) + list, err := utils.RenderList(gui.State.StashEntries) + if err != nil { + return err + } + fmt.Fprint(v, list) return gui.resetOrigin(v) }) @@ -35,7 +34,7 @@ func (gui *Gui) getSelectedStashEntry(v *gocui.View) *commands.StashEntry { } stashView, _ := gui.g.View("stash") lineNumber := gui.getItemPosition(stashView) - return &gui.State.StashEntries[lineNumber] + return gui.State.StashEntries[lineNumber] } func (gui *Gui) renderStashOptions(g *gocui.Gui) error { diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index cb23eb75e..f1a56116b 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -1,10 +1,12 @@ package utils import ( + "errors" "fmt" "log" "os" "path/filepath" + "reflect" "strings" "time" @@ -107,3 +109,67 @@ func Min(x, y int) int { } return y } + +type Displayable interface { + GetDisplayStrings() []string +} + +// RenderList takes a slice of items, confirms they implement the Displayable +// interface, then generates a list of their displaystrings to write to a panel's +// buffer +func RenderList(slice interface{}) (string, error) { + s := reflect.ValueOf(slice) + if s.Kind() != reflect.Slice { + return "", errors.New("RenderList given a non-slice type") + } + + displayables := make([]Displayable, s.Len()) + + for i := 0; i < s.Len(); i++ { + value, ok := s.Index(i).Interface().(Displayable) + if !ok { + return "", errors.New("item does not implement the Displayable interface") + } + displayables[i] = value + } + + return renderDisplayableList(displayables) +} + +// renderDisplayableList takes a list of displayable items, obtains their display +// strings via GetDisplayStrings() and then returns a single string containing +// each item's string representation on its own line, with appropriate horizontal +// padding between the item's own strings +func renderDisplayableList(items []Displayable) (string, error) { + displayStrings := make([][]string, len(items)) + + for i, item := range items { + displayStrings[i] = item.GetDisplayStrings() + } + + if len(displayStrings) == 0 { + return "", nil + } + + // use first element to determine how many times to do this + padWidths := make([]int, len(displayStrings[0])) + for i, _ := range padWidths { + for _, strings := range displayStrings { + if len(strings) != len(padWidths) { + return "", errors.New("Each item must return the same number of strings to display") + } + if len(strings[i]) > padWidths[i] { + padWidths[i] = len(strings[i]) + } + } + } + + paddedDisplayStrings := make([]string, len(displayStrings)) + for i, strings := range displayStrings { + for j, padWidth := range padWidths { + paddedDisplayStrings[i] += WithPadding(strings[j], padWidth) + " " + } + } + + return strings.Join(paddedDisplayStrings, "\n"), nil +} From a66ac8092e30f6c3f9a72c10a6b3eed5fb2265eb Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Mon, 17 Sep 2018 21:11:47 +1000 Subject: [PATCH 07/14] minor refactor --- pkg/gui/menu_panel.go | 11 +++++--- pkg/utils/utils.go | 60 ++++++++++++++++++++++++++++++------------- 2 files changed, 49 insertions(+), 22 deletions(-) diff --git a/pkg/gui/menu_panel.go b/pkg/gui/menu_panel.go index ee0773a51..945d0020a 100644 --- a/pkg/gui/menu_panel.go +++ b/pkg/gui/menu_panel.go @@ -49,12 +49,11 @@ func (gui *Gui) handleMenuClose(g *gocui.Gui, v *gocui.View) error { return gui.returnFocus(g, v) } -func (gui *Gui) handleMenu(g *gocui.Gui, v *gocui.View) error { +func (gui *Gui) getBindings(v *gocui.View) []*Binding { var ( bindingsGlobal, bindingsPanel []*Binding ) - // clear keys slice, so we don't have ghost elements - gui.State.Keys = gui.State.Keys[:0] + bindings := gui.GetKeybindings() for _, binding := range bindings { @@ -71,7 +70,11 @@ func (gui *Gui) handleMenu(g *gocui.Gui, v *gocui.View) error { // append dummy element to have a separator between // panel and global keybindings bindingsPanel = append(bindingsPanel, &Binding{}) - gui.State.Keys = append(bindingsPanel, bindingsGlobal...) + return append(bindingsPanel, bindingsGlobal...) +} + +func (gui *Gui) handleMenu(g *gocui.Gui, v *gocui.View) error { + gui.State.Keys = gui.getBindings(v) list, err := utils.RenderList(gui.State.Keys) if err != nil { diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index f1a56116b..3efa67a91 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -141,35 +141,59 @@ func RenderList(slice interface{}) (string, error) { // each item's string representation on its own line, with appropriate horizontal // padding between the item's own strings func renderDisplayableList(items []Displayable) (string, error) { - displayStrings := make([][]string, len(items)) - - for i, item := range items { - displayStrings[i] = item.GetDisplayStrings() - } - - if len(displayStrings) == 0 { + if len(items) == 0 { return "", nil } - // use first element to determine how many times to do this - padWidths := make([]int, len(displayStrings[0])) + stringArrays := getDisplayStringArrays(items) + + if !displayArraysAligned(stringArrays) { + return "", errors.New("Each item must return the same number of strings to display") + } + + padWidths := getPadWidths(stringArrays) + paddedDisplayStrings := getPaddedDisplayStrings(stringArrays, padWidths) + + return strings.Join(paddedDisplayStrings, "\n"), nil +} + +func getPadWidths(stringArrays [][]string) []int { + padWidths := make([]int, len(stringArrays[0])) for i, _ := range padWidths { - for _, strings := range displayStrings { - if len(strings) != len(padWidths) { - return "", errors.New("Each item must return the same number of strings to display") - } + for _, strings := range stringArrays { if len(strings[i]) > padWidths[i] { padWidths[i] = len(strings[i]) } } } + return padWidths +} - paddedDisplayStrings := make([]string, len(displayStrings)) - for i, strings := range displayStrings { +func getPaddedDisplayStrings(stringArrays [][]string, padWidths []int) []string { + paddedDisplayStrings := make([]string, len(stringArrays)) + for i, stringArray := range stringArrays { for j, padWidth := range padWidths { - paddedDisplayStrings[i] += WithPadding(strings[j], padWidth) + " " + paddedDisplayStrings[i] += WithPadding(stringArray[j], padWidth) + " " } } - - return strings.Join(paddedDisplayStrings, "\n"), nil + return paddedDisplayStrings +} + +// displayArraysAligned returns true if every string array returned from our +// list of displayables has the same length +func displayArraysAligned(stringArrays [][]string) bool { + for _, strings := range stringArrays { + if len(strings) != len(stringArrays[0]) { + return false + } + } + return true +} + +func getDisplayStringArrays(displayables []Displayable) [][]string { + stringArrays := make([][]string, len(displayables)) + for i, item := range displayables { + stringArrays[i] = item.GetDisplayStrings() + } + return stringArrays } From f89bc10af1f9fb4211badd2fee51e903bbc03f6c Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Mon, 17 Sep 2018 21:32:19 +1000 Subject: [PATCH 08/14] appease golangci --- pkg/gui/files_panel.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 6fd05552f..574e6bc1c 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -185,7 +185,9 @@ func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View) error { gui.renderString(g, "main", gui.Tr.SLocalize("NoChangedFiles")) return gui.renderfilesOptions(g, nil) } - gui.renderfilesOptions(g, file) + if err := gui.renderfilesOptions(g, file); err != nil { + return err + } var content string if file.HasMergeConflicts { return gui.refreshMergePanel(g) From b384fcf6af4e5248616712414e0164887d7bbcbd Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Tue, 18 Sep 2018 21:07:25 +1000 Subject: [PATCH 09/14] generalise popup menu panel --- pkg/gui/gui.go | 1 - pkg/gui/keybindings.go | 7 +--- pkg/gui/menu_panel.go | 74 ++++++++++------------------------- pkg/gui/options_menu_panel.go | 51 ++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 60 deletions(-) create mode 100644 pkg/gui/options_menu_panel.go diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index f527e8c5d..8a2aaea81 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -83,7 +83,6 @@ type guiState struct { EditHistory *stack.Stack Platform commands.Platform Updating bool - Keys []*Binding } // NewGui builds a new gui handler diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index e05caaec7..72a187472 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -93,7 +93,7 @@ func (gui *Gui) GetKeybindings() []*Binding { ViewName: "", Key: 'x', Modifier: gocui.ModNone, - Handler: gui.handleMenu, + Handler: gui.handleCreateOptionsMenu, }, { ViewName: "status", Key: 'e', @@ -369,11 +369,6 @@ func (gui *Gui) GetKeybindings() []*Binding { Key: 'q', Modifier: gocui.ModNone, Handler: gui.handleMenuClose, - }, { - ViewName: "menu", - Key: gocui.KeySpace, - Modifier: gocui.ModNone, - Handler: gui.handleMenuPress, }, } diff --git a/pkg/gui/menu_panel.go b/pkg/gui/menu_panel.go index 945d0020a..753e8f84d 100644 --- a/pkg/gui/menu_panel.go +++ b/pkg/gui/menu_panel.go @@ -8,21 +8,6 @@ import ( "github.com/jesseduffield/lazygit/pkg/utils" ) -func (gui *Gui) handleMenuPress(g *gocui.Gui, v *gocui.View) error { - lineNumber := gui.getItemPosition(v) - if gui.State.Keys[lineNumber].Key == nil { - return nil - } - if len(gui.State.Keys) > lineNumber { - err := gui.handleMenuClose(g, v) - if err != nil { - return err - } - return gui.State.Keys[lineNumber].Handler(g, v) - } - return nil -} - func (gui *Gui) handleMenuSelect(g *gocui.Gui, v *gocui.View) error { // doing nothing for now // but it is needed for switch in newLineFocused @@ -39,9 +24,9 @@ func (gui *Gui) renderMenuOptions(g *gocui.Gui) error { } func (gui *Gui) handleMenuClose(g *gocui.Gui, v *gocui.View) error { - // better to delete because for example after closing update confirmation panel, - // the focus isn't set back to any of panels and one is unable to even quit - //_, err := g.SetViewOnBottom(v.Name()) + if err := g.DeleteKeybinding("menu", gocui.KeySpace, gocui.ModNone); err != nil { + return err + } err := g.DeleteView("menu") if err != nil { return err @@ -49,55 +34,38 @@ func (gui *Gui) handleMenuClose(g *gocui.Gui, v *gocui.View) error { return gui.returnFocus(g, v) } -func (gui *Gui) getBindings(v *gocui.View) []*Binding { - var ( - bindingsGlobal, bindingsPanel []*Binding - ) - - bindings := gui.GetKeybindings() - - for _, binding := range bindings { - if binding.GetKey() != "" && binding.Description != "" { - switch binding.ViewName { - case "": - bindingsGlobal = append(bindingsGlobal, binding) - case v.Name(): - bindingsPanel = append(bindingsPanel, binding) - } - } - } - - // append dummy element to have a separator between - // panel and global keybindings - bindingsPanel = append(bindingsPanel, &Binding{}) - return append(bindingsPanel, bindingsGlobal...) -} - -func (gui *Gui) handleMenu(g *gocui.Gui, v *gocui.View) error { - gui.State.Keys = gui.getBindings(v) - - list, err := utils.RenderList(gui.State.Keys) +func (gui *Gui) createMenu(items interface{}, handlePress func(int) error) error { + list, err := utils.RenderList(items) if err != nil { return err } - x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, list) - menuView, _ := g.SetView("menu", x0, y0, x1, y1, 0) + x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(gui.g, list) + menuView, _ := gui.g.SetView("menu", x0, y0, x1, y1, 0) menuView.Title = strings.Title(gui.Tr.SLocalize("menu")) menuView.FgColor = gocui.ColorWhite menuView.Clear() fmt.Fprint(menuView, list) - if err := gui.renderMenuOptions(g); err != nil { + if err := gui.renderMenuOptions(gui.g); err != nil { return err } - g.Update(func(g *gocui.Gui) error { - _, err := g.SetViewOnTop("menu") - if err != nil { + wrappedHandlePress := func(g *gocui.Gui, v *gocui.View) error { + lineNumber := gui.getItemPosition(v) + return handlePress(lineNumber) + } + + if err := gui.g.SetKeybinding("menu", gocui.KeySpace, gocui.ModNone, wrappedHandlePress); err != nil { + return err + } + + gui.g.Update(func(g *gocui.Gui) error { + if _, err := g.SetViewOnTop("menu"); err != nil { return err } - return gui.switchFocus(g, v, menuView) + currentView := gui.g.CurrentView() + return gui.switchFocus(gui.g, currentView, menuView) }) return nil } diff --git a/pkg/gui/options_menu_panel.go b/pkg/gui/options_menu_panel.go new file mode 100644 index 000000000..2da002b43 --- /dev/null +++ b/pkg/gui/options_menu_panel.go @@ -0,0 +1,51 @@ +package gui + +import ( + "errors" + + "github.com/jesseduffield/gocui" +) + +func (gui *Gui) getBindings(v *gocui.View) []*Binding { + var ( + bindingsGlobal, bindingsPanel []*Binding + ) + + bindings := gui.GetKeybindings() + + for _, binding := range bindings { + if binding.GetKey() != "" && binding.Description != "" { + switch binding.ViewName { + case "": + bindingsGlobal = append(bindingsGlobal, binding) + case v.Name(): + bindingsPanel = append(bindingsPanel, binding) + } + } + } + + // append dummy element to have a separator between + // panel and global keybindings + bindingsPanel = append(bindingsPanel, &Binding{}) + return append(bindingsPanel, bindingsGlobal...) +} + +func (gui *Gui) handleCreateOptionsMenu(g *gocui.Gui, v *gocui.View) error { + bindings := gui.getBindings(v) + + handleOptionsMenuPress := func(index int) error { + if bindings[index].Key == nil { + return nil + } + if index <= len(bindings) { + return errors.New("Index is greater than size of bindings") + } + err := gui.handleMenuClose(g, v) + if err != nil { + return err + } + return bindings[index].Handler(g, v) + } + + return gui.createMenu(bindings, handleOptionsMenuPress) +} From 950cfeff6f97d94b4ab5166214ea6cb5b38b77f0 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 19 Sep 2018 18:03:44 +1000 Subject: [PATCH 10/14] add specs for menu utils --- pkg/utils/utils.go | 11 ++- pkg/utils/utils_test.go | 181 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 174 insertions(+), 18 deletions(-) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 3efa67a91..aef653c50 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -158,8 +158,11 @@ func renderDisplayableList(items []Displayable) (string, error) { } func getPadWidths(stringArrays [][]string) []int { - padWidths := make([]int, len(stringArrays[0])) - for i, _ := range padWidths { + if len(stringArrays[0]) <= 1 { + return []int{} + } + padWidths := make([]int, len(stringArrays[0])-1) + for i := range padWidths { for _, strings := range stringArrays { if len(strings[i]) > padWidths[i] { padWidths[i] = len(strings[i]) @@ -172,9 +175,13 @@ func getPadWidths(stringArrays [][]string) []int { func getPaddedDisplayStrings(stringArrays [][]string, padWidths []int) []string { paddedDisplayStrings := make([]string, len(stringArrays)) for i, stringArray := range stringArrays { + if len(stringArray) == 0 { + continue + } for j, padWidth := range padWidths { paddedDisplayStrings[i] += WithPadding(stringArray[j], padWidth) + " " } + paddedDisplayStrings[i] += stringArray[len(padWidths)] } return paddedDisplayStrings } diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index a088ed849..045bf9c64 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -1,6 +1,7 @@ package utils import ( + "errors" "testing" "github.com/stretchr/testify/assert" @@ -168,32 +169,180 @@ func TestResolvePlaceholderString(t *testing.T) { } } -func TestMin(t *testing.T) { +func TestDisplayArraysAligned(t *testing.T) { type scenario struct { - a int - b int - expected int + input [][]string + expected bool } scenarios := []scenario{ { - 2, - 4, - 2, + [][]string{{"", ""}, {"", ""}}, + true, }, { - 2, - 1, - 1, - }, - { - 1, - 1, - 1, + [][]string{{""}, {"", ""}}, + false, }, } for _, s := range scenarios { - assert.EqualValues(t, s.expected, Min(s.a, s.b)) + assert.EqualValues(t, s.expected, displayArraysAligned(s.input)) + } +} + +type myDisplayable struct { + strings []string +} + +type myStruct struct{} + +func (d *myDisplayable) GetDisplayStrings() []string { + return d.strings +} + +func TestGetDisplayStringArrays(t *testing.T) { + type scenario struct { + input []Displayable + expected [][]string + } + + scenarios := []scenario{ + { + []Displayable{ + Displayable(&myDisplayable{[]string{"a", "b"}}), + Displayable(&myDisplayable{[]string{"a", "b"}}), + }, + [][]string{{"a", "b"}, {"c", "d"}}, + }, + } + + for _, s := range scenarios { + assert.EqualValues(t, s.expected, getDisplayStringArrays(s.input)) + } +} + +func TestRenderDisplayableList(t *testing.T) { + type scenario struct { + input []Displayable + expectedString string + expectedError error + } + + scenarios := []scenario{ + { + []Displayable{ + Displayable(&myDisplayable{[]string{}}), + Displayable(&myDisplayable{[]string{}}), + }, + "\n", + nil, + }, + { + []Displayable{ + Displayable(&myDisplayable{[]string{"aa", "b"}}), + Displayable(&myDisplayable{[]string{"c", "d"}}), + }, + "aa b\nc d", + nil, + }, + { + []Displayable{ + Displayable(&myDisplayable{[]string{"a"}}), + Displayable(&myDisplayable{[]string{"b", "c"}}), + }, + "", + errors.New("Each item must return the same number of strings to display"), + }, + } + + for _, s := range scenarios { + str, err := renderDisplayableList(s.input) + assert.EqualValues(t, s.expectedString, str) + assert.EqualValues(t, s.expectedError, err) + } +} + +func TestRenderList(t *testing.T) { + type scenario struct { + input interface{} + expectedString string + expectedError error + } + + scenarios := []scenario{ + { + []*myDisplayable{ + {[]string{"aa", "b"}}, + {[]string{"c", "d"}}, + }, + "aa b\nc d", + nil, + }, + { + []*myStruct{ + {}, + {}, + }, + "", + errors.New("item does not implement the Displayable interface"), + }, + { + &myStruct{}, + "", + errors.New("RenderList given a non-slice type"), + }, + } + + for _, s := range scenarios { + str, err := RenderList(s.input) + assert.EqualValues(t, s.expectedString, str) + assert.EqualValues(t, s.expectedError, err) + } +} + +func TestGetPaddedDisplayStrings(t *testing.T) { + type scenario struct { + stringArrays [][]string + padWidths []int + expected []string + } + + scenarios := []scenario{ + { + [][]string{{"a", "b"}, {"c", "d"}}, + []int{1}, + []string{"a b", "c d"}, + }, + } + + for _, s := range scenarios { + assert.EqualValues(t, s.expected, getPaddedDisplayStrings(s.stringArrays, s.padWidths)) + } +} + +func TestGetPadWidths(t *testing.T) { + type scenario struct { + stringArrays [][]string + expected []int + } + + scenarios := []scenario{ + { + [][]string{{""}, {""}}, + []int{}, + }, + { + [][]string{{"a"}, {""}}, + []int{}, + }, + { + [][]string{{"aa", "b", "ccc"}, {"c", "d", "e"}}, + []int{2, 1}, + }, + } + + for _, s := range scenarios { + assert.EqualValues(t, s.expected, getPadWidths(s.stringArrays)) } } From e95b2e5f0b2aef3afcc6fa4e8b1f24d74cd1fb2a Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 19 Sep 2018 18:31:54 +1000 Subject: [PATCH 11/14] update specs --- pkg/commands/git_test.go | 40 ++++++++++++++++++++-------------------- pkg/utils/utils_test.go | 2 +- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index 88ecaea66..d1c27cd30 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -275,7 +275,7 @@ func TestGitCommandGetStashEntries(t *testing.T) { type scenario struct { testName string command func(string, ...string) *exec.Cmd - test func([]StashEntry) + test func([]*StashEntry) } scenarios := []scenario{ @@ -284,7 +284,7 @@ func TestGitCommandGetStashEntries(t *testing.T) { func(string, ...string) *exec.Cmd { return exec.Command("echo") }, - func(entries []StashEntry) { + func(entries []*StashEntry) { assert.Len(t, entries, 0) }, }, @@ -293,8 +293,8 @@ func TestGitCommandGetStashEntries(t *testing.T) { func(string, ...string) *exec.Cmd { return exec.Command("echo", "WIP on add-pkg-commands-test: 55c6af2 increase parallel build\nWIP on master: bb86a3f update github template") }, - func(entries []StashEntry) { - expected := []StashEntry{ + func(entries []*StashEntry) { + expected := []*StashEntry{ { 0, "WIP on add-pkg-commands-test: 55c6af2 increase parallel build", @@ -341,7 +341,7 @@ func TestGitCommandGetStatusFiles(t *testing.T) { type scenario struct { testName string command func(string, ...string) *exec.Cmd - test func([]File) + test func([]*File) } scenarios := []scenario{ @@ -350,7 +350,7 @@ func TestGitCommandGetStatusFiles(t *testing.T) { func(cmd string, args ...string) *exec.Cmd { return exec.Command("echo") }, - func(files []File) { + func(files []*File) { assert.Len(t, files, 0) }, }, @@ -362,10 +362,10 @@ func TestGitCommandGetStatusFiles(t *testing.T) { "MM file1.txt\nA file3.txt\nAM file2.txt\n?? file4.txt", ) }, - func(files []File) { + func(files []*File) { assert.Len(t, files, 4) - expected := []File{ + expected := []*File{ { Name: "file1.txt", HasStagedChanges: true, @@ -463,22 +463,22 @@ func TestGitCommandCommitAmend(t *testing.T) { func TestGitCommandMergeStatusFiles(t *testing.T) { type scenario struct { testName string - oldFiles []File - newFiles []File - test func([]File) + oldFiles []*File + newFiles []*File + test func([]*File) } scenarios := []scenario{ { "Old file and new file are the same", - []File{}, - []File{ + []*File{}, + []*File{ { Name: "new_file.txt", }, }, - func(files []File) { - expected := []File{ + func(files []*File) { + expected := []*File{ { Name: "new_file.txt", }, @@ -490,7 +490,7 @@ func TestGitCommandMergeStatusFiles(t *testing.T) { }, { "Several files to merge, with some identical", - []File{ + []*File{ { Name: "new_file1.txt", }, @@ -501,7 +501,7 @@ func TestGitCommandMergeStatusFiles(t *testing.T) { Name: "new_file3.txt", }, }, - []File{ + []*File{ { Name: "new_file4.txt", }, @@ -512,8 +512,8 @@ func TestGitCommandMergeStatusFiles(t *testing.T) { Name: "new_file1.txt", }, }, - func(files []File) { - expected := []File{ + func(files []*File) { + expected := []*File{ { Name: "new_file1.txt", }, @@ -1223,7 +1223,7 @@ func TestGitCommandDiff(t *testing.T) { gitCommand := newDummyGitCommand() assert.NoError(t, test.GenerateRepo("lots_of_diffs.sh")) - files := []File{ + files := []*File{ { Name: "deleted_staged", HasStagedChanges: false, diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index 045bf9c64..76a9f8b95 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -211,7 +211,7 @@ func TestGetDisplayStringArrays(t *testing.T) { { []Displayable{ Displayable(&myDisplayable{[]string{"a", "b"}}), - Displayable(&myDisplayable{[]string{"a", "b"}}), + Displayable(&myDisplayable{[]string{"c", "d"}}), }, [][]string{{"a", "b"}, {"c", "d"}}, }, From fcaf4e339c4e76aafb9e89114a7eabe1bcbe0362 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 19 Sep 2018 19:16:55 +1000 Subject: [PATCH 12/14] fix specs --- pkg/commands/git.go | 4 ++-- pkg/commands/git_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 2a127d407..c37ca9b47 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -231,9 +231,9 @@ func (c *GitCommand) UpstreamDifferenceCount() (string, string) { return strings.TrimSpace(pushableCount), strings.TrimSpace(pullableCount) } -// getCommitsToPush Returns the sha's of the commits that have not yet been pushed +// GetCommitsToPush Returns the sha's of the commits that have not yet been pushed // to the remote branch of the current branch, a map is returned to ease look up -func (c *GitCommand) getCommitsToPush() map[string]bool { +func (c *GitCommand) GetCommitsToPush() map[string]bool { pushables := map[string]bool{} o, err := c.OSCommand.RunCommandWithOutput("git rev-list @{u}..head --abbrev-commit") if err != nil { diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index 6d19a1629..6e3b81bf5 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -631,7 +631,7 @@ func TestGitCommandGetCommitsToPush(t *testing.T) { t.Run(s.testName, func(t *testing.T) { gitCmd := newDummyGitCommand() gitCmd.OSCommand.command = s.command - s.test(gitCmd.getCommitsToPush()) + s.test(gitCmd.GetCommitsToPush()) }) } } From 64f0eeb42e8c19251f0ca823e43c62468066474c Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 19 Sep 2018 19:23:31 +1000 Subject: [PATCH 13/14] fix specs --- pkg/commands/git_test.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index 6e3b81bf5..790a34690 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -1225,7 +1225,7 @@ func TestGitCommandRemoveFile(t *testing.T) { testName string command func() (func(string, ...string) *exec.Cmd, *[][]string) test func(*[][]string, error) - file File + file *File removeFile func(string) error } @@ -1247,7 +1247,7 @@ func TestGitCommandRemoveFile(t *testing.T) { {"reset", "--", "test"}, }) }, - File{ + &File{ Name: "test", HasStagedChanges: true, }, @@ -1270,7 +1270,7 @@ func TestGitCommandRemoveFile(t *testing.T) { assert.EqualError(t, err, "an error occurred when removing file") assert.Len(t, *cmdsCalled, 0) }, - File{ + &File{ Name: "test", Tracked: false, }, @@ -1295,7 +1295,7 @@ func TestGitCommandRemoveFile(t *testing.T) { {"checkout", "--", "test"}, }) }, - File{ + &File{ Name: "test", Tracked: true, HasStagedChanges: false, @@ -1321,7 +1321,7 @@ func TestGitCommandRemoveFile(t *testing.T) { {"checkout", "--", "test"}, }) }, - File{ + &File{ Name: "test", Tracked: true, HasStagedChanges: false, @@ -1348,7 +1348,7 @@ func TestGitCommandRemoveFile(t *testing.T) { {"checkout", "--", "test"}, }) }, - File{ + &File{ Name: "test", Tracked: true, HasStagedChanges: true, @@ -1374,7 +1374,7 @@ func TestGitCommandRemoveFile(t *testing.T) { {"reset", "--", "test"}, }) }, - File{ + &File{ Name: "test", Tracked: false, HasStagedChanges: true, @@ -1398,7 +1398,7 @@ func TestGitCommandRemoveFile(t *testing.T) { assert.NoError(t, err) assert.Len(t, *cmdsCalled, 0) }, - File{ + &File{ Name: "test", Tracked: false, HasStagedChanges: false, @@ -1484,7 +1484,7 @@ func TestGitCommandGetCommits(t *testing.T) { type scenario struct { testName string command func(string, ...string) *exec.Cmd - test func([]Commit) + test func([]*Commit) } scenarios := []scenario{ @@ -1504,7 +1504,7 @@ func TestGitCommandGetCommits(t *testing.T) { return nil }, - func(commits []Commit) { + func(commits []*Commit) { assert.Len(t, commits, 0) }, }, @@ -1524,9 +1524,9 @@ func TestGitCommandGetCommits(t *testing.T) { return nil }, - func(commits []Commit) { + func(commits []*Commit) { assert.Len(t, commits, 2) - assert.EqualValues(t, []Commit{ + assert.EqualValues(t, []*Commit{ { Sha: "8a2bb0e", Name: "commit 1", From 5a76b579528bb90b5aff388f8aa68594c22a03c3 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 19 Sep 2018 19:31:29 +1000 Subject: [PATCH 14/14] one more spec to increase coverage --- pkg/utils/utils_test.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index 76a9f8b95..918031512 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -346,3 +346,33 @@ func TestGetPadWidths(t *testing.T) { assert.EqualValues(t, s.expected, getPadWidths(s.stringArrays)) } } + +func TestMin(t *testing.T) { + type scenario struct { + a int + b int + expected int + } + + scenarios := []scenario{ + { + 1, + 1, + 1, + }, + { + 1, + 2, + 1, + }, + { + 2, + 1, + 1, + }, + } + + for _, s := range scenarios { + assert.EqualValues(t, s.expected, Min(s.a, s.b)) + } +}