diff --git a/image/image.go b/image/image.go index 638efcd..b923433 100644 --- a/image/image.go +++ b/image/image.go @@ -135,8 +135,8 @@ func InitializeData(imageID string) ([]*Layer, []*filetree.FileTree) { // save this image to disk temporarily to get the content info fmt.Println("Fetching image...") imageTarPath, tmpDir := saveImage(imageID) - // imageTarPath := "/tmp/dive932744808/image.tar" - // tmpDir := "/tmp/dive031537738" + // imageTarPath := "/tmp/dive229500681/image.tar" + // tmpDir := "/tmp/dive229500681" // fmt.Println(tmpDir) defer os.RemoveAll(tmpDir) diff --git a/ui/detailsview.go b/ui/detailsview.go new file mode 100644 index 0000000..b215521 --- /dev/null +++ b/ui/detailsview.go @@ -0,0 +1,95 @@ +package ui + +import ( + "fmt" + + "github.com/jroimartin/gocui" + "github.com/lunixbochs/vtclean" + "strings" +) + +type DetailsView struct { + Name string + gui *gocui.Gui + view *gocui.View + header *gocui.View +} + +func NewStatisticsView(name string, gui *gocui.Gui) (detailsview *DetailsView) { + detailsview = new(DetailsView) + + // populate main fields + detailsview.Name = name + detailsview.gui = gui + + return detailsview +} + +func (view *DetailsView) Setup(v *gocui.View, header *gocui.View) error { + + // set view options + view.view = v + view.view.Editable = false + view.view.Wrap = true + view.view.Highlight = false + view.view.Frame = false + + view.header = header + view.header.Editable = false + view.header.Wrap = false + view.header.Frame = false + + // set keybindings + if err := view.gui.SetKeybinding(view.Name, gocui.KeyArrowDown, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.CursorDown() }); err != nil { + return err + } + if err := view.gui.SetKeybinding(view.Name, gocui.KeyArrowUp, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.CursorUp() }); err != nil { + return err + } + + return view.Render() +} + +func (view *DetailsView) IsVisible() bool { + if view == nil {return false} + return true +} + +func (view *DetailsView) Update() error { + return nil +} + +func (view *DetailsView) Render() error { + currentLayer := Views.Layer.currentLayer() + + view.gui.Update(func(g *gocui.Gui) error { + // update header + view.header.Clear() + width, _ := g.Size() + headerStr := fmt.Sprintf("[Image & Layer Details]%s", strings.Repeat("─",width*2)) + fmt.Fprintln(view.header, Formatting.Header(vtclean.Clean(headerStr, false))) + + // update contents + view.view.Clear() + fmt.Fprintln(view.view, Formatting.Header("Command")) + fmt.Fprintln(view.view, currentLayer.History.CreatedBy) + + return nil + }) + return nil +} + +func (view *DetailsView) CursorDown() error { + return CursorDown(view.gui, view.view) +} + +func (view *DetailsView) CursorUp() error { + return CursorUp(view.gui, view.view) +} + + +func (view *DetailsView) KeyHelp() string { + return "TBD" + // return renderStatusOption("^L","Layer changes", view.CompareMode == CompareLayer) + + // renderStatusOption("^A","All changes", view.CompareMode == CompareAll) +} diff --git a/ui/filetreeview.go b/ui/filetreeview.go index cec927c..b1622dc 100644 --- a/ui/filetreeview.go +++ b/ui/filetreeview.go @@ -101,6 +101,13 @@ func (view *FileTreeView) IsVisible() bool { return true } +func (view *FileTreeView) resetCursor() { + view.view.SetCursor(0, 0) + view.TreeIndex = 0 + view.bufferIndex = 0 + view.bufferIndexLowerBound = 0 + view.bufferIndexUpperBound = view.height() +} func (view *FileTreeView) setTreeByLayer(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop int) error { if topTreeStop > len(view.RefTrees)-1 { @@ -122,8 +129,8 @@ func (view *FileTreeView) setTreeByLayer(bottomTreeStart, bottomTreeStop, topTre } view.ModelTree.VisitDepthChildFirst(visitor, nil) - view.view.SetCursor(0, 0) - view.TreeIndex = 0 + view.resetCursor() + view.ModelTree = newTree view.Update() return view.Render() @@ -227,8 +234,7 @@ func (view *FileTreeView) toggleCollapse() error { func (view *FileTreeView) toggleShowDiffType(diffType filetree.DiffType) error { view.HiddenDiffTypes[diffType] = !view.HiddenDiffTypes[diffType] - view.view.SetCursor(0, 0) - view.TreeIndex = 0 + view.resetCursor() Update() Render() @@ -299,10 +305,22 @@ func (view *FileTreeView) Render() error { view.doCursorUp() } + title := "Current Layer Contents" + if Views.Layer.CompareMode == CompareAll { + title = "Aggregated Layer Contents" + } + + // indicate when selected + if view.gui.CurrentView() == view.view { + title = "● "+title + } + view.gui.Update(func(g *gocui.Gui) error { // update the header view.header.Clear() - headerStr := fmt.Sprintf(filetree.AttributeFormat+" %s", "P", "ermission", "UID:GID", "Size", "Filetree") + width, _ := g.Size() + headerStr := fmt.Sprintf("[%s]%s\n", title, strings.Repeat("─", width*2)) + headerStr += fmt.Sprintf(filetree.AttributeFormat+" %s", "P", "ermission", "UID:GID", "Size", "Filetree") fmt.Fprintln(view.header, Formatting.Header(vtclean.Clean(headerStr, false))) // update the contents diff --git a/ui/filterview.go b/ui/filterview.go index 5849891..a7fad08 100644 --- a/ui/filterview.go +++ b/ui/filterview.go @@ -18,13 +18,6 @@ type FilterView struct { hidden bool } -type Input struct { - name string - x, y int - w int - maxLength int -} - func NewFilterView(name string, gui *gocui.Gui) (filterview *FilterView) { filterview = new(FilterView) diff --git a/ui/layerview.go b/ui/layerview.go index 1881ee6..b36f6ea 100644 --- a/ui/layerview.go +++ b/ui/layerview.go @@ -7,6 +7,7 @@ import ( "github.com/wagoodman/dive/image" "github.com/lunixbochs/vtclean" "github.com/dustin/go-humanize" + "strings" ) type LayerView struct { @@ -38,9 +39,6 @@ func (view *LayerView) Setup(v *gocui.View, header *gocui.View) error { view.view = v view.view.Editable = false view.view.Wrap = false - //view.view.Highlight = true - //view.view.SelBgColor = gocui.ColorGreen - //view.view.SelFgColor = gocui.ColorBlack view.view.Frame = false view.header = header @@ -70,6 +68,10 @@ func (view *LayerView) IsVisible() bool { return true } +func (view *LayerView) currentLayer() *image.Layer { + return view.Layers[(len(view.Layers)-1)-view.LayerIndex] +} + func (view *LayerView) setCompareMode(compareMode CompareType) error { view.CompareMode = compareMode Update() @@ -99,12 +101,6 @@ func (view *LayerView) renderCompareBar(layerIdx int) string { bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop := view.getCompareIndexes() result := " " - //if debug { - // v, _ := view.gui.View("debug") - // v.Clear() - // _, _ = fmt.Fprintf(v, "bStart: %d bStop: %d tStart: %d tStop: %d", bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop) - //} - if layerIdx >= bottomTreeStart && layerIdx <= bottomTreeStop { result = Formatting.CompareBottom(" ") } @@ -112,18 +108,6 @@ func (view *LayerView) renderCompareBar(layerIdx int) string { result = Formatting.CompareTop(" ") } - //if bottomTreeStop == topTreeStart { - // result += " " - //} else { - // if layerIdx == bottomTreeStop { - // result += "─┐" - // } else if layerIdx == topTreeStart { - // result += "─┘" - // } else { - // result += " " - // } - //} - return result } @@ -132,9 +116,19 @@ func (view *LayerView) Update() error { } func (view *LayerView) Render() error { + + // indicate when selected + title := "Layers" + if view.gui.CurrentView() == view.view { + title = "● "+title + } + view.gui.Update(func(g *gocui.Gui) error { // update header - headerStr := fmt.Sprintf("Cmp "+image.LayerFormat, "Image ID", "%Eff.", "Size", "Filter") + view.header.Clear() + width, _ := g.Size() + headerStr := fmt.Sprintf("[%s]%s\n", title, strings.Repeat("─", width*2)) + headerStr += fmt.Sprintf("Cmp "+image.LayerFormat, "Image ID", "%Eff.", "Size", "Command") fmt.Fprintln(view.header, Formatting.Header(vtclean.Clean(headerStr, false))) // update contents @@ -174,10 +168,7 @@ func (view *LayerView) CursorDown() error { if view.LayerIndex < len(view.Layers) { err := CursorDown(view.gui, view.view) if err == nil { - view.LayerIndex++ - Views.Tree.setTreeByLayer(view.getCompareIndexes()) - view.Render() - // debugPrint(fmt.Sprintf("%d",len(filetree.Cache))) + view.SetCursor(view.LayerIndex+1) } } return nil @@ -187,25 +178,22 @@ func (view *LayerView) CursorUp() error { if view.LayerIndex > 0 { err := CursorUp(view.gui, view.view) if err == nil { - view.LayerIndex-- - Views.Tree.setTreeByLayer(view.getCompareIndexes()) - view.Render() - // debugPrint(fmt.Sprintf("%d",len(filetree.Cache))) + view.SetCursor(view.LayerIndex-1) } } return nil } func (view *LayerView) SetCursor(layer int) error { - // view.view.SetCursor(0, layer) view.LayerIndex = layer Views.Tree.setTreeByLayer(view.getCompareIndexes()) + Views.Details.Render() view.Render() return nil } func (view *LayerView) KeyHelp() string { - return renderStatusOption("^L","Layer changes", view.CompareMode == CompareLayer) + - renderStatusOption("^A","All changes", view.CompareMode == CompareAll) + return renderStatusOption("^L","Show layer changes", view.CompareMode == CompareLayer) + + renderStatusOption("^A","Show aggregated changes", view.CompareMode == CompareAll) } diff --git a/ui/ui.go b/ui/ui.go index 0f11759..ca7cd04 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -41,11 +41,12 @@ var Formatting struct { } var Views struct { - Tree *FileTreeView - Layer *LayerView - Status *StatusView - Filter *FilterView - lookup map[string]View + Tree *FileTreeView + Layer *LayerView + Status *StatusView + Filter *FilterView + Details *DetailsView + lookup map[string]View } type View interface { @@ -178,7 +179,7 @@ func layout(g *gocui.Gui) error { } debugCols := maxX - debugWidth bottomRows := 1 - headerRows := 1 + headerRows := 2 filterBarHeight := 1 statusBarHeight := 1 @@ -186,6 +187,8 @@ func layout(g *gocui.Gui) error { statusBarIndex := 1 filterBarIndex := 2 + layersHeight := len(Views.Layer.Layers) + headerRows + 1 // layers + header + base image layer row + var view, header *gocui.View var viewErr, headerErr, err error @@ -204,7 +207,7 @@ func layout(g *gocui.Gui) error { } // Layers - view, viewErr = g.SetView(Views.Layer.Name, -1, -1+headerRows, splitCols, maxY-bottomRows) + view, viewErr = g.SetView(Views.Layer.Name, -1, -1+headerRows, splitCols, layersHeight) header, headerErr = g.SetView(Views.Layer.Name+"header", -1, -1, splitCols, headerRows) if isNewView(viewErr, headerErr) { Views.Layer.Setup(view, header) @@ -212,6 +215,15 @@ func layout(g *gocui.Gui) error { if _, err = g.SetCurrentView(Views.Layer.Name); err != nil { return err } + // since we are selecting the view, we should rerender to indicate it is selected + Views.Layer.Render() + } + + // Details + view, viewErr = g.SetView(Views.Details.Name, -1, -1+layersHeight+headerRows, splitCols, maxY-bottomRows) + header, headerErr = g.SetView(Views.Details.Name+"header", -1, -1+layersHeight, splitCols, layersHeight+headerRows) + if isNewView(viewErr, headerErr) { + Views.Details.Setup(view, header) } // Filetree @@ -291,6 +303,10 @@ func Run(layers []*image.Layer, refTrees []*filetree.FileTree) { Views.Filter = NewFilterView("command", g) Views.lookup[Views.Filter.Name] = Views.Filter + Views.Details = NewStatisticsView("details", g) + Views.lookup[Views.Details.Name] = Views.Details + + g.Cursor = false //g.Mouse = true g.SetManagerFunc(layout)