From f94d0be2c9b0eaaf539d2cdcfaf258f754162a2e Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Tue, 25 Feb 2020 20:55:36 +1100 Subject: [PATCH] refactor the way we render lists --- pkg/commands/branch.go | 37 ----- pkg/commands/commit_file.go | 23 ---- pkg/commands/commit_list_builder.go | 2 +- pkg/commands/commit_list_builder_test.go | 167 ----------------------- pkg/commands/file.go | 22 --- pkg/commands/remote_branch.go | 12 -- pkg/commands/tag.go | 5 - pkg/gui/branches_panel.go | 13 +- pkg/gui/commit_files_panel.go | 9 +- pkg/gui/commits_panel.go | 8 +- pkg/gui/files_panel.go | 12 +- pkg/gui/gui.go | 11 +- pkg/gui/presentation/branches.go | 48 +++++++ pkg/gui/presentation/commit_files.go | 35 +++++ pkg/gui/presentation/commits.go | 2 +- pkg/gui/presentation/files.go | 36 +++++ pkg/gui/presentation/remote_branches.go | 23 ++++ pkg/gui/presentation/remotes.go | 20 +++ pkg/gui/presentation/stash_entries.go | 20 +++ pkg/gui/presentation/tags.go | 20 +++ pkg/gui/reflog_panel.go | 6 +- pkg/gui/remote_branches_panel.go | 6 +- pkg/gui/remotes_panel.go | 8 +- pkg/gui/stash_panel.go | 17 +-- pkg/gui/status_panel.go | 4 +- pkg/gui/tags_panel.go | 6 +- pkg/gui/view_helpers.go | 14 -- pkg/utils/utils.go | 51 ------- pkg/utils/utils_test.go | 158 --------------------- 29 files changed, 250 insertions(+), 545 deletions(-) create mode 100644 pkg/gui/presentation/branches.go create mode 100644 pkg/gui/presentation/commit_files.go create mode 100644 pkg/gui/presentation/files.go create mode 100644 pkg/gui/presentation/remote_branches.go create mode 100644 pkg/gui/presentation/remotes.go create mode 100644 pkg/gui/presentation/stash_entries.go create mode 100644 pkg/gui/presentation/tags.go diff --git a/pkg/commands/branch.go b/pkg/commands/branch.go index 42d43507f..8da8918d1 100644 --- a/pkg/commands/branch.go +++ b/pkg/commands/branch.go @@ -1,15 +1,5 @@ package commands -import ( - "fmt" - "strings" - - "github.com/jesseduffield/lazygit/pkg/theme" - - "github.com/fatih/color" - "github.com/jesseduffield/lazygit/pkg/utils" -) - // Branch : A git branch // duplicating this for now type Branch struct { @@ -17,31 +7,4 @@ type Branch struct { Recency string Pushables string Pullables string - Selected bool -} - -// GetDisplayStrings returns the display string of branch -func (b *Branch) GetDisplayStrings(isFocused bool) []string { - displayName := utils.ColoredString(b.Name, GetBranchColor(b.Name)) - if isFocused && b.Selected && b.Pushables != "" && b.Pullables != "" { - displayName = fmt.Sprintf("%s ↑%s↓%s", displayName, b.Pushables, b.Pullables) - } - - return []string{b.Recency, displayName} -} - -// GetBranchColor branch color -func GetBranchColor(name string) color.Attribute { - branchType := strings.Split(name, "/")[0] - - switch branchType { - case "feature": - return color.FgGreen - case "bugfix": - return color.FgYellow - case "hotfix": - return color.FgRed - default: - return theme.DefaultTextColor - } } diff --git a/pkg/commands/commit_file.go b/pkg/commands/commit_file.go index ddd09b23b..03614be24 100644 --- a/pkg/commands/commit_file.go +++ b/pkg/commands/commit_file.go @@ -1,10 +1,5 @@ package commands -import ( - "github.com/fatih/color" - "github.com/jesseduffield/lazygit/pkg/theme" -) - // CommitFile : A git commit file type CommitFile struct { Sha string @@ -22,21 +17,3 @@ const ( // PART is for when you're only talking about specific lines that have been modified PART ) - -// GetDisplayStrings is a function. -func (f *CommitFile) GetDisplayStrings(isFocused bool) []string { - yellow := color.New(color.FgYellow) - green := color.New(color.FgGreen) - defaultColor := color.New(theme.DefaultTextColor) - - var colour *color.Color - switch f.Status { - case UNSELECTED: - colour = defaultColor - case WHOLE: - colour = green - case PART: - colour = yellow - } - return []string{colour.Sprint(f.DisplayString)} -} diff --git a/pkg/commands/commit_list_builder.go b/pkg/commands/commit_list_builder.go index 1e621f4e4..aa701d6bd 100644 --- a/pkg/commands/commit_list_builder.go +++ b/pkg/commands/commit_list_builder.go @@ -58,7 +58,7 @@ func (c *CommitListBuilder) extractCommitFromLine(line string) *Commit { date := split[1] author := split[2] extraInfo := strings.TrimSpace(split[3]) - message := strings.Join(split[4:len(split)], SEPARATION_CHAR) + message := strings.Join(split[4:], SEPARATION_CHAR) tags := []string{} if extraInfo != "" { diff --git a/pkg/commands/commit_list_builder_test.go b/pkg/commands/commit_list_builder_test.go index c4ded4bb9..541dfd99c 100644 --- a/pkg/commands/commit_list_builder_test.go +++ b/pkg/commands/commit_list_builder_test.go @@ -149,170 +149,3 @@ func TestCommitListBuilderGetMergeBase(t *testing.T) { }) } } - -// TestCommitListBuilderGetLog is a function. -func TestCommitListBuilderGetLog(t *testing.T) { - type scenario struct { - testName string - command func(string, ...string) *exec.Cmd - test func(string) - } - - scenarios := []scenario{ - { - "Retrieves logs", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"log", "--decorate", "--oneline", "-30", "--abbrev=20"}, args) - - return exec.Command("echo", "6f0b32f commands/git : add GetCommits tests refactor\n9d9d775 circle : remove new line") - }, - func(output string) { - assert.EqualValues(t, "6f0b32f commands/git : add GetCommits tests refactor\n9d9d775 circle : remove new line\n", output) - }, - }, - { - "An error occurred when retrieving logs", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"log", "--decorate", "--oneline", "-30", "--abbrev=20"}, args) - return exec.Command("test") - }, - func(output string) { - assert.Empty(t, output) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - c := NewDummyCommitListBuilder() - c.OSCommand.SetCommand(s.command) - s.test(c.getLog(true)) - }) - } -} - -// TestCommitListBuilderGetCommits is a function. -func TestCommitListBuilderGetCommits(t *testing.T) { - type scenario struct { - testName string - command func(string, ...string) *exec.Cmd - test func([]*Commit, error) - } - - scenarios := []scenario{ - { - "No data found", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - - switch args[0] { - case "rev-list": - assert.EqualValues(t, []string{"rev-list", "@{u}..HEAD", "--abbrev-commit"}, args) - return exec.Command("echo") - case "log": - assert.EqualValues(t, []string{"log", "--decorate", "--oneline", "-30", "--abbrev=20"}, args) - return exec.Command("echo") - case "merge-base": - assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args) - return exec.Command("test") - case "symbolic-ref": - assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args) - return exec.Command("echo", "master") - } - - return nil - }, - func(commits []*Commit, err error) { - assert.NoError(t, err) - assert.Len(t, commits, 0) - }, - }, - { - "GetCommits returns 2 commits, 1 unpushed, the other merged", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - - switch args[0] { - case "rev-list": - assert.EqualValues(t, []string{"rev-list", "@{u}..HEAD", "--abbrev-commit"}, args) - return exec.Command("echo", "8a2bb0e") - case "log": - assert.EqualValues(t, []string{"log", "--decorate", "--oneline", "-30", "--abbrev=20"}, args) - return exec.Command("echo", "8a2bb0e commit 1\n78976bc commit 2") - case "merge-base": - assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args) - return exec.Command("echo", "78976bc") - case "symbolic-ref": - assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args) - return exec.Command("echo", "master") - } - - return nil - }, - func(commits []*Commit, err error) { - assert.NoError(t, err) - assert.Len(t, commits, 2) - assert.EqualValues(t, []*Commit{ - { - Sha: "8a2bb0e", - Name: "commit 1", - Status: "unpushed", - DisplayString: "8a2bb0e commit 1", - }, - { - Sha: "78976bc", - Name: "commit 2", - Status: "merged", - DisplayString: "78976bc commit 2", - }, - }, commits) - }, - }, - { - "GetCommits bubbles up an error from setCommitMergedStatuses", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - - switch args[0] { - case "rev-list": - assert.EqualValues(t, []string{"rev-list", "@{u}..HEAD", "--abbrev-commit"}, args) - return exec.Command("echo", "8a2bb0e") - case "log": - assert.EqualValues(t, []string{"log", "--decorate", "--oneline", "-30", "--abbrev=20"}, args) - return exec.Command("echo", "8a2bb0e commit 1\n78976bc commit 2") - case "merge-base": - assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args) - return exec.Command("echo", "78976bc") - case "symbolic-ref": - assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args) - // here's where we are returning the error - return exec.Command("test") - case "branch": - assert.EqualValues(t, []string{"branch", "--contains"}, args) - // here too - return exec.Command("test") - case "rev-parse": - assert.EqualValues(t, []string{"rev-parse", "--short", "HEAD"}, args) - // here too - return exec.Command("test") - } - - return nil - }, - func(commits []*Commit, err error) { - assert.Error(t, err) - assert.Len(t, commits, 0) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - c := NewDummyCommitListBuilder() - c.OSCommand.SetCommand(s.command) - s.test(c.GetCommits(true)) - }) - } -} diff --git a/pkg/commands/file.go b/pkg/commands/file.go index 3c4e13f06..37a1dcccb 100644 --- a/pkg/commands/file.go +++ b/pkg/commands/file.go @@ -1,7 +1,5 @@ package commands -import "github.com/fatih/color" - // File : A file from git status // duplicating this for now type File struct { @@ -16,23 +14,3 @@ type File struct { Type string // one of 'file', 'directory', and 'other' ShortStatus string // e.g. 'AD', ' A', 'M ', '??' } - -// GetDisplayStrings returns the display string of a file -func (f *File) GetDisplayStrings(isFocused bool) []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/remote_branch.go b/pkg/commands/remote_branch.go index 5016dc5bd..0435a050f 100644 --- a/pkg/commands/remote_branch.go +++ b/pkg/commands/remote_branch.go @@ -1,19 +1,7 @@ package commands -import ( - "github.com/jesseduffield/lazygit/pkg/utils" -) - // Remote Branch : A git remote branch type RemoteBranch struct { Name string - Selected bool RemoteName string } - -// GetDisplayStrings returns the display string of branch -func (b *RemoteBranch) GetDisplayStrings(isFocused bool) []string { - displayName := utils.ColoredString(b.Name, GetBranchColor(b.Name)) - - return []string{displayName} -} diff --git a/pkg/commands/tag.go b/pkg/commands/tag.go index 99a4a7f0e..b6910b4b0 100644 --- a/pkg/commands/tag.go +++ b/pkg/commands/tag.go @@ -4,8 +4,3 @@ package commands type Tag struct { Name string } - -// GetDisplayStrings returns the display string of a remote -func (r *Tag) GetDisplayStrings(isFocused bool) []string { - return []string{r.Name} -} diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index 5ecb563f2..90d8a2758 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -6,6 +6,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" ) @@ -57,13 +58,6 @@ func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) RenderSelectedBranchUpstreamDifferences() error { return gui.newTask("branches", func(stop chan struct{}) error { - // here we tell the selected branch that it is selected. - // this is necessary for showing stats on a branch that is selected, because - // the displaystring function doesn't have access to gui state to tell if it's selected - for i, branch := range gui.State.Branches { - branch.Selected = i == gui.State.Panels.Branches.SelectedLine - } - branch := gui.getSelectedBranch() branch.Pushables, branch.Pullables = gui.GitCommand.GetBranchUpstreamDifferenceCount(branch.Name) @@ -73,7 +67,10 @@ func (gui *Gui) RenderSelectedBranchUpstreamDifferences() error { default: } - return gui.renderListPanel(gui.getBranchesView(), gui.State.Branches) + branchesView := gui.getBranchesView() + displayStrings := presentation.GetBranchListDisplayStrings(gui.State.Branches, gui.currentViewName() == "branches", gui.State.Panels.Branches.SelectedLine) + gui.renderDisplayStrings(branchesView, displayStrings) + return nil }) } diff --git a/pkg/gui/commit_files_panel.go b/pkg/gui/commit_files_panel.go index fbfeeb11c..63458926b 100644 --- a/pkg/gui/commit_files_panel.go +++ b/pkg/gui/commit_files_panel.go @@ -4,6 +4,7 @@ import ( "github.com/go-errors/errors" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/gui/presentation" ) func (gui *Gui) getSelectedCommitFile(g *gocui.Gui) *commands.CommitFile { @@ -112,11 +113,11 @@ func (gui *Gui) refreshCommitFilesView() error { gui.refreshSelectedLine(&gui.State.Panels.CommitFiles.SelectedLine, len(gui.State.CommitFiles)) - if err := gui.renderListPanel(gui.getCommitFilesView(), gui.State.CommitFiles); err != nil { - return err - } + commitsFileView := gui.getCommitFilesView() + displayStrings := presentation.GetCommitFileListDisplayStrings(gui.State.CommitFiles) + gui.renderDisplayStrings(commitsFileView, displayStrings) - return gui.handleCommitFileSelect(gui.g, gui.getCommitFilesView()) + return gui.handleCommitFileSelect(gui.g, commitsFileView) } func (gui *Gui) handleOpenOldCommitFile(g *gocui.Gui, v *gocui.View) error { diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index e42aabfd0..fe6b7acb9 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -632,7 +632,13 @@ func (gui *Gui) switchCommitsPanelContext(context string) error { commitsView.TabIndex = contextTabIndexMap[context] - switch context { + return gui.refreshCommitsViewWithSelection() +} + +func (gui *Gui) refreshCommitsViewWithSelection() error { + commitsView := gui.getCommitsView() + + switch commitsView.Context { case "branch-commits": return gui.renderBranchCommitsWithSelection() case "reflog-commits": diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 9e81009ea..4e00dc523 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -12,7 +12,7 @@ import ( "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" - "github.com/jesseduffield/lazygit/pkg/utils" + "github.com/jesseduffield/lazygit/pkg/gui/presentation" ) // list panel functions @@ -103,14 +103,8 @@ func (gui *Gui) refreshFiles() error { } gui.g.Update(func(g *gocui.Gui) error { - - filesView.Clear() - isFocused := gui.g.CurrentView().Name() == "files" - list, err := utils.RenderList(gui.State.Files, isFocused) - if err != nil { - return err - } - fmt.Fprint(filesView, list) + displayStrings := presentation.GetFileListDisplayStrings(gui.State.Files) + gui.renderDisplayStrings(filesView, displayStrings) if g.CurrentView() == filesView || (g.CurrentView() == gui.getMainView() && g.CurrentView().Context == "merging") { newSelectedFile, _ := gui.getSelectedFile(gui.g) diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 4cf435b8f..70c3c3f51 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -24,6 +24,7 @@ import ( "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/gui/presentation" "github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/tasks" "github.com/jesseduffield/lazygit/pkg/theme" @@ -270,7 +271,7 @@ 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 { + if err := gui.refreshCommitsViewWithSelection(); err != nil { return err } @@ -280,7 +281,7 @@ func (gui *Gui) nextScreenMode(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) // commits render differently depending on whether we're in fullscreen more or not - if err := gui.renderBranchCommitsWithSelection(); err != nil { + if err := gui.refreshCommitsViewWithSelection(); err != nil { return err } @@ -382,10 +383,8 @@ func (gui *Gui) onFocusLost(v *gocui.View, newView *gocui.View) error { case "branches": if v.Context == "local-branches" { // This stops the branches panel from showing the upstream/downstream changes to the selected branch, when it loses focus - // inside renderListPanel it checks to see if the panel has focus - if err := gui.renderListPanel(gui.getBranchesView(), gui.State.Branches); err != nil { - return err - } + displayStrings := presentation.GetBranchListDisplayStrings(gui.State.Branches, false, -1) + gui.renderDisplayStrings(gui.getBranchesView(), displayStrings) } case "main": // if we have lost focus to a first-class panel, we need to do some cleanup diff --git a/pkg/gui/presentation/branches.go b/pkg/gui/presentation/branches.go new file mode 100644 index 000000000..3b918116b --- /dev/null +++ b/pkg/gui/presentation/branches.go @@ -0,0 +1,48 @@ +package presentation + +import ( + "fmt" + "strings" + + "github.com/fatih/color" + "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/theme" + "github.com/jesseduffield/lazygit/pkg/utils" +) + +func GetBranchListDisplayStrings(branches []*commands.Branch, isFocused bool, selectedLine int) [][]string { + lines := make([][]string, len(branches)) + + for i := range branches { + showUpstreamDifferences := isFocused && i == selectedLine + lines[i] = getBranchDisplayStrings(branches[i], showUpstreamDifferences) + } + + return lines +} + +// getBranchDisplayStrings returns the display string of branch +func getBranchDisplayStrings(b *commands.Branch, showUpstreamDifferences bool) []string { + displayName := utils.ColoredString(b.Name, GetBranchColor(b.Name)) + if showUpstreamDifferences && b.Pushables != "" && b.Pullables != "" { + displayName = fmt.Sprintf("%s ↑%s↓%s", displayName, b.Pushables, b.Pullables) + } + + return []string{b.Recency, displayName} +} + +// GetBranchColor branch color +func GetBranchColor(name string) color.Attribute { + branchType := strings.Split(name, "/")[0] + + switch branchType { + case "feature": + return color.FgGreen + case "bugfix": + return color.FgYellow + case "hotfix": + return color.FgRed + default: + return theme.DefaultTextColor + } +} diff --git a/pkg/gui/presentation/commit_files.go b/pkg/gui/presentation/commit_files.go new file mode 100644 index 000000000..4ea0f85db --- /dev/null +++ b/pkg/gui/presentation/commit_files.go @@ -0,0 +1,35 @@ +package presentation + +import ( + "github.com/fatih/color" + "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/theme" +) + +func GetCommitFileListDisplayStrings(branches []*commands.CommitFile) [][]string { + lines := make([][]string, len(branches)) + + for i := range branches { + lines[i] = getCommitFileDisplayStrings(branches[i]) + } + + return lines +} + +// getCommitFileDisplayStrings returns the display string of branch +func getCommitFileDisplayStrings(f *commands.CommitFile) []string { + yellow := color.New(color.FgYellow) + green := color.New(color.FgGreen) + defaultColor := color.New(theme.DefaultTextColor) + + var colour *color.Color + switch f.Status { + case commands.UNSELECTED: + colour = defaultColor + case commands.WHOLE: + colour = green + case commands.PART: + colour = yellow + } + return []string{colour.Sprint(f.DisplayString)} +} diff --git a/pkg/gui/presentation/commits.go b/pkg/gui/presentation/commits.go index 04dbb0780..740abab78 100644 --- a/pkg/gui/presentation/commits.go +++ b/pkg/gui/presentation/commits.go @@ -67,7 +67,7 @@ func getFullDescriptionDisplayStringsForCommit(c *commands.Commit) []string { secondColumnString := blue.Sprint(truncatedDate) if c.Action != "" { secondColumnString = cyan.Sprint(c.Action) - } else if len(c.Tags) > 0 { + } else if c.ExtraInfo != "" { tagColor := color.New(color.FgMagenta, color.Bold) tagString = utils.ColoredStringDirect(c.ExtraInfo, tagColor) + " " } diff --git a/pkg/gui/presentation/files.go b/pkg/gui/presentation/files.go new file mode 100644 index 000000000..589e8aecc --- /dev/null +++ b/pkg/gui/presentation/files.go @@ -0,0 +1,36 @@ +package presentation + +import ( + "github.com/fatih/color" + "github.com/jesseduffield/lazygit/pkg/commands" +) + +func GetFileListDisplayStrings(files []*commands.File) [][]string { + lines := make([][]string, len(files)) + + for i := range files { + lines[i] = getFileDisplayStrings(files[i]) + } + + return lines +} + +// getFileDisplayStrings returns the display string of branch +func getFileDisplayStrings(f *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 !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/gui/presentation/remote_branches.go b/pkg/gui/presentation/remote_branches.go new file mode 100644 index 000000000..d3094c8cb --- /dev/null +++ b/pkg/gui/presentation/remote_branches.go @@ -0,0 +1,23 @@ +package presentation + +import ( + "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/utils" +) + +func GetRemoteBranchListDisplayStrings(branches []*commands.RemoteBranch) [][]string { + lines := make([][]string, len(branches)) + + for i := range branches { + lines[i] = getRemoteBranchDisplayStrings(branches[i]) + } + + return lines +} + +// getRemoteBranchDisplayStrings returns the display string of branch +func getRemoteBranchDisplayStrings(b *commands.RemoteBranch) []string { + displayName := utils.ColoredString(b.Name, GetBranchColor(b.Name)) + + return []string{displayName} +} diff --git a/pkg/gui/presentation/remotes.go b/pkg/gui/presentation/remotes.go new file mode 100644 index 000000000..24fcd0b97 --- /dev/null +++ b/pkg/gui/presentation/remotes.go @@ -0,0 +1,20 @@ +package presentation + +import ( + "github.com/jesseduffield/lazygit/pkg/commands" +) + +func GetRemoteListDisplayStrings(remotes []*commands.Remote) [][]string { + lines := make([][]string, len(remotes)) + + for i := range remotes { + lines[i] = getRemoteDisplayStrings(remotes[i]) + } + + return lines +} + +// getRemoteDisplayStrings returns the display string of branch +func getRemoteDisplayStrings(r *commands.Remote) []string { + return []string{r.Name} +} diff --git a/pkg/gui/presentation/stash_entries.go b/pkg/gui/presentation/stash_entries.go new file mode 100644 index 000000000..8598b5e5f --- /dev/null +++ b/pkg/gui/presentation/stash_entries.go @@ -0,0 +1,20 @@ +package presentation + +import ( + "github.com/jesseduffield/lazygit/pkg/commands" +) + +func GetStashEntryListDisplayStrings(stashEntries []*commands.StashEntry) [][]string { + lines := make([][]string, len(stashEntries)) + + for i := range stashEntries { + lines[i] = getStashEntryDisplayStrings(stashEntries[i]) + } + + return lines +} + +// getStashEntryDisplayStrings returns the display string of branch +func getStashEntryDisplayStrings(s *commands.StashEntry) []string { + return []string{s.DisplayString} +} diff --git a/pkg/gui/presentation/tags.go b/pkg/gui/presentation/tags.go new file mode 100644 index 000000000..13f2b8b59 --- /dev/null +++ b/pkg/gui/presentation/tags.go @@ -0,0 +1,20 @@ +package presentation + +import ( + "github.com/jesseduffield/lazygit/pkg/commands" +) + +func GetTagListDisplayStrings(tags []*commands.Tag) [][]string { + lines := make([][]string, len(tags)) + + for i := range tags { + lines[i] = getTagDisplayStrings(tags[i]) + } + + return lines +} + +// getTagDisplayStrings returns the display string of branch +func getTagDisplayStrings(t *commands.Tag) []string { + return []string{t.Name} +} diff --git a/pkg/gui/reflog_panel.go b/pkg/gui/reflog_panel.go index fc4ff98c7..37144d8c6 100644 --- a/pkg/gui/reflog_panel.go +++ b/pkg/gui/reflog_panel.go @@ -3,6 +3,7 @@ package gui import ( "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/gui/presentation" ) // list panel functions @@ -66,9 +67,8 @@ func (gui *Gui) renderReflogCommitsWithSelection() error { commitsView := gui.getCommitsView() gui.refreshSelectedLine(&gui.State.Panels.ReflogCommits.SelectedLine, len(gui.State.ReflogCommits)) - if err := gui.renderListPanel(commitsView, gui.State.ReflogCommits); err != nil { - return err - } + displayStrings := presentation.GetCommitListDisplayStrings(gui.State.ReflogCommits, gui.State.ScreenMode != SCREEN_NORMAL) + gui.renderDisplayStrings(commitsView, displayStrings) if gui.g.CurrentView() == commitsView && commitsView.Context == "reflog-commits" { if err := gui.handleReflogCommitSelect(gui.g, commitsView); err != nil { return err diff --git a/pkg/gui/remote_branches_panel.go b/pkg/gui/remote_branches_panel.go index 858252f82..33cd0075e 100644 --- a/pkg/gui/remote_branches_panel.go +++ b/pkg/gui/remote_branches_panel.go @@ -5,6 +5,7 @@ import ( "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/gui/presentation" ) // list panel functions @@ -62,9 +63,8 @@ func (gui *Gui) renderRemoteBranchesWithSelection() error { branchesView := gui.getBranchesView() gui.refreshSelectedLine(&gui.State.Panels.RemoteBranches.SelectedLine, len(gui.State.RemoteBranches)) - if err := gui.renderListPanel(branchesView, gui.State.RemoteBranches); err != nil { - return err - } + displayStrings := presentation.GetRemoteBranchListDisplayStrings(gui.State.RemoteBranches) + gui.renderDisplayStrings(branchesView, displayStrings) if gui.g.CurrentView() == branchesView && branchesView.Context == "remote-branches" { if err := gui.handleRemoteBranchSelect(gui.g, branchesView); err != nil { return err diff --git a/pkg/gui/remotes_panel.go b/pkg/gui/remotes_panel.go index ca3e3aae1..a5bbfeff7 100644 --- a/pkg/gui/remotes_panel.go +++ b/pkg/gui/remotes_panel.go @@ -7,6 +7,7 @@ import ( "github.com/fatih/color" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/gui/presentation" "github.com/jesseduffield/lazygit/pkg/utils" ) @@ -80,9 +81,10 @@ func (gui *Gui) renderRemotesWithSelection() error { branchesView := gui.getBranchesView() gui.refreshSelectedLine(&gui.State.Panels.Remotes.SelectedLine, len(gui.State.Remotes)) - if err := gui.renderListPanel(branchesView, gui.State.Remotes); err != nil { - return err - } + + displayStrings := presentation.GetRemoteListDisplayStrings(gui.State.Remotes) + gui.renderDisplayStrings(branchesView, displayStrings) + if gui.g.CurrentView() == branchesView && branchesView.Context == "remotes" { if err := gui.handleRemoteSelect(gui.g, branchesView); err != nil { return err diff --git a/pkg/gui/stash_panel.go b/pkg/gui/stash_panel.go index 9ca72dee0..db12767e8 100644 --- a/pkg/gui/stash_panel.go +++ b/pkg/gui/stash_panel.go @@ -1,11 +1,9 @@ package gui import ( - "fmt" - "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" - "github.com/jesseduffield/lazygit/pkg/utils" + "github.com/jesseduffield/lazygit/pkg/gui/presentation" ) // list panel functions @@ -56,17 +54,12 @@ func (gui *Gui) refreshStashEntries(g *gocui.Gui) error { gui.refreshSelectedLine(&gui.State.Panels.Stash.SelectedLine, len(gui.State.StashEntries)) - isFocused := gui.g.CurrentView().Name() == "stash" - list, err := utils.RenderList(gui.State.StashEntries, isFocused) - if err != nil { - return err - } + stashView := gui.getStashView() - v := gui.getStashView() - v.Clear() - fmt.Fprint(v, list) + displayStrings := presentation.GetStashEntryListDisplayStrings(gui.State.StashEntries) + gui.renderDisplayStrings(stashView, displayStrings) - if err := gui.resetOrigin(v); err != nil { + if err := gui.resetOrigin(stashView); err != nil { return err } return nil diff --git a/pkg/gui/status_panel.go b/pkg/gui/status_panel.go index 8eb6a92f9..39d85411e 100644 --- a/pkg/gui/status_panel.go +++ b/pkg/gui/status_panel.go @@ -6,7 +6,7 @@ import ( "github.com/fatih/color" "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/gui/presentation" "github.com/jesseduffield/lazygit/pkg/utils" ) @@ -35,7 +35,7 @@ func (gui *Gui) refreshStatus(g *gocui.Gui) error { if len(branches) > 0 { branch := branches[0] - name := utils.ColoredString(branch.Name, commands.GetBranchColor(branch.Name)) + name := utils.ColoredString(branch.Name, presentation.GetBranchColor(branch.Name)) repoName := utils.GetCurrentRepoName() status += fmt.Sprintf(" %s → %s", repoName, name) } diff --git a/pkg/gui/tags_panel.go b/pkg/gui/tags_panel.go index 61cded63d..daa53b40e 100644 --- a/pkg/gui/tags_panel.go +++ b/pkg/gui/tags_panel.go @@ -3,6 +3,7 @@ package gui import ( "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/gui/presentation" ) // list panel functions @@ -66,9 +67,8 @@ func (gui *Gui) renderTagsWithSelection() error { branchesView := gui.getBranchesView() gui.refreshSelectedLine(&gui.State.Panels.Tags.SelectedLine, len(gui.State.Tags)) - if err := gui.renderListPanel(branchesView, gui.State.Tags); err != nil { - return err - } + displayStrings := presentation.GetTagListDisplayStrings(gui.State.Tags) + gui.renderDisplayStrings(branchesView, displayStrings) if gui.g.CurrentView() == branchesView && branchesView.Context == "tags" { if err := gui.handleTagSelect(gui.g, branchesView); err != nil { return err diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index 119014802..a41b481b9 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -396,20 +396,6 @@ func (gui *Gui) renderDisplayStrings(v *gocui.View, displayStrings [][]string) { }) } -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() - list, err := utils.RenderList(items, isFocused) - if err != nil { - return gui.createErrorPanel(gui.g, err.Error()) - } - v.Clear() - fmt.Fprint(v, list) - return nil - }) - return nil -} - func (gui *Gui) renderPanelOptions() error { currentView := gui.g.CurrentView() switch currentView.Name() { diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 7fa33e769..f42ee130c 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -6,13 +6,10 @@ import ( "log" "os" "path/filepath" - "reflect" "regexp" "strings" "time" - "github.com/go-errors/errors" - "github.com/fatih/color" ) @@ -114,46 +111,6 @@ func Min(x, y int) int { return y } -type Displayable interface { - GetDisplayStrings(bool) []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{}, isFocused bool) (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, isFocused) -} - -// 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, isFocused bool) (string, error) { - if len(items) == 0 { - return "", nil - } - - stringArrays := getDisplayStringArrays(items, isFocused) - - return RenderDisplayStrings(stringArrays), nil -} - func RenderDisplayStrings(displayStringsArr [][]string) string { padWidths := getPadWidths(displayStringsArr) paddedDisplayStrings := getPaddedDisplayStrings(displayStringsArr, padWidths) @@ -220,14 +177,6 @@ func displayArraysAligned(stringArrays [][]string) bool { return true } -func getDisplayStringArrays(displayables []Displayable, isFocused bool) [][]string { - stringArrays := make([][]string, len(displayables)) - for i, item := range displayables { - stringArrays[i] = item.GetDisplayStrings(isFocused) - } - return stringArrays -} - // IncludesString if the list contains the string func IncludesString(list []string, a string) bool { for _, b := range list { diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index 7cf8e958b..82df3e94a 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -196,166 +196,8 @@ func TestDisplayArraysAligned(t *testing.T) { } } -type myDisplayable struct { - strings []string -} - type myStruct struct{} -// GetDisplayStrings is a function. -func (d *myDisplayable) GetDisplayStrings(isFocused bool) []string { - if isFocused { - return append(d.strings, "blah") - } - return d.strings -} - -// TestGetDisplayStringArrays is a function. -func TestGetDisplayStringArrays(t *testing.T) { - type scenario struct { - input []Displayable - isFocused bool - expected [][]string - } - - scenarios := []scenario{ - { - []Displayable{ - Displayable(&myDisplayable{[]string{"a", "b"}}), - Displayable(&myDisplayable{[]string{"c", "d"}}), - }, - false, - [][]string{{"a", "b"}, {"c", "d"}}, - }, - { - []Displayable{ - Displayable(&myDisplayable{[]string{"a", "b"}}), - Displayable(&myDisplayable{[]string{"c", "d"}}), - }, - true, - [][]string{{"a", "b", "blah"}, {"c", "d", "blah"}}, - }, - } - - for _, s := range scenarios { - assert.EqualValues(t, s.expected, getDisplayStringArrays(s.input, s.isFocused)) - } -} - -// TestRenderDisplayableList is a function. -func TestRenderDisplayableList(t *testing.T) { - type scenario struct { - input []Displayable - isFocused bool - expectedString string - expectedErrorMessage string - } - - scenarios := []scenario{ - { - []Displayable{ - Displayable(&myDisplayable{[]string{}}), - Displayable(&myDisplayable{[]string{}}), - }, - false, - "\n", - "", - }, - { - []Displayable{ - Displayable(&myDisplayable{[]string{"aa", "b"}}), - Displayable(&myDisplayable{[]string{"c", "d"}}), - }, - false, - "aa b\nc d", - "", - }, - { - []Displayable{ - Displayable(&myDisplayable{[]string{"a"}}), - Displayable(&myDisplayable{[]string{"b", "c"}}), - }, - false, - "a \nb c", - "", - }, - { - []Displayable{ - Displayable(&myDisplayable{[]string{"a"}}), - Displayable(&myDisplayable{[]string{"b"}}), - }, - true, - "a blah\nb blah", - "", - }, - } - - for _, s := range scenarios { - str, err := renderDisplayableList(s.input, s.isFocused) - assert.EqualValues(t, s.expectedString, str) - if s.expectedErrorMessage != "" { - assert.EqualError(t, err, s.expectedErrorMessage) - } else { - assert.NoError(t, err) - } - } -} - -// TestRenderList is a function. -func TestRenderList(t *testing.T) { - type scenario struct { - input interface{} - isFocused bool - expectedString string - expectedErrorMessage string - } - - scenarios := []scenario{ - { - []*myDisplayable{ - {[]string{"aa", "b"}}, - {[]string{"c", "d"}}, - }, - false, - "aa b\nc d", - "", - }, - { - []*myStruct{ - {}, - {}, - }, - false, - "", - "item does not implement the Displayable interface", - }, - { - &myStruct{}, - false, - "", - "RenderList given a non-slice type", - }, - { - []*myDisplayable{ - {[]string{"a"}}, - }, - true, - "a blah", - "", - }, - } - - for _, s := range scenarios { - str, err := RenderList(s.input, s.isFocused) - assert.EqualValues(t, s.expectedString, str) - if s.expectedErrorMessage != "" { - assert.EqualError(t, err, s.expectedErrorMessage) - } else { - assert.NoError(t, err) - } - } -} - // TestGetPaddedDisplayStrings is a function. func TestGetPaddedDisplayStrings(t *testing.T) { type scenario struct {