gofmt
This commit is contained in:
parent
8ca96849da
commit
e700ec3745
@ -1,12 +1,12 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"fmt"
|
||||
"os"
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/wagoodman/dive/image"
|
||||
"github.com/wagoodman/dive/ui"
|
||||
"github.com/fatih/color"
|
||||
"os"
|
||||
)
|
||||
|
||||
// analyze takes a docker image tag, digest, or id and displayes the
|
||||
@ -20,4 +20,4 @@ func analyze(cmd *cobra.Command, args []string) {
|
||||
color.New(color.Bold).Println("Analyzing Image")
|
||||
manifest, refTrees := image.InitializeData(userImage)
|
||||
ui.Run(manifest, refTrees)
|
||||
}
|
||||
}
|
||||
|
22
cmd/build.go
22
cmd/build.go
@ -1,23 +1,23 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"os/exec"
|
||||
"os"
|
||||
"strings"
|
||||
"io/ioutil"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/wagoodman/dive/image"
|
||||
"github.com/wagoodman/dive/ui"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// buildCmd represents the build command
|
||||
var buildCmd = &cobra.Command{
|
||||
Use: "build",
|
||||
Short: "Build and analyze a docker image",
|
||||
Long: `Build and analyze a docker image`,
|
||||
Use: "build",
|
||||
Short: "Build and analyze a docker image",
|
||||
Long: `Build and analyze a docker image`,
|
||||
DisableFlagParsing: true,
|
||||
Run: doBuild,
|
||||
Run: doBuild,
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -48,7 +48,7 @@ func doBuild(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
|
||||
// runDockerCmd runs a given Docker command in the current tty
|
||||
func runDockerCmd(cmdStr string, args... string) error {
|
||||
func runDockerCmd(cmdStr string, args ...string) error {
|
||||
|
||||
allArgs := cleanArgs(append([]string{cmdStr}, args...))
|
||||
|
||||
@ -70,4 +70,4 @@ func cleanArgs(s []string) []string {
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
16
cmd/root.go
16
cmd/root.go
@ -2,11 +2,11 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"os"
|
||||
)
|
||||
|
||||
var cfgFile string
|
||||
@ -15,9 +15,9 @@ var cfgFile string
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "dive",
|
||||
Short: "Docker Image Visualizer & Explorer",
|
||||
Long: `Docker Image Visualizer & Explorer`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: analyze,
|
||||
Long: `Docker Image Visualizer & Explorer`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: analyze,
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
@ -73,7 +73,7 @@ func initLogging() {
|
||||
// TODO: clean this up and make more configurable
|
||||
var filename string = "dive.log"
|
||||
// create the log file if doesn't exist. And append to it if it already exists.
|
||||
f, err := os.OpenFile(filename, os.O_WRONLY | os.O_APPEND | os.O_CREATE, 0644)
|
||||
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
|
||||
Formatter := new(log.TextFormatter)
|
||||
Formatter.DisableTimestamp = true
|
||||
log.SetFormatter(Formatter)
|
||||
@ -81,8 +81,8 @@ func initLogging() {
|
||||
if err != nil {
|
||||
// cannot open log file. Logging to stderr
|
||||
fmt.Println(err)
|
||||
}else{
|
||||
} else {
|
||||
log.SetOutput(f)
|
||||
}
|
||||
log.Debug("Starting Dive...")
|
||||
}
|
||||
}
|
||||
|
@ -17,9 +17,9 @@ const (
|
||||
|
||||
// NodeData is the payload for a FileNode
|
||||
type NodeData struct {
|
||||
ViewInfo ViewInfo
|
||||
FileInfo FileInfo
|
||||
DiffType DiffType
|
||||
ViewInfo ViewInfo
|
||||
FileInfo FileInfo
|
||||
DiffType DiffType
|
||||
}
|
||||
|
||||
// ViewInfo contains UI specific detail for a specific FileNode
|
||||
@ -40,7 +40,7 @@ type FileInfo struct {
|
||||
type DiffType int
|
||||
|
||||
// NewNodeData creates an empty NodeData struct for a FileNode
|
||||
func NewNodeData() (*NodeData) {
|
||||
func NewNodeData() *NodeData {
|
||||
return &NodeData{
|
||||
ViewInfo: *NewViewInfo(),
|
||||
FileInfo: FileInfo{},
|
||||
@ -49,7 +49,7 @@ func NewNodeData() (*NodeData) {
|
||||
}
|
||||
|
||||
// Copy duplicates a NodeData
|
||||
func (data *NodeData) Copy() (*NodeData) {
|
||||
func (data *NodeData) Copy() *NodeData {
|
||||
return &NodeData{
|
||||
ViewInfo: *data.ViewInfo.Copy(),
|
||||
FileInfo: *data.FileInfo.Copy(),
|
||||
@ -57,12 +57,11 @@ func (data *NodeData) Copy() (*NodeData) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// NewViewInfo creates a default ViewInfo
|
||||
func NewViewInfo() (view *ViewInfo) {
|
||||
return &ViewInfo{
|
||||
Collapsed: false,
|
||||
Hidden: false,
|
||||
Hidden: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,7 +119,6 @@ func (data *FileInfo) Compare(other FileInfo) DiffType {
|
||||
return Changed
|
||||
}
|
||||
|
||||
|
||||
// String of a DiffType
|
||||
func (diff DiffType) String() string {
|
||||
switch diff {
|
||||
@ -145,5 +143,3 @@ func (diff DiffType) merge(other DiffType) DiffType {
|
||||
}
|
||||
return Changed
|
||||
}
|
||||
|
||||
|
||||
|
@ -42,7 +42,7 @@ func Efficiency(trees []*FileTree) (float64, EfficiencySlice) {
|
||||
if _, ok := efficiencyMap[path]; !ok {
|
||||
efficiencyMap[path] = &EfficiencyData{
|
||||
Path: path,
|
||||
Nodes: make([]*FileNode,0),
|
||||
Nodes: make([]*FileNode, 0),
|
||||
minDiscoveredSize: -1,
|
||||
}
|
||||
}
|
||||
@ -66,7 +66,6 @@ func Efficiency(trees []*FileTree) (float64, EfficiencySlice) {
|
||||
tree.VisitDepthChildFirst(visitor, visitEvaluator)
|
||||
}
|
||||
|
||||
|
||||
// calculate the score
|
||||
var minimumPathSizes int64
|
||||
var discoveredPathSizes int64
|
||||
@ -81,5 +80,3 @@ func Efficiency(trees []*FileTree) (float64, EfficiencySlice) {
|
||||
|
||||
return score, inefficientMatches
|
||||
}
|
||||
|
||||
|
||||
|
@ -47,4 +47,3 @@ package filetree
|
||||
// t.Fatalf("Expected %f but got %f", expected, actual)
|
||||
// }
|
||||
// }
|
||||
|
||||
|
@ -15,10 +15,10 @@ const (
|
||||
AttributeFormat = "%s%s %10s %10s "
|
||||
)
|
||||
|
||||
var diffTypeColor = map[DiffType]*color.Color {
|
||||
Added: color.New(color.FgGreen),
|
||||
Removed: color.New(color.FgRed),
|
||||
Changed: color.New(color.FgYellow),
|
||||
var diffTypeColor = map[DiffType]*color.Color{
|
||||
Added: color.New(color.FgGreen),
|
||||
Removed: color.New(color.FgRed),
|
||||
Changed: color.New(color.FgYellow),
|
||||
Unchanged: color.New(color.Reset),
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,6 @@ func TestAddChild(t *testing.T) {
|
||||
t.Errorf("Expected 'ones' payload to be %+v got %+v.", expectedFC, actualFC)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
func TestRemoveChild(t *testing.T) {
|
||||
|
@ -2,9 +2,9 @@ package filetree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"github.com/satori/go.uuid"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -20,11 +20,11 @@ const (
|
||||
|
||||
// FileTree represents a set of files, directories, and their relations.
|
||||
type FileTree struct {
|
||||
Root *FileNode
|
||||
Size int
|
||||
Root *FileNode
|
||||
Size int
|
||||
FileSize uint64
|
||||
Name string
|
||||
Id uuid.UUID
|
||||
Name string
|
||||
Id uuid.UUID
|
||||
}
|
||||
|
||||
// NewFileTree creates an empty FileTree
|
||||
@ -40,23 +40,23 @@ func NewFileTree() (tree *FileTree) {
|
||||
|
||||
// renderParams is a representation of a FileNode in the context of the greater tree. All
|
||||
// data stored is necessary for rendering a single line in a tree format.
|
||||
type renderParams struct{
|
||||
node *FileNode
|
||||
spaces []bool
|
||||
childSpaces []bool
|
||||
type renderParams struct {
|
||||
node *FileNode
|
||||
spaces []bool
|
||||
childSpaces []bool
|
||||
showCollapsed bool
|
||||
isLast bool
|
||||
isLast bool
|
||||
}
|
||||
|
||||
// renderStringTreeBetween returns a string representing the given tree between the given rows. Since each node
|
||||
// is rendered on its own line, the returned string shows the visible nodes not affected by a collapsed parent.
|
||||
func (tree *FileTree) renderStringTreeBetween(startRow, stopRow int, showAttributes bool) string {
|
||||
// generate a list of nodes to render
|
||||
var params = make([]renderParams,0)
|
||||
var params = make([]renderParams, 0)
|
||||
var result string
|
||||
|
||||
// visit from the front of the list
|
||||
var paramsToVisit = []renderParams{ {node: tree.Root, spaces: []bool{}, showCollapsed: false, isLast: false} }
|
||||
var paramsToVisit = []renderParams{{node: tree.Root, spaces: []bool{}, showCollapsed: false, isLast: false}}
|
||||
for currentRow := 0; len(paramsToVisit) > 0 && currentRow <= stopRow; currentRow++ {
|
||||
// pop the first node
|
||||
var currentParams renderParams
|
||||
@ -70,7 +70,7 @@ func (tree *FileTree) renderStringTreeBetween(startRow, stopRow int, showAttribu
|
||||
// we should always visit nodes in order
|
||||
sort.Strings(keys)
|
||||
|
||||
var childParams = make([]renderParams,0)
|
||||
var childParams = make([]renderParams, 0)
|
||||
for idx, name := range keys {
|
||||
child := currentParams.node.Children[name]
|
||||
// don't visit this node...
|
||||
@ -91,11 +91,11 @@ func (tree *FileTree) renderStringTreeBetween(startRow, stopRow int, showAttribu
|
||||
}
|
||||
|
||||
childParams = append(childParams, renderParams{
|
||||
node: child,
|
||||
spaces: currentParams.childSpaces,
|
||||
childSpaces: childSpaces,
|
||||
node: child,
|
||||
spaces: currentParams.childSpaces,
|
||||
childSpaces: childSpaces,
|
||||
showCollapsed: showCollapsed,
|
||||
isLast: isLast,
|
||||
isLast: isLast,
|
||||
})
|
||||
}
|
||||
// keep the child nodes to visit later
|
||||
|
@ -13,10 +13,10 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/wagoodman/dive/filetree"
|
||||
"golang.org/x/net/context"
|
||||
"github.com/wagoodman/jotframe"
|
||||
"github.com/k0kubun/go-ansi"
|
||||
"github.com/wagoodman/dive/filetree"
|
||||
"github.com/wagoodman/jotframe"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// TODO: this file should be rethought... but since it's only for preprocessing it'll be tech debt for now.
|
||||
@ -28,12 +28,12 @@ func check(e error) {
|
||||
}
|
||||
|
||||
type ProgressBar struct {
|
||||
percent int
|
||||
rawTotal int64
|
||||
rawCurrent int64
|
||||
percent int
|
||||
rawTotal int64
|
||||
rawCurrent int64
|
||||
}
|
||||
|
||||
func NewProgressBar(total int64) *ProgressBar{
|
||||
func NewProgressBar(total int64) *ProgressBar {
|
||||
return &ProgressBar{
|
||||
rawTotal: total,
|
||||
}
|
||||
@ -46,7 +46,7 @@ func (pb *ProgressBar) Done() {
|
||||
|
||||
func (pb *ProgressBar) Update(currentValue int64) (hasChanged bool) {
|
||||
pb.rawCurrent = currentValue
|
||||
percent := int(100.0*(float64(pb.rawCurrent) / float64(pb.rawTotal)))
|
||||
percent := int(100.0 * (float64(pb.rawCurrent) / float64(pb.rawTotal)))
|
||||
if percent != pb.percent {
|
||||
hasChanged = true
|
||||
}
|
||||
@ -56,7 +56,7 @@ func (pb *ProgressBar) Update(currentValue int64) (hasChanged bool) {
|
||||
|
||||
func (pb *ProgressBar) String() string {
|
||||
width := 40
|
||||
done := int((pb.percent*width)/100.0)
|
||||
done := int((pb.percent * width) / 100.0)
|
||||
todo := width - done
|
||||
head := 1
|
||||
// if pb.percent >= 100 {
|
||||
@ -74,21 +74,21 @@ type ImageManifest struct {
|
||||
|
||||
type ImageConfig struct {
|
||||
History []ImageHistoryEntry `json:"history"`
|
||||
RootFs RootFs `json:"rootfs"`
|
||||
RootFs RootFs `json:"rootfs"`
|
||||
}
|
||||
|
||||
type RootFs struct {
|
||||
Type string `json:"type"`
|
||||
Type string `json:"type"`
|
||||
DiffIds []string `json:"diff_ids"`
|
||||
}
|
||||
|
||||
type ImageHistoryEntry struct {
|
||||
ID string
|
||||
Size uint64
|
||||
Created string `json:"created"`
|
||||
Author string `json:"author"`
|
||||
CreatedBy string `json:"created_by"`
|
||||
EmptyLayer bool `json:"empty_layer"`
|
||||
ID string
|
||||
Size uint64
|
||||
Created string `json:"created"`
|
||||
Author string `json:"author"`
|
||||
CreatedBy string `json:"created_by"`
|
||||
EmptyLayer bool `json:"empty_layer"`
|
||||
}
|
||||
|
||||
func NewImageManifest(reader *tar.Reader, header *tar.Header) ImageManifest {
|
||||
@ -132,7 +132,7 @@ func NewImageConfig(reader *tar.Reader, header *tar.Header) ImageConfig {
|
||||
return imageConfig
|
||||
}
|
||||
|
||||
func GetImageConfig(imageTarPath string, manifest ImageManifest) ImageConfig{
|
||||
func GetImageConfig(imageTarPath string, manifest ImageManifest) ImageConfig {
|
||||
var config ImageConfig
|
||||
// read through the image contents and build a tree
|
||||
fmt.Println(" Fetching image config...")
|
||||
@ -239,7 +239,7 @@ func InitializeData(imageID string) ([]*Layer, []*filetree.FileTree) {
|
||||
}
|
||||
|
||||
observedBytes += header.Size
|
||||
percent = int(100.0*(float64(observedBytes) / float64(totalSize)))
|
||||
percent = int(100.0 * (float64(observedBytes) / float64(totalSize)))
|
||||
io.WriteString(frame.Header(), fmt.Sprintf(" Discovering layers... %d %%", percent))
|
||||
|
||||
name := header.Name
|
||||
@ -254,7 +254,7 @@ func InitializeData(imageID string) ([]*Layer, []*filetree.FileTree) {
|
||||
panic(err)
|
||||
}
|
||||
shortName := name[:15]
|
||||
io.WriteString(line, " ├─ " + shortName + " : loading...")
|
||||
io.WriteString(line, " ├─ "+shortName+" : loading...")
|
||||
|
||||
var tarredBytes = make([]byte, header.Size)
|
||||
|
||||
@ -263,7 +263,6 @@ func InitializeData(imageID string) ([]*Layer, []*filetree.FileTree) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
|
||||
go processLayerTar(line, layerMap, name, tarredBytes)
|
||||
|
||||
} else if name == "manifest.json" {
|
||||
@ -292,7 +291,7 @@ func InitializeData(imageID string) ([]*Layer, []*filetree.FileTree) {
|
||||
|
||||
// note that the image config stores images in reverse chronological order, so iterate backwards through layers
|
||||
// as you iterate chronologically through history (ignoring history items that have no layer contents)
|
||||
layerIdx := len(trees)-1
|
||||
layerIdx := len(trees) - 1
|
||||
for idx := 0; idx < len(config.History); idx++ {
|
||||
// ignore empty layers, we are only observing layers with content
|
||||
if config.History[idx].EmptyLayer {
|
||||
@ -302,9 +301,9 @@ func InitializeData(imageID string) ([]*Layer, []*filetree.FileTree) {
|
||||
config.History[idx].Size = uint64(trees[(len(trees)-1)-layerIdx].FileSize)
|
||||
|
||||
layers[layerIdx] = &Layer{
|
||||
History: config.History[idx],
|
||||
Index: layerIdx,
|
||||
Tree: trees[layerIdx],
|
||||
History: config.History[idx],
|
||||
Index: layerIdx,
|
||||
Tree: trees[layerIdx],
|
||||
RefTrees: trees,
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"github.com/wagoodman/dive/filetree"
|
||||
"strings"
|
||||
"fmt"
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/wagoodman/dive/filetree"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -14,7 +14,7 @@ const (
|
||||
// Layer represents a Docker image layer and metadata
|
||||
type Layer struct {
|
||||
TarPath string
|
||||
History ImageHistoryEntry
|
||||
History ImageHistoryEntry
|
||||
Index int
|
||||
Tree *filetree.FileTree
|
||||
RefTrees []*filetree.FileTree
|
||||
@ -44,4 +44,3 @@ func (layer *Layer) String() string {
|
||||
humanize.Bytes(uint64(layer.History.Size)),
|
||||
strings.TrimPrefix(layer.History.CreatedBy, "/bin/sh -c "))
|
||||
}
|
||||
|
||||
|
7
main.go
7
main.go
@ -25,12 +25,11 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
version = "No version provided"
|
||||
commit = "No commit provided"
|
||||
buildTime = "No build timestamp provided"
|
||||
version = "No version provided"
|
||||
commit = "No commit provided"
|
||||
buildTime = "No build timestamp provided"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
||||
|
||||
|
@ -3,22 +3,22 @@ package ui
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/jroimartin/gocui"
|
||||
"github.com/lunixbochs/vtclean"
|
||||
"strings"
|
||||
"github.com/wagoodman/dive/filetree"
|
||||
"strconv"
|
||||
"github.com/dustin/go-humanize"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DetailsView holds the UI objects and data models for populating the lower-left pane. Specifically the pane that
|
||||
// shows the layer details and image statistics.
|
||||
type DetailsView struct {
|
||||
Name string
|
||||
gui *gocui.Gui
|
||||
view *gocui.View
|
||||
header *gocui.View
|
||||
efficiency float64
|
||||
Name string
|
||||
gui *gocui.Gui
|
||||
view *gocui.View
|
||||
header *gocui.View
|
||||
efficiency float64
|
||||
inefficiencies filetree.EfficiencySlice
|
||||
}
|
||||
|
||||
@ -61,7 +61,9 @@ func (view *DetailsView) Setup(v *gocui.View, header *gocui.View) error {
|
||||
|
||||
// IsVisible indicates if the details view pane is currently initialized.
|
||||
func (view *DetailsView) IsVisible() bool {
|
||||
if view == nil {return false}
|
||||
if view == nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -95,7 +97,7 @@ func (view *DetailsView) Render() error {
|
||||
template := "%5s %12s %-s\n"
|
||||
var trueInefficiencies int
|
||||
inefficiencyReport := fmt.Sprintf(Formatting.Header(template), "Count", "Total Space", "Path")
|
||||
for idx := len(view.inefficiencies)-1; idx > 0; idx-- {
|
||||
for idx := len(view.inefficiencies) - 1; idx > 0; idx-- {
|
||||
data := view.inefficiencies[idx]
|
||||
if data.CumulativeSize == 0 {
|
||||
continue
|
||||
@ -109,13 +111,13 @@ func (view *DetailsView) Render() error {
|
||||
}
|
||||
|
||||
effStr := fmt.Sprintf("\n%s %d %%", Formatting.Header("Image efficiency score:"), int(100.0*view.efficiency))
|
||||
spaceStr := fmt.Sprintf("%s %s\n", Formatting.Header("Potential wasted space:"), humanize.Bytes(uint64(wastedSpace)))
|
||||
spaceStr := fmt.Sprintf("%s %s\n", Formatting.Header("Potential wasted space:"), humanize.Bytes(uint64(wastedSpace)))
|
||||
|
||||
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))
|
||||
headerStr := fmt.Sprintf("[Image & Layer Details]%s", strings.Repeat("─", width*2))
|
||||
fmt.Fprintln(view.header, Formatting.Header(vtclean.Clean(headerStr, false)))
|
||||
|
||||
// update contents
|
||||
|
@ -6,8 +6,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/jroimartin/gocui"
|
||||
"github.com/wagoodman/dive/filetree"
|
||||
"github.com/lunixbochs/vtclean"
|
||||
"github.com/wagoodman/dive/filetree"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -102,7 +102,9 @@ func (view *FileTreeView) height() uint {
|
||||
|
||||
// IsVisible indicates if the file tree view pane is currently initialized
|
||||
func (view *FileTreeView) IsVisible() bool {
|
||||
if view == nil {return false}
|
||||
if view == nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -178,7 +180,6 @@ func (view *FileTreeView) CursorDown() error {
|
||||
return view.Render()
|
||||
}
|
||||
|
||||
|
||||
// CursorUp moves the cursor up and renders the view.
|
||||
// Note: we cannot use the gocui buffer since any state change requires writing the entire tree to the buffer.
|
||||
// Instead we are keeping an upper and lower bounds of the tree string to render and only flushing
|
||||
@ -303,7 +304,7 @@ func (view *FileTreeView) Update() error {
|
||||
|
||||
// Render flushes the state objects (file tree) to the pane.
|
||||
func (view *FileTreeView) Render() error {
|
||||
treeString := view.ViewTree.StringBetween(view.bufferIndexLowerBound, view.bufferIndexUpperBound,true)
|
||||
treeString := view.ViewTree.StringBetween(view.bufferIndexLowerBound, view.bufferIndexUpperBound, true)
|
||||
lines := strings.Split(treeString, "\n")
|
||||
|
||||
// undo a cursor down that has gone past bottom of the visible tree
|
||||
@ -318,7 +319,7 @@ func (view *FileTreeView) Render() error {
|
||||
|
||||
// indicate when selected
|
||||
if view.gui.CurrentView() == view.view {
|
||||
title = "● "+title
|
||||
title = "● " + title
|
||||
}
|
||||
|
||||
view.gui.Update(func(g *gocui.Gui) error {
|
||||
@ -346,9 +347,9 @@ func (view *FileTreeView) Render() error {
|
||||
|
||||
// KeyHelp indicates all the possible actions a user can take while the current pane is selected.
|
||||
func (view *FileTreeView) KeyHelp() string {
|
||||
return renderStatusOption("Space","Collapse dir", false) +
|
||||
renderStatusOption("^A","Added files", !view.HiddenDiffTypes[filetree.Added]) +
|
||||
renderStatusOption("^R","Removed files", !view.HiddenDiffTypes[filetree.Removed]) +
|
||||
renderStatusOption("^M","Modified files", !view.HiddenDiffTypes[filetree.Changed]) +
|
||||
renderStatusOption("^U","Unmodified files", !view.HiddenDiffTypes[filetree.Unchanged])
|
||||
}
|
||||
return renderStatusOption("Space", "Collapse dir", false) +
|
||||
renderStatusOption("^A", "Added files", !view.HiddenDiffTypes[filetree.Added]) +
|
||||
renderStatusOption("^R", "Removed files", !view.HiddenDiffTypes[filetree.Removed]) +
|
||||
renderStatusOption("^M", "Modified files", !view.HiddenDiffTypes[filetree.Changed]) +
|
||||
renderStatusOption("^U", "Unmodified files", !view.HiddenDiffTypes[filetree.Unchanged])
|
||||
}
|
||||
|
@ -55,7 +55,9 @@ func (view *FilterView) Setup(v *gocui.View, header *gocui.View) error {
|
||||
|
||||
// IsVisible indicates if the filter view pane is currently initialized
|
||||
func (view *FilterView) IsVisible() bool {
|
||||
if view == nil {return false}
|
||||
if view == nil {
|
||||
return false
|
||||
}
|
||||
return !view.hidden
|
||||
}
|
||||
|
||||
@ -111,4 +113,4 @@ func (view *FilterView) Render() error {
|
||||
// KeyHelp indicates all the possible actions a user can take while the current pane is selected.
|
||||
func (view *FilterView) KeyHelp() string {
|
||||
return Formatting.StatusControlNormal("▏Type to filter the file tree ")
|
||||
}
|
||||
}
|
||||
|
@ -3,22 +3,22 @@ package ui
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jroimartin/gocui"
|
||||
"github.com/wagoodman/dive/image"
|
||||
"github.com/lunixbochs/vtclean"
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/jroimartin/gocui"
|
||||
"github.com/lunixbochs/vtclean"
|
||||
"github.com/wagoodman/dive/image"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// LayerView holds the UI objects and data models for populating the lower-left pane. Specifically the pane that
|
||||
// shows the image layers and layer selector.
|
||||
type LayerView struct {
|
||||
Name string
|
||||
gui *gocui.Gui
|
||||
view *gocui.View
|
||||
header *gocui.View
|
||||
LayerIndex int
|
||||
Layers []*image.Layer
|
||||
Name string
|
||||
gui *gocui.Gui
|
||||
view *gocui.View
|
||||
header *gocui.View
|
||||
LayerIndex int
|
||||
Layers []*image.Layer
|
||||
CompareMode CompareType
|
||||
CompareStartIndex int
|
||||
}
|
||||
@ -69,7 +69,9 @@ func (view *LayerView) Setup(v *gocui.View, header *gocui.View) error {
|
||||
|
||||
// IsVisible indicates if the layer view pane is currently initialized.
|
||||
func (view *LayerView) IsVisible() bool {
|
||||
if view == nil {return false}
|
||||
if view == nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -78,7 +80,7 @@ func (view *LayerView) CursorDown() error {
|
||||
if view.LayerIndex < len(view.Layers) {
|
||||
err := CursorDown(view.gui, view.view)
|
||||
if err == nil {
|
||||
view.SetCursor(view.LayerIndex+1)
|
||||
view.SetCursor(view.LayerIndex + 1)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -89,7 +91,7 @@ func (view *LayerView) CursorUp() error {
|
||||
if view.LayerIndex > 0 {
|
||||
err := CursorUp(view.gui, view.view)
|
||||
if err == nil {
|
||||
view.SetCursor(view.LayerIndex-1)
|
||||
view.SetCursor(view.LayerIndex - 1)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -127,11 +129,11 @@ func (view *LayerView) getCompareIndexes() (bottomTreeStart, bottomTreeStop, top
|
||||
bottomTreeStop = view.LayerIndex
|
||||
topTreeStart = view.LayerIndex
|
||||
} else if view.CompareMode == CompareLayer {
|
||||
bottomTreeStop = view.LayerIndex -1
|
||||
bottomTreeStop = view.LayerIndex - 1
|
||||
topTreeStart = view.LayerIndex
|
||||
} else {
|
||||
bottomTreeStop = view.CompareStartIndex
|
||||
topTreeStart = view.CompareStartIndex+1
|
||||
topTreeStart = view.CompareStartIndex + 1
|
||||
}
|
||||
|
||||
return bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop
|
||||
@ -165,7 +167,7 @@ func (view *LayerView) Render() error {
|
||||
// indicate when selected
|
||||
title := "Layers"
|
||||
if view.gui.CurrentView() == view.view {
|
||||
title = "● "+title
|
||||
title = "● " + title
|
||||
}
|
||||
|
||||
view.gui.Update(func(g *gocui.Gui) error {
|
||||
@ -180,7 +182,7 @@ func (view *LayerView) Render() error {
|
||||
view.view.Clear()
|
||||
for revIdx := len(view.Layers) - 1; revIdx >= 0; revIdx-- {
|
||||
layer := view.Layers[revIdx]
|
||||
idx := (len(view.Layers)-1) - revIdx
|
||||
idx := (len(view.Layers) - 1) - revIdx
|
||||
|
||||
layerStr := layer.String()
|
||||
if idx == 0 {
|
||||
@ -197,9 +199,9 @@ func (view *LayerView) Render() error {
|
||||
compareBar := view.renderCompareBar(idx)
|
||||
|
||||
if idx == view.LayerIndex {
|
||||
fmt.Fprintln(view.view, compareBar + " " + Formatting.Selected(layerStr))
|
||||
fmt.Fprintln(view.view, compareBar+" "+Formatting.Selected(layerStr))
|
||||
} else {
|
||||
fmt.Fprintln(view.view, compareBar + " " + layerStr)
|
||||
fmt.Fprintln(view.view, compareBar+" "+layerStr)
|
||||
}
|
||||
|
||||
}
|
||||
@ -208,9 +210,8 @@ func (view *LayerView) Render() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// KeyHelp indicates all the possible actions a user can take while the current pane is selected.
|
||||
func (view *LayerView) KeyHelp() string {
|
||||
return renderStatusOption("^L","Show layer changes", view.CompareMode == CompareLayer) +
|
||||
renderStatusOption("^A","Show aggregated changes", view.CompareMode == CompareAll)
|
||||
return renderStatusOption("^L", "Show layer changes", view.CompareMode == CompareLayer) +
|
||||
renderStatusOption("^A", "Show aggregated changes", view.CompareMode == CompareAll)
|
||||
}
|
||||
|
@ -40,7 +40,9 @@ func (view *StatusView) Setup(v *gocui.View, header *gocui.View) error {
|
||||
|
||||
// IsVisible indicates if the status view pane is currently initialized.
|
||||
func (view *StatusView) IsVisible() bool {
|
||||
if view == nil {return false}
|
||||
if view == nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -63,7 +65,7 @@ func (view *StatusView) Update() error {
|
||||
func (view *StatusView) Render() error {
|
||||
view.gui.Update(func(g *gocui.Gui) error {
|
||||
view.view.Clear()
|
||||
fmt.Fprintln(view.view, view.KeyHelp()+Views.lookup[view.gui.CurrentView().Name()].KeyHelp() + Formatting.StatusNormal("▏" + strings.Repeat(" ", 1000)))
|
||||
fmt.Fprintln(view.view, view.KeyHelp()+Views.lookup[view.gui.CurrentView().Name()].KeyHelp()+Formatting.StatusNormal("▏"+strings.Repeat(" ", 1000)))
|
||||
|
||||
return nil
|
||||
})
|
||||
@ -73,7 +75,7 @@ func (view *StatusView) Render() error {
|
||||
|
||||
// KeyHelp indicates all the possible global actions a user can take when any pane is selected.
|
||||
func (view *StatusView) KeyHelp() string {
|
||||
return renderStatusOption("^C","Quit", false) +
|
||||
renderStatusOption("^Space","Switch view", false) +
|
||||
renderStatusOption("^/","Filter files", Views.Filter.IsVisible())
|
||||
}
|
||||
return renderStatusOption("^C", "Quit", false) +
|
||||
renderStatusOption("^Space", "Switch view", false) +
|
||||
renderStatusOption("^/", "Filter files", Views.Filter.IsVisible())
|
||||
}
|
||||
|
31
ui/ui.go
31
ui/ui.go
@ -5,10 +5,10 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jroimartin/gocui"
|
||||
"github.com/wagoodman/dive/filetree"
|
||||
"github.com/wagoodman/dive/image"
|
||||
"github.com/fatih/color"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
@ -35,14 +35,14 @@ func debugPrint(s string) {
|
||||
|
||||
// Formatting defines standard functions for formatting UI sections.
|
||||
var Formatting struct {
|
||||
Header func(...interface{})(string)
|
||||
Selected func(...interface{})(string)
|
||||
StatusSelected func(...interface{})(string)
|
||||
StatusNormal func(...interface{})(string)
|
||||
StatusControlSelected func(...interface{})(string)
|
||||
StatusControlNormal func(...interface{})(string)
|
||||
CompareTop func(...interface{})(string)
|
||||
CompareBottom func(...interface{})(string)
|
||||
Header func(...interface{}) string
|
||||
Selected func(...interface{}) string
|
||||
StatusSelected func(...interface{}) string
|
||||
StatusNormal func(...interface{}) string
|
||||
StatusControlSelected func(...interface{}) string
|
||||
StatusControlNormal func(...interface{}) string
|
||||
CompareTop func(...interface{}) string
|
||||
CompareBottom func(...interface{}) string
|
||||
}
|
||||
|
||||
// Views contains all rendered UI panes.
|
||||
@ -84,7 +84,7 @@ func toggleView(g *gocui.Gui, v *gocui.View) error {
|
||||
func toggleFilterView(g *gocui.Gui, v *gocui.View) error {
|
||||
// delete all user input from the tree view
|
||||
Views.Filter.view.Clear()
|
||||
Views.Filter.view.SetCursor(0,0)
|
||||
Views.Filter.view.SetCursor(0, 0)
|
||||
|
||||
// toggle hiding
|
||||
Views.Filter.hidden = !Views.Filter.hidden
|
||||
@ -179,7 +179,6 @@ func isNewView(errs ...error) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
// layout defines the definition of the window pane size and placement relations to one another. This
|
||||
// is invoked at application start and whenever the screen dimensions change.
|
||||
func layout(g *gocui.Gui) error {
|
||||
@ -255,12 +254,11 @@ func layout(g *gocui.Gui) error {
|
||||
|
||||
// Filter Bar
|
||||
view, viewErr = g.SetView(Views.Filter.Name, len(Views.Filter.headerStr)-1, maxY-filterBarHeight-filterBarIndex, maxX, maxY-(filterBarIndex-1))
|
||||
header, headerErr = g.SetView(Views.Filter.Name+"header", -1, maxY-filterBarHeight - filterBarIndex, len(Views.Filter.headerStr), maxY-(filterBarIndex-1))
|
||||
header, headerErr = g.SetView(Views.Filter.Name+"header", -1, maxY-filterBarHeight-filterBarIndex, len(Views.Filter.headerStr), maxY-(filterBarIndex-1))
|
||||
if isNewView(viewErr, headerErr) {
|
||||
Views.Filter.Setup(view, header)
|
||||
}
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -283,9 +281,9 @@ func Render() {
|
||||
// renderStatusOption formats key help bindings-to-title pairs.
|
||||
func renderStatusOption(control, title string, selected bool) string {
|
||||
if selected {
|
||||
return Formatting.StatusSelected("▏") + Formatting.StatusControlSelected(control) + Formatting.StatusSelected(" " + title + " ")
|
||||
return Formatting.StatusSelected("▏") + Formatting.StatusControlSelected(control) + Formatting.StatusSelected(" "+title+" ")
|
||||
} else {
|
||||
return Formatting.StatusNormal("▏") + Formatting.StatusControlNormal(control) + Formatting.StatusNormal(" " + title + " ")
|
||||
return Formatting.StatusNormal("▏") + Formatting.StatusControlNormal(control) + Formatting.StatusNormal(" "+title+" ")
|
||||
}
|
||||
}
|
||||
|
||||
@ -312,7 +310,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,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)
|
||||
@ -324,7 +322,6 @@ func Run(layers []*image.Layer, refTrees []*filetree.FileTree) {
|
||||
Views.Details = NewDetailsView("details", g)
|
||||
Views.lookup[Views.Details.Name] = Views.Details
|
||||
|
||||
|
||||
g.Cursor = false
|
||||
//g.Mouse = true
|
||||
g.SetManagerFunc(layout)
|
||||
|
Loading…
x
Reference in New Issue
Block a user