From 08fd01072c87b1765c92f38834a4ec625c870d30 Mon Sep 17 00:00:00 2001
From: Alex Goodman <wagoodman@gmail.com>
Date: Sun, 24 Jun 2018 15:26:56 -0400
Subject: [PATCH] first take at layer multiselect

---
 filetree/node.go      |  3 +--
 filetree/tree.go      |  4 +--
 filetree/tree_test.go |  2 +-
 image/image.go        |  9 +++++--
 ui/filetreeview.go    | 62 +++++++++++++++++++++++++------------------
 ui/layerview.go       | 26 +++++++++++++++---
 ui/statusview.go      |  7 ++---
 ui/ui.go              |  6 +++--
 8 files changed, 76 insertions(+), 43 deletions(-)

diff --git a/filetree/node.go b/filetree/node.go
index 979ecf1..8ba41f0 100644
--- a/filetree/node.go
+++ b/filetree/node.go
@@ -1,14 +1,13 @@
 package filetree
 
 import (
+	"archive/tar"
 	"sort"
 	"strings"
-
 	"github.com/fatih/color"
 	"fmt"
 	"github.com/phayes/permbits"
 	"github.com/dustin/go-humanize"
-	"github.com/wagoodman/docker-image-explorer/_vendor-20180604210951/github.com/Microsoft/go-winio/archive/tar"
 )
 
 const (
diff --git a/filetree/tree.go b/filetree/tree.go
index 01e405f..5fdbcbe 100644
--- a/filetree/tree.go
+++ b/filetree/tree.go
@@ -217,9 +217,9 @@ func (tree *FileTree) MarkRemoved(path string) error {
 	return node.AssignDiffType(Removed)
 }
 
-func StackRange(trees []*FileTree, index int) *FileTree {
+func StackRange(trees []*FileTree, start, stop int) *FileTree {
 	tree := trees[0].Copy()
-	for idx := 0; idx <= index; idx++ {
+	for idx := start; idx <= stop; idx++ {
 		tree.Stack(trees[idx])
 	}
 	return tree
diff --git a/filetree/tree_test.go b/filetree/tree_test.go
index 44b8e60..429d0de 100644
--- a/filetree/tree_test.go
+++ b/filetree/tree_test.go
@@ -423,7 +423,7 @@ func TestStackRange(t *testing.T) {
 		upperTree.AddPath(value, fakeData)
 	}
 	trees := []*FileTree{lowerTree, upperTree, tree}
-	StackRange(trees, 2)
+	StackRange(trees, 0, 2)
 }
 
 
diff --git a/image/image.go b/image/image.go
index 4934fee..6faf921 100644
--- a/image/image.go
+++ b/image/image.go
@@ -55,12 +55,17 @@ type Layer struct {
 	History types.ImageHistory
 }
 
-func (layer *Layer) String() string {
+func (layer *Layer) Id() string {
 	id := layer.History.ID[0:25]
 	if len(layer.History.Tags) > 0 {
 		id = "[" + strings.Join(layer.History.Tags, ",") + "]"
 	}
-	return fmt.Sprintf(LayerFormat, id, humanize.Bytes(uint64(layer.History.Size)), layer.History.CreatedBy)
+	return id
+}
+
+func (layer *Layer) String() string {
+
+	return fmt.Sprintf(LayerFormat, layer.Id(), humanize.Bytes(uint64(layer.History.Size)), strings.TrimPrefix(layer.History.CreatedBy, "/bin/sh -c "))
 }
 
 func InitializeData(imageID string) ([]*Layer, []*filetree.FileTree) {
diff --git a/ui/filetreeview.go b/ui/filetreeview.go
index 8b49f8e..5f95592 100644
--- a/ui/filetreeview.go
+++ b/ui/filetreeview.go
@@ -6,21 +6,30 @@ import (
 
 	"github.com/jroimartin/gocui"
 	"github.com/wagoodman/docker-image-explorer/filetree"
-	"github.com/fatih/color"
 	"strings"
 	"github.com/lunixbochs/vtclean"
 )
 
+const (
+	CompareLayer CompareType = iota
+	CompareAll
+)
+
+type CompareType int
+
+
 type FileTreeView struct {
-	Name            string
-	gui             *gocui.Gui
-	view            *gocui.View
-	header          *gocui.View
-	TreeIndex       int
-	ModelTree       *filetree.FileTree
-	ViewTree        *filetree.FileTree
-	RefTrees        []*filetree.FileTree
-	HiddenDiffTypes []bool
+	Name              string
+	gui               *gocui.Gui
+	view              *gocui.View
+	header            *gocui.View
+	ModelTree         *filetree.FileTree
+	ViewTree          *filetree.FileTree
+	RefTrees          []*filetree.FileTree
+	HiddenDiffTypes   []bool
+	CompareMode       CompareType
+	CompareStartIndex int
+	CompareStopIndex  int
 }
 
 func NewFileTreeView(name string, gui *gocui.Gui, tree *filetree.FileTree, refTrees []*filetree.FileTree) (treeview *FileTreeView) {
@@ -32,6 +41,7 @@ func NewFileTreeView(name string, gui *gocui.Gui, tree *filetree.FileTree, refTr
 	treeview.ModelTree = tree
 	treeview.RefTrees = refTrees
 	treeview.HiddenDiffTypes = make([]bool, 4)
+	treeview.CompareMode = CompareLayer
 
 	return treeview
 }
@@ -88,8 +98,9 @@ func (view *FileTreeView) setLayer(layerIndex int) error {
 	if layerIndex > len(view.RefTrees)-1 {
 		return errors.New(fmt.Sprintf("Invalid layer index given: %d of %d", layerIndex, len(view.RefTrees)-1))
 	}
-	newTree := filetree.StackRange(view.RefTrees, layerIndex-1)
-	newTree.Compare(view.RefTrees[layerIndex])
+	view.CompareStopIndex = layerIndex
+	newTree := filetree.StackRange(view.RefTrees, view.CompareStartIndex, view.CompareStopIndex-1)
+	newTree.Compare(view.RefTrees[view.CompareStopIndex])
 
 	// preserve view state on copy
 	visitor := func(node *filetree.FileNode) error {
@@ -104,11 +115,11 @@ func (view *FileTreeView) setLayer(layerIndex int) error {
 	if debug {
 		v, _ := view.gui.View("debug")
 		v.Clear()
-		_, _ = fmt.Fprintln(v, view.RefTrees[layerIndex])
+		_, _ = fmt.Fprintln(v, view.RefTrees[view.CompareStopIndex])
 	}
 
 	view.view.SetCursor(0, 0)
-	view.TreeIndex = 0
+	view.CompareStopIndex = 0
 	view.ModelTree = newTree
 	view.updateViewTree()
 	return view.Render()
@@ -119,16 +130,16 @@ func (view *FileTreeView) CursorDown() error {
 	// to let us know what is a valid bounds (i.e. when it hits an empty line)
 	err := CursorDown(view.gui, view.view)
 	if err == nil {
-		view.TreeIndex++
+		view.CompareStopIndex++
 	}
 	return view.Render()
 }
 
 func (view *FileTreeView) CursorUp() error {
-	if view.TreeIndex > 0 {
+	if view.CompareStopIndex > 0 {
 		err := CursorUp(view.gui, view.view)
 		if err == nil {
-			view.TreeIndex--
+			view.CompareStopIndex--
 		}
 	}
 	return view.Render()
@@ -140,7 +151,7 @@ func (view *FileTreeView) getAbsPositionNode() (node *filetree.FileNode) {
 	var dfsCounter int
 
 	visiter = func(curNode *filetree.FileNode) error {
-		if dfsCounter == view.TreeIndex {
+		if dfsCounter == view.CompareStopIndex {
 			node = curNode
 		}
 		dfsCounter++
@@ -170,7 +181,7 @@ func (view *FileTreeView) toggleShowDiffType(diffType filetree.DiffType) error {
 	view.HiddenDiffTypes[diffType] = !view.HiddenDiffTypes[diffType]
 
 	view.view.SetCursor(0, 0)
-	view.TreeIndex = 0
+	view.CompareStopIndex = 0
 	view.updateViewTree()
 	return view.Render()
 }
@@ -193,12 +204,11 @@ func (view *FileTreeView) updateViewTree() {
 }
 
 func (view *FileTreeView) KeyHelp() string {
-	control := color.New(color.Bold).SprintFunc()
-	return  control("[Space]") + ": Collapse dir " +
-	        control("[^A]") + ": Added files " +
-			control("[^R]") + ": Removed files " +
-			control("[^M]") + ": Modified files " +
-			control("[^U]") + ": Unmodified files"
+	return  Formatting.Control("[Space]") + ": Collapse dir " +
+		Formatting.Control("[^A]") + ": Added files " +
+		Formatting.Control("[^R]") + ": Removed files " +
+		Formatting.Control("[^M]") + ": Modified files " +
+		Formatting.Control("[^U]") + ": Unmodified files"
 }
 
 func (view *FileTreeView) Render() error {
@@ -207,7 +217,7 @@ func (view *FileTreeView) Render() error {
 	view.gui.Update(func(g *gocui.Gui) error {
 		view.view.Clear()
 		for idx, line := range lines {
-			if idx == view.TreeIndex {
+			if idx == view.CompareStopIndex {
 				fmt.Fprintln(view.view, Formatting.StatusBar(vtclean.Clean(line, false)))
 			} else {
 				fmt.Fprintln(view.view, line)
diff --git a/ui/layerview.go b/ui/layerview.go
index d100194..f4eefa9 100644
--- a/ui/layerview.go
+++ b/ui/layerview.go
@@ -51,6 +51,13 @@ func (view *LayerView) Setup(v *gocui.View, header *gocui.View) error {
 	if err := view.gui.SetKeybinding(view.Name, gocui.KeyArrowUp, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.CursorUp() }); err != nil {
 		return err
 	}
+	if err := view.gui.SetKeybinding(view.Name, gocui.KeyCtrlL, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.setCompareMode(CompareLayer) }); err != nil {
+		return err
+	}
+	if err := view.gui.SetKeybinding(view.Name, gocui.KeyCtrlA, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.setCompareMode(CompareAll) }); err != nil {
+		return err
+	}
+
 
 	headerStr := fmt.Sprintf(image.LayerFormat, "Image ID", "Size", "Command")
 	fmt.Fprintln(view.header, Formatting.Header(vtclean.Clean(headerStr, false)))
@@ -58,6 +65,12 @@ func (view *LayerView) Setup(v *gocui.View, header *gocui.View) error {
 	return view.Render()
 }
 
+func (view *LayerView) setCompareMode(compareMode CompareType) error {
+	Views.Tree.CompareMode = compareMode
+	view.Render()
+	return Views.Tree.setLayer(Views.Tree.CompareStopIndex)
+}
+
 func (view *LayerView) Render() error {
 	view.gui.Update(func(g *gocui.Gui) error {
 		view.view.Clear()
@@ -65,10 +78,16 @@ func (view *LayerView) Render() error {
 			layer := view.Layers[revIdx]
 			idx := (len(view.Layers)-1) - revIdx
 
+			layerStr := layer.String()
+			if idx == 0 {
+				// TODO: add size
+				layerStr = fmt.Sprintf(image.LayerFormat, layer.History.ID[0:25], "", "FROM "+layer.Id())
+			}
+
 			if idx == view.LayerIndex {
-				fmt.Fprintln(view.view, Formatting.StatusBar(layer.String()))
+				fmt.Fprintln(view.view, Formatting.StatusBar(layerStr))
 			} else {
-				fmt.Fprintln(view.view, layer.String())
+				fmt.Fprintln(view.view, layerStr)
 			}
 
 		}
@@ -103,5 +122,6 @@ func (view *LayerView) CursorUp() error {
 }
 
 func (view *LayerView) KeyHelp() string {
-	return "blerg"
+	return  Formatting.Control("[^L]") + ": Layer Changes " +
+		Formatting.Control("[^A]") + ": All Changes "
 }
diff --git a/ui/statusview.go b/ui/statusview.go
index 99cc579..a6329bc 100644
--- a/ui/statusview.go
+++ b/ui/statusview.go
@@ -4,7 +4,6 @@ import (
 	"fmt"
 
 	"github.com/jroimartin/gocui"
-	"github.com/fatih/color"
 )
 
 type StatusView struct {
@@ -53,10 +52,8 @@ func (view *StatusView) CursorUp() error {
 }
 
 func (view *StatusView) KeyHelp() string {
-	control := color.New(color.Bold).SprintFunc()
-	return  control("[^C]") + ": Quit " +
-		control("[^Space]") + ": Switch View "
-
+	return  Formatting.Control("[^C]") + ": Quit " +
+		Formatting.Control("[^Space]") + ": Switch View "
 }
 
 func (view *StatusView) Render() error {
diff --git a/ui/ui.go b/ui/ui.go
index 434adfd..8b58eb3 100644
--- a/ui/ui.go
+++ b/ui/ui.go
@@ -7,7 +7,7 @@ import (
 	"github.com/wagoodman/docker-image-explorer/filetree"
 	"github.com/wagoodman/docker-image-explorer/image"
 	"github.com/fatih/color"
-	"github.com/wagoodman/docker-image-explorer/_vendor-20180604210951/github.com/pkg/errors"
+	"errors"
 )
 
 const debug = false
@@ -15,6 +15,7 @@ const debug = false
 var Formatting struct {
 	Header func(...interface{})(string)
 	StatusBar func(...interface{})(string)
+	Control func(...interface{})(string)
 }
 
 var Views struct {
@@ -168,6 +169,7 @@ func Render() {
 func Run(layers []*image.Layer, refTrees []*filetree.FileTree) {
 	Formatting.StatusBar = color.New(color.ReverseVideo, color.Bold).SprintFunc()
 	Formatting.Header = color.New(color.Bold).SprintFunc()
+	Formatting.Control = color.New(color.Bold).SprintFunc()
 
 	g, err := gocui.NewGui(gocui.OutputNormal)
 	if err != nil {
@@ -180,7 +182,7 @@ func Run(layers []*image.Layer, refTrees []*filetree.FileTree) {
 	Views.Layer = NewLayerView("side", g, layers)
 	Views.lookup[Views.Layer.Name] = Views.Layer
 
-	Views.Tree = NewFileTreeView("main", g, filetree.StackRange(refTrees, 0), refTrees)
+	Views.Tree = NewFileTreeView("main", g, filetree.StackRange(refTrees, 0,0), refTrees)
 	Views.lookup[Views.Tree.Name] = Views.Tree
 
 	Views.Status = NewStatusView("status", g)