basic pane swap working

This commit is contained in:
Alex Goodman 2020-02-09 14:33:52 -05:00
parent b46180936e
commit 91d58ab09d
No known key found for this signature in database
GPG key ID: 150587AB82D3C4E6
11 changed files with 247 additions and 99 deletions

View file

@ -73,6 +73,7 @@ func initConfig() {
viper.SetDefault("keybinding.quit", "ctrl+c")
viper.SetDefault("keybinding.toggle-view", "tab")
viper.SetDefault("keybinding.filter-files", "ctrl+f, ctrl+slash")
viper.SetDefault("keybinding.toggle-details", "ctrl+d")
// keybindings: layer view
viper.SetDefault("keybinding.compare-all", "ctrl+a")
viper.SetDefault("keybinding.compare-layer", "ctrl+l")

View file

@ -16,9 +16,10 @@ const debug = false
// type global
type app struct {
gui *gocui.Gui
controllers *Controller
layout *layout.Manager
gui *gocui.Gui
controller *Controller
layout *layout.Manager
detailedMode bool
}
var (
@ -32,7 +33,7 @@ func newApp(gui *gocui.Gui, analysis *image.AnalysisResult, cache filetree.Compa
var controller *Controller
var globalHelpKeys []*key.Binding
controller, err = NewCollection(gui, analysis, cache)
controller, err = NewController(gui, analysis, cache)
if err != nil {
return
}
@ -59,9 +60,10 @@ func newApp(gui *gocui.Gui, analysis *image.AnalysisResult, cache filetree.Compa
// }
appSingleton = &app{
gui: gui,
controllers: controller,
layout: lm,
gui: gui,
controller: controller,
layout: lm,
detailedMode: false,
}
var infos = []key.BindingInfo{
@ -90,6 +92,19 @@ func newApp(gui *gocui.Gui, analysis *image.AnalysisResult, cache filetree.Compa
controller.views.Status.AddHelpKeys(globalHelpKeys...)
// dont show these key bindings on the status pane
quietKeys := []key.BindingInfo{
{
ConfigKeys: []string{"keybinding.toggle-details"},
OnAction: appSingleton.toggleDetails,
Display: "",
},
}
_, err = key.GenerateBindings(gui, "", quietKeys)
if err != nil {
return
}
// perform the first update and render now that all resources have been loaded
err = controller.UpdateAndRender()
if err != nil {
@ -106,8 +121,8 @@ func newApp(gui *gocui.Gui, analysis *image.AnalysisResult, cache filetree.Compa
// debugPrint writes the given string to the debug pane (if the debug pane is enabled)
// func debugPrint(s string) {
// if controllers.Tree != nil && controllers.Tree.gui != nil {
// v, _ := controllers.Tree.gui.View("debug")
// if controller.Tree != nil && controller.Tree.gui != nil {
// v, _ := controller.Tree.gui.View("debug")
// if v != nil {
// if len(v.BufferLines()) > 20 {
// v.Clear()
@ -126,6 +141,39 @@ func (a *app) quit() error {
return gocui.ErrQuit
}
func (a *app) toggleDetails() error {
if a.detailedMode {
err := a.layout.Remove(a.controller.views.Debug)
if err != nil {
logrus.Errorf("could not remove DEBUG pane")
}
a.layout.Add(a.controller.views.Tree, layout.LocationColumn)
a.controller.views.Debug.ToggleHide()
a.controller.views.Tree.ToggleHide()
a.controller.views.Status.Retop()
a.controller.views.Filter.Retop()
a.detailedMode = false
} else {
err := a.layout.Remove(a.controller.views.Tree)
if err != nil {
logrus.Errorf("could not remove TREE pane")
}
a.layout.Add(a.controller.views.Debug, layout.LocationColumn)
a.controller.views.Debug.ToggleHide()
a.controller.views.Tree.ToggleHide()
a.controller.views.Status.Retop()
a.controller.views.Filter.Retop()
a.detailedMode = true
}
return nil
}
// Run is the UI entrypoint.
func Run(analysis *image.AnalysisResult, treeStack filetree.Comparer) error {
var err error

View file

@ -13,9 +13,10 @@ import (
type Controller struct {
gui *gocui.Gui
views *view.Views
}
func NewCollection(g *gocui.Gui, analysis *image.AnalysisResult, cache filetree.Comparer) (*Controller, error) {
func NewController(g *gocui.Gui, analysis *image.AnalysisResult, cache filetree.Comparer) (*Controller, error) {
views, err := view.NewViews(g, analysis, cache)
if err != nil {
return nil, err

View file

@ -65,7 +65,11 @@ func RenderNoHeader(width int, selected bool) string {
return strings.Repeat(fillStr, width)
}
func RenderHeader(title string, width int, selected bool) string {
func RenderHeader(title string, width int, selected bool, nl bool) string {
newLine := "\n"
if !nl{
newLine = ""
}
if selected {
body := Header(fmt.Sprintf("%s%s ", selectStr, title))
bodyLen := len(vtclean.Clean(body, false))
@ -73,7 +77,7 @@ func RenderHeader(title string, width int, selected bool) string {
if repeatCount < 0 {
repeatCount = 0
}
return fmt.Sprintf("%s%s%s%s\n", selectedLeftBracketStr, body, selectedRightBracketStr, strings.Repeat(selectedFillStr, repeatCount))
return fmt.Sprintf("%s%s%s%s%s", selectedLeftBracketStr, body, selectedRightBracketStr, strings.Repeat(selectedFillStr, repeatCount), newLine)
//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))
}
@ -83,7 +87,7 @@ func RenderHeader(title string, width int, selected bool) string {
if repeatCount < 0 {
repeatCount = 0
}
return fmt.Sprintf("%s%s%s%s\n", leftBracketStr, body, rightBracketStr, strings.Repeat(fillStr, repeatCount))
return fmt.Sprintf("%s%s%s%s%s", leftBracketStr, body, rightBracketStr, strings.Repeat(fillStr, repeatCount), newLine)
}
func RenderHelpKey(control, title string, selected bool) string {

View file

@ -1,6 +1,7 @@
package layout
import (
"fmt"
"github.com/jroimartin/gocui"
"github.com/sirupsen/logrus"
)
@ -26,6 +27,22 @@ func (lm *Manager) Add(element Layout, location Location) {
lm.elements[location] = append(lm.elements[location], element)
}
func (lm *Manager) Remove(element Layout) error {
for location, elements := range lm.elements {
idx := -1
for i, el := range elements {
if el == element {
idx = i
}
}
if idx >= 0 {
lm.elements[location] = append(elements[:idx], elements[idx+1:]...)
return nil
}
}
return fmt.Errorf("could not remove element from layout manager")
}
func (lm *Manager) planAndLayoutHeaders(g *gocui.Gui, area Area) (Area, error) {
// layout headers top down
if elements, exists := lm.elements[LocationHeader]; exists {

View file

@ -14,6 +14,8 @@ type Debug struct {
gui *gocui.Gui
view *gocui.View
header *gocui.View
//once sync.Once
hidden bool
selectedView Helper
}
@ -25,6 +27,7 @@ func newDebugView(gui *gocui.Gui) (controller *Debug) {
// populate main fields
controller.name = "debug"
controller.gui = gui
controller.hidden = true
return controller
}
@ -37,6 +40,29 @@ func (v *Debug) Name() string {
return v.name
}
func (v *Debug) ToggleHide() error {
v.hidden = !v.hidden
if v.hidden {
logrus.Trace("hiding debug view...")
// take note: deleting a view will invoke layout again, so ensure this call is protected from an infinite loop
err := v.gui.DeleteView(v.Name())
if err != nil {
return err
}
// take note: deleting a view will invoke layout again, so ensure this call is protected from an infinite loop
err = v.gui.DeleteView(v.Name() + "header")
if err != nil {
return err
}
}
return nil
}
func (v *Debug) IsHidden() bool {
return v.hidden
}
// Setup initializes the UI concerns within the context of a global [gocui] view object.
func (v *Debug) Setup(view *gocui.View, header *gocui.View) error {
logrus.Tracef("view.Setup() %s", v.Name())
@ -82,7 +108,7 @@ func (v *Debug) Render() error {
// update header...
v.header.Clear()
width, _ := g.Size()
headerStr := format.RenderHeader("Debug", width, false)
headerStr := format.RenderHeader("Debug", width, false, false)
_, _ = fmt.Fprintln(v.header, headerStr)
// update view...
@ -98,7 +124,7 @@ func (v *Debug) Render() error {
}
func (v *Debug) Layout(g *gocui.Gui, minX, minY, maxX, maxY int) error {
logrus.Tracef("view.Layout(minX: %d, minY: %d, maxX: %d, maxY: %d) %s", minX, minY, maxX, maxY, v.Name())
logrus.Tracef("view.Layout(minX: %d, minY: %d, maxX: %d, maxY: %d, hidden: %v) %s", minX, minY, maxX, maxY, v.hidden, v.Name())
// header
headerSize := 1

View file

@ -157,8 +157,8 @@ func (v *Details) Render() error {
v.header.Clear()
width, _ := v.view.Size()
layerHeaderStr := format.RenderHeader("Layer Details", width, false)
imageHeaderStr := format.RenderHeader("Image Details", width, false)
layerHeaderStr := format.RenderHeader("Layer Details", width, false, false)
imageHeaderStr := format.RenderHeader("Image", width, false, false)
_, err := fmt.Fprintln(v.header, layerHeaderStr)
if err != nil {
@ -182,7 +182,8 @@ func (v *Details) Render() error {
lines = append(lines, imageSizeStr)
lines = append(lines, wastedSpaceStr)
lines = append(lines, effStr+"\n")
lines = append(lines, inefficiencyReport)
lines = append(lines, format.Header("[^D for detailed image information]"))
//lines = append(lines, inefficiencyReport)
_, err = fmt.Fprintln(v.view, strings.Join(lines, "\n"))
if err != nil {

View file

@ -11,6 +11,7 @@ import (
"github.com/wagoodman/dive/runtime/ui/viewmodel"
"github.com/wagoodman/dive/utils"
"regexp"
"sync"
)
type ViewOptionChangeListener func() error
@ -22,6 +23,8 @@ type FileTree struct {
gui *gocui.Gui
view *gocui.View
header *gocui.View
once sync.Once
hidden bool
vm *viewmodel.FileTree
title string
@ -73,6 +76,7 @@ func (v *FileTree) Name() string {
// Setup initializes the UI concerns within the context of a global [gocui] view object.
func (v *FileTree) Setup(view *gocui.View, header *gocui.View) error {
logrus.Tracef("view.Setup() %s", v.Name())
var err error
// set controller options
v.view = view
@ -85,89 +89,90 @@ func (v *FileTree) Setup(view *gocui.View, header *gocui.View) error {
v.header.Wrap = false
v.header.Frame = false
var infos = []key.BindingInfo{
{
ConfigKeys: []string{"keybinding.toggle-collapse-dir"},
OnAction: v.toggleCollapse,
Display: "Collapse dir",
},
{
ConfigKeys: []string{"keybinding.toggle-collapse-all-dir"},
OnAction: v.toggleCollapseAll,
Display: "Collapse all dir",
},
{
ConfigKeys: []string{"keybinding.toggle-added-files"},
OnAction: func() error { return v.toggleShowDiffType(filetree.Added) },
IsSelected: func() bool { return !v.vm.HiddenDiffTypes[filetree.Added] },
Display: "Added",
},
{
ConfigKeys: []string{"keybinding.toggle-removed-files"},
OnAction: func() error { return v.toggleShowDiffType(filetree.Removed) },
IsSelected: func() bool { return !v.vm.HiddenDiffTypes[filetree.Removed] },
Display: "Removed",
},
{
ConfigKeys: []string{"keybinding.toggle-modified-files"},
OnAction: func() error { return v.toggleShowDiffType(filetree.Modified) },
IsSelected: func() bool { return !v.vm.HiddenDiffTypes[filetree.Modified] },
Display: "Modified",
},
{
ConfigKeys: []string{"keybinding.toggle-unchanged-files", "keybinding.toggle-unmodified-files"},
OnAction: func() error { return v.toggleShowDiffType(filetree.Unmodified) },
IsSelected: func() bool { return !v.vm.HiddenDiffTypes[filetree.Unmodified] },
Display: "Unmodified",
},
{
ConfigKeys: []string{"keybinding.toggle-filetree-attributes"},
OnAction: v.toggleAttributes,
IsSelected: func() bool { return v.vm.ShowAttributes },
Display: "Attributes",
},
{
ConfigKeys: []string{"keybinding.page-up"},
OnAction: v.PageUp,
},
{
ConfigKeys: []string{"keybinding.page-down"},
OnAction: v.PageDown,
},
{
Key: gocui.KeyArrowDown,
Modifier: gocui.ModNone,
OnAction: v.CursorDown,
},
{
Key: gocui.KeyArrowUp,
Modifier: gocui.ModNone,
OnAction: v.CursorUp,
},
{
Key: gocui.KeyArrowLeft,
Modifier: gocui.ModNone,
OnAction: v.CursorLeft,
},
{
Key: gocui.KeyArrowRight,
Modifier: gocui.ModNone,
OnAction: v.CursorRight,
},
}
v.once.Do(func() {
var infos = []key.BindingInfo{
{
ConfigKeys: []string{"keybinding.toggle-collapse-dir"},
OnAction: v.toggleCollapse,
Display: "Collapse dir",
},
{
ConfigKeys: []string{"keybinding.toggle-collapse-all-dir"},
OnAction: v.toggleCollapseAll,
Display: "Collapse all dir",
},
{
ConfigKeys: []string{"keybinding.toggle-added-files"},
OnAction: func() error { return v.toggleShowDiffType(filetree.Added) },
IsSelected: func() bool { return !v.vm.HiddenDiffTypes[filetree.Added] },
Display: "Added",
},
{
ConfigKeys: []string{"keybinding.toggle-removed-files"},
OnAction: func() error { return v.toggleShowDiffType(filetree.Removed) },
IsSelected: func() bool { return !v.vm.HiddenDiffTypes[filetree.Removed] },
Display: "Removed",
},
{
ConfigKeys: []string{"keybinding.toggle-modified-files"},
OnAction: func() error { return v.toggleShowDiffType(filetree.Modified) },
IsSelected: func() bool { return !v.vm.HiddenDiffTypes[filetree.Modified] },
Display: "Modified",
},
{
ConfigKeys: []string{"keybinding.toggle-unchanged-files", "keybinding.toggle-unmodified-files"},
OnAction: func() error { return v.toggleShowDiffType(filetree.Unmodified) },
IsSelected: func() bool { return !v.vm.HiddenDiffTypes[filetree.Unmodified] },
Display: "Unmodified",
},
{
ConfigKeys: []string{"keybinding.toggle-filetree-attributes"},
OnAction: v.toggleAttributes,
IsSelected: func() bool { return v.vm.ShowAttributes },
Display: "Attributes",
},
{
ConfigKeys: []string{"keybinding.page-up"},
OnAction: v.PageUp,
},
{
ConfigKeys: []string{"keybinding.page-down"},
OnAction: v.PageDown,
},
{
Key: gocui.KeyArrowDown,
Modifier: gocui.ModNone,
OnAction: v.CursorDown,
},
{
Key: gocui.KeyArrowUp,
Modifier: gocui.ModNone,
OnAction: v.CursorUp,
},
{
Key: gocui.KeyArrowLeft,
Modifier: gocui.ModNone,
OnAction: v.CursorLeft,
},
{
Key: gocui.KeyArrowRight,
Modifier: gocui.ModNone,
OnAction: v.CursorRight,
},
}
helpKeys, err := key.GenerateBindings(v.gui, v.name, infos)
if err != nil {
return err
}
v.helpKeys = helpKeys
helpKeys, err := key.GenerateBindings(v.gui, v.name, infos)
if err != nil {
return
}
v.helpKeys = helpKeys
})
_, height := v.view.Size()
v.vm.Setup(0, height)
_ = v.Update()
_ = v.Render()
return nil
return err
}
// IsVisible indicates if the file tree view pane is currently initialized
@ -328,6 +333,30 @@ func (v *FileTree) toggleShowDiffType(diffType filetree.DiffType) error {
return v.notifyOnViewOptionChangeListeners()
}
func (v *FileTree) ToggleHide() error {
v.hidden = !v.hidden
if v.hidden {
logrus.Trace("hiding filetree view...")
// take note: deleting a view will invoke layout again, so ensure this call is protected from an infinite loop
err := v.gui.DeleteView(v.Name())
if err != nil {
return err
}
// take note: deleting a view will invoke layout again, so ensure this call is protected from an infinite loop
err = v.gui.DeleteView(v.Name() + "header")
if err != nil {
return err
}
}
return nil
}
func (v *FileTree) IsHidden() bool {
return v.hidden
}
// OnLayoutChange is called by the UI framework to inform the view-model of the new screen dimensions
func (v *FileTree) OnLayoutChange() error {
err := v.Update()
@ -362,7 +391,7 @@ func (v *FileTree) Render() error {
// update the header
v.header.Clear()
width, _ := g.Size()
headerStr := format.RenderHeader(title, width, isSelected)
headerStr := format.RenderHeader(title, width, isSelected, true)
if v.vm.ShowAttributes {
headerStr += fmt.Sprintf(filetree.AttributeFormat+" %s", "P", "ermission", "UID:GID", "Size", "Filetree")
}
@ -391,7 +420,7 @@ func (v *FileTree) KeyHelp() string {
}
func (v *FileTree) Layout(g *gocui.Gui, minX, minY, maxX, maxY int) error {
logrus.Tracef("view.Layout(minX: %d, minY: %d, maxX: %d, maxY: %d) %s", minX, minY, maxX, maxY, v.Name())
logrus.Tracef("view.Layout(minX: %d, minY: %d, maxX: %d, maxY: %d, hidden: %v) %s", minX, minY, maxX, maxY, v.hidden, v.Name())
attributeRowSize := 0
// make the layout responsive to the available realestate. Make more room for the main content by hiding auxillary
@ -408,7 +437,6 @@ func (v *FileTree) Layout(g *gocui.Gui, minX, minY, maxX, maxY int) error {
// header + attribute header
headerSize := 1 + attributeRowSize
// note: maxY needs to account for the (invisible) border, thus a +1
header, headerErr := g.SetView(v.Name()+"header", minX, minY, maxX, minY+headerSize+1)
// we are going to overlap the view over the (invisible) border (so minY will be one less than expected).
// additionally, maxY will be bumped by one to include the border

View file

@ -169,6 +169,19 @@ func (v *Filter) OnLayoutChange() error {
return v.Render()
}
func (v *Filter) Retop() {
logrus.Trace("asserting filter on top...")
// take note: deleting a view will invoke layout again, so ensure this call is protected from an infinite loop
err := v.gui.DeleteView(v.Name()+"label")
if err != nil {
logrus.Errorf("could not put filter label on top:", err)
}
err = v.gui.DeleteView(v.Name())
if err != nil {
logrus.Errorf("could not put filter on top:", err)
}
}
func (v *Filter) Layout(g *gocui.Gui, minX, minY, maxX, maxY int) error {
logrus.Tracef("view.Layout(minX: %d, minY: %d, maxX: %d, maxY: %d) %s", minX, minY, maxX, maxY, v.Name())

View file

@ -307,7 +307,7 @@ func (v *Layer) Render() error {
return err
}
} else {
headerStr := format.RenderHeader(title, width, isSelected)
headerStr := format.RenderHeader(title, width, isSelected, true)
headerStr += fmt.Sprintf("Cmp"+image.LayerFormat, "Size", "Command")
_, err := fmt.Fprintln(v.header, headerStr)
if err != nil {

View file

@ -110,6 +110,15 @@ func (v *Status) KeyHelp() string {
return help
}
func (v *Status) Retop() {
logrus.Trace("asserting status on top...")
// take note: deleting a view will invoke layout again, so ensure this call is protected from an infinite loop
err := v.gui.DeleteView(v.Name())
if err != nil {
logrus.Errorf("could not put status on top:", err)
}
}
func (v *Status) Layout(g *gocui.Gui, minX, minY, maxX, maxY int) error {
logrus.Tracef("view.Layout(minX: %d, minY: %d, maxX: %d, maxY: %d) %s", minX, minY, maxX, maxY, v.Name())