How Dive Presents Image Analysis Results Using a TUI
Dive renders container image analysis through an event-driven, modular TUI built on gocui, where immutable analysis data flows from the image engine through a centralized bus to specialized view components that render interactive, color-coded panes for layers, file trees, and efficiency metrics.
Dive, developed by wagoodman, is an open-source tool for exploring Docker and OCI image layers. After analyzing an image's filesystem and efficiency metrics, Dive presents the results in a full-screen, keyboard-driven text-user interface (TUI) that allows developers to interactively inspect layer-by-layer changes. Understanding how Dive transforms raw analysis data into this terminal interface reveals a clean separation between heavy computation and UI rendering.
The Analysis-to-UI Data Pipeline
The journey from raw image data to rendered ASCII interface follows a strict pipeline that keeps the UI responsive by completing all heavy analysis before the TUI initializes.
Generating Immutable Analysis Results
All filesystem analysis happens in dive/image/analysis.go, where the image.Analyze function walks the image layers and constructs an image.Analysis struct. This struct contains immutable snapshots of layer metadata, file-tree references (RefTrees), efficiency metrics, and the list of inefficient files. By completing this work upfront, Dive ensures the TUI only reads pre-computed data rather than performing I/O during rendering.
Event Bus Communication
Once analysis completes, the CLI command emits the ExploreAnalysis event via bus.ExploreAnalysis, defined in internal/bus/event/event.go. This event carries both the Analysis struct and a ContentReader for on-demand file extraction. The event-driven pattern decouples the analysis engine from the presentation layer.
UI Initialization and gocui Setup
The V1UI struct in cmd/dive/cli/internal/ui/v1.go subscribes to the event bus during setup. When it receives the ExploreAnalysis event, the Handle method disables logging to prevent terminal interference and invokes app.Run from cmd/dive/cli/internal/ui/v1/app/app.go. This function initializes the gocui GUI framework, creates the master controller, and starts the main event loop.
Controller and View Instantiation
The controller.newController function in cmd/dive/cli/internal/ui/v1/app/controller.go orchestrates the interface by calling view.NewViews from cmd/dive/cli/internal/ui/v1/view/views.go. This factory function instantiates all view objects—including Layer, FileTree, Status, Filter, ImageDetails, and LayerDetails—and wires them to their respective viewmodels seeded with the cfg.Analysis data.
The TUI Layout Architecture
Dive's interface is composed of discrete view components arranged by a dedicated layout manager that translates terminal dimensions into pane coordinates.
Layout Management Strategy
The cmd/dive/cli/internal/ui/v1/layout/manager.go file defines how terminal real estate is partitioned: the Layer view occupies the bottom-left, the FileTree takes the right side, LayerDetails and ImageDetails stack in the top-right column, and the Status bar anchors the footer. This layout is dynamic, recalculating positions whenever the terminal resizes.
View Components and ViewModels
Each pane is implemented as a separate view file (e.g., cmd/dive/cli/internal/ui/v1/view/layer.go, cmd/dive/cli/internal/ui/v1/view/filetree.go). Rather than accessing raw analysis data directly, views read from viewmodels like viewmodel.LayerSetState and viewmodel.FileTreeViewModel. These abstractions wrap the underlying data and provide formatting helpers for the ASCII interface.
Rendering Image Analysis Data
The TUI presents four main content areas, each rendering specific aspects of the image.Analysis struct.
Layer Navigation Pane
The Layer view, implemented in cmd/dive/cli/internal/ui/v1/view/layer.go, renders a scrollable list of image layers using viewmodel.NewLayerSetState. Each line displays the layer ID, size, and a visual indicator of bytes added, removed, or modified. The view handles keyboard inputs for layer selection, notifying the controller via controller.onLayerChange to update dependent views.
Filesystem Tree Visualization
The FileTree view in cmd/dive/cli/internal/ui/v1/view/filetree.go displays hierarchical filesystem changes using a viewmodel.FileTreeViewModel backed by filetree.FileTree from the analysis. Files are color-coded by change type: added, removed, modified, or unchanged. The view supports real-time regex filtering and collapse/expand operations without re-querying the analysis data.
Details and Efficiency Metrics
The LayerDetails and ImageDetails views (top-right column) render metadata from cfg.Analysis. LayerDetails shows the current layer's command, creation time, and size, while ImageDetails displays aggregate statistics including total size, efficiency percentage, and wasted bytes identified during analysis.
Status Bar and Help System
The Status view in cmd/dive/cli/internal/ui/v1/view/status.go assembles the footer bar by collecting helpKeys from each active view. It displays current keybindings and view context, providing a thin separator line that spans the terminal width.
User Interaction and Real-Time Updates
The Update Loop
When users navigate layers or modify filters, the controller's UpdateAndRender method in cmd/dive/cli/internal/ui/v1/app/controller.go propagates state changes to all viewmodels and invokes each visible view's Render() method. Because the underlying image.Analysis data is immutable, views can redraw efficiently without synchronization locks or data fetching delays.
Keyboard Navigation and Configuration
All keyboard shortcuts are bound through gocui and defined in the Preferences.KeyBindings configuration. Users can customize these in ~/.config/dive/config.yaml; for example, changing the "toggle view" shortcut from Ctrl+Space to Ctrl+N updates both the Status footer text and the underlying app.controller.NextPane binding without code changes.
Practical Implementation Examples
Analyzing an Image via CLI
The standard command-line flow demonstrates the complete pipeline:
# Analyze an image and launch the interactive TUI
dive my-image:latest
Under the hood, as simplified from cmd/dive/cli/internal/command/build.go:
analysis, err := analyzer.Analyze(ctx, img)
if err != nil {
// handle error
}
content := image.NewContentReader(img)
bus.ExploreAnalysis(*analysis, content)
Embedding Dive's TUI in Another Application
The UI components can be embedded in external Go programs:
package main
import (
"context"
"os"
"github.com/wagoodman/dive/cmd/dive/cli/internal/ui/v1"
"github.com/wagoodman/dive/dive/image"
"github.com/wagoodman/dive/internal/bus"
)
func main() {
// Resolve image (Docker, OCI, or tarball)
img, _ := image.FromDocker("my-image:latest")
// Run analysis
analysis, _ := image.Analyze(context.Background(), img)
// Configure UI
cfg := v1.Config{
Analysis: *analysis,
Content: image.NewContentReader(img),
Preferences: v1.DefaultPreferences(),
}
// Initialize and subscribe UI
ui := v1.NewV1UI(v1.DefaultPreferences(), os.Stdout, false, 0)
bus.Subscribe(ui)
// Launch TUI via event
bus.ExploreAnalysis(*analysis, cfg.Content)
}
Customizing Key Bindings
Modify shortcuts via configuration file:
# ~/.config/dive/config.yaml
keybindings:
global:
toggle-view:
key: "Ctrl+N"
This change automatically updates the help text in the Status view and the gocui keybinding registration.
Summary
- Immutable Analysis First: Dive completes all image analysis in
dive/image/analysis.gobefore the TUI initializes, ensuring the interface remains responsive by reading static data structures. - Event-Driven Activation: The
ExploreAnalysisevent ininternal/bus/event/event.godecouples analysis from presentation, triggering the UI creation incmd/dive/cli/internal/ui/v1.go. - Modular View Architecture: The controller in
cmd/dive/cli/internal/ui/v1/app/controller.gomanages discrete view components (Layer, FileTree, Status) that render via gocui primitives. - Layout Management: A dedicated layout manager in
cmd/dive/cli/internal/ui/v1/layout/manager.gopositions four main panes: Layer, FileTree, Details columns, and Status footer. - Data Binding: Views consume
viewmodel.LayerSetStateandviewmodel.FileTreeViewModelabstractions rather than raw analysis structs, enabling efficient re-rendering during navigation.
Frequently Asked Questions
How does Dive handle large images without freezing the terminal interface?
Dive separates computation from rendering by running image.Analyze to completion before launching the TUI. The analysis phase builds immutable structs containing all layer and filesystem data, which the UI then accesses as read-only references. Because the TUI never performs blocking I/O or heavy calculation during the render loop, it remains responsive even when displaying hundreds of layers or millions of files.
Can the Dive TUI be used to analyze images programmatically without the CLI wrapper?
Yes. The v1 package in cmd/dive/cli/internal/ui/v1 exposes NewV1UI and Config structs that allow direct embedding. By creating a v1.Config with a pre-computed image.Analysis and ContentReader, then emitting the bus.ExploreAnalysis event, any Go program can launch the identical interface used by the command-line tool. This is useful for building custom image inspection tools that leverage Dive's visualization engine.
What library does Dive use for terminal rendering and why?
Dive uses gocui, a minimalist console UI library for Go. gocui provides the primitive *gocui.View objects that Dive's view components (Layer, FileTree, etc.) use to draw ASCII panes. The library supports overlapping views, mouse-free keyboard navigation, and dynamic layout changes—features that align with Dive's need to display four simultaneous information panes in a responsive, terminal-native interface.
How are keyboard shortcuts configured and displayed in the interface?
Keybindings are defined in the Preferences.KeyBindings configuration structure, typically loaded from ~/.config/dive/config.yaml. During initialization, the controller binds these keys to gocui actions. Each view exports its relevant key descriptions via helpKeys, which the Status view aggregates into the footer help bar. When users modify bindings in the config file, both the underlying gocui registration and the displayed help text update automatically without code changes.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →