How Dive's File Tree Sorting Works: A Deep Dive into the Implementation
Dive sorts file tree nodes using a pluggable strategy pattern that supports alphabetical (ByName) and size-descending (BySizeDesc) ordering, storing the current mode in the SortOrder field of the FileTree struct and applying it during every tree traversal and render operation.
The open-source tool wagoodman/dive provides an interactive interface for exploring Docker image layers. Central to this experience is the file tree view, which organizes image contents into a navigable hierarchy. Understanding how Dive's file tree sorting works reveals a clean strategy pattern implementation that separates sorting logic from tree structure.
Sort Order Storage and Configuration
The FileTree struct maintains the current sort preference in a dedicated field. Located in dive/filetree/file_tree.go, the struct definition includes:
type FileTree struct {
…
SortOrder SortOrder // ← current order
}
By default, every new tree initializes with alphabetical ordering. The constructor explicitly sets this value at line 42 of the same file:
tree.SortOrder = ByName
The available strategies are defined as constants representing the SortOrder type, with NumSortOrderConventions tracking the total count for UI cycling purposes.
The Sorting Strategy Pattern
Dive delegates actual sorting logic to strategy objects selected by the GetSortOrderStrategy function in dive/filetree/order_strategy.go. This function maps the current SortOrder enum value to a concrete implementation:
func GetSortOrderStrategy(sortOrder SortOrder) OrderStrategy {
switch sortOrder {
case ByName:
return orderByNameStrategy{}
case BySizeDesc:
return orderBySizeDescStrategy{}
}
return orderByNameStrategy{}
}
Each strategy implements the orderKeys(files map[string]*FileNode) []string method, returning a deterministically ordered slice of child keys.
Alphabetical Sorting (ByName)
The orderByNameStrategy relies on Go's standard library for stable alphabetical sorting:
func (orderByNameStrategy) orderKeys(files map[string]*FileNode) []string {
var keys []string
for k := range files {
keys = append(keys, k)
}
sort.Strings(keys) // ← Go's stable alphabetical sort
return keys
}
This implementation appears in dive/filetree/order_strategy.go (lines 32-40) and provides predictable, path-based ordering suitable for general exploration.
Size-Descending Sorting (BySizeDesc)
For analyzing image bloat, orderBySizeDescStrategy sorts by file size in descending order, using the name as a deterministic tie-breaker:
func (orderBySizeDescStrategy) orderKeys(files map[string]*FileNode) []string {
var keys []string
for k := range files {
keys = append(keys, k)
}
sort.Slice(keys, func(i, j int) bool {
ki, kj := keys[i], keys[j]
ni, nj := files[ki], files[kj]
if ni.GetSize() == nj.GetSize() {
return ki < kj // deterministic tie‑breaker
}
return ni.GetSize() > nj.GetSize() // larger first
})
return keys
}
This code (lines 45-60 in order_strategy.go) ensures that the largest files appear first in the tree view, making it immediately obvious which assets consume the most layer space.
How Sorting Is Applied During Tree Traversal
The sorting strategy is invoked whenever Dive renders the tree or walks its nodes. In dive/filetree/file_tree.go (lines 71-73), the String() method obtains the sorter and retrieves ordered keys before processing children:
sorter := GetSortOrderStrategy(tree.SortOrder)
keys := sorter.orderKeys(currentParams.node.Children)
This same pattern applies to visitor traversals including VisitDepthParentFirst and VisitDepthChildFirst, guaranteeing that any programmatic walk of the tree respects the chosen ordering. The FileNode implementation in dive/filetree/file_node.go also calls GetSortOrderStrategy(ByName) for internal traversal defaults when no explicit order is specified.
Changing Sort Order via the UI
Users toggle between strategies using Ctrl + O, which cycles through available conventions modulo NumSortOrderConventions. The UI layer implements this in cmd/dive/cli/internal/ui/v1/viewmodel/filetree.go:
func (vm *FileTreeViewModel) ToggleSortOrder() error {
vm.ModelTree.SortOrder = (vm.ModelTree.SortOrder + 1) % filetree.NumSortOrderConventions
return nil
}
This method (lines 368-371) increments the enum value, causing the next render cycle to instantiate a different strategy via GetSortOrderStrategy. The key binding itself is defined in cmd/dive/cli/internal/ui/v1/view/filetree.go, wiring the Ctrl + O keystroke directly to vm.ToggleSortOrder().
Programmatic Code Examples
Setting Size-Descending Order Programmatically
import (
"github.com/wagoodman/dive/dive/filetree"
)
func setSizeDesc(tree *filetree.FileTree) {
// Switch to “largest first” ordering
tree.SortOrder = filetree.BySizeDesc
}
Walking the Tree in Current Order
func walk(tree *filetree.FileTree) error {
// The visitor will receive nodes ordered per tree.SortOrder
return tree.VisitDepthParentFirst(
func(node *filetree.FileNode) error {
fmt.Println(node.Path(), node.GetSize())
return nil
},
nil,
)
}
Testing the UI Toggle
func TestToggleSortOrder(t *testing.T) {
vm := newFileTreeViewModel() // builds a model with a FileTree
original := vm.ModelTree.SortOrder
if err := vm.ToggleSortOrder(); err != nil {
t.Fatalf("toggle failed: %v", err)
}
if vm.ModelTree.SortOrder == original {
t.Fatalf("sort order did not change")
}
}
Summary
- Strategy Pattern: Dive encapsulates sorting logic in interchangeable strategy objects (
orderByNameStrategy,orderBySizeDescStrategy) selected byGetSortOrderStrategyindive/filetree/order_strategy.go. - State Storage: Each
FileTreeinstance stores its currentSortOrderpreference, defaulting toByNameupon initialization. - Consistent Application: The
orderKeysmethod is invoked during rendering and all visitor traversals, ensuring the entire UI respects the selected ordering. - User Control: The Ctrl + O shortcut cycles through available strategies by incrementing
SortOrdermoduloNumSortOrderConventions, implemented in the viewmodel atcmd/dive/cli/internal/ui/v1/viewmodel/filetree.go. - Determinism: Both strategies provide stable ordering; the size-descending strategy uses file path as a tie-breaker when sizes match.
Frequently Asked Questions
How do I change the sort order in Dive's file tree view?
Press Ctrl + O to cycle between available sorting strategies. This keybinding triggers ToggleSortOrder() in the viewmodel, which increments the SortOrder field modulo the number of available conventions, switching between alphabetical and size-descending modes.
What sorting algorithms does Dive use for file tree display?
Dive uses Go's sort.Strings() for alphabetical ordering (ByName) and sort.Slice() with a custom comparator for size-descending ordering (BySizeDesc). The size comparator places larger files first and uses file path comparison as a deterministic tie-breaker when sizes are equal.
Where is the file tree sort order stored in Dive's codebase?
The sort order is stored in the SortOrder field of the FileTree struct defined in dive/filetree/file_tree.go (line 31). The UI viewmodel maintains a reference to this tree and modifies the field via ToggleSortOrder(), while rendering logic reads it to select the appropriate strategy via GetSortOrderStrategy().
Can I programmatically sort a FileTree in Dive?
Yes. Set tree.SortOrder = filetree.BySizeDesc (or ByName) before calling traversal methods like VisitDepthParentFirst() or String(). The tree will use GetSortOrderStrategy() to obtain the correct sorter and apply it to child nodes during the walk.
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 →