Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for alternative ordering strategies #424

Merged
merged 1 commit into from
Jul 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func initConfig() {
// keybindings: filetree view
viper.SetDefault("keybinding.toggle-collapse-dir", "space")
viper.SetDefault("keybinding.toggle-collapse-all-dir", "ctrl+space")
viper.SetDefault("keybinding.toggle-sort-order", "ctrl+o")
viper.SetDefault("keybinding.toggle-filetree-attributes", "ctrl+b")
viper.SetDefault("keybinding.toggle-added-files", "ctrl+a")
viper.SetDefault("keybinding.toggle-removed-files", "ctrl+r")
Expand Down
2 changes: 1 addition & 1 deletion dive/filetree/efficiency.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func Efficiency(trees []*FileTree) (float64, EfficiencySlice) {
}

if previousTreeNode.Data.FileInfo.IsDir {
err = previousTreeNode.VisitDepthChildFirst(sizer, nil)
err = previousTreeNode.VisitDepthChildFirst(sizer, nil, nil)
if err != nil {
logrus.Errorf("unable to propagate whiteout dir: %+v", err)
return err
Expand Down
50 changes: 29 additions & 21 deletions dive/filetree/file_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package filetree
import (
"archive/tar"
"fmt"
"sort"
"strings"

"github.com/dustin/go-humanize"
Expand All @@ -27,6 +26,7 @@ var diffTypeColor = map[DiffType]*color.Color{
type FileNode struct {
Tree *FileTree
Parent *FileNode
Size int64 // memoized total size of file or directory
Name string
Data NodeData
Children map[string]*FileNode
Expand All @@ -39,6 +39,7 @@ func NewNode(parent *FileNode, name string, data FileInfo) (node *FileNode) {
node.Name = name
node.Data = *NewNodeData()
node.Data.FileInfo = *data.Copy()
node.Size = -1 // signal lazy load later

node.Children = make(map[string]*FileNode)
node.Parent = parent
Expand Down Expand Up @@ -149,41 +150,49 @@ func (node *FileNode) MetadataString() string {
group := node.Data.FileInfo.Gid
userGroup := fmt.Sprintf("%d:%d", user, group)

// don't include file sizes of children that have been removed (unless the node in question is a removed dir,
// then show the accumulated size of removed files)
sizeBytes := node.GetSize()

size := humanize.Bytes(uint64(sizeBytes))

return diffTypeColor[node.Data.DiffType].Sprint(fmt.Sprintf(AttributeFormat, dir, fileMode, userGroup, size))
}

func (node *FileNode) GetSize() int64 {
if 0 <= node.Size {
return node.Size
}
var sizeBytes int64

if node.IsLeaf() {
sizeBytes = node.Data.FileInfo.Size
} else {
sizer := func(curNode *FileNode) error {
// don't include file sizes of children that have been removed (unless the node in question is a removed dir,
// then show the accumulated size of removed files)

if curNode.Data.DiffType != Removed || node.Data.DiffType == Removed {
sizeBytes += curNode.Data.FileInfo.Size
}
return nil
}

err := node.VisitDepthChildFirst(sizer, nil)
err := node.VisitDepthChildFirst(sizer, nil, nil)
if err != nil {
logrus.Errorf("unable to propagate node for metadata: %+v", err)
}
}

size := humanize.Bytes(uint64(sizeBytes))

return diffTypeColor[node.Data.DiffType].Sprint(fmt.Sprintf(AttributeFormat, dir, fileMode, userGroup, size))
node.Size = sizeBytes
return node.Size
}

// VisitDepthChildFirst iterates a tree depth-first (starting at this FileNode), evaluating the deepest depths first (visit on bubble up)
func (node *FileNode) VisitDepthChildFirst(visitor Visitor, evaluator VisitEvaluator) error {
var keys []string
for key := range node.Children {
keys = append(keys, key)
func (node *FileNode) VisitDepthChildFirst(visitor Visitor, evaluator VisitEvaluator, sorter OrderStrategy) error {
if sorter == nil {
sorter = GetSortOrderStrategy(ByName)
}
sort.Strings(keys)
keys := sorter.orderKeys(node.Children)
for _, name := range keys {
child := node.Children[name]
err := child.VisitDepthChildFirst(visitor, evaluator)
err := child.VisitDepthChildFirst(visitor, evaluator, sorter)
if err != nil {
return err
}
Expand All @@ -199,7 +208,7 @@ func (node *FileNode) VisitDepthChildFirst(visitor Visitor, evaluator VisitEvalu
}

// VisitDepthParentFirst iterates a tree depth-first (starting at this FileNode), evaluating the shallowest depths first (visit while sinking down)
func (node *FileNode) VisitDepthParentFirst(visitor Visitor, evaluator VisitEvaluator) error {
func (node *FileNode) VisitDepthParentFirst(visitor Visitor, evaluator VisitEvaluator, sorter OrderStrategy) error {
var err error

doVisit := evaluator != nil && evaluator(node) || evaluator == nil
Expand All @@ -216,14 +225,13 @@ func (node *FileNode) VisitDepthParentFirst(visitor Visitor, evaluator VisitEval
}
}

var keys []string
for key := range node.Children {
keys = append(keys, key)
if sorter == nil {
sorter = GetSortOrderStrategy(ByName)
}
sort.Strings(keys)
keys := sorter.orderKeys(node.Children)
for _, name := range keys {
child := node.Children[name]
err = child.VisitDepthParentFirst(visitor, evaluator)
err = child.VisitDepthParentFirst(visitor, evaluator, sorter)
if err != nil {
return err
}
Expand Down
28 changes: 14 additions & 14 deletions dive/filetree/file_tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package filetree
import (
"fmt"
"path"
"sort"
"strings"

"github.com/google/uuid"
Expand All @@ -24,11 +23,12 @@ const (

// FileTree represents a set of files, directories, and their relations.
type FileTree struct {
Root *FileNode
Size int
FileSize uint64
Name string
Id uuid.UUID
Root *FileNode
Size int
FileSize uint64
Name string
Id uuid.UUID
SortOrder SortOrder
}

// NewFileTree creates an empty FileTree
Expand All @@ -39,6 +39,7 @@ func NewFileTree() (tree *FileTree) {
tree.Root.Tree = tree
tree.Root.Children = make(map[string]*FileNode)
tree.Id = uuid.New()
tree.SortOrder = ByName
return tree
}

Expand Down Expand Up @@ -67,12 +68,8 @@ func (tree *FileTree) renderStringTreeBetween(startRow, stopRow int, showAttribu
currentParams, paramsToVisit = paramsToVisit[0], paramsToVisit[1:]

// take note of the next nodes to visit later
var keys []string
for key := range currentParams.node.Children {
keys = append(keys, key)
}
// we should always visit nodes in order
sort.Strings(keys)
sorter := GetSortOrderStrategy(tree.SortOrder)
keys := sorter.orderKeys(currentParams.node.Children)

var childParams = make([]renderParams, 0)
for idx, name := range keys {
Expand Down Expand Up @@ -174,6 +171,7 @@ func (tree *FileTree) Copy() *FileTree {
newTree.Size = tree.Size
newTree.FileSize = tree.FileSize
newTree.Root = tree.Root.Copy(newTree.Root)
newTree.SortOrder = tree.SortOrder

// update the tree pointers
err := newTree.VisitDepthChildFirst(func(node *FileNode) error {
Expand All @@ -196,12 +194,14 @@ type VisitEvaluator func(*FileNode) bool

// VisitDepthChildFirst iterates the given tree depth-first, evaluating the deepest depths first (visit on bubble up)
func (tree *FileTree) VisitDepthChildFirst(visitor Visitor, evaluator VisitEvaluator) error {
return tree.Root.VisitDepthChildFirst(visitor, evaluator)
sorter := GetSortOrderStrategy(tree.SortOrder)
return tree.Root.VisitDepthChildFirst(visitor, evaluator, sorter)
}

// VisitDepthParentFirst iterates the given tree depth-first, evaluating the shallowest depths first (visit while sinking down)
func (tree *FileTree) VisitDepthParentFirst(visitor Visitor, evaluator VisitEvaluator) error {
return tree.Root.VisitDepthParentFirst(visitor, evaluator)
sorter := GetSortOrderStrategy(tree.SortOrder)
return tree.Root.VisitDepthParentFirst(visitor, evaluator, sorter)
}

// Stack takes two trees and combines them together. This is done by "stacking" the given tree on top of the owning tree.
Expand Down
61 changes: 61 additions & 0 deletions dive/filetree/order_strategy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package filetree

import (
"sort"
)

type SortOrder int

const (
ByName = iota
BySizeDesc

NumSortOrderConventions
)

type OrderStrategy interface {
orderKeys(files map[string]*FileNode) []string
}

func GetSortOrderStrategy(sortOrder SortOrder) OrderStrategy {
switch sortOrder {
case ByName:
return orderByNameStrategy{}
case BySizeDesc:
return orderBySizeDescStrategy{}
}
return orderByNameStrategy{}
}

type orderByNameStrategy struct{}

func (orderByNameStrategy) orderKeys(files map[string]*FileNode) []string {
var keys []string
for key := range files {
keys = append(keys, key)
}

sort.Strings(keys)

return keys
}

type orderBySizeDescStrategy struct{}

func (orderBySizeDescStrategy) orderKeys(files map[string]*FileNode) []string {
var keys []string
for key := range files {
keys = append(keys, key)
}

sort.Slice(keys, func(i, j int) bool {
ki, kj := keys[i], keys[j]
ni, nj := files[ki], files[kj]
if ni.GetSize() == nj.GetSize() {
return ki < kj
}
return ni.GetSize() > nj.GetSize()
})

return keys
}
17 changes: 16 additions & 1 deletion runtime/ui/view/filetree.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type FileTree struct {
gui *gocui.Gui
view *gocui.View
header *gocui.View
vm *viewmodel.FileTree
vm *viewmodel.FileTreeViewModel
title string

filterRegex *regexp.Regexp
Expand Down Expand Up @@ -98,6 +98,11 @@ func (v *FileTree) Setup(view, header *gocui.View) error {
OnAction: v.toggleCollapseAll,
Display: "Collapse all dir",
},
{
ConfigKeys: []string{"keybinding.toggle-sort-order"},
OnAction: v.toggleSortOrder,
Display: "Toggle sort order",
},
{
ConfigKeys: []string{"keybinding.toggle-added-files"},
OnAction: func() error { return v.toggleShowDiffType(filetree.Added) },
Expand Down Expand Up @@ -288,6 +293,16 @@ func (v *FileTree) toggleCollapseAll() error {
return v.Render()
}

func (v *FileTree) toggleSortOrder() error {
err := v.vm.ToggleSortOrder()
if err != nil {
return err
}
v.resetCursor()
_ = v.Update()
return v.Render()
}

func (v *FileTree) toggleWrapTree() error {
v.view.Wrap = !v.view.Wrap
return nil
Expand Down
Loading