fix: clamp cursor position to visible range when toggling file tree views

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 <noreply@anthropic.com>
Signed-off-by: majiayu000 <1835304752@qq.com>
This commit is contained in:
majiayu000 2025-12-31 02:22:18 +08:00
commit 85564b319b
2 changed files with 69 additions and 0 deletions

View file

@ -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
}

View file

@ -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)