first take at layer multiselect
This commit is contained in:
parent
02418eaad3
commit
08fd01072c
@ -1,14 +1,13 @@
|
|||||||
package filetree
|
package filetree
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/tar"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/phayes/permbits"
|
"github.com/phayes/permbits"
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/wagoodman/docker-image-explorer/_vendor-20180604210951/github.com/Microsoft/go-winio/archive/tar"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -217,9 +217,9 @@ func (tree *FileTree) MarkRemoved(path string) error {
|
|||||||
return node.AssignDiffType(Removed)
|
return node.AssignDiffType(Removed)
|
||||||
}
|
}
|
||||||
|
|
||||||
func StackRange(trees []*FileTree, index int) *FileTree {
|
func StackRange(trees []*FileTree, start, stop int) *FileTree {
|
||||||
tree := trees[0].Copy()
|
tree := trees[0].Copy()
|
||||||
for idx := 0; idx <= index; idx++ {
|
for idx := start; idx <= stop; idx++ {
|
||||||
tree.Stack(trees[idx])
|
tree.Stack(trees[idx])
|
||||||
}
|
}
|
||||||
return tree
|
return tree
|
||||||
|
@ -423,7 +423,7 @@ func TestStackRange(t *testing.T) {
|
|||||||
upperTree.AddPath(value, fakeData)
|
upperTree.AddPath(value, fakeData)
|
||||||
}
|
}
|
||||||
trees := []*FileTree{lowerTree, upperTree, tree}
|
trees := []*FileTree{lowerTree, upperTree, tree}
|
||||||
StackRange(trees, 2)
|
StackRange(trees, 0, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,12 +55,17 @@ type Layer struct {
|
|||||||
History types.ImageHistory
|
History types.ImageHistory
|
||||||
}
|
}
|
||||||
|
|
||||||
func (layer *Layer) String() string {
|
func (layer *Layer) Id() string {
|
||||||
id := layer.History.ID[0:25]
|
id := layer.History.ID[0:25]
|
||||||
if len(layer.History.Tags) > 0 {
|
if len(layer.History.Tags) > 0 {
|
||||||
id = "[" + strings.Join(layer.History.Tags, ",") + "]"
|
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) {
|
func InitializeData(imageID string) ([]*Layer, []*filetree.FileTree) {
|
||||||
|
@ -6,21 +6,30 @@ import (
|
|||||||
|
|
||||||
"github.com/jroimartin/gocui"
|
"github.com/jroimartin/gocui"
|
||||||
"github.com/wagoodman/docker-image-explorer/filetree"
|
"github.com/wagoodman/docker-image-explorer/filetree"
|
||||||
"github.com/fatih/color"
|
|
||||||
"strings"
|
"strings"
|
||||||
"github.com/lunixbochs/vtclean"
|
"github.com/lunixbochs/vtclean"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CompareLayer CompareType = iota
|
||||||
|
CompareAll
|
||||||
|
)
|
||||||
|
|
||||||
|
type CompareType int
|
||||||
|
|
||||||
|
|
||||||
type FileTreeView struct {
|
type FileTreeView struct {
|
||||||
Name string
|
Name string
|
||||||
gui *gocui.Gui
|
gui *gocui.Gui
|
||||||
view *gocui.View
|
view *gocui.View
|
||||||
header *gocui.View
|
header *gocui.View
|
||||||
TreeIndex int
|
ModelTree *filetree.FileTree
|
||||||
ModelTree *filetree.FileTree
|
ViewTree *filetree.FileTree
|
||||||
ViewTree *filetree.FileTree
|
RefTrees []*filetree.FileTree
|
||||||
RefTrees []*filetree.FileTree
|
HiddenDiffTypes []bool
|
||||||
HiddenDiffTypes []bool
|
CompareMode CompareType
|
||||||
|
CompareStartIndex int
|
||||||
|
CompareStopIndex int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileTreeView(name string, gui *gocui.Gui, tree *filetree.FileTree, refTrees []*filetree.FileTree) (treeview *FileTreeView) {
|
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.ModelTree = tree
|
||||||
treeview.RefTrees = refTrees
|
treeview.RefTrees = refTrees
|
||||||
treeview.HiddenDiffTypes = make([]bool, 4)
|
treeview.HiddenDiffTypes = make([]bool, 4)
|
||||||
|
treeview.CompareMode = CompareLayer
|
||||||
|
|
||||||
return treeview
|
return treeview
|
||||||
}
|
}
|
||||||
@ -88,8 +98,9 @@ func (view *FileTreeView) setLayer(layerIndex int) error {
|
|||||||
if layerIndex > len(view.RefTrees)-1 {
|
if layerIndex > len(view.RefTrees)-1 {
|
||||||
return errors.New(fmt.Sprintf("Invalid layer index given: %d of %d", 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)
|
view.CompareStopIndex = layerIndex
|
||||||
newTree.Compare(view.RefTrees[layerIndex])
|
newTree := filetree.StackRange(view.RefTrees, view.CompareStartIndex, view.CompareStopIndex-1)
|
||||||
|
newTree.Compare(view.RefTrees[view.CompareStopIndex])
|
||||||
|
|
||||||
// preserve view state on copy
|
// preserve view state on copy
|
||||||
visitor := func(node *filetree.FileNode) error {
|
visitor := func(node *filetree.FileNode) error {
|
||||||
@ -104,11 +115,11 @@ func (view *FileTreeView) setLayer(layerIndex int) error {
|
|||||||
if debug {
|
if debug {
|
||||||
v, _ := view.gui.View("debug")
|
v, _ := view.gui.View("debug")
|
||||||
v.Clear()
|
v.Clear()
|
||||||
_, _ = fmt.Fprintln(v, view.RefTrees[layerIndex])
|
_, _ = fmt.Fprintln(v, view.RefTrees[view.CompareStopIndex])
|
||||||
}
|
}
|
||||||
|
|
||||||
view.view.SetCursor(0, 0)
|
view.view.SetCursor(0, 0)
|
||||||
view.TreeIndex = 0
|
view.CompareStopIndex = 0
|
||||||
view.ModelTree = newTree
|
view.ModelTree = newTree
|
||||||
view.updateViewTree()
|
view.updateViewTree()
|
||||||
return view.Render()
|
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)
|
// to let us know what is a valid bounds (i.e. when it hits an empty line)
|
||||||
err := CursorDown(view.gui, view.view)
|
err := CursorDown(view.gui, view.view)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
view.TreeIndex++
|
view.CompareStopIndex++
|
||||||
}
|
}
|
||||||
return view.Render()
|
return view.Render()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *FileTreeView) CursorUp() error {
|
func (view *FileTreeView) CursorUp() error {
|
||||||
if view.TreeIndex > 0 {
|
if view.CompareStopIndex > 0 {
|
||||||
err := CursorUp(view.gui, view.view)
|
err := CursorUp(view.gui, view.view)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
view.TreeIndex--
|
view.CompareStopIndex--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return view.Render()
|
return view.Render()
|
||||||
@ -140,7 +151,7 @@ func (view *FileTreeView) getAbsPositionNode() (node *filetree.FileNode) {
|
|||||||
var dfsCounter int
|
var dfsCounter int
|
||||||
|
|
||||||
visiter = func(curNode *filetree.FileNode) error {
|
visiter = func(curNode *filetree.FileNode) error {
|
||||||
if dfsCounter == view.TreeIndex {
|
if dfsCounter == view.CompareStopIndex {
|
||||||
node = curNode
|
node = curNode
|
||||||
}
|
}
|
||||||
dfsCounter++
|
dfsCounter++
|
||||||
@ -170,7 +181,7 @@ func (view *FileTreeView) toggleShowDiffType(diffType filetree.DiffType) error {
|
|||||||
view.HiddenDiffTypes[diffType] = !view.HiddenDiffTypes[diffType]
|
view.HiddenDiffTypes[diffType] = !view.HiddenDiffTypes[diffType]
|
||||||
|
|
||||||
view.view.SetCursor(0, 0)
|
view.view.SetCursor(0, 0)
|
||||||
view.TreeIndex = 0
|
view.CompareStopIndex = 0
|
||||||
view.updateViewTree()
|
view.updateViewTree()
|
||||||
return view.Render()
|
return view.Render()
|
||||||
}
|
}
|
||||||
@ -193,12 +204,11 @@ func (view *FileTreeView) updateViewTree() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (view *FileTreeView) KeyHelp() string {
|
func (view *FileTreeView) KeyHelp() string {
|
||||||
control := color.New(color.Bold).SprintFunc()
|
return Formatting.Control("[Space]") + ": Collapse dir " +
|
||||||
return control("[Space]") + ": Collapse dir " +
|
Formatting.Control("[^A]") + ": Added files " +
|
||||||
control("[^A]") + ": Added files " +
|
Formatting.Control("[^R]") + ": Removed files " +
|
||||||
control("[^R]") + ": Removed files " +
|
Formatting.Control("[^M]") + ": Modified files " +
|
||||||
control("[^M]") + ": Modified files " +
|
Formatting.Control("[^U]") + ": Unmodified files"
|
||||||
control("[^U]") + ": Unmodified files"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *FileTreeView) Render() error {
|
func (view *FileTreeView) Render() error {
|
||||||
@ -207,7 +217,7 @@ func (view *FileTreeView) Render() error {
|
|||||||
view.gui.Update(func(g *gocui.Gui) error {
|
view.gui.Update(func(g *gocui.Gui) error {
|
||||||
view.view.Clear()
|
view.view.Clear()
|
||||||
for idx, line := range lines {
|
for idx, line := range lines {
|
||||||
if idx == view.TreeIndex {
|
if idx == view.CompareStopIndex {
|
||||||
fmt.Fprintln(view.view, Formatting.StatusBar(vtclean.Clean(line, false)))
|
fmt.Fprintln(view.view, Formatting.StatusBar(vtclean.Clean(line, false)))
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintln(view.view, line)
|
fmt.Fprintln(view.view, line)
|
||||||
|
@ -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 {
|
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 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")
|
headerStr := fmt.Sprintf(image.LayerFormat, "Image ID", "Size", "Command")
|
||||||
fmt.Fprintln(view.header, Formatting.Header(vtclean.Clean(headerStr, false)))
|
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()
|
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 {
|
func (view *LayerView) Render() error {
|
||||||
view.gui.Update(func(g *gocui.Gui) error {
|
view.gui.Update(func(g *gocui.Gui) error {
|
||||||
view.view.Clear()
|
view.view.Clear()
|
||||||
@ -65,10 +78,16 @@ func (view *LayerView) Render() error {
|
|||||||
layer := view.Layers[revIdx]
|
layer := view.Layers[revIdx]
|
||||||
idx := (len(view.Layers)-1) - 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 {
|
if idx == view.LayerIndex {
|
||||||
fmt.Fprintln(view.view, Formatting.StatusBar(layer.String()))
|
fmt.Fprintln(view.view, Formatting.StatusBar(layerStr))
|
||||||
} else {
|
} 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 {
|
func (view *LayerView) KeyHelp() string {
|
||||||
return "blerg"
|
return Formatting.Control("[^L]") + ": Layer Changes " +
|
||||||
|
Formatting.Control("[^A]") + ": All Changes "
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/jroimartin/gocui"
|
"github.com/jroimartin/gocui"
|
||||||
"github.com/fatih/color"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type StatusView struct {
|
type StatusView struct {
|
||||||
@ -53,10 +52,8 @@ func (view *StatusView) CursorUp() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (view *StatusView) KeyHelp() string {
|
func (view *StatusView) KeyHelp() string {
|
||||||
control := color.New(color.Bold).SprintFunc()
|
return Formatting.Control("[^C]") + ": Quit " +
|
||||||
return control("[^C]") + ": Quit " +
|
Formatting.Control("[^Space]") + ": Switch View "
|
||||||
control("[^Space]") + ": Switch View "
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *StatusView) Render() error {
|
func (view *StatusView) Render() error {
|
||||||
|
6
ui/ui.go
6
ui/ui.go
@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/wagoodman/docker-image-explorer/filetree"
|
"github.com/wagoodman/docker-image-explorer/filetree"
|
||||||
"github.com/wagoodman/docker-image-explorer/image"
|
"github.com/wagoodman/docker-image-explorer/image"
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/wagoodman/docker-image-explorer/_vendor-20180604210951/github.com/pkg/errors"
|
"errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const debug = false
|
const debug = false
|
||||||
@ -15,6 +15,7 @@ const debug = false
|
|||||||
var Formatting struct {
|
var Formatting struct {
|
||||||
Header func(...interface{})(string)
|
Header func(...interface{})(string)
|
||||||
StatusBar func(...interface{})(string)
|
StatusBar func(...interface{})(string)
|
||||||
|
Control func(...interface{})(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
var Views struct {
|
var Views struct {
|
||||||
@ -168,6 +169,7 @@ func Render() {
|
|||||||
func Run(layers []*image.Layer, refTrees []*filetree.FileTree) {
|
func Run(layers []*image.Layer, refTrees []*filetree.FileTree) {
|
||||||
Formatting.StatusBar = color.New(color.ReverseVideo, color.Bold).SprintFunc()
|
Formatting.StatusBar = color.New(color.ReverseVideo, color.Bold).SprintFunc()
|
||||||
Formatting.Header = color.New(color.Bold).SprintFunc()
|
Formatting.Header = color.New(color.Bold).SprintFunc()
|
||||||
|
Formatting.Control = color.New(color.Bold).SprintFunc()
|
||||||
|
|
||||||
g, err := gocui.NewGui(gocui.OutputNormal)
|
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -180,7 +182,7 @@ func Run(layers []*image.Layer, refTrees []*filetree.FileTree) {
|
|||||||
Views.Layer = NewLayerView("side", g, layers)
|
Views.Layer = NewLayerView("side", g, layers)
|
||||||
Views.lookup[Views.Layer.Name] = Views.Layer
|
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.lookup[Views.Tree.Name] = Views.Tree
|
||||||
|
|
||||||
Views.Status = NewStatusView("status", g)
|
Views.Status = NewStatusView("status", g)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user