summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Goodman <wagoodman@gmail.com>2019-10-11 12:11:07 -0400
committerAlex Goodman <wagoodman@gmail.com>2019-10-11 12:11:07 -0400
commit45604e5c66ac8b2fc0b8f6d4706c3f38a20ad68a (patch)
tree33372d63f34ea919f99a4b86f9e878ad28fb3c29
parentc8ab7098d84bb647d48d2f8b20aa053e2870eeba (diff)
add keybinding package
-rw-r--r--runtime/run.go7
-rw-r--r--runtime/ui/controller.go20
-rw-r--r--runtime/ui/controller_collection.go52
-rw-r--r--runtime/ui/details_controller.go72
-rw-r--r--runtime/ui/filetree_controller.go275
-rw-r--r--runtime/ui/filetree_viewmodel.go51
-rw-r--r--runtime/ui/filetree_viewmodel_test.go9
-rw-r--r--runtime/ui/filter_controller.go41
-rw-r--r--runtime/ui/format/format.go35
-rw-r--r--runtime/ui/key/binding.go117
-rw-r--r--runtime/ui/layer_controller.go169
-rw-r--r--runtime/ui/layout_manager.go11
-rw-r--r--runtime/ui/status_controller.go53
-rw-r--r--runtime/ui/ui.go277
14 files changed, 693 insertions, 496 deletions
diff --git a/runtime/run.go b/runtime/run.go
index 0dbcfa8..3da39ce 100644
--- a/runtime/run.go
+++ b/runtime/run.go
@@ -103,7 +103,7 @@ func run(enableUi bool, options Options, imageResolver image.Resolver, events ev
err = ui.Run(analysis, cache)
if err != nil {
- events.exitWithErrorMessage("runtime error", err)
+ events.exitWithError(err)
return
}
}
@@ -131,7 +131,6 @@ func Run(options Options) {
}
if event.stderr != "" {
- logrus.Error(event.stderr)
_, err := fmt.Fprintln(os.Stderr, event.stderr)
if err != nil {
fmt.Println("error: could not write to buffer:", err)
@@ -140,6 +139,10 @@ func Run(options Options) {
if event.err != nil {
logrus.Error(event.err)
+ _, err := fmt.Fprintln(os.Stderr, event.err.Error())
+ if err != nil {
+ fmt.Println("error: could not write to buffer:", err)
+ }
}
if event.errorOnExit {
diff --git a/runtime/ui/controller.go b/runtime/ui/controller.go
new file mode 100644
index 0000000..fb1e500
--- /dev/null
+++ b/runtime/ui/controller.go
@@ -0,0 +1,20 @@
+package ui
+
+import (
+ "github.com/jroimartin/gocui"
+)
+
+type Renderable interface {
+ Update() error
+ Render() error
+}
+
+// Controller defines the a renderable terminal screen pane.
+type Controller interface {
+ Renderable
+ Setup(*gocui.View, *gocui.View) error
+ CursorDown() error
+ CursorUp() error
+ KeyHelp() string
+ IsVisible() bool
+}
diff --git a/runtime/ui/controller_collection.go b/runtime/ui/controller_collection.go
new file mode 100644
index 0000000..47be653
--- /dev/null
+++ b/runtime/ui/controller_collection.go
@@ -0,0 +1,52 @@
+package ui
+
+import (
+ "github.com/jroimartin/gocui"
+ "github.com/wagoodman/dive/dive/filetree"
+ "github.com/wagoodman/dive/dive/image"
+)
+
+// var ccOnce sync.Once
+var controllers *controllerCollection
+
+type controllerCollection struct {
+ Tree *fileTreeController
+ Layer *layerController
+ Status *statusController
+ Filter *filterController
+ Details *detailsController
+ lookup map[string]Controller
+}
+
+func newControllerCollection(g *gocui.Gui, analysis *image.AnalysisResult, cache filetree.TreeCache) (*controllerCollection, error) {
+ var err error
+
+ controllers = &controllerCollection{}
+ controllers.lookup = make(map[string]Controller)
+
+ controllers.Layer, err = newLayerController("layers", g, analysis.Layers)
+ if err != nil {
+ return nil, err
+ }
+ controllers.lookup[controllers.Layer.name] = controllers.Layer
+
+ treeStack, err := filetree.StackTreeRange(analysis.RefTrees, 0, 0)
+ if err != nil {
+ return nil, err
+ }
+ controllers.Tree, err = newFileTreeController("filetree", g, treeStack, analysis.RefTrees, cache)
+ if err != nil {
+ return nil, err
+ }
+ controllers.lookup[controllers.Tree.name] = controllers.Tree
+
+ controllers.Status = newStatusController("status", g)
+ controllers.lookup[controllers.Status.name] = controllers.Status
+
+ controllers.Filter = newFilterController("filter", g)
+ controllers.lookup[controllers.Filter.name] = controllers.Filter
+
+ controllers.Details = newDetailsController("details", g, analysis.Efficiency, analysis.Inefficiencies)
+ controllers.lookup[controllers.Details.name] = controllers.Details
+ return controllers, nil
+}
diff --git a/runtime/ui/details_controller.go b/runtime/ui/details_controller.go
index 0b9de63..ed01836 100644
--- a/runtime/ui/details_controller.go
+++ b/runtime/ui/details_controller.go
@@ -4,6 +4,8 @@ import (
"fmt"
"github.com/sirupsen/logrus"
"github.com/wagoodman/dive/dive/filetree"
+ "github.com/wagoodman/dive/runtime/ui/format"
+ "github.com/wagoodman/dive/runtime/ui/key"
"strconv"
"strings"
@@ -12,10 +14,10 @@ import (
"github.com/lunixbochs/vtclean"
)
-// DetailsController holds the UI objects and data models for populating the lower-left pane. Specifically the pane that
+// detailsController holds the UI objects and data models for populating the lower-left pane. Specifically the pane that
// shows the layer details and image statistics.
-type DetailsController struct {
- Name string
+type detailsController struct {
+ name string
gui *gocui.Gui
view *gocui.View
header *gocui.View
@@ -23,12 +25,12 @@ type DetailsController struct {
inefficiencies filetree.EfficiencySlice
}
-// NewDetailsController creates a new view object attached the the global [gocui] screen object.
-func NewDetailsController(name string, gui *gocui.Gui, efficiency float64, inefficiencies filetree.EfficiencySlice) (controller *DetailsController) {
- controller = new(DetailsController)
+// newDetailsController creates a new view object attached the the global [gocui] screen object.
+func newDetailsController(name string, gui *gocui.Gui, efficiency float64, inefficiencies filetree.EfficiencySlice) (controller *detailsController) {
+ controller = new(detailsController)
// populate main fields
- controller.Name = name
+ controller.name = name
controller.gui = gui
controller.efficiency = efficiency
controller.inefficiencies = inefficiencies
@@ -37,7 +39,7 @@ func NewDetailsController(name string, gui *gocui.Gui, efficiency float64, ineff
}
// Setup initializes the UI concerns within the context of a global [gocui] view object.
-func (controller *DetailsController) Setup(v *gocui.View, header *gocui.View) error {
+func (controller *detailsController) Setup(v *gocui.View, header *gocui.View) error {
// set controller options
controller.view = v
@@ -51,11 +53,21 @@ func (controller *DetailsController) Setup(v *gocui.View, header *gocui.View) er
controller.header.Wrap = false
controller.header.Frame = false
- // set keybindings
- if err := controller.gui.SetKeybinding(controller.Name, gocui.KeyArrowDown, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return controller.CursorDown() }); err != nil {
- return err
+ var infos = []key.BindingInfo{
+ {
+ Key: gocui.KeyArrowDown,
+ Modifier: gocui.ModNone,
+ OnAction: controller.CursorDown,
+ },
+ {
+ Key: gocui.KeyArrowUp,
+ Modifier: gocui.ModNone,
+ OnAction: controller.CursorUp,
+ },
}
- if err := controller.gui.SetKeybinding(controller.Name, gocui.KeyArrowUp, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return controller.CursorUp() }); err != nil {
+
+ _, err := key.GenerateBindings(controller.gui, controller.name, infos)
+ if err != nil {
return err
}
@@ -63,22 +75,22 @@ func (controller *DetailsController) Setup(v *gocui.View, header *gocui.View) er
}
// IsVisible indicates if the details view pane is currently initialized.
-func (controller *DetailsController) IsVisible() bool {
+func (controller *detailsController) IsVisible() bool {
return controller != nil
}
// CursorDown moves the cursor down in the details pane (currently indicates nothing).
-func (controller *DetailsController) CursorDown() error {
+func (controller *detailsController) CursorDown() error {
return CursorDown(controller.gui, controller.view)
}
// CursorUp moves the cursor up in the details pane (currently indicates nothing).
-func (controller *DetailsController) CursorUp() error {
+func (controller *detailsController) CursorUp() error {
return CursorUp(controller.gui, controller.view)
}
// Update refreshes the state objects for future rendering.
-func (controller *DetailsController) Update() error {
+func (controller *detailsController) Update() error {
return nil
}
@@ -87,13 +99,13 @@ func (controller *DetailsController) Update() error {
// 2. the image efficiency score
// 3. the estimated wasted image space
// 4. a list of inefficient file allocations
-func (controller *DetailsController) Render() error {
- currentLayer := Controllers.Layer.currentLayer()
+func (controller *detailsController) Render() error {
+ currentLayer := controllers.Layer.currentLayer()
var wastedSpace int64
template := "%5s %12s %-s\n"
- inefficiencyReport := fmt.Sprintf(Formatting.Header(template), "Count", "Total Space", "Path")
+ inefficiencyReport := fmt.Sprintf(format.Header(template), "Count", "Total Space", "Path")
height := 100
if controller.view != nil {
@@ -110,9 +122,9 @@ func (controller *DetailsController) Render() error {
}
}
- imageSizeStr := fmt.Sprintf("%s %s", Formatting.Header("Total Image size:"), humanize.Bytes(Controllers.Layer.ImageSize))
- effStr := fmt.Sprintf("%s %d %%", Formatting.Header("Image efficiency score:"), int(100.0*controller.efficiency))
- wastedSpaceStr := fmt.Sprintf("%s %s", Formatting.Header("Potential wasted space:"), humanize.Bytes(uint64(wastedSpace)))
+ imageSizeStr := fmt.Sprintf("%s %s", format.Header("Total Image size:"), humanize.Bytes(controllers.Layer.ImageSize))
+ effStr := fmt.Sprintf("%s %d %%", format.Header("Image efficiency score:"), int(100.0*controller.efficiency))
+ wastedSpaceStr := fmt.Sprintf("%s %s", format.Header("Potential wasted space:"), humanize.Bytes(uint64(wastedSpace)))
controller.gui.Update(func(g *gocui.Gui) error {
// update header
@@ -122,7 +134,7 @@ func (controller *DetailsController) Render() error {
layerHeaderStr := fmt.Sprintf("[Layer Details]%s", strings.Repeat("─", width-15))
imageHeaderStr := fmt.Sprintf("[Image Details]%s", strings.Repeat("─", width-15))
- _, err := fmt.Fprintln(controller.header, Formatting.Header(vtclean.Clean(layerHeaderStr, false)))
+ _, err := fmt.Fprintln(controller.header, format.Header(vtclean.Clean(layerHeaderStr, false)))
if err != nil {
return err
}
@@ -132,15 +144,15 @@ func (controller *DetailsController) Render() error {
var lines = make([]string, 0)
if currentLayer.Names != nil && len(currentLayer.Names) > 0 {
- lines = append(lines, Formatting.Header("Tags: ")+strings.Join(currentLayer.Names, ", "))
+ lines = append(lines, format.Header("Tags: ")+strings.Join(currentLayer.Names, ", "))
} else {
- lines = append(lines, Formatting.Header("Tags: ")+"(none)")
+ lines = append(lines, format.Header("Tags: ")+"(none)")
}
- lines = append(lines, Formatting.Header("Id: ")+currentLayer.Id)
- lines = append(lines, Formatting.Header("Digest: ")+currentLayer.Digest)
- lines = append(lines, Formatting.Header("Command:"))
+ lines = append(lines, format.Header("Id: ")+currentLayer.Id)
+ lines = append(lines, format.Header("Digest: ")+currentLayer.Digest)
+ lines = append(lines, format.Header("Command:"))
lines = append(lines, currentLayer.Command)
- lines = append(lines, "\n"+Formatting.Header(vtclean.Clean(imageHeaderStr, false)))
+ lines = append(lines, "\n"+format.Header(vtclean.Clean(imageHeaderStr, false)))
lines = append(lines, imageSizeStr)
lines = append(lines, wastedSpaceStr)
lines = append(lines, effStr+"\n")
@@ -156,6 +168,6 @@ func (controller *DetailsController) Render() error {
}
// KeyHelp indicates all the possible actions a user can take while the current pane is selected (currently does nothing).
-func (controller *DetailsController) KeyHelp() string {
+func (controller *detailsController) KeyHelp() string {
return "TBD"
}
diff --git a/runtime/ui/filetree_controller.go b/runtime/ui/filetree_controller.go
index 16a72b2..250289a 100644
--- a/runtime/ui/filetree_controller.go
+++ b/runtime/ui/filetree_controller.go
@@ -2,15 +2,13 @@ package ui
import (
"fmt"
+ "github.com/wagoodman/dive/runtime/ui/format"
+ "github.com/wagoodman/dive/runtime/ui/key"
"regexp"
"strings"
- "github.com/lunixbochs/vtclean"
- "github.com/sirupsen/logrus"
- "github.com/spf13/viper"
- "github.com/wagoodman/keybinding"
-
"github.com/jroimartin/gocui"
+ "github.com/lunixbochs/vtclean"
"github.com/wagoodman/dive/dive/filetree"
)
@@ -21,92 +19,36 @@ const (
type CompareType int
-// FileTreeController holds the UI objects and data models for populating the right pane. Specifically the pane that
+// fileTreeController holds the UI objects and data models for populating the right pane. Specifically the pane that
// shows selected layer or aggregate file ASCII tree.
-type FileTreeController struct {
- Name string
+type fileTreeController struct {
+ name string
gui *gocui.Gui
view *gocui.View
header *gocui.View
- vm *FileTreeViewModel
-
- keybindingToggleCollapse []keybinding.Key
- keybindingToggleCollapseAll []keybinding.Key
- keybindingToggleAttributes []keybinding.Key
- keybindingToggleAdded []keybinding.Key
- keybindingToggleRemoved []keybinding.Key
- keybindingToggleModified []keybinding.Key
- keybindingToggleUnmodified []keybinding.Key
- keybindingPageDown []keybinding.Key
- keybindingPageUp []keybinding.Key
+ vm *fileTreeViewModel
+
+ helpKeys []*key.Binding
}
-// NewFileTreeController creates a new view object attached the the global [gocui] screen object.
-func NewFileTreeController(name string, gui *gocui.Gui, tree *filetree.FileTree, refTrees []*filetree.FileTree, cache filetree.TreeCache) (controller *FileTreeController, err error) {
- controller = new(FileTreeController)
+// newFileTreeController creates a new view object attached the the global [gocui] screen object.
+func newFileTreeController(name string, gui *gocui.Gui, tree *filetree.FileTree, refTrees []*filetree.FileTree, cache filetree.TreeCache) (controller *fileTreeController, err error) {
+ controller = new(fileTreeController)
// populate main fields
- controller.Name = name
+ controller.name = name
controller.gui = gui
- controller.vm, err = NewFileTreeViewModel(tree, refTrees, cache)
+ controller.vm, err = newFileTreeViewModel(tree, refTrees, cache)
if err != nil {
return nil, err
}
- controller.keybindingToggleCollapse, err = keybinding.ParseAll(viper.GetString("keybinding.toggle-collapse-dir"))
- if err != nil {
- logrus.Error(err)
- }
-
- controller.keybindingToggleCollapseAll, err = keybinding.ParseAll(viper.GetString("keybinding.toggle-collapse-all-dir"))
- if err != nil {
- logrus.Error(err)
- }
-
- controller.keybindingToggleAttributes, err = keybinding.ParseAll(viper.GetString("keybinding.toggle-filetree-attributes"))
- if err != nil {
- logrus.Error(err)
- }
-
- controller.keybindingToggleAdded, err = keybinding.ParseAll(viper.GetString("keybinding.toggle-added-files"))
- if err != nil {
- logrus.Error(err)
- }
-
- controller.keybindingToggleRemoved, err = keybinding.ParseAll(viper.GetString("keybinding.toggle-removed-files"))
- if err != nil {
- logrus.Error(err)
- }
-
- controller.keybindingToggleModified, err = keybinding.ParseAll(viper.GetString("keybinding.toggle-modified-files"))
- if err != nil {
- logrus.Error(err)
- }
-
- // support legacy behavior first, then use default behavior
- controller.keybindingToggleUnmodified, err = keybinding.ParseAll(viper.GetString("keybinding.toggle-unchanged-files"))
- if err != nil {
- controller.keybindingToggleUnmodified, err = keybinding.ParseAll(viper.GetString("keybinding.toggle-unmodified-files"))
- if err != nil {
- logrus.Error(err)
- }
- }
-
- controller.keybindingPageUp, err = keybinding.ParseAll(viper.GetString("keybinding.page-up"))
- if err != nil {
- logrus.Error(err)
- }
-
- controller.keybindingPageDown, err = keybinding.ParseAll(viper.GetString("keybinding.page-down"))
- if err != nil {
- logrus.Error(err)
- }
return controller, err
}
// Setup initializes the UI concerns within the context of a global [gocui] view object.
-func (controller *FileTreeController) Setup(v *gocui.View, header *gocui.View) error {
+func (controller *fileTreeController) Setup(v *gocui.View, header *gocui.View) error {
// set controller options
controller.view = v
@@ -119,65 +61,82 @@ func (controller *FileTreeController) Setup(v *gocui.View, header *gocui.View) e
controller.header.Wrap = false
controller.header.Frame = false
- // set keybindings
- if err := controller.gui.SetKeybinding(controller.Name, gocui.KeyArrowDown, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return controller.CursorDown() }); err != nil {
- return err
- }
- if err := controller.gui.SetKeybinding(controller.Name, gocui.KeyArrowUp, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return controller.CursorUp() }); err != nil {
- return err
- }
- if err := controller.gui.SetKeybinding(controller.Name, gocui.KeyArrowLeft, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return controller.CursorLeft() }); err != nil {
- return err
- }
- if err := controller.gui.SetKeybinding(controller.Name, gocui.KeyArrowRight, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return controller.CursorRight() }); err != nil {
+ var infos = []key.BindingInfo{
+ {
+ ConfigKeys: []string{"keybinding.toggle-collapse-dir"},
+ OnAction: controller.toggleCollapse,
+ Display: "Collapse dir",
+ },
+ {
+ ConfigKeys: []string{"keybinding.toggle-collapse-all-dir"},
+ OnAction: controller.toggleCollapseAll,
+ Display: "Collapse all dir",
+ },
+ {
+ ConfigKeys: []string{"keybinding.toggle-added-files"},
+ OnAction: func() error { return controller.toggleShowDiffType(filetree.Added) },
+ IsSelected: func() bool { return !controller.vm.HiddenDiffTypes[filetree.Added] },
+ Display: "Added",
+ },
+ {
+ ConfigKeys: []string{"keybinding.toggle-removed-files"},
+ OnAction: func() error { return controller.toggleShowDiffType(filetree.Removed) },
+ IsSelected: func() bool { return !controller.vm.HiddenDiffTypes[filetree.Removed] },
+ Display: "Removed",
+ },
+ {
+ ConfigKeys: []string{"keybinding.toggle-modified-files"},
+ OnAction: func() error { return controller.toggleShowDiffType(filetree.Modified) },
+ IsSelected: func() bool { return !controller.vm.HiddenDiffTypes[filetree.Modified] },
+ Display: "Modified",
+ },
+ {
+ ConfigKeys: []string{"keybinding.toggle-unchanged-files", "keybinding.toggle-unmodified-files"},
+ OnAction: func() error { return controller.toggleShowDiffType(filetree.Unmodified) },
+ IsSelected: func() bool { return !controller.vm.HiddenDiffTypes[filetree.Unmodified] },
+ Display: "Unmodified",
+ },
+ {
+ ConfigKeys: []string{"keybinding.toggle-filetree-attributes"},
+ OnAction: controller.toggleAttributes,
+ IsSelected: func() bool { return controller.vm.ShowAttributes },
+ Display: "Attributes",
+ },
+ {
+ ConfigKeys: []string{"keybinding.page-up"},
+ OnAction: controller.PageUp,
+ },
+ {
+ ConfigKeys: []string{"keybinding.page-down"},
+ OnAction: controller.PageDown,
+ },
+ {
+ Key: gocui.KeyArrowDown,
+ Modifier: gocui.ModNone,
+ OnAction: controller.CursorDown,
+ },
+ {
+ Key: gocui.KeyArrowUp,
+ Modifier: gocui.ModNone,
+ OnAction: controller.CursorUp,
+ },
+ {
+ Key: gocui.KeyArrowLeft,
+ Modifier: gocui.ModNone,
+ OnAction: controller.CursorLeft,
+ },
+ {
+ Key: gocui.KeyArrowRight,
+ Modifier: gocui.ModNone,
+ OnAction: controller.CursorRight,
+ },
+ }
+
+ helpKeys, err := key.GenerateBindings(controller.gui, controller.name, infos)
+ if err != nil {
return err
}
-
- for _, key := range controller.keybindingPageUp {
- if err := controller.gui.SetKeybinding(controller.Name, key.Value, key.Modifier, func(*gocui.Gui, *gocui.View) error { return controller.PageUp() }); err != nil {
- return err
- }
- }
- for _, key := range controller.keybindingPageDown {
- if err := controller.gui.SetKeybinding(controller.Name, key.Value, key.Modifier, func(*gocui.Gui, *gocui.View) error { return controller.PageDown() }); err != nil {
- return err
- }
- }
- for _, key := range controller.keybindingToggleCollapse {
- if err := controller.gui.SetKeybinding(controller.Name, key.Value, key.Modifier, func(*gocui.Gui, *gocui.View) error { return controller.toggleCollapse() }); err != nil {
- return err
- }
- }
- for _, key := range controller.keybindingToggleCollapseAll {
- if err := controller.gui.SetKeybinding(controller.Name, key.Value, key.Modifier, func(*gocui.Gui, *gocui.View) error { return controller.toggleCollapseAll() }); err != nil {
- return err
- }
- }
- for _, key := range controller.keybindingToggleAttributes {
- if err := controller.gui.SetKeybinding(controller.Name, key.Value, key.Modifier, func(*gocui.Gui, *gocui.View) error { return controller.toggleAttributes() }); err != nil {
- return err
- }
- }
- for _, key := range controller.keybindingToggleAdded {
- if err := controller.gui.SetKeybinding(controller.Name, key.Value, key.Modifier, func(*gocui.Gui, *gocui.View) error { return controller.toggleShowDiffType(filetree.Added) }); err != nil {
- return err
- }
- }
- for _, key := range controller.keybindingToggleRemoved {
- if err := controller.gui.SetKeybinding(controller.Name, key.Value, key.Modifier, func(*gocui.Gui, *gocui.View) error { return controller.toggleShowDiffType(filetree.Removed) }); err != nil {
- return err
- }
- }
- for _, key := range controller.keybindingToggleModified {
- if err := controller.gui.SetKeybinding(controller.Name, key.Value, key.Modifier, func(*gocui.Gui, *gocui.View) error { return controller.toggleShowDiffType(filetree.Modified) }); err != nil {
- return err
- }
- }
- for _, key := range controller.keybindingToggleUnmodified {
- if err := controller.gui.SetKeybinding(controller.Name, key.Value, key.Modifier, func(*gocui.Gui, *gocui.View) error { return controller.toggleShowDiffType(filetree.Unmodified) }); err != nil {
- return err
- }
- }
+ controller.helpKeys = helpKeys
_, height := controller.view.Size()
controller.vm.Setup(0, height)
@@ -188,18 +147,18 @@ func (controller *FileTreeController) Setup(v *gocui.View, header *gocui.View) e
}
// IsVisible indicates if the file tree view pane is currently initialized
-func (controller *FileTreeController) IsVisible() bool {
+func (controller *fileTreeController) IsVisible() bool {
return controller != nil
}
// resetCursor moves the cursor back to the top of the buffer and translates to the top of the buffer.
-func (controller *FileTreeController) resetCursor() {
+func (controller *fileTreeController) resetCursor() {
_ = controller.view.SetCursor(0, 0)
controller.vm.resetCursor()
}
// setTreeByLayer populates the view model by stacking the indicated image layer file trees.
-func (controller *FileTreeController) setTreeByLayer(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop int) error {
+func (controller *fileTreeController) setTreeByLayer(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop int) error {
err := controller.vm.setTreeByLayer(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop)
if err != nil {
return err
@@ -214,7 +173,7 @@ func (controller *FileTreeController) setTreeByLayer(bottomTreeStart, bottomTree
// Note: we cannot use the gocui buffer since any state change requires writing the entire tree to the buffer.
// Instead we are keeping an upper and lower bounds of the tree string to render and only flushing
// this range into the view buffer. This is much faster when tree sizes are large.
-func (controller *FileTreeController) CursorDown() error {
+func (controller *fileTreeController) CursorDown() error {
if controller.vm.CursorDown() {
return controller.Render()
}
@@ -225,7 +184,7 @@ func (controller *FileTreeController) CursorDown() error {
// Note: we cannot use the gocui buffer since any state change requires writing the entire tree to the buffer.
// Instead we are keeping an upper and lower bounds of the tree string to render and only flushing
// this range into the view buffer. This is much faster when tree sizes are large.
-func (controller *FileTreeController) CursorUp() error {
+func (controller *fileTreeController) CursorUp() error {
if controller.vm.CursorUp() {
return controller.Render()
}
@@ -233,7 +192,7 @@ func (controller *FileTreeController) CursorUp() error {
}
// CursorLeft moves the cursor up until we reach the Parent Node or top of the tree
-func (controller *FileTreeController) CursorLeft() error {
+func (controller *fileTreeController) CursorLeft() error {
err := controller.vm.CursorLeft(filterRegex())
if err != nil {
return err
@@ -243,7 +202,7 @@ func (controller *FileTreeController) CursorLeft() error {
}
// CursorRight descends into directory expanding it if needed
-func (controller *FileTreeController) CursorRight() error {
+func (controller *fileTreeController) CursorRight() error {
err := controller.vm.CursorRight(filterRegex())
if err != nil {
return err
@@ -253,7 +212,7 @@ func (controller *FileTreeController) CursorRight() error {
}
// PageDown moves to next page putting the cursor on top
-func (controller *FileTreeController) PageDown() error {
+func (controller *fileTreeController) PageDown() error {
err := controller.vm.PageDown()
if err != nil {
return err
@@ -262,7 +221,7 @@ func (controller *FileTreeController) PageDown() error {
}
// PageUp moves to previous page putting the cursor on top
-func (controller *FileTreeController) PageUp() error {
+func (controller *fileTreeController) PageUp() error {
err := controller.vm.PageUp()
if err != nil {
return err
@@ -271,12 +230,12 @@ func (controller *FileTreeController) PageUp() error {
}
// getAbsPositionNode determines the selected screen cursor's location in the file tree, returning the selected FileNode.
-// func (controller *FileTreeController) getAbsPositionNode() (node *filetree.FileNode) {
+// func (controller *fileTreeController) getAbsPositionNode() (node *filetree.FileNode) {
// return controller.vm.getAbsPositionNode(filterRegex())
// }
// toggleCollapse will collapse/expand the selected FileNode.
-func (controller *FileTreeController) toggleCollapse() error {
+func (controller *fileTreeController) toggleCollapse() error {
err := controller.vm.toggleCollapse(filterRegex())
if err != nil {
return err
@@ -286,7 +245,7 @@ func (controller *FileTreeController) toggleCollapse() error {
}
// toggleCollapseAll will collapse/expand the all directories.
-func (controller *FileTreeController) toggleCollapseAll() error {
+func (controller *fileTreeController) toggleCollapseAll() error {
err := controller.vm.toggleCollapseAll()
if err != nil {
return err
@@ -299,7 +258,7 @@ func (controller *FileTreeController) toggleCollapseAll() error {
}
// toggleAttributes will show/hide file attributes
-func (controller *FileTreeController) toggleAttributes() error {
+func (controller *fileTreeController) toggleAttributes() error {
err := controller.vm.toggleAttributes()
if err != nil {
return err
@@ -309,7 +268,7 @@ func (controller *FileTreeController) toggleAttributes() error {
}
// toggleShowDiffType will show/hide the selected DiffType in the filetree pane.
-func (controller *FileTreeController) toggleShowDiffType(diffType filetree.DiffType) error {
+func (controller *fileTreeController) toggleShowDiffType(diffType filetree.DiffType) error {
controller.vm.toggleShowDiffType(diffType)
// we need to render the changes to the status pane as well (not just this contoller/view)
return UpdateAndRender()
@@ -317,10 +276,10 @@ func (controller *FileTreeController) toggleShowDiffType(diffType filetree.DiffT
// filterRegex will return a regular expression object to match the user's filter input.
func filterRegex() *regexp.Regexp {
- if Controllers.Filter == nil || Controllers.Filter.view == nil {
+ if controllers.Filter == nil || controllers.Filter.view == nil {
return nil
}
- filterString := strings.TrimSpace(Controllers.Filter.view.Buffer())
+ filterString := strings.TrimSpace(controllers.Filter.view.Buffer())
if len(filterString) == 0 {
return nil
}
@@ -334,7 +293,7 @@ func filterRegex() *regexp.Regexp {
}
// onLayoutChange is called by the UI framework to inform the view-model of the new screen dimensions
-func (controller *FileTreeController) onLayoutChange(resized bool) error {
+func (controller *fileTreeController) onLayoutChange(resized bool) error {
_ = controller.Update()
if resized {
return controller.Render()
@@ -343,7 +302,7 @@ func (controller *FileTreeController) onLayoutChange(resized bool) error {
}
// Update refreshes the state objects for future rendering.
-func (controller *FileTreeController) Update() error {
+func (controller *fileTreeController) Update() error {
var width, height int
if controller.view != nil {
@@ -357,9 +316,9 @@ func (controller *FileTreeController) Update() error {
}
// Render flushes the state objects (file tree) to the pane.
-func (controller *FileTreeController) Render() error {
+func (controller *fileTreeController) Render() error {
title := "Current Layer Contents"
- if Controllers.Layer.CompareMode == CompareAll {
+ if controllers.Layer.CompareMode == CompareAll {
title = "Aggregated Layer Contents"
}
@@ -377,7 +336,7 @@ func (controller *FileTreeController) Render() error {
headerStr += fmt.Sprintf(filetree.AttributeFormat+" %s", "P", "ermission", "UID:GID", "Size", "Filetree")
}
- _, _ = fmt.Fprintln(controller.header, Formatting.Header(vtclean.Clean(headerStr, false)))
+ _, _ = fmt.Fprintln(controller.header, format.Header(vtclean.Clean(headerStr, false)))
// update the contents
controller.view.Clear()
@@ -393,12 +352,10 @@ func (controller *FileTreeController) Render() error {
}
// KeyHelp indicates all the possible actions a user can take while the current pane is selected.
-func (controller *FileTreeController) KeyHelp() string {
- return renderStatusOption(controller.keybindingToggleCollapse[0].String(), "Collapse dir", false) +
- renderStatusOption(controller.keybindingToggleCollapseAll[0].String(), "Collapse all dir", false) +
- renderStatusOption(controller.keybindingToggleAdded[0].String(), "Added", !controller.vm.HiddenDiffTypes[filetree.Added]) +
- renderStatusOption(controller.keybindingToggleRemoved[0].String(), "Removed", !controller.vm.HiddenDiffTypes[filetree.Removed]) +
- renderStatusOption(controller.keybindingToggleModified[0].String(), "Modified", !controller.vm.HiddenDiffTypes[filetree.Modified]) +
- renderStatusOption(controller.keybindingToggleUnmodified[0].String(), "Unmodified", !controller.vm.HiddenDiffTypes[filetree.Unmodified]) +
- renderStatusOption(controller.keybindingToggleAttributes[0].String(), "Attributes", controller.vm.ShowAttributes)
+func (controller *fileTreeController) KeyHelp() string {
+ var help string
+ for _, binding := range controller.helpKeys {
+ help += binding.RenderKeyHelp()
+ }
+ return help
}
diff --git a/runtime/ui/filetree_viewmodel.go b/runtime/ui/filetree_viewmodel.go
index 18ebfdd..5a640e8 100644
--- a/runtime/ui/filetree_viewmodel.go
+++ b/runtime/ui/filetree_viewmodel.go
@@ -3,6 +3,7 @@ package ui
import (
"bytes"
"fmt"
+ "github.com/wagoodman/dive/runtime/ui/format"
"regexp"
"strings"
@@ -12,9 +13,9 @@ import (
"github.com/wagoodman/dive/dive/filetree"
)
-// 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.
-type FileTreeViewModel struct {
+type fileTreeViewModel struct {
ModelTree *filetree.FileTree
ViewTree *filetree.FileTree
RefTrees []*filetree.FileTree
@@ -33,9 +34,9 @@ type FileTreeViewModel struct {
mainBuf bytes.Buffer
}
-// NewFileTreeController creates a new view object attached the the global [gocui] screen object.
-func NewFileTreeViewModel(tree *filetree.FileTree, refTrees []*filetree.FileTree, cache filetree.TreeCache) (treeViewModel *FileTreeViewModel, err error) {
- treeViewModel = new(FileTreeViewModel)
+// newFileTreeViewModel creates a new view object attached the the global [gocui] screen object.
+func newFileTreeViewModel(tree *filetree.FileTree, refTrees []*filetree.FileTree, cache filetree.TreeCache) (treeViewModel *fileTreeViewModel, err error) {
+ treeViewModel = new(fileTreeViewModel)
// populate main fields
treeViewModel.ShowAttributes = viper.GetBool("filetree.show-attributes")
@@ -65,13 +66,13 @@ func NewFileTreeViewModel(tree *filetree.FileTree, refTrees []*filetree.FileTree
}
// Setup initializes the UI concerns within the context of a global [gocui] view object.
-func (vm *FileTreeViewModel) Setup(lowerBound, height int) {
+func (vm *fileTreeViewModel) Setup(lowerBound, height int) {
vm.bufferIndexLowerBound = lowerBound
vm.refHeight = height
}
// height returns the current height and considers the header
-func (vm *FileTreeViewModel) height() int {
+func (vm *fileTreeViewModel) height() int {
if vm.ShowAttributes {
return vm.refHeight - 1
}
@@ -79,24 +80,24 @@ func (vm *FileTreeViewModel) height() int {
}
// bufferIndexUpperBound returns the current upper bounds for the view
-func (vm *FileTreeViewModel) bufferIndexUpperBound() int {
+func (vm *fileTreeViewModel) bufferIndexUpperBound() int {
return vm.bufferIndexLowerBound + vm.height()
}
// IsVisible indicates if the file tree view pane is currently initialized
-func (vm *FileTreeViewModel) IsVisible() bool {
+func (vm *fileTreeViewModel) IsVisible() bool {
return vm != nil
}
// resetCursor moves the cursor back to the top of the buffer and translates to the top of the buffer.
-func (vm *FileTreeViewModel) resetCursor() {
+func (vm *fileTreeViewModel) resetCursor() {
vm.TreeIndex = 0
vm.bufferIndex = 0
vm.bufferIndexLowerBound = 0
}
// setTreeByLayer populates the view model by stacking the indicated image layer file trees.
-func (vm *FileTreeViewModel) setTreeByLayer(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop int) error {
+func (vm *fileTreeViewModel) setTreeByLayer(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop int) error {
if topTreeStop > len(vm.RefTrees)-1 {
return fmt.Errorf("invalid layer index given: %d of %d", topTreeStop, len(vm.RefTrees)-1)
}
@@ -125,7 +126,7 @@ func (vm *FileTreeViewModel) setTreeByLayer(bottomTreeStart, bottomTreeStop, top
}
// doCursorUp perfo