feat: add support for alternative ordering strategies (#424)
This commit is contained in:
parent
d5e8a92968
commit
6f20438ae4
@ -86,6 +86,7 @@ func initConfig() {
|
|||||||
// keybindings: filetree view
|
// keybindings: filetree view
|
||||||
viper.SetDefault("keybinding.toggle-collapse-dir", "space")
|
viper.SetDefault("keybinding.toggle-collapse-dir", "space")
|
||||||
viper.SetDefault("keybinding.toggle-collapse-all-dir", "ctrl+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-filetree-attributes", "ctrl+b")
|
||||||
viper.SetDefault("keybinding.toggle-added-files", "ctrl+a")
|
viper.SetDefault("keybinding.toggle-added-files", "ctrl+a")
|
||||||
viper.SetDefault("keybinding.toggle-removed-files", "ctrl+r")
|
viper.SetDefault("keybinding.toggle-removed-files", "ctrl+r")
|
||||||
|
@ -79,7 +79,7 @@ func Efficiency(trees []*FileTree) (float64, EfficiencySlice) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if previousTreeNode.Data.FileInfo.IsDir {
|
if previousTreeNode.Data.FileInfo.IsDir {
|
||||||
err = previousTreeNode.VisitDepthChildFirst(sizer, nil)
|
err = previousTreeNode.VisitDepthChildFirst(sizer, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("unable to propagate whiteout dir: %+v", err)
|
logrus.Errorf("unable to propagate whiteout dir: %+v", err)
|
||||||
return err
|
return err
|
||||||
|
@ -3,7 +3,6 @@ package filetree
|
|||||||
import (
|
import (
|
||||||
"archive/tar"
|
"archive/tar"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
@ -27,6 +26,7 @@ var diffTypeColor = map[DiffType]*color.Color{
|
|||||||
type FileNode struct {
|
type FileNode struct {
|
||||||
Tree *FileTree
|
Tree *FileTree
|
||||||
Parent *FileNode
|
Parent *FileNode
|
||||||
|
Size int64 // memoized total size of file or directory
|
||||||
Name string
|
Name string
|
||||||
Data NodeData
|
Data NodeData
|
||||||
Children map[string]*FileNode
|
Children map[string]*FileNode
|
||||||
@ -39,6 +39,7 @@ func NewNode(parent *FileNode, name string, data FileInfo) (node *FileNode) {
|
|||||||
node.Name = name
|
node.Name = name
|
||||||
node.Data = *NewNodeData()
|
node.Data = *NewNodeData()
|
||||||
node.Data.FileInfo = *data.Copy()
|
node.Data.FileInfo = *data.Copy()
|
||||||
|
node.Size = -1 // signal lazy load later
|
||||||
|
|
||||||
node.Children = make(map[string]*FileNode)
|
node.Children = make(map[string]*FileNode)
|
||||||
node.Parent = parent
|
node.Parent = parent
|
||||||
@ -149,41 +150,49 @@ func (node *FileNode) MetadataString() string {
|
|||||||
group := node.Data.FileInfo.Gid
|
group := node.Data.FileInfo.Gid
|
||||||
userGroup := fmt.Sprintf("%d:%d", user, group)
|
userGroup := fmt.Sprintf("%d:%d", user, group)
|
||||||
|
|
||||||
var sizeBytes int64
|
// 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 node.IsLeaf() {
|
sizeBytes := node.GetSize()
|
||||||
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)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Errorf("unable to propagate node for metadata: %+v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
size := humanize.Bytes(uint64(sizeBytes))
|
size := humanize.Bytes(uint64(sizeBytes))
|
||||||
|
|
||||||
return diffTypeColor[node.Data.DiffType].Sprint(fmt.Sprintf(AttributeFormat, dir, fileMode, userGroup, size))
|
return diffTypeColor[node.Data.DiffType].Sprint(fmt.Sprintf(AttributeFormat, dir, fileMode, userGroup, size))
|
||||||
}
|
}
|
||||||
|
|
||||||
// VisitDepthChildFirst iterates a tree depth-first (starting at this FileNode), evaluating the deepest depths first (visit on bubble up)
|
func (node *FileNode) GetSize() int64 {
|
||||||
func (node *FileNode) VisitDepthChildFirst(visitor Visitor, evaluator VisitEvaluator) error {
|
if 0 <= node.Size {
|
||||||
var keys []string
|
return node.Size
|
||||||
for key := range node.Children {
|
|
||||||
keys = append(keys, key)
|
|
||||||
}
|
}
|
||||||
sort.Strings(keys)
|
var sizeBytes int64
|
||||||
|
|
||||||
|
if node.IsLeaf() {
|
||||||
|
sizeBytes = node.Data.FileInfo.Size
|
||||||
|
} else {
|
||||||
|
sizer := func(curNode *FileNode) error {
|
||||||
|
|
||||||
|
if curNode.Data.DiffType != Removed || node.Data.DiffType == Removed {
|
||||||
|
sizeBytes += curNode.Data.FileInfo.Size
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := node.VisitDepthChildFirst(sizer, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("unable to propagate node for metadata: %+v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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, sorter OrderStrategy) error {
|
||||||
|
if sorter == nil {
|
||||||
|
sorter = GetSortOrderStrategy(ByName)
|
||||||
|
}
|
||||||
|
keys := sorter.orderKeys(node.Children)
|
||||||
for _, name := range keys {
|
for _, name := range keys {
|
||||||
child := node.Children[name]
|
child := node.Children[name]
|
||||||
err := child.VisitDepthChildFirst(visitor, evaluator)
|
err := child.VisitDepthChildFirst(visitor, evaluator, sorter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -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)
|
// 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
|
var err error
|
||||||
|
|
||||||
doVisit := evaluator != nil && evaluator(node) || evaluator == nil
|
doVisit := evaluator != nil && evaluator(node) || evaluator == nil
|
||||||
@ -216,14 +225,13 @@ func (node *FileNode) VisitDepthParentFirst(visitor Visitor, evaluator VisitEval
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var keys []string
|
if sorter == nil {
|
||||||
for key := range node.Children {
|
sorter = GetSortOrderStrategy(ByName)
|
||||||
keys = append(keys, key)
|
|
||||||
}
|
}
|
||||||
sort.Strings(keys)
|
keys := sorter.orderKeys(node.Children)
|
||||||
for _, name := range keys {
|
for _, name := range keys {
|
||||||
child := node.Children[name]
|
child := node.Children[name]
|
||||||
err = child.VisitDepthParentFirst(visitor, evaluator)
|
err = child.VisitDepthParentFirst(visitor, evaluator, sorter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ package filetree
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@ -24,11 +23,12 @@ const (
|
|||||||
|
|
||||||
// FileTree represents a set of files, directories, and their relations.
|
// FileTree represents a set of files, directories, and their relations.
|
||||||
type FileTree struct {
|
type FileTree struct {
|
||||||
Root *FileNode
|
Root *FileNode
|
||||||
Size int
|
Size int
|
||||||
FileSize uint64
|
FileSize uint64
|
||||||
Name string
|
Name string
|
||||||
Id uuid.UUID
|
Id uuid.UUID
|
||||||
|
SortOrder SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFileTree creates an empty FileTree
|
// NewFileTree creates an empty FileTree
|
||||||
@ -39,6 +39,7 @@ func NewFileTree() (tree *FileTree) {
|
|||||||
tree.Root.Tree = tree
|
tree.Root.Tree = tree
|
||||||
tree.Root.Children = make(map[string]*FileNode)
|
tree.Root.Children = make(map[string]*FileNode)
|
||||||
tree.Id = uuid.New()
|
tree.Id = uuid.New()
|
||||||
|
tree.SortOrder = ByName
|
||||||
return tree
|
return tree
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,12 +68,8 @@ func (tree *FileTree) renderStringTreeBetween(startRow, stopRow int, showAttribu
|
|||||||
currentParams, paramsToVisit = paramsToVisit[0], paramsToVisit[1:]
|
currentParams, paramsToVisit = paramsToVisit[0], paramsToVisit[1:]
|
||||||
|
|
||||||
// take note of the next nodes to visit later
|
// take note of the next nodes to visit later
|
||||||
var keys []string
|
sorter := GetSortOrderStrategy(tree.SortOrder)
|
||||||
for key := range currentParams.node.Children {
|
keys := sorter.orderKeys(currentParams.node.Children)
|
||||||
keys = append(keys, key)
|
|
||||||
}
|
|
||||||
// we should always visit nodes in order
|
|
||||||
sort.Strings(keys)
|
|
||||||
|
|
||||||
var childParams = make([]renderParams, 0)
|
var childParams = make([]renderParams, 0)
|
||||||
for idx, name := range keys {
|
for idx, name := range keys {
|
||||||
@ -174,6 +171,7 @@ func (tree *FileTree) Copy() *FileTree {
|
|||||||
newTree.Size = tree.Size
|
newTree.Size = tree.Size
|
||||||
newTree.FileSize = tree.FileSize
|
newTree.FileSize = tree.FileSize
|
||||||
newTree.Root = tree.Root.Copy(newTree.Root)
|
newTree.Root = tree.Root.Copy(newTree.Root)
|
||||||
|
newTree.SortOrder = tree.SortOrder
|
||||||
|
|
||||||
// update the tree pointers
|
// update the tree pointers
|
||||||
err := newTree.VisitDepthChildFirst(func(node *FileNode) error {
|
err := newTree.VisitDepthChildFirst(func(node *FileNode) error {
|
||||||
@ -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)
|
// 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 {
|
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)
|
// 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 {
|
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.
|
// Stack takes two trees and combines them together. This is done by "stacking" the given tree on top of the owning tree.
|
||||||
|
61
dive/filetree/order_strategy.go
Normal file
61
dive/filetree/order_strategy.go
Normal file
@ -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
|
||||||
|
}
|
@ -24,7 +24,7 @@ type FileTree struct {
|
|||||||
gui *gocui.Gui
|
gui *gocui.Gui
|
||||||
view *gocui.View
|
view *gocui.View
|
||||||
header *gocui.View
|
header *gocui.View
|
||||||
vm *viewmodel.FileTree
|
vm *viewmodel.FileTreeViewModel
|
||||||
title string
|
title string
|
||||||
|
|
||||||
filterRegex *regexp.Regexp
|
filterRegex *regexp.Regexp
|
||||||
@ -98,6 +98,11 @@ func (v *FileTree) Setup(view, header *gocui.View) error {
|
|||||||
OnAction: v.toggleCollapseAll,
|
OnAction: v.toggleCollapseAll,
|
||||||
Display: "Collapse all dir",
|
Display: "Collapse all dir",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ConfigKeys: []string{"keybinding.toggle-sort-order"},
|
||||||
|
OnAction: v.toggleSortOrder,
|
||||||
|
Display: "Toggle sort order",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ConfigKeys: []string{"keybinding.toggle-added-files"},
|
ConfigKeys: []string{"keybinding.toggle-added-files"},
|
||||||
OnAction: func() error { return v.toggleShowDiffType(filetree.Added) },
|
OnAction: func() error { return v.toggleShowDiffType(filetree.Added) },
|
||||||
@ -288,6 +293,16 @@ func (v *FileTree) toggleCollapseAll() error {
|
|||||||
return v.Render()
|
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 {
|
func (v *FileTree) toggleWrapTree() error {
|
||||||
v.view.Wrap = !v.view.Wrap
|
v.view.Wrap = !v.view.Wrap
|
||||||
return nil
|
return nil
|
||||||
|
@ -16,7 +16,7 @@ import (
|
|||||||
|
|
||||||
// FileTreeViewModel holds the UI objects and data models for populating the right pane. Specifically the pane that
|
// FileTreeViewModel holds the UI objects and data models for populating the right pane. Specifically the pane that
|
||||||
// shows selected layer or aggregate file ASCII tree.
|
// shows selected layer or aggregate file ASCII tree.
|
||||||
type FileTree struct {
|
type FileTreeViewModel struct {
|
||||||
ModelTree *filetree.FileTree
|
ModelTree *filetree.FileTree
|
||||||
ViewTree *filetree.FileTree
|
ViewTree *filetree.FileTree
|
||||||
RefTrees []*filetree.FileTree
|
RefTrees []*filetree.FileTree
|
||||||
@ -39,8 +39,8 @@ type FileTree struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewFileTreeViewModel creates a new view object attached the the global [gocui] screen object.
|
// NewFileTreeViewModel creates a new view object attached the the global [gocui] screen object.
|
||||||
func NewFileTreeViewModel(tree *filetree.FileTree, refTrees []*filetree.FileTree, cache filetree.Comparer) (treeViewModel *FileTree, err error) {
|
func NewFileTreeViewModel(tree *filetree.FileTree, refTrees []*filetree.FileTree, cache filetree.Comparer) (treeViewModel *FileTreeViewModel, err error) {
|
||||||
treeViewModel = new(FileTree)
|
treeViewModel = new(FileTreeViewModel)
|
||||||
|
|
||||||
// populate main fields
|
// populate main fields
|
||||||
treeViewModel.ShowAttributes = viper.GetBool("filetree.show-attributes")
|
treeViewModel.ShowAttributes = viper.GetBool("filetree.show-attributes")
|
||||||
@ -71,13 +71,13 @@ func NewFileTreeViewModel(tree *filetree.FileTree, refTrees []*filetree.FileTree
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Setup initializes the UI concerns within the context of a global [gocui] view object.
|
// Setup initializes the UI concerns within the context of a global [gocui] view object.
|
||||||
func (vm *FileTree) Setup(lowerBound, height int) {
|
func (vm *FileTreeViewModel) Setup(lowerBound, height int) {
|
||||||
vm.bufferIndexLowerBound = lowerBound
|
vm.bufferIndexLowerBound = lowerBound
|
||||||
vm.refHeight = height
|
vm.refHeight = height
|
||||||
}
|
}
|
||||||
|
|
||||||
// height returns the current height and considers the header
|
// height returns the current height and considers the header
|
||||||
func (vm *FileTree) height() int {
|
func (vm *FileTreeViewModel) height() int {
|
||||||
if vm.ShowAttributes {
|
if vm.ShowAttributes {
|
||||||
return vm.refHeight - 1
|
return vm.refHeight - 1
|
||||||
}
|
}
|
||||||
@ -85,24 +85,24 @@ func (vm *FileTree) height() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// bufferIndexUpperBound returns the current upper bounds for the view
|
// bufferIndexUpperBound returns the current upper bounds for the view
|
||||||
func (vm *FileTree) bufferIndexUpperBound() int {
|
func (vm *FileTreeViewModel) bufferIndexUpperBound() int {
|
||||||
return vm.bufferIndexLowerBound + vm.height()
|
return vm.bufferIndexLowerBound + vm.height()
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsVisible indicates if the file tree view pane is currently initialized
|
// IsVisible indicates if the file tree view pane is currently initialized
|
||||||
func (vm *FileTree) IsVisible() bool {
|
func (vm *FileTreeViewModel) IsVisible() bool {
|
||||||
return vm != nil
|
return vm != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResetCursor moves the cursor back to the top of the buffer and translates to the top of the buffer.
|
// ResetCursor moves the cursor back to the top of the buffer and translates to the top of the buffer.
|
||||||
func (vm *FileTree) ResetCursor() {
|
func (vm *FileTreeViewModel) ResetCursor() {
|
||||||
vm.TreeIndex = 0
|
vm.TreeIndex = 0
|
||||||
vm.bufferIndex = 0
|
vm.bufferIndex = 0
|
||||||
vm.bufferIndexLowerBound = 0
|
vm.bufferIndexLowerBound = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTreeByLayer populates the view model by stacking the indicated image layer file trees.
|
// SetTreeByLayer populates the view model by stacking the indicated image layer file trees.
|
||||||
func (vm *FileTree) SetTreeByLayer(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop int) error {
|
func (vm *FileTreeViewModel) SetTreeByLayer(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop int) error {
|
||||||
if topTreeStop > len(vm.RefTrees)-1 {
|
if topTreeStop > len(vm.RefTrees)-1 {
|
||||||
return fmt.Errorf("invalid layer index given: %d of %d", topTreeStop, len(vm.RefTrees)-1)
|
return fmt.Errorf("invalid layer index given: %d of %d", topTreeStop, len(vm.RefTrees)-1)
|
||||||
}
|
}
|
||||||
@ -131,7 +131,7 @@ func (vm *FileTree) SetTreeByLayer(bottomTreeStart, bottomTreeStop, topTreeStart
|
|||||||
}
|
}
|
||||||
|
|
||||||
// doCursorUp performs the internal view's buffer adjustments on cursor up. Note: this is independent of the gocui buffer.
|
// doCursorUp performs the internal view's buffer adjustments on cursor up. Note: this is independent of the gocui buffer.
|
||||||
func (vm *FileTree) CursorUp() bool {
|
func (vm *FileTreeViewModel) CursorUp() bool {
|
||||||
if vm.TreeIndex <= 0 {
|
if vm.TreeIndex <= 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -146,7 +146,7 @@ func (vm *FileTree) CursorUp() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// doCursorDown performs the internal view's buffer adjustments on cursor down. Note: this is independent of the gocui buffer.
|
// doCursorDown performs the internal view's buffer adjustments on cursor down. Note: this is independent of the gocui buffer.
|
||||||
func (vm *FileTree) CursorDown() bool {
|
func (vm *FileTreeViewModel) CursorDown() bool {
|
||||||
if vm.TreeIndex >= vm.ModelTree.VisibleSize() {
|
if vm.TreeIndex >= vm.ModelTree.VisibleSize() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -162,7 +162,7 @@ func (vm *FileTree) CursorDown() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CursorLeft moves the cursor up until we reach the Parent Node or top of the tree
|
// CursorLeft moves the cursor up until we reach the Parent Node or top of the tree
|
||||||
func (vm *FileTree) CursorLeft(filterRegex *regexp.Regexp) error {
|
func (vm *FileTreeViewModel) CursorLeft(filterRegex *regexp.Regexp) error {
|
||||||
var visitor func(*filetree.FileNode) error
|
var visitor func(*filetree.FileNode) error
|
||||||
var evaluator func(*filetree.FileNode) bool
|
var evaluator func(*filetree.FileNode) bool
|
||||||
var dfsCounter, newIndex int
|
var dfsCounter, newIndex int
|
||||||
@ -213,7 +213,7 @@ func (vm *FileTree) CursorLeft(filterRegex *regexp.Regexp) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CursorRight descends into directory expanding it if needed
|
// CursorRight descends into directory expanding it if needed
|
||||||
func (vm *FileTree) CursorRight(filterRegex *regexp.Regexp) error {
|
func (vm *FileTreeViewModel) CursorRight(filterRegex *regexp.Regexp) error {
|
||||||
node := vm.getAbsPositionNode(filterRegex)
|
node := vm.getAbsPositionNode(filterRegex)
|
||||||
if node == nil {
|
if node == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -245,7 +245,7 @@ func (vm *FileTree) CursorRight(filterRegex *regexp.Regexp) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PageDown moves to next page putting the cursor on top
|
// PageDown moves to next page putting the cursor on top
|
||||||
func (vm *FileTree) PageDown() error {
|
func (vm *FileTreeViewModel) PageDown() error {
|
||||||
nextBufferIndexLowerBound := vm.bufferIndexLowerBound + vm.height()
|
nextBufferIndexLowerBound := vm.bufferIndexLowerBound + vm.height()
|
||||||
nextBufferIndexUpperBound := nextBufferIndexLowerBound + vm.height()
|
nextBufferIndexUpperBound := nextBufferIndexLowerBound + vm.height()
|
||||||
|
|
||||||
@ -271,7 +271,7 @@ func (vm *FileTree) PageDown() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PageUp moves to previous page putting the cursor on top
|
// PageUp moves to previous page putting the cursor on top
|
||||||
func (vm *FileTree) PageUp() error {
|
func (vm *FileTreeViewModel) PageUp() error {
|
||||||
nextBufferIndexLowerBound := vm.bufferIndexLowerBound - vm.height()
|
nextBufferIndexLowerBound := vm.bufferIndexLowerBound - vm.height()
|
||||||
nextBufferIndexUpperBound := nextBufferIndexLowerBound + vm.height()
|
nextBufferIndexUpperBound := nextBufferIndexLowerBound + vm.height()
|
||||||
|
|
||||||
@ -296,7 +296,7 @@ func (vm *FileTree) PageUp() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getAbsPositionNode determines the selected screen cursor's location in the file tree, returning the selected FileNode.
|
// getAbsPositionNode determines the selected screen cursor's location in the file tree, returning the selected FileNode.
|
||||||
func (vm *FileTree) getAbsPositionNode(filterRegex *regexp.Regexp) (node *filetree.FileNode) {
|
func (vm *FileTreeViewModel) getAbsPositionNode(filterRegex *regexp.Regexp) (node *filetree.FileNode) {
|
||||||
var visitor func(*filetree.FileNode) error
|
var visitor func(*filetree.FileNode) error
|
||||||
var evaluator func(*filetree.FileNode) bool
|
var evaluator func(*filetree.FileNode) bool
|
||||||
var dfsCounter int
|
var dfsCounter int
|
||||||
@ -327,7 +327,7 @@ func (vm *FileTree) getAbsPositionNode(filterRegex *regexp.Regexp) (node *filetr
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ToggleCollapse will collapse/expand the selected FileNode.
|
// ToggleCollapse will collapse/expand the selected FileNode.
|
||||||
func (vm *FileTree) ToggleCollapse(filterRegex *regexp.Regexp) error {
|
func (vm *FileTreeViewModel) ToggleCollapse(filterRegex *regexp.Regexp) error {
|
||||||
node := vm.getAbsPositionNode(filterRegex)
|
node := vm.getAbsPositionNode(filterRegex)
|
||||||
if node != nil && node.Data.FileInfo.IsDir {
|
if node != nil && node.Data.FileInfo.IsDir {
|
||||||
node.Data.ViewInfo.Collapsed = !node.Data.ViewInfo.Collapsed
|
node.Data.ViewInfo.Collapsed = !node.Data.ViewInfo.Collapsed
|
||||||
@ -336,7 +336,7 @@ func (vm *FileTree) ToggleCollapse(filterRegex *regexp.Regexp) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ToggleCollapseAll will collapse/expand the all directories.
|
// ToggleCollapseAll will collapse/expand the all directories.
|
||||||
func (vm *FileTree) ToggleCollapseAll() error {
|
func (vm *FileTreeViewModel) ToggleCollapseAll() error {
|
||||||
vm.CollapseAll = !vm.CollapseAll
|
vm.CollapseAll = !vm.CollapseAll
|
||||||
|
|
||||||
visitor := func(curNode *filetree.FileNode) error {
|
visitor := func(curNode *filetree.FileNode) error {
|
||||||
@ -356,7 +356,14 @@ func (vm *FileTree) ToggleCollapseAll() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *FileTree) ConstrainLayout() {
|
// ToggleSortOrder will toggle the sort order in which files are displayed
|
||||||
|
func (vm *FileTreeViewModel) ToggleSortOrder() error {
|
||||||
|
vm.ModelTree.SortOrder = (vm.ModelTree.SortOrder + 1) % filetree.NumSortOrderConventions
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vm *FileTreeViewModel) ConstrainLayout() {
|
||||||
if !vm.constrainedRealEstate {
|
if !vm.constrainedRealEstate {
|
||||||
logrus.Debugf("constraining filetree layout")
|
logrus.Debugf("constraining filetree layout")
|
||||||
vm.constrainedRealEstate = true
|
vm.constrainedRealEstate = true
|
||||||
@ -365,7 +372,7 @@ func (vm *FileTree) ConstrainLayout() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *FileTree) ExpandLayout() {
|
func (vm *FileTreeViewModel) ExpandLayout() {
|
||||||
if vm.constrainedRealEstate {
|
if vm.constrainedRealEstate {
|
||||||
logrus.Debugf("expanding filetree layout")
|
logrus.Debugf("expanding filetree layout")
|
||||||
vm.ShowAttributes = vm.unconstrainedShowAttributes
|
vm.ShowAttributes = vm.unconstrainedShowAttributes
|
||||||
@ -374,7 +381,7 @@ func (vm *FileTree) ExpandLayout() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ToggleCollapse will collapse/expand the selected FileNode.
|
// ToggleCollapse will collapse/expand the selected FileNode.
|
||||||
func (vm *FileTree) ToggleAttributes() error {
|
func (vm *FileTreeViewModel) ToggleAttributes() error {
|
||||||
// ignore any attempt to show the attributes when the layout is constrained
|
// ignore any attempt to show the attributes when the layout is constrained
|
||||||
if vm.constrainedRealEstate {
|
if vm.constrainedRealEstate {
|
||||||
return nil
|
return nil
|
||||||
@ -384,12 +391,12 @@ func (vm *FileTree) ToggleAttributes() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ToggleShowDiffType will show/hide the selected DiffType in the filetree pane.
|
// ToggleShowDiffType will show/hide the selected DiffType in the filetree pane.
|
||||||
func (vm *FileTree) ToggleShowDiffType(diffType filetree.DiffType) {
|
func (vm *FileTreeViewModel) ToggleShowDiffType(diffType filetree.DiffType) {
|
||||||
vm.HiddenDiffTypes[diffType] = !vm.HiddenDiffTypes[diffType]
|
vm.HiddenDiffTypes[diffType] = !vm.HiddenDiffTypes[diffType]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update refreshes the state objects for future rendering.
|
// Update refreshes the state objects for future rendering.
|
||||||
func (vm *FileTree) Update(filterRegex *regexp.Regexp, width, height int) error {
|
func (vm *FileTreeViewModel) Update(filterRegex *regexp.Regexp, width, height int) error {
|
||||||
vm.refWidth = width
|
vm.refWidth = width
|
||||||
vm.refHeight = height
|
vm.refHeight = height
|
||||||
|
|
||||||
@ -437,7 +444,7 @@ func (vm *FileTree) Update(filterRegex *regexp.Regexp, width, height int) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Render flushes the state objects (file tree) to the pane.
|
// Render flushes the state objects (file tree) to the pane.
|
||||||
func (vm *FileTree) Render() error {
|
func (vm *FileTreeViewModel) Render() error {
|
||||||
treeString := vm.ViewTree.StringBetween(vm.bufferIndexLowerBound, vm.bufferIndexUpperBound(), vm.ShowAttributes)
|
treeString := vm.ViewTree.StringBetween(vm.bufferIndexLowerBound, vm.bufferIndexUpperBound(), vm.ShowAttributes)
|
||||||
lines := strings.Split(treeString, "\n")
|
lines := strings.Split(treeString, "\n")
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ func assertTestData(t *testing.T, actualBytes []byte) {
|
|||||||
helperCheckDiff(t, expectedBytes, actualBytes)
|
helperCheckDiff(t, expectedBytes, actualBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initializeTestViewModel(t *testing.T) *FileTree {
|
func initializeTestViewModel(t *testing.T) *FileTreeViewModel {
|
||||||
result := docker.TestAnalysisFromArchive(t, "../../../.data/test-docker-image.tar")
|
result := docker.TestAnalysisFromArchive(t, "../../../.data/test-docker-image.tar")
|
||||||
|
|
||||||
cache := filetree.NewComparer(result.RefTrees)
|
cache := filetree.NewComparer(result.RefTrees)
|
||||||
@ -98,7 +98,7 @@ func initializeTestViewModel(t *testing.T) *FileTree {
|
|||||||
return vm
|
return vm
|
||||||
}
|
}
|
||||||
|
|
||||||
func runTestCase(t *testing.T, vm *FileTree, width, height int, filterRegex *regexp.Regexp) {
|
func runTestCase(t *testing.T, vm *FileTreeViewModel, width, height int, filterRegex *regexp.Regexp) {
|
||||||
err := vm.Update(filterRegex, width, height)
|
err := vm.Update(filterRegex, width, height)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to update viewmodel: %v", err)
|
t.Errorf("failed to update viewmodel: %v", err)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user