Fix Image Details window scroll not working

The scroll functionality in the Image Details window was broken when the
cursor moved beyond the visible area. This was caused by the CursorStep
function not properly handling the relationship between visual cursor
position and buffer lines when Wrap=true.

This fix follows the same pattern used by the Layer view:
- Track cursor position internally with cursorIndex
- Count total lines for bounds checking
- Adjust origin in Render() to keep cursor visible
- Disable Wrap and manage selection display ourselves

Fixes #647

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: majiayu000 <1835304752@qq.com>
This commit is contained in:
majiayu000 2025-12-30 17:04:20 +08:00
commit f446b2d669
No known key found for this signature in database
GPG key ID: 1C9764AD60647215

View file

@ -2,16 +2,15 @@ package view
import (
"fmt"
"github.com/anchore/go-logger"
"github.com/wagoodman/dive/cmd/dive/cli/internal/ui/v1/format"
"github.com/wagoodman/dive/cmd/dive/cli/internal/ui/v1/key"
"github.com/wagoodman/dive/internal/log"
"strconv"
"strings"
"github.com/anchore/go-logger"
"github.com/awesome-gocui/gocui"
"github.com/dustin/go-humanize"
"github.com/wagoodman/dive/cmd/dive/cli/internal/ui/v1/format"
"github.com/wagoodman/dive/cmd/dive/cli/internal/ui/v1/key"
"github.com/wagoodman/dive/dive/filetree"
"github.com/wagoodman/dive/internal/log"
)
type ImageDetails struct {
@ -25,6 +24,9 @@ type ImageDetails struct {
efficiency float64
inefficiencies filetree.EfficiencySlice
kb key.Bindings
cursorIndex int
lineCount int
}
func (v *ImageDetails) Name() string {
@ -37,8 +39,8 @@ func (v *ImageDetails) Setup(body, header *gocui.View) error {
v.body = body
v.body.Editable = false
v.body.Wrap = true
v.body.Highlight = true
v.body.Wrap = false
v.body.Highlight = false
v.body.Frame = false
v.header = header
@ -80,15 +82,16 @@ func (v *ImageDetails) Setup(body, header *gocui.View) error {
// 2. the estimated wasted image space
// 3. a list of inefficient file allocations
func (v *ImageDetails) Render() error {
analysisTemplate := "%5s %12s %-s\n"
inefficiencyReport := fmt.Sprintf(format.Header(analysisTemplate), "Count", "Total Space", "Path")
analysisTemplate := "%5s %12s %-s"
inefficiencyHeader := fmt.Sprintf(format.Header(analysisTemplate), "Count", "Total Space", "Path")
var wastedSpace int64
var inefficiencyLines []string
for idx := 0; idx < len(v.inefficiencies); idx++ {
data := v.inefficiencies[len(v.inefficiencies)-1-idx]
wastedSpace += data.CumulativeSize
inefficiencyReport += fmt.Sprintf(analysisTemplate, strconv.Itoa(len(data.Nodes)), humanize.Bytes(uint64(data.CumulativeSize)), data.Path)
inefficiencyLines = append(inefficiencyLines, fmt.Sprintf(analysisTemplate, strconv.Itoa(len(data.Nodes)), humanize.Bytes(uint64(data.CumulativeSize)), data.Path))
}
imageNameStr := fmt.Sprintf("%s %s", format.Header("Image name:"), v.imageName)
@ -96,8 +99,30 @@ func (v *ImageDetails) Render() error {
efficiencyStr := fmt.Sprintf("%s %d %%", format.Header("Image efficiency score:"), int(100.0*v.efficiency))
wastedSpaceStr := fmt.Sprintf("%s %s", format.Header("Potential wasted space:"), humanize.Bytes(uint64(wastedSpace)))
// Build all lines as a slice for proper line counting
var lines = []string{
imageNameStr,
imageSizeStr,
wastedSpaceStr,
efficiencyStr,
" ", // to avoid an empty line so CursorDown can work as expected
inefficiencyHeader,
}
lines = append(lines, inefficiencyLines...)
// Update line count for cursor bounds checking
v.lineCount = len(lines)
// Ensure cursor index is within bounds
if v.cursorIndex >= v.lineCount {
v.cursorIndex = v.lineCount - 1
}
if v.cursorIndex < 0 {
v.cursorIndex = 0
}
v.gui.Update(func(g *gocui.Gui) error {
width, _ := v.body.Size()
width, height := v.body.Size()
imageHeaderStr := format.RenderHeader("Image Details", width, v.gui.CurrentView() == v.body)
@ -107,21 +132,33 @@ func (v *ImageDetails) Render() error {
log.WithFields("error", err).Debug("unable to write to buffer")
}
var lines = []string{
imageNameStr,
imageSizeStr,
wastedSpaceStr,
efficiencyStr,
" ", // to avoid an empty line so CursorDown can work as expected
inefficiencyReport,
v.body.Clear()
for idx, line := range lines {
if idx == v.cursorIndex {
_, err = fmt.Fprintln(v.body, format.Selected(line))
} else {
_, err = fmt.Fprintln(v.body, line)
}
if err != nil {
log.WithFields("error", err).Debug("unable to write to buffer")
}
}
v.body.Clear()
_, err = fmt.Fprintln(v.body, strings.Join(lines, "\n"))
if err != nil {
log.WithFields("error", err).Debug("unable to write to buffer")
// Adjust origin to keep cursor visible
if height > 0 {
maxVisibleIdx := height - 1
if v.cursorIndex > maxVisibleIdx {
if err := v.body.SetOrigin(0, v.cursorIndex-maxVisibleIdx); err != nil {
v.logger.WithFields("error", err).Debug("unable to set origin")
}
} else {
if err := v.body.SetOrigin(0, 0); err != nil {
v.logger.WithFields("error", err).Debug("unable to reset origin")
}
}
}
return err
return nil
})
return nil
@ -141,30 +178,45 @@ func (v *ImageDetails) IsVisible() bool {
func (v *ImageDetails) PageUp() error {
_, height := v.body.Size()
if err := CursorStep(v.gui, v.body, -height); err != nil {
v.logger.WithFields("error", err).Debugf("couldn't move the cursor up by %d steps", height)
newIndex := v.cursorIndex - height
if newIndex < 0 {
newIndex = 0
}
if newIndex != v.cursorIndex {
v.cursorIndex = newIndex
return v.Render()
}
return nil
}
func (v *ImageDetails) PageDown() error {
_, height := v.body.Size()
if err := CursorStep(v.gui, v.body, height); err != nil {
v.logger.WithFields("error", err).Debugf("couldn't move the cursor down by %d steps", height)
newIndex := v.cursorIndex + height
if newIndex >= v.lineCount {
newIndex = v.lineCount - 1
}
if newIndex < 0 {
newIndex = 0
}
if newIndex != v.cursorIndex {
v.cursorIndex = newIndex
return v.Render()
}
return nil
}
func (v *ImageDetails) CursorUp() error {
if err := CursorUp(v.gui, v.body); err != nil {
v.logger.WithFields("error", err).Debug("couldn't move the cursor up")
if v.cursorIndex > 0 {
v.cursorIndex--
return v.Render()
}
return nil
}
func (v *ImageDetails) CursorDown() error {
if err := CursorDown(v.gui, v.body); err != nil {
v.logger.WithFields("error", err).Debug("couldn't move the cursor down")
if v.cursorIndex < v.lineCount-1 {
v.cursorIndex++
return v.Render()
}
return nil
}