diff --git a/filetree/node.go b/filetree/node.go index 898fd9e..7acd9d4 100644 --- a/filetree/node.go +++ b/filetree/node.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/fatih/color" + "fmt" ) type FileNode struct { @@ -30,10 +31,12 @@ func NewNode(parent *FileNode, name string, data *FileInfo) (node *FileNode) { return node } -func (node *FileNode) Copy() *FileNode { - newNode := NewNode(node.Parent, node.Name, node.Data.FileInfo) +func (node *FileNode) Copy(parent *FileNode) *FileNode { + newNode := NewNode(parent, node.Name, node.Data.FileInfo) + newNode.Data.ViewInfo = node.Data.ViewInfo + newNode.Data.DiffType = node.Data.DiffType for name, child := range node.Children { - newNode.Children[name] = child.Copy() + newNode.Children[name] = child.Copy(newNode) child.Parent = newNode } return newNode @@ -52,6 +55,9 @@ func (node *FileNode) AddChild(name string, data *FileInfo) (child *FileNode) { } func (node *FileNode) Remove() error { + if node == node.Tree.Root { + return fmt.Errorf("cannot remove the tree root") + } for _, child := range node.Children { child.Remove() } @@ -87,24 +93,37 @@ func (node *FileNode) VisitDepthChildFirst(visiter Visiter, evaluator VisitEvalu } sort.Strings(keys) for _, name := range keys { - if evaluator != nil { - if !evaluator(node) { - continue - } - } child := node.Children[name] err := child.VisitDepthChildFirst(visiter, evaluator) if err != nil { return err } } - return visiter(node) + // never visit the root node + if node == node.Tree.Root { + return nil + } else if evaluator != nil && evaluator(node) || evaluator == nil { + return visiter(node) + } + + return nil } func (node *FileNode) VisitDepthParentFirst(visiter Visiter, evaluator VisitEvaluator) error { - err := visiter(node) - if err != nil { - return err + var err error + + doVisit := evaluator != nil && evaluator(node) || evaluator == nil + + if !doVisit { + return nil + } + + // never visit the root node + if node != node.Tree.Root{ + err = visiter(node) + if err != nil { + return err + } } var keys []string @@ -113,11 +132,6 @@ func (node *FileNode) VisitDepthParentFirst(visiter Visiter, evaluator VisitEval } sort.Strings(keys) for _, name := range keys { - if evaluator != nil { - if !evaluator(node) { - continue - } - } child := node.Children[name] err = child.VisitDepthParentFirst(visiter, evaluator) if err != nil { diff --git a/filetree/tree.go b/filetree/tree.go index 47b6a6b..e1b4efe 100644 --- a/filetree/tree.go +++ b/filetree/tree.go @@ -1,7 +1,6 @@ package filetree import ( - "errors" "fmt" "sort" "strings" @@ -88,8 +87,10 @@ func (tree *FileTree) String() string { func (tree *FileTree) Copy() *FileTree { newTree := NewFileTree() - *newTree = *tree - newTree.Root = tree.Root.Copy() + newTree.Size = tree.Size + newTree.Root = tree.Root.Copy(newTree.Root) + + // update the tree pointers newTree.VisitDepthChildFirst(func(node *FileNode) error { node.Tree = newTree return nil @@ -116,12 +117,12 @@ func (tree *FileTree) Stack(upper *FileTree) error { if node.IsWhiteout() { err := tree.RemovePath(node.Path()) if err != nil { - return fmt.Errorf("Cannot remove node %s: %v", node.Path(), err.Error()) + return fmt.Errorf("cannot remove node %s: %v", node.Path(), err.Error()) } } else { newNode, err := tree.AddPath(node.Path(), node.Data.FileInfo) if err != nil { - return fmt.Errorf("Cannot add node %s: %v", newNode.Path(), err.Error()) + return fmt.Errorf("cannot add node %s: %v", newNode.Path(), err.Error()) } } return nil @@ -137,7 +138,7 @@ func (tree *FileTree) GetNode(path string) (*FileNode, error) { continue } if node.Children[name] == nil { - return nil, errors.New("Path does not exist") + return nil, fmt.Errorf("path does not exist: %s", path) } node = node.Children[name] } @@ -183,7 +184,7 @@ func (tree *FileTree) Compare(upper *FileTree) error { if upperNode.IsWhiteout() { err := tree.MarkRemoved(upperNode.Path()) if err != nil { - return fmt.Errorf("Cannot remove upperNode %s: %v", upperNode.Path(), err.Error()) + return fmt.Errorf("cannot remove upperNode %s: %v", upperNode.Path(), err.Error()) } } else { lowerNode, _ := tree.GetNode(upperNode.Path()) @@ -191,7 +192,7 @@ func (tree *FileTree) Compare(upper *FileTree) error { newNode, err := tree.AddPath(upperNode.Path(), upperNode.Data.FileInfo) // fmt.Printf("added new upperNode at %s\n", newNode.Path()) if err != nil { - return fmt.Errorf("Cannot add new upperNode %s: %v", upperNode.Path(), err.Error()) + return fmt.Errorf("cannot add new upperNode %s: %v", upperNode.Path(), err.Error()) } newNode.AssignDiffType(Added) } else { diff --git a/filetree/tree_test.go b/filetree/tree_test.go index 1010124..283bd39 100644 --- a/filetree/tree_test.go +++ b/filetree/tree_test.go @@ -432,3 +432,41 @@ func TestStackRange(t *testing.T) { trees := []*FileTree{lowerTree, upperTree, tree} StackRange(trees, 2) } + + +func TestRemoveOnIterate(t *testing.T) { + + tree := NewFileTree() + paths := [...]string{"/etc", "/usr", "/etc/hosts", "/etc/sudoers", "/usr/bin", "/usr/something"} + + for _, value := range paths { + fakeData := FileInfo{ + Path: value, + Typeflag: 1, + MD5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + } + node, err := tree.AddPath(value, &fakeData) + if err == nil && stringInSlice(node.Path(), []string{"/etc"}) { + node.Data.ViewInfo.Hidden = true + } + } + + tree.VisitDepthChildFirst(func(node *FileNode) error { + if node.Data.ViewInfo.Hidden { + tree.RemovePath(node.Path()) + } + return nil + }, nil) + + expected := `. +└── usr + ├── bin + └── something +` + actual := tree.String() + if expected != actual { + t.Errorf("Expected tree string:\n--->%s<---\nGot:\n--->%s<---", expected, actual) + } + +} + diff --git a/ui/filetreeview.go b/ui/filetreeview.go index b72b99d..ce2fa98 100644 --- a/ui/filetreeview.go +++ b/ui/filetreeview.go @@ -13,7 +13,8 @@ type FileTreeView struct { gui *gocui.Gui view *gocui.View TreeIndex int - Tree *filetree.FileTree + ModelTree *filetree.FileTree + ViewTree *filetree.FileTree RefTrees []*filetree.FileTree HiddenDiffTypes []bool } @@ -24,7 +25,7 @@ func NewFileTreeView(name string, gui *gocui.Gui, tree *filetree.FileTree, refTr // populate main fields treeview.Name = name treeview.gui = gui - treeview.Tree = tree + treeview.ModelTree = tree treeview.RefTrees = refTrees treeview.HiddenDiffTypes = make([]bool, 4) @@ -65,6 +66,7 @@ func (view *FileTreeView) Setup(v *gocui.View) error { return err } + view.updateViewTree() view.Render() return nil @@ -81,14 +83,11 @@ func (view *FileTreeView) setLayer(layerIndex int) error { visitor := func(node *filetree.FileNode) error { newNode, err := newTree.GetNode(node.Path()) if err == nil { - newNode.Data.ViewInfo.Collapsed = node.Data.ViewInfo.Collapsed + newNode.Data.ViewInfo = node.Data.ViewInfo } return nil } - view.Tree.VisitDepthChildFirst(visitor, nil) - - // now that the tree has been rebuilt, keep the view seleciton in parity with the previous selection - view.setHiddenFromDiffTypes() + view.ModelTree.VisitDepthChildFirst(visitor, nil) if debug { v, _ := view.gui.View("debug") @@ -96,9 +95,11 @@ func (view *FileTreeView) setLayer(layerIndex int) error { _, _ = fmt.Fprintln(v, view.RefTrees[layerIndex]) } + view.view.SetCursor(0, 0) view.TreeIndex = 0 - view.Tree = newTree + view.ModelTree = newTree + view.updateViewTree() return view.Render() } @@ -115,6 +116,8 @@ func (view *FileTreeView) CursorUp() error { if err == nil { view.TreeIndex-- } + // tmp tmp tmp + view.getAbsPositionNode() return nil } @@ -123,21 +126,26 @@ func (view *FileTreeView) getAbsPositionNode() (node *filetree.FileNode) { var evaluator func(*filetree.FileNode) bool var dfsCounter int + // special case: the root node is never visited + if view.TreeIndex == 0 { + return view.ModelTree.Root + } + visiter = func(curNode *filetree.FileNode) error { + dfsCounter++ if dfsCounter == view.TreeIndex { node = curNode } - dfsCounter++ return nil } evaluator = func(curNode *filetree.FileNode) bool { - return !curNode.Data.ViewInfo.Collapsed && !curNode.Data.ViewInfo.Hidden + return !curNode.Parent.Data.ViewInfo.Collapsed && !curNode.Data.ViewInfo.Hidden } - err := view.Tree.VisitDepthParentFirst(visiter, evaluator) + err := view.ModelTree.VisitDepthParentFirst(visiter, evaluator) if err != nil { - // todo: you guessed it, check errors + panic(err) } return node @@ -146,25 +154,39 @@ func (view *FileTreeView) getAbsPositionNode() (node *filetree.FileNode) { func (view *FileTreeView) toggleCollapse() error { node := view.getAbsPositionNode() node.Data.ViewInfo.Collapsed = !node.Data.ViewInfo.Collapsed - return view.Render() -} - -func (view *FileTreeView) setHiddenFromDiffTypes() error { - visitor := func(node *filetree.FileNode) error { - node.Data.ViewInfo.Hidden = view.HiddenDiffTypes[node.Data.DiffType] - return nil - } - view.Tree.VisitDepthChildFirst(visitor, nil) + view.updateViewTree() return view.Render() } func (view *FileTreeView) toggleShowDiffType(diffType filetree.DiffType) error { view.HiddenDiffTypes[diffType] = !view.HiddenDiffTypes[diffType] - return view.setHiddenFromDiffTypes() + + view.view.SetCursor(0, 0) + view.TreeIndex = 0 + view.updateViewTree() + return view.Render() +} + +func (view *FileTreeView) updateViewTree() { + // keep the view selection in parity with the current DiffType selection + view.ModelTree.VisitDepthChildFirst(func(node *filetree.FileNode) error { + node.Data.ViewInfo.Hidden = view.HiddenDiffTypes[node.Data.DiffType] + return nil + }, nil) + + // make a new tree with only visible nodes + view.ViewTree = view.ModelTree.Copy() + view.ViewTree.VisitDepthParentFirst(func(node *filetree.FileNode) error { + if node.Data.ViewInfo.Hidden { + view.ViewTree.RemovePath(node.Path()) + } + return nil + }, nil) } func (view *FileTreeView) Render() error { - renderString := view.Tree.String() + // print the tree to the view + renderString := view.ViewTree.String() view.gui.Update(func(g *gocui.Gui) error { view.view.Clear() _, err := fmt.Fprintln(view.view, renderString)