Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add scrolling to layers pane #478

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM alpine:3.12
FROM alpine:3.18

ARG DOCKER_CLI_VERSION=${DOCKER_CLI_VERSION}
RUN wget -O- https://download.docker.com/linux/static/stable/$(uname -m)/docker-${DOCKER_CLI_VERSION}.tgz | \
Expand Down
106 changes: 34 additions & 72 deletions runtime/ui/view/layer.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,12 @@ func (v *Layer) Setup(body *gocui.View, header *gocui.View) error {
}
v.helpKeys = helpKeys

return v.Render()
_, height := v.body.Size()
v.vm.Setup(0, height)
_ = v.Update()
_ = v.Render()

return nil
}

// height obtains the height of the current pane (taking into account the lost space due to the header).
Expand All @@ -161,62 +166,48 @@ func (v *Layer) IsVisible() bool {

// PageDown moves to next page putting the cursor on top
func (v *Layer) PageDown() error {
step := int(v.height()) + 1
targetLayerIndex := v.vm.LayerIndex + step

if targetLayerIndex > len(v.vm.Layers) {
step -= targetLayerIndex - (len(v.vm.Layers) - 1)
}

if step > 0 {
// err := CursorStep(v.gui, v.body, step)
err := error(nil)
if err == nil {
return v.SetCursor(v.vm.LayerIndex + step)
if v.vm.PageDown() {
err := v.notifyLayerChangeListeners()
if err != nil {
return err
}
return v.Render()
}
return nil
}

// PageUp moves to previous page putting the cursor on top
func (v *Layer) PageUp() error {
step := int(v.height()) + 1
targetLayerIndex := v.vm.LayerIndex - step

if targetLayerIndex < 0 {
step += targetLayerIndex
}

if step > 0 {
// err := CursorStep(v.gui, v.body, -step)
err := error(nil)
if err == nil {
return v.SetCursor(v.vm.LayerIndex - step)
if v.vm.PageUp() {
err := v.notifyLayerChangeListeners()
if err != nil {
return err
}
return v.Render()
}
return nil
}

// CursorDown moves the cursor down in the layer pane (selecting a higher layer).
func (v *Layer) CursorDown() error {
if v.vm.LayerIndex < len(v.vm.Layers)-1 {
// err := CursorDown(v.gui, v.body)
err := error(nil)
if err == nil {
return v.SetCursor(v.vm.LayerIndex + 1)
if v.vm.CursorDown() {
err := v.notifyLayerChangeListeners()
if err != nil {
return err
}
return v.Render()
}
return nil
}

// CursorUp moves the cursor up in the layer pane (selecting a lower layer).
func (v *Layer) CursorUp() error {
if v.vm.LayerIndex > 0 {
// err := CursorUp(v.gui, v.body)
err := error(nil)
if err == nil {
return v.SetCursor(v.vm.LayerIndex - 1)
if v.vm.CursorUp() {
err := v.notifyLayerChangeListeners()
if err != nil {
return err
}
return v.Render()
}
return nil
}
Expand All @@ -243,21 +234,6 @@ func (v *Layer) setCompareMode(compareMode viewmodel.LayerCompareMode) error {
return v.notifyLayerChangeListeners()
}

// renderCompareBar returns the formatted string for the given layer.
func (v *Layer) renderCompareBar(layerIdx int) string {
bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop := v.vm.GetCompareIndexes()
result := " "

if layerIdx >= bottomTreeStart && layerIdx <= bottomTreeStop {
result = format.CompareBottom(" ")
}
if layerIdx >= topTreeStart && layerIdx <= topTreeStop {
result = format.CompareTop(" ")
}

return result
}

func (v *Layer) ConstrainLayout() {
if !v.constrainedRealEstate {
logrus.Debugf("constraining layer layout")
Expand Down Expand Up @@ -293,7 +269,7 @@ func (v *Layer) Render() error {
logrus.Tracef("view.Render() %s", v.Name())

// indicate when selected
title := "Layers"
title := fmt.Sprintf("Layers (%d / %d)", v.vm.LayerIndex+1, len(v.vm.Layers))
isSelected := v.gui.CurrentView() == v.body

v.gui.Update(func(g *gocui.Gui) error {
Expand All @@ -319,28 +295,14 @@ func (v *Layer) Render() error {

// update contents
v.body.Clear()
for idx, layer := range v.vm.Layers {
var layerStr string
if v.constrainedRealEstate {
layerStr = fmt.Sprintf("%-4d", layer.Index)
} else {
layerStr = layer.String()
}

compareBar := v.renderCompareBar(idx)

if idx == v.vm.LayerIndex {
_, err = fmt.Fprintln(v.body, compareBar+" "+format.Selected(layerStr))
} else {
_, err = fmt.Fprintln(v.body, compareBar+" "+layerStr)
}

if err != nil {
logrus.Debug("unable to write to buffer: ", err)
return err
}
v.vm.Update(v.constrainedRealEstate)
err = v.vm.Render()
if err != nil {
return err
}
return nil
_, err = fmt.Fprint(v.body, v.vm.Buffer.String())

return err
})
return nil
}
Expand Down
170 changes: 169 additions & 1 deletion runtime/ui/viewmodel/layer_set_state.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,187 @@
package viewmodel

import "github.com/wagoodman/dive/dive/image"
import (
"bytes"
"fmt"

"github.com/sirupsen/logrus"

"github.com/wagoodman/dive/dive/image"
"github.com/wagoodman/dive/runtime/ui/format"
)

type LayerSetState struct {
LayerIndex int
Layers []*image.Layer
CompareMode LayerCompareMode
CompareStartIndex int

constrainedRealEstate bool
viewStartIndex int
viewHeight int

Buffer bytes.Buffer
}

func NewLayerSetState(layers []*image.Layer, compareMode LayerCompareMode) *LayerSetState {
return &LayerSetState{
Layers: layers,
CompareMode: compareMode,
LayerIndex: 0,
viewStartIndex: 0,
}
}

// Setup initializes the UI concerns within the context of a global [gocui] view object.
func (vm *LayerSetState) Setup(lowerBound, height int) {
vm.viewStartIndex = lowerBound
vm.viewHeight = height
}

// height returns the current height and considers the header
func (vm *LayerSetState) height() int {
return vm.viewHeight - 1
}

// IsVisible indicates if the layer view pane is currently initialized
func (vm *LayerSetState) IsVisible() bool {
return vm != nil
}

// ResetCursor moves the cursor back to the top of the buffer and translates to the top of the buffer.
func (vm *LayerSetState) ResetCursor() {
vm.LayerIndex = 0
vm.viewStartIndex = 0
}


// PageUp moves to previous page putting the cursor on top
func (vm *LayerSetState) PageUp() bool {
prevPageEndIndex := vm.viewStartIndex
prevPageStartIndex := vm.viewStartIndex - vm.viewHeight + 1

if prevPageStartIndex < 0 {
prevPageStartIndex = 0
vm.LayerIndex = 0
prevPageEndIndex = vm.viewHeight
if prevPageEndIndex >= len(vm.Layers) {
return true
}
}

vm.viewStartIndex = prevPageStartIndex

if vm.LayerIndex >= prevPageEndIndex {
vm.LayerIndex = prevPageEndIndex
}
return true
}

// PageDown moves to next page putting the cursor on top
func (vm *LayerSetState) PageDown() bool {
nextPageStartIndex := vm.viewStartIndex + vm.viewHeight - 1
nextPageEndIndex := nextPageStartIndex + vm.viewHeight

if nextPageEndIndex > len(vm.Layers) {
nextPageEndIndex = len(vm.Layers) - 1
vm.LayerIndex = nextPageEndIndex
nextPageStartIndex = nextPageEndIndex - vm.viewHeight + 1
if (nextPageStartIndex < 0) {
return true
}
}

vm.viewStartIndex = nextPageStartIndex

if vm.LayerIndex < nextPageStartIndex {
vm.LayerIndex = nextPageStartIndex
}

return true
}

// doCursorUp performs the internal view's adjustments on cursor up. Note: this is independent of the gocui buffer.
func (vm *LayerSetState) CursorUp() bool {
if vm.LayerIndex <= 0 {
return false
}
vm.LayerIndex--
if vm.LayerIndex < vm.viewStartIndex {
vm.viewStartIndex--
}
return true
}

// doCursorDown performs the internal view's adjustments on cursor down. Note: this is independent of the gocui buffer.
func (vm *LayerSetState) CursorDown() bool {
if vm.LayerIndex >= len(vm.Layers) - 1 {
return false
}
vm.LayerIndex++
if vm.LayerIndex >= vm.viewStartIndex + vm.viewHeight {
vm.viewStartIndex++
}
return true
}

// renderCompareBar returns the formatted string for the given layer.
func (vm *LayerSetState) renderCompareBar(layerIdx int) string {
bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop := vm.GetCompareIndexes()
result := " "

if layerIdx >= bottomTreeStart && layerIdx <= bottomTreeStop {
result = format.CompareBottom(" ")
}
if layerIdx >= topTreeStart && layerIdx <= topTreeStop {
result = format.CompareTop(" ")
}

return result
}

// Update refreshes the state objects for future rendering
func (vm *LayerSetState) Update(isConstrainedRealEstate bool) error {
vm.constrainedRealEstate = isConstrainedRealEstate
return nil
}

// Render flushes the state objects to the screen. The layers pane reports:
// 1. the layers of the image surrounding the currently selected layer
// 2. the current selected layer
func (vm *LayerSetState) Render() error {
logrus.Tracef("viewmodel.LayerSetState.Render() %s", vm.Layers[vm.LayerIndex].Id)

// write contents of pane
vm.Buffer.Reset()
for idx, layer := range vm.Layers {
if idx < vm.viewStartIndex {
continue
}
if idx > vm.viewStartIndex + vm.viewHeight {
break
}
var layerStr string
if vm.constrainedRealEstate {
layerStr = fmt.Sprintf("%-4d", layer.Index)
} else {
layerStr = layer.String()
}

compareBar := vm.renderCompareBar(idx)

err := error(nil)
if idx == vm.LayerIndex {
_, err = fmt.Fprintln(&vm.Buffer, compareBar+" "+format.Selected(layerStr))
} else {
_, err = fmt.Fprintln(&vm.Buffer, compareBar+" "+layerStr)
}

if err != nil {
logrus.Debug("unable to write to buffer: ", err)
return err
}
}
return nil
}

// getCompareIndexes determines the layer boundaries to use for comparison (based on the current compare mode)
Expand Down