From 85564b319b4addd80fc67a6c2b28b6dbe77a554f Mon Sep 17 00:00:00 2001 From: majiayu000 <1835304752@qq.com> Date: Wed, 31 Dec 2025 02:22:18 +0800 Subject: [PATCH] fix: clamp cursor position to visible range when toggling file tree views MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When toggling file visibility in the Layer Contents pane (e.g., hiding diff types), the cursor position could become invalid if it pointed beyond the new visible range. This caused the highlighted line to disappear. The fix clamps TreeIndex, bufferIndex, and bufferIndexLowerBound to valid ranges after updating visibility in the Update() method. Fixes #543 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 Signed-off-by: majiayu000 <1835304752@qq.com> --- .../cli/internal/ui/v1/viewmodel/filetree.go | 21 ++++++++ .../internal/ui/v1/viewmodel/filetree_test.go | 48 +++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/cmd/dive/cli/internal/ui/v1/viewmodel/filetree.go b/cmd/dive/cli/internal/ui/v1/viewmodel/filetree.go index 48332d7..626489e 100644 --- a/cmd/dive/cli/internal/ui/v1/viewmodel/filetree.go +++ b/cmd/dive/cli/internal/ui/v1/viewmodel/filetree.go @@ -445,6 +445,27 @@ func (vm *FileTreeViewModel) Update(filterRegex *regexp.Regexp, width, height in return fmt.Errorf("unable to propagate vm view tree: %w", err) } + // Clamp cursor position to visible range after visibility changes + visibleSize := vm.ModelTree.VisibleSize() + if visibleSize == 0 { + vm.TreeIndex = 0 + vm.bufferIndex = 0 + vm.bufferIndexLowerBound = 0 + } else if vm.TreeIndex >= visibleSize { + vm.TreeIndex = visibleSize - 1 + // Adjust buffer position to keep cursor in view + if vm.TreeIndex < vm.bufferIndexLowerBound { + vm.bufferIndexLowerBound = vm.TreeIndex + vm.bufferIndex = 0 + } else { + vm.bufferIndex = vm.TreeIndex - vm.bufferIndexLowerBound + if vm.bufferIndex > vm.height() { + vm.bufferIndex = vm.height() + vm.bufferIndexLowerBound = vm.TreeIndex - vm.height() + } + } + } + return nil } diff --git a/cmd/dive/cli/internal/ui/v1/viewmodel/filetree_test.go b/cmd/dive/cli/internal/ui/v1/viewmodel/filetree_test.go index dd406ec..1469a5a 100644 --- a/cmd/dive/cli/internal/ui/v1/viewmodel/filetree_test.go +++ b/cmd/dive/cli/internal/ui/v1/viewmodel/filetree_test.go @@ -399,6 +399,54 @@ func TestFileTreeHideTypeWithFilter(t *testing.T) { runTestCase(t, vm, width, height, regex) } +func TestFileTreeCursorClampingOnToggleHideDiffType(t *testing.T) { + // Test that cursor position is clamped when toggling file visibility + // This is a regression test for issue #543 + vm := initializeTestViewModel(t) + + width, height := 100, 100 + vm.Setup(0, height) + vm.ShowAttributes = false + + // Select a layer that has different diff types + err := vm.SetTreeByLayer(0, 0, 1, 7) + require.NoError(t, err, "unable to SetTreeByLayer") + + err = vm.Update(nil, width, height) + require.NoError(t, err, "unable to update") + + // Move cursor down significantly + initialVisibleSize := vm.ModelTree.VisibleSize() + targetPosition := initialVisibleSize / 2 + for i := 0; i < targetPosition; i++ { + vm.CursorDown() + } + assert.Equal(t, targetPosition, vm.TreeIndex, "cursor should be at target position") + + // Now hide unmodified files - this should significantly reduce visible items + vm.ToggleShowDiffType(filetree.Unmodified) + err = vm.Update(nil, width, height) + require.NoError(t, err, "unable to update after toggle") + + newVisibleSize := vm.ModelTree.VisibleSize() + + // Verify cursor is clamped to valid range + if newVisibleSize == 0 { + assert.Equal(t, 0, vm.TreeIndex, "cursor should be 0 when no visible items") + } else { + assert.Less(t, vm.TreeIndex, newVisibleSize, "cursor should be within visible range") + assert.GreaterOrEqual(t, vm.TreeIndex, 0, "cursor should not be negative") + } + + // Verify buffer indices are consistent + assert.GreaterOrEqual(t, vm.bufferIndex, 0, "bufferIndex should not be negative") + assert.GreaterOrEqual(t, vm.bufferIndexLowerBound, 0, "bufferIndexLowerBound should not be negative") + + // Verify render works without panic + err = vm.Render() + require.NoError(t, err, "render should succeed after cursor clamping") +} + func repoPath(t testing.TB, path string) string { t.Helper() root := repoRoot(t)