This commit is contained in:
Alex Goodman 2018-10-16 23:37:19 -04:00
parent 8ca96849da
commit e700ec3745
No known key found for this signature in database
GPG Key ID: 05328C611D8A520E
18 changed files with 154 additions and 161 deletions

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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...")
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -47,4 +47,3 @@ package filetree
// t.Fatalf("Expected %f but got %f", expected, actual)
// }
// }

View File

@ -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),
}

View File

@ -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) {

View File

@ -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

View File

@ -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,
}

View File

@ -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 "))
}

View File

@ -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()
}

View File

@ -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

View File

@ -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])
}

View File

@ -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 ")
}
}

View File

@ -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)
}

View File

@ -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())
}

View File

@ -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)