Skip to content

Commit

Permalink
Merge pull request #256 from wagoodman/simple-layout-manager
Browse files Browse the repository at this point in the history
Add UI layout manager
  • Loading branch information
wagoodman committed Nov 28, 2019
2 parents 700332b + 776c1f9 commit 8d8c84c
Show file tree
Hide file tree
Showing 23 changed files with 1,564 additions and 640 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Expand Up @@ -22,7 +22,7 @@ jobs:
- "/go/pkg/mod"
- run:
name: run static analysis
command: make ci-static-analyses
command: make ci-static-analysis

run-tests:
parameters:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pipeline.yml
Expand Up @@ -64,7 +64,7 @@ jobs:
run: go get ./...

- name: Linting, formatting, and other static code analyses
run: make ci-static-analyses
run: make ci-static-analysis

- name: Build snapshot artifacts
run: make ci-build-snapshot-packages
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Expand Up @@ -12,7 +12,7 @@ all: clean build
ci-unit-test:
go test -cover -v -race ./...

ci-static-analyses:
ci-static-analysis:
grep -R 'const allowTestDataCapture = false' runtime/ui/viewmodel
go vet ./...
@! gofmt -s -l . 2>&1 | grep -vE '^\.git/' | grep -vE '^\.cache/'
Expand Down
4 changes: 4 additions & 0 deletions cmd/root.go
Expand Up @@ -141,6 +141,10 @@ func initLogging() {

log.SetLevel(level)
log.Debug("Starting Dive...")
log.Debugf("config filepath: %s", viper.ConfigFileUsed())
for k, v := range viper.AllSettings() {
log.Debug("config value: ", k, " : ", v)
}
}

// getCfgFile checks for config file in paths from xdg specs
Expand Down
35 changes: 22 additions & 13 deletions runtime/ui/app.go
Expand Up @@ -3,6 +3,8 @@ package ui
import (
"github.com/wagoodman/dive/dive/image"
"github.com/wagoodman/dive/runtime/ui/key"
"github.com/wagoodman/dive/runtime/ui/layout"
"github.com/wagoodman/dive/runtime/ui/layout/compound"
"sync"

"github.com/jroimartin/gocui"
Expand All @@ -16,7 +18,7 @@ const debug = false
type app struct {
gui *gocui.Gui
controllers *Controller
layout *layoutManager
layout *layout.Manager
}

var (
Expand All @@ -27,19 +29,28 @@ var (
func newApp(gui *gocui.Gui, analysis *image.AnalysisResult, cache filetree.Comparer) (*app, error) {
var err error
once.Do(func() {
var theControls *Controller
var controller *Controller
var globalHelpKeys []*key.Binding

theControls, err = NewCollection(gui, analysis, cache)
controller, err = NewCollection(gui, analysis, cache)
if err != nil {
return
}

lm := newLayoutManager(theControls)
// note: order matters when adding elements to the layout
lm := layout.NewManager()
lm.Add(controller.views.Status, layout.LocationFooter)
lm.Add(controller.views.Filter, layout.LocationFooter)
lm.Add(compound.NewLayerDetailsCompoundLayout(controller.views.Layer, controller.views.Details), layout.LocationColumn)
lm.Add(controller.views.Tree, layout.LocationColumn)

// todo: access this more programmatically
if debug {
lm.Add(controller.views.Debug, layout.LocationColumn)
}
gui.Cursor = false
//g.Mouse = true
gui.SetManagerFunc(lm.layout)
gui.SetManagerFunc(lm.Layout)

// var profileObj = profile.Start(profile.CPUProfile, profile.ProfilePath("."), profile.NoShutdownHook)
//
Expand All @@ -49,7 +60,7 @@ func newApp(gui *gocui.Gui, analysis *image.AnalysisResult, cache filetree.Compa

appSingleton = &app{
gui: gui,
controllers: theControls,
controllers: controller,
layout: lm,
}

Expand All @@ -61,13 +72,13 @@ func newApp(gui *gocui.Gui, analysis *image.AnalysisResult, cache filetree.Compa
},
{
ConfigKeys: []string{"keybinding.toggle-view"},
OnAction: theControls.ToggleView,
OnAction: controller.ToggleView,
Display: "Switch view",
},
{
ConfigKeys: []string{"keybinding.filter-files"},
OnAction: theControls.ToggleFilterView,
IsSelected: theControls.Filter.IsVisible,
OnAction: controller.ToggleFilterView,
IsSelected: controller.views.Filter.IsVisible,
Display: "Filter",
},
}
Expand All @@ -77,10 +88,10 @@ func newApp(gui *gocui.Gui, analysis *image.AnalysisResult, cache filetree.Compa
return
}

theControls.Status.AddHelpKeys(globalHelpKeys...)
controller.views.Status.AddHelpKeys(globalHelpKeys...)

// perform the first update and render now that all resources have been loaded
err = theControls.UpdateAndRender()
err = controller.UpdateAndRender()
if err != nil {
return
}
Expand All @@ -106,8 +117,6 @@ func newApp(gui *gocui.Gui, analysis *image.AnalysisResult, cache filetree.Compa
// }
// }

var lastX, lastY int

// quit is the gocui callback invoked when the user hits Ctrl+C
func (a *app) quit() error {

Expand Down
90 changes: 31 additions & 59 deletions runtime/ui/controller.go
Expand Up @@ -11,61 +11,33 @@ import (
)

type Controller struct {
gui *gocui.Gui
Tree *view.FileTree
Layer *view.Layer
Status *view.Status
Filter *view.Filter
Details *view.Details
lookup map[string]view.Renderer
gui *gocui.Gui
views *view.Views
}

func NewCollection(g *gocui.Gui, analysis *image.AnalysisResult, cache filetree.Comparer) (*Controller, error) {
var err error

controller := &Controller{
gui: g,
}
controller.lookup = make(map[string]view.Renderer)

controller.Layer, err = view.NewLayerView("layers", g, analysis.Layers)
views, err := view.NewViews(g, analysis, cache)
if err != nil {
return nil, err
}
controller.lookup[controller.Layer.Name()] = controller.Layer

//treeStack, err := filetree.StackTreeRange(analysis.RefTrees, 0, 0)
//if err != nil {
// return nil, err
//}
treeStack := analysis.RefTrees[0]
controller.Tree, err = view.NewFileTreeView("filetree", g, treeStack, analysis.RefTrees, cache)
if err != nil {
return nil, err
controller := &Controller{
gui: g,
views: views,
}
controller.lookup[controller.Tree.Name()] = controller.Tree

// layer view cursor down event should trigger an update in the file tree
controller.Layer.AddLayerChangeListener(controller.onLayerChange)

controller.Status = view.NewStatusView("status", g)
controller.lookup[controller.Status.Name()] = controller.Status
// set the layer view as the first selected view
controller.Status.SetCurrentView(controller.Layer)
controller.views.Layer.AddLayerChangeListener(controller.onLayerChange)

// update the status pane when a filetree option is changed by the user
controller.Tree.AddViewOptionChangeListener(controller.onFileTreeViewOptionChange)

controller.Filter = view.NewFilterView("filter", g)
controller.lookup[controller.Filter.Name()] = controller.Filter
controller.Filter.AddFilterEditListener(controller.onFilterEdit)
controller.views.Tree.AddViewOptionChangeListener(controller.onFileTreeViewOptionChange)

controller.Details = view.NewDetailsView("details", g, analysis.Efficiency, analysis.Inefficiencies, analysis.SizeBytes)
controller.lookup[controller.Details.Name()] = controller.Details
// update the tree view while the user types into the filter view
controller.views.Filter.AddFilterEditListener(controller.onFilterEdit)

// propagate initial conditions to necessary views
err = controller.onLayerChange(viewmodel.LayerSelection{
Layer: controller.Layer.CurrentLayer(),
Layer: controller.views.Layer.CurrentLayer(),
BottomTreeStart: 0,
BottomTreeStop: 0,
TopTreeStart: 0,
Expand All @@ -80,11 +52,11 @@ func NewCollection(g *gocui.Gui, analysis *image.AnalysisResult, cache filetree.
}

func (c *Controller) onFileTreeViewOptionChange() error {
err := c.Status.Update()
err := c.views.Status.Update()
if err != nil {
return err
}
return c.Status.Render()
return c.views.Status.Render()
}

func (c *Controller) onFilterEdit(filter string) error {
Expand All @@ -98,30 +70,30 @@ func (c *Controller) onFilterEdit(filter string) error {
}
}

c.Tree.SetFilterRegex(filterRegex)
c.views.Tree.SetFilterRegex(filterRegex)

err = c.Tree.Update()
err = c.views.Tree.Update()
if err != nil {
return err
}

return c.Tree.Render()
return c.views.Tree.Render()
}

func (c *Controller) onLayerChange(selection viewmodel.LayerSelection) error {
// update the details
c.Details.SetCurrentLayer(selection.Layer)
c.views.Details.SetCurrentLayer(selection.Layer)

// update the filetree
err := c.Tree.SetTree(selection.BottomTreeStart, selection.BottomTreeStop, selection.TopTreeStart, selection.TopTreeStop)
err := c.views.Tree.SetTree(selection.BottomTreeStart, selection.BottomTreeStop, selection.TopTreeStart, selection.TopTreeStop)
if err != nil {
return err
}

if c.Layer.CompareMode == view.CompareAll {
c.Tree.SetTitle("Aggregated Layer Contents")
if c.views.Layer.CompareMode == view.CompareAll {
c.views.Tree.SetTitle("Aggregated Layer Contents")
} else {
c.Tree.SetTitle("Current Layer Contents")
c.views.Tree.SetTitle("Current Layer Contents")
}

// update details and filetree panes
Expand All @@ -146,7 +118,7 @@ func (c *Controller) UpdateAndRender() error {

// Update refreshes the state objects for future rendering.
func (c *Controller) Update() error {
for _, controller := range c.lookup {
for _, controller := range c.views.All() {
err := controller.Update()
if err != nil {
logrus.Debug("unable to update controller: ")
Expand All @@ -158,7 +130,7 @@ func (c *Controller) Update() error {

// Render flushes the state objects to the screen.
func (c *Controller) Render() error {
for _, controller := range c.lookup {
for _, controller := range c.views.All() {
if controller.IsVisible() {
err := controller.Render()
if err != nil {
Expand All @@ -172,12 +144,12 @@ func (c *Controller) Render() error {
// ToggleView switches between the file view and the layer view and re-renders the screen.
func (c *Controller) ToggleView() (err error) {
v := c.gui.CurrentView()
if v == nil || v.Name() == c.Layer.Name() {
_, err = c.gui.SetCurrentView(c.Tree.Name())
c.Status.SetCurrentView(c.Tree)
if v == nil || v.Name() == c.views.Layer.Name() {
_, err = c.gui.SetCurrentView(c.views.Tree.Name())
c.views.Status.SetCurrentView(c.views.Tree)
} else {
_, err = c.gui.SetCurrentView(c.Layer.Name())
c.Status.SetCurrentView(c.Layer)
_, err = c.gui.SetCurrentView(c.views.Layer.Name())
c.views.Status.SetCurrentView(c.views.Layer)
}

if err != nil {
Expand All @@ -190,16 +162,16 @@ func (c *Controller) ToggleView() (err error) {

func (c *Controller) ToggleFilterView() error {
// delete all user input from the tree view
err := c.Filter.ToggleVisible()
err := c.views.Filter.ToggleVisible()
if err != nil {
logrus.Error("unable to toggle filter visibility: ", err)
return err
}

// we have just hidden the filter view...
if !c.Filter.IsVisible() {
if !c.views.Filter.IsVisible() {
// ...remove any filter from the tree
c.Tree.SetFilterRegex(nil)
c.views.Tree.SetFilterRegex(nil)

// ...adjust focus to a valid (visible) view
err = c.ToggleView()
Expand Down
45 changes: 45 additions & 0 deletions runtime/ui/format/format.go
@@ -1,7 +1,39 @@
package format

import (
"fmt"
"github.com/fatih/color"
"github.com/lunixbochs/vtclean"
"strings"
)

const (
//selectedLeftBracketStr = " "
//selectedRightBracketStr = " "
//selectedFillStr = " "
//
//leftBracketStr = "▏"
//rightBracketStr = "▕"
//fillStr = "─"

//selectedLeftBracketStr = " "
//selectedRightBracketStr = " "
//selectedFillStr = "━"
//
//leftBracketStr = "▏"
//rightBracketStr = "▕"
//fillStr = "─"

selectedLeftBracketStr = "┃"
selectedRightBracketStr = "┣"
selectedFillStr = "━"

leftBracketStr = "│"
rightBracketStr = "├"
fillStr = "─"

selectStr = " ● "
//selectStr = " "
)

var (
Expand All @@ -26,6 +58,19 @@ func init() {
CompareBottom = color.New(color.BgGreen).SprintFunc()
}

func RenderHeader(title string, width int, selected bool) string {
if selected {
body := Header(fmt.Sprintf("%s%s ", selectStr, title))
bodyLen := len(vtclean.Clean(body, false))
return fmt.Sprintf("%s%s%s%s\n", selectedLeftBracketStr, body, selectedRightBracketStr, strings.Repeat(selectedFillStr, width-bodyLen-2))
//return fmt.Sprintf("%s%s%s%s\n", Selected(selectedLeftBracketStr), body, Selected(selectedRightBracketStr), Selected(strings.Repeat(selectedFillStr, width-bodyLen-2)))
//return fmt.Sprintf("%s%s%s%s\n", Selected(selectedLeftBracketStr), body, Selected(selectedRightBracketStr), strings.Repeat(selectedFillStr, width-bodyLen-2))
}
body := Header(fmt.Sprintf(" %s ", title))
bodyLen := len(vtclean.Clean(body, false))
return fmt.Sprintf("%s%s%s%s\n", leftBracketStr, body, rightBracketStr, strings.Repeat(fillStr, width-bodyLen-2))
}

func RenderHelpKey(control, title string, selected bool) string {
if selected {
return StatusSelected("▏") + StatusControlSelected(control) + StatusSelected(" "+title+" ")
Expand Down
5 changes: 5 additions & 0 deletions runtime/ui/layout/area.go
@@ -0,0 +1,5 @@
package layout

type Area struct {
minX, minY, maxX, maxY int
}

0 comments on commit 8d8c84c

Please sign in to comment.