From 79079b54eaf96a48d5d7bf2cf42fd6b2e59e2e59 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sun, 14 Mar 2021 19:38:59 +1100 Subject: [PATCH] combining nodes when only one child exists --- pkg/commands/models/status_line_node.go | 37 ++++++++- pkg/commands/models/status_line_node_test.go | 59 ++++++++++--- pkg/gui/status_line_manager_test.go | 87 ++++++++++++++++++++ pkg/gui/status_tree.go | 3 +- 4 files changed, 173 insertions(+), 13 deletions(-) create mode 100644 pkg/gui/status_line_manager_test.go diff --git a/pkg/commands/models/status_line_node.go b/pkg/commands/models/status_line_node.go index 92f0bba03..b33e9e731 100644 --- a/pkg/commands/models/status_line_node.go +++ b/pkg/commands/models/status_line_node.go @@ -1,6 +1,7 @@ package models import ( + "fmt" "sort" ) @@ -106,11 +107,11 @@ func (s *StatusLineNode) Flatten() []*StatusLineNode { return arr } -func (s *StatusLineNode) SortTree() { +func (s *StatusLineNode) Sort() { s.sortChildren() for _, child := range s.Children { - child.SortTree() + child.Sort() } } @@ -149,3 +150,35 @@ func (s *StatusLineNode) GetIsTracked() bool { func (s *StatusLineNode) GetPath() string { return s.Path } + +func (s *StatusLineNode) HasExactlyOneChild() bool { + return len(s.Children) == 1 +} + +func (s *StatusLineNode) Compress() { + if s == nil { + return + } + + s.compressAux() +} + +func (s *StatusLineNode) compressAux() *StatusLineNode { + if s.IsLeaf() { + return s + } + + for i, child := range s.Children { + if child.HasExactlyOneChild() { + grandchild := child.Children[0] + grandchild.Name = fmt.Sprintf("%s/%s", child.Name, grandchild.Name) + s.Children[i] = grandchild + } + } + + for i, child := range s.Children { + s.Children[i] = child.compressAux() + } + + return s +} diff --git a/pkg/commands/models/status_line_node_test.go b/pkg/commands/models/status_line_node_test.go index f8114fcf1..c68199c18 100644 --- a/pkg/commands/models/status_line_node_test.go +++ b/pkg/commands/models/status_line_node_test.go @@ -6,16 +6,16 @@ import ( "github.com/stretchr/testify/assert" ) -func TestRender(t *testing.T) { +func TestCompress(t *testing.T) { scenarios := []struct { name string root *StatusLineNode - expected []string + expected *StatusLineNode }{ { name: "nil node", root: nil, - expected: []string{}, + expected: nil, }, { name: "leaf node", @@ -25,7 +25,12 @@ func TestRender(t *testing.T) { {File: &File{Name: "test", ShortStatus: " M", HasStagedChanges: true}, Name: "test"}, }, }, - expected: []string{" M test"}, + expected: &StatusLineNode{ + Name: "", + Children: []*StatusLineNode{ + {File: &File{Name: "test", ShortStatus: " M", HasStagedChanges: true}, Name: "test"}, + }, + }, }, { name: "big example", @@ -33,44 +38,78 @@ func TestRender(t *testing.T) { Name: "", Children: []*StatusLineNode{ { - Name: "dir1", - Collapsed: true, + Name: "dir1", + Path: "dir1", Children: []*StatusLineNode{ { File: &File{Name: "file2", ShortStatus: "M ", HasUnstagedChanges: true}, Name: "file2", + Path: "dir1/file2", }, }, }, { Name: "dir2", + Path: "dir2", Children: []*StatusLineNode{ { File: &File{Name: "file3", ShortStatus: " M", HasStagedChanges: true}, Name: "file3", + Path: "dir2/file3", }, { File: &File{Name: "file4", ShortStatus: "M ", HasUnstagedChanges: true}, Name: "file4", + Path: "dir2/file4", }, }, }, { File: &File{Name: "file1", ShortStatus: "M ", HasUnstagedChanges: true}, Name: "file1", + Path: "file1", + }, + }, + }, + expected: &StatusLineNode{ + Name: "", + Children: []*StatusLineNode{ + { + Name: "dir1/file2", + File: &File{Name: "file2", ShortStatus: "M ", HasUnstagedChanges: true}, + Path: "dir1/file2", + }, + { + Name: "dir2", + Path: "dir2", + Children: []*StatusLineNode{ + { + File: &File{Name: "file3", ShortStatus: " M", HasStagedChanges: true}, + Name: "file3", + Path: "dir2/file3", + }, + { + File: &File{Name: "file4", ShortStatus: "M ", HasUnstagedChanges: true}, + Name: "file4", + Path: "dir2/file4", + }, + }, + }, + { + File: &File{Name: "file1", ShortStatus: "M ", HasUnstagedChanges: true}, + Name: "file1", + Path: "file1", }, }, }, - - expected: []string{"M dir1 ►", "MM dir2 ▼", " M file3", " M file4", "M file1"}, }, } for _, s := range scenarios { s := s t.Run(s.name, func(t *testing.T) { - result := s.root.Render()[1:] - assert.EqualValues(t, s.expected, result) + s.root.Compress() + assert.EqualValues(t, s.expected, s.root) }) } } diff --git a/pkg/gui/status_line_manager_test.go b/pkg/gui/status_line_manager_test.go new file mode 100644 index 000000000..84d7bb9f1 --- /dev/null +++ b/pkg/gui/status_line_manager_test.go @@ -0,0 +1,87 @@ +package gui + +import ( + "testing" + + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/stretchr/testify/assert" +) + +func TestRender(t *testing.T) { + scenarios := []struct { + name string + root *models.StatusLineNode + expected []string + }{ + { + name: "nil node", + root: nil, + expected: []string{}, + }, + { + name: "leaf node", + root: &models.StatusLineNode{ + Name: "", + Children: []*models.StatusLineNode{ + {File: &models.File{Name: "test", ShortStatus: " M", HasStagedChanges: true}, Name: "test"}, + }, + }, + expected: []string{" M test"}, + }, + { + name: "big example", + root: &models.StatusLineNode{ + Name: "", + Children: []*models.StatusLineNode{ + { + Name: "dir1", + Collapsed: true, + Children: []*models.StatusLineNode{ + { + File: &models.File{Name: "file2", ShortStatus: "M ", HasUnstagedChanges: true}, + Name: "file2", + }, + }, + }, + { + Name: "dir2", + Children: []*models.StatusLineNode{ + { + Name: "dir2", + Children: []*models.StatusLineNode{ + { + File: &models.File{Name: "file3", ShortStatus: " M", HasStagedChanges: true}, + Name: "file3", + }, + { + File: &models.File{Name: "file4", ShortStatus: "M ", HasUnstagedChanges: true}, + Name: "file4", + }, + }, + }, + { + File: &models.File{Name: "file5", ShortStatus: "M ", HasUnstagedChanges: true}, + Name: "file5", + }, + }, + }, + { + File: &models.File{Name: "file1", ShortStatus: "M ", HasUnstagedChanges: true}, + Name: "file1", + }, + }, + }, + + expected: []string{" M dir1 ►", "MM dir2 ▼", "├─ MM dir2 ▼", "│ ├─ M file3", "│ └─ M file4", "└─ M file5", "M file1"}, + }, + } + + for _, s := range scenarios { + s := s + t.Run(s.name, func(t *testing.T) { + mngr := &StatusLineManager{Tree: s.root} + result := mngr.Render("", nil) + assert.EqualValues(t, s.expected, result) + }) + } +} diff --git a/pkg/gui/status_tree.go b/pkg/gui/status_tree.go index 77a02ce51..193d34fd8 100644 --- a/pkg/gui/status_tree.go +++ b/pkg/gui/status_tree.go @@ -43,7 +43,8 @@ func GetTreeFromStatusFiles(files []*models.File) *models.StatusLineNode { } } - root.SortTree() + root.Sort() + root.Compress() return root }