dive/runtime/ui/viewmodels/tree_view_model.go
2021-02-15 13:13:08 -05:00

203 lines
5.5 KiB
Go

package viewmodels
import (
"fmt"
"regexp"
"github.com/wagoodman/dive/dive/filetree"
"github.com/wagoodman/dive/dive/image"
"github.com/wagoodman/dive/internal/log"
)
//go:generate faux --interface FilterModel --output fakes/fake_filter_model.go
type FilterModel interface {
SetFilter(r *regexp.Regexp)
GetFilter() *regexp.Regexp
}
//go:generate faux --interface LayersModel --output fakes/fake_layers_model.go
type LayersModel interface {
SetLayerIndex(index int) bool
GetCompareIndicies() filetree.TreeIndexKey
GetCurrentLayer() *image.Layer
GetPrintableLayers() []fmt.Stringer
GetMode() LayerCompareMode
SwitchLayerMode() error
}
//go:generate faux --interface TreeCache --output fakes/fake_tree_cache.go
type TreeCache interface {
GetTree(key filetree.TreeIndexKey) (TreeModel, error)
}
//go:generate faux --interface TreeModel --output fakes/fake_tree_model.go
type TreeModel interface {
StringBetween(start, stop int, showAttributes bool) string
VisitDepthParentFirst(visitor filetree.Visitor, evaluator filetree.VisitEvaluator) error
VisitDepthChildFirst(visitor filetree.Visitor, evaluator filetree.VisitEvaluator) error
RemovePath(path string) error
VisibleSize() int
}
type TreeViewModel struct {
currentTree TreeModel
cache TreeCache
hiddenDiffTypes []bool
// Make this an interface that is composed with the FilterView
FilterModel
LayersModel
}
func NewTreeViewModel(cache TreeCache, lModel LayersModel, fModel FilterModel) (*TreeViewModel, error) {
curTreeIndex := filetree.NewTreeIndexKey(0, 0, 0, 0)
tree, err := cache.GetTree(curTreeIndex)
if err != nil {
return nil, err
}
return &TreeViewModel{
currentTree: tree,
cache: cache,
hiddenDiffTypes: make([]bool, 4),
FilterModel: fModel,
LayersModel: lModel,
}, nil
}
func (tvm *TreeViewModel) StringBetween(startRow, stopRow int, showAttributes bool) string {
return tvm.currentTree.StringBetween(startRow, stopRow, showAttributes)
}
func (tvm *TreeViewModel) VisitDepthParentFirst(visitor filetree.Visitor, evaluator filetree.VisitEvaluator) error {
return tvm.currentTree.VisitDepthParentFirst(visitor, evaluator)
}
func (tvm *TreeViewModel) VisitDepthChildFirst(visitor filetree.Visitor, evaluator filetree.VisitEvaluator) error {
return tvm.currentTree.VisitDepthChildFirst(visitor, evaluator)
}
func (tvm *TreeViewModel) RemovePath(path string) error {
return tvm.currentTree.RemovePath(path)
}
func (tvm *TreeViewModel) VisibleSize() int {
return tvm.currentTree.VisibleSize()
}
func (tvm *TreeViewModel) SetFilter(filterRegex *regexp.Regexp) {
tvm.FilterModel.SetFilter(filterRegex)
if err := tvm.filterUpdate(); err != nil {
// TODO -Dan- handle panics
panic(err)
}
}
// TODO: this seems like a very expensive operration, look for ways to optimize.
// TODO make type int a strongly typed argument
// TODO: handle errors correctly
func (tvm *TreeViewModel) ToggleHiddenFileType(filetype filetree.DiffType) bool {
tvm.hiddenDiffTypes[filetype] = !tvm.hiddenDiffTypes[filetype]
if err := tvm.filterUpdate(); err != nil {
//panic(err)
return false
}
return true
}
func (tvm *TreeViewModel) GetHiddenFileType(filetype filetree.DiffType) bool {
return tvm.hiddenDiffTypes[filetype]
}
func (tvm *TreeViewModel) filterUpdate() error {
log.WithFields(
"model", "treeView",
).Trace("updating filter")
// keep the t selection in parity with the current DiffType selection
filter := tvm.GetFilter()
err := tvm.currentTree.VisitDepthChildFirst(func(node *filetree.FileNode) error {
node.Data.ViewInfo.Hidden = tvm.hiddenDiffTypes[node.Data.DiffType]
for _, child := range node.Children {
if !child.Data.ViewInfo.Hidden {
node.Data.ViewInfo.Hidden = false
return nil
}
}
if len(node.Children) > 0 {
node.Data.ViewInfo.Hidden = true
}
if filter != nil && !node.Data.ViewInfo.Hidden { // hide nodes that do not match the current file filter regex (also don't unhide nodes that are already hidden)
match := filter.FindString(node.Path())
node.Data.ViewInfo.Hidden = len(match) == 0
}
return nil
}, nil)
if err != nil {
log.Errorf("error updating filter on current tree: %s", err)
return err
}
return nil
}
// Override functions
func (tvm *TreeViewModel) SetLayerIndex(index int) bool {
var err error
if tvm.LayersModel.SetLayerIndex(index) {
err = tvm.setCurrentTree(tvm.GetCompareIndicies())
}
return err == nil
}
func (tvm *TreeViewModel) setCurrentTree(key filetree.TreeIndexKey) error {
collapsedList := map[string]interface{}{}
newTree, err := tvm.cache.GetTree(key)
if err != nil {
return err
}
evaluateFunc := func(node *filetree.FileNode) bool {
if node.Parent != nil && node.Parent.Data.ViewInfo.Hidden {
return false
}
return true
}
if err = tvm.currentTree.VisitDepthParentFirst(func(node *filetree.FileNode) error {
if node.Data.ViewInfo.Collapsed {
collapsedList[node.Path()] = true
}
return nil
}, evaluateFunc); err != nil {
return err
}
if err = newTree.VisitDepthParentFirst(func(node *filetree.FileNode) error {
_, ok := collapsedList[node.Path()]
if ok {
node.Data.ViewInfo.Collapsed = true
}
return nil
}, evaluateFunc); err != nil {
return err
}
tvm.currentTree = newTree
if err := tvm.filterUpdate(); err != nil {
return err
}
return nil
}
func (tvm *TreeViewModel) SwitchLayerMode() error {
if err := tvm.LayersModel.SwitchLayerMode(); err != nil {
return err
}
// TODO: Handle this error
if err := tvm.setCurrentTree(tvm.GetCompareIndicies()); err != nil {
return err
}
return nil
}