package ui import ( "github.com/jroimartin/gocui" "github.com/sirupsen/logrus" "github.com/wagoodman/dive/dive/filetree" "github.com/wagoodman/dive/dive/image" "github.com/wagoodman/dive/runtime/ui/view" "github.com/wagoodman/dive/runtime/ui/viewmodel" "regexp" ) 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 } func NewCollection(g *gocui.Gui, analysis *image.AnalysisResult, cache filetree.TreeCache) (*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) 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 } controller.Tree, err = view.NewFileTreeView("filetree", g, treeStack, analysis.RefTrees, cache) if err != nil { return nil, err } 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) // 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.Details = view.NewDetailsView("details", g, analysis.Efficiency, analysis.Inefficiencies, analysis.SizeBytes) controller.lookup[controller.Details.Name()] = controller.Details // propagate initial conditions to necessary views err = controller.onLayerChange(viewmodel.LayerSelection{ Layer: controller.Layer.CurrentLayer(), BottomTreeStart: 0, BottomTreeStop: 0, TopTreeStart: 0, TopTreeStop: 0, }) if err != nil { return nil, err } return controller, nil } func (c *Controller) onFileTreeViewOptionChange() error { err := c.Status.Update() if err != nil { return err } return c.Status.Render() } func (c *Controller) onFilterEdit(filter string) error { var filterRegex *regexp.Regexp var err error if len(filter) > 0 { filterRegex, err = regexp.Compile(filter) if err != nil { return err } } c.Tree.SetFilterRegex(filterRegex) err = c.Tree.Update() if err != nil { return err } return c.Tree.Render() } func (c *Controller) onLayerChange(selection viewmodel.LayerSelection) error { // update the details c.Details.SetCurrentLayer(selection.Layer) // update the filetree err := c.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") } else { c.Tree.SetTitle("Current Layer Contents") } // update details and filetree panes return c.UpdateAndRender() } func (c *Controller) UpdateAndRender() error { err := c.Update() if err != nil { logrus.Debug("failed update: ", err) return err } err = c.Render() if err != nil { logrus.Debug("failed render: ", err) return err } return nil } // Update refreshes the state objects for future rendering. func (c *Controller) Update() error { for _, controller := range c.lookup { err := controller.Update() if err != nil { logrus.Debug("unable to update controller: ") return err } } return nil } // Render flushes the state objects to the screen. func (c *Controller) Render() error { for _, controller := range c.lookup { if controller.IsVisible() { err := controller.Render() if err != nil { return err } } } return nil } // 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) } else { _, err = c.gui.SetCurrentView(c.Layer.Name()) c.Status.SetCurrentView(c.Layer) } if err != nil { logrus.Error("unable to toggle view: ", err) return err } return c.UpdateAndRender() } func (c *Controller) ToggleFilterView() error { // delete all user input from the tree view err := c.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() { // ...remove any filter from the tree c.Tree.SetFilterRegex(nil) // ...adjust focus to a valid (visible) view err = c.ToggleView() if err != nil { logrus.Error("unable to toggle filter view (back): ", err) return err } } return c.UpdateAndRender() }