Просмотр исходного кода

Feat: Implement Wrap-Around Navigation for List Selection (for Models and Tools modal) (#1768)

spoons-and-mirrors 6 месяцев назад
Родитель
Сommit
ab2df0ae33

+ 17 - 12
packages/tui/internal/components/list/list.go

@@ -173,7 +173,13 @@ func (c *listComponent[T]) moveUp() {
 		}
 	}
 
-	// If no selectable item found above, stay at current position
+	// If no selectable item found above, wrap to the bottom
+	for i := len(c.items) - 1; i > c.selectedIdx; i-- {
+		if c.isSelectable(c.items[i]) {
+			c.selectedIdx = i
+			return
+		}
+	}
 }
 
 // moveDown moves the selection down, skipping non-selectable items
@@ -183,20 +189,19 @@ func (c *listComponent[T]) moveDown() {
 	}
 
 	originalIdx := c.selectedIdx
-	for {
-		if c.selectedIdx < len(c.items)-1 {
-			c.selectedIdx++
-		} else {
-			break
-		}
-
-		if c.isSelectable(c.items[c.selectedIdx]) {
+	// First try moving down from current position
+	for i := c.selectedIdx + 1; i < len(c.items); i++ {
+		if c.isSelectable(c.items[i]) {
+			c.selectedIdx = i
 			return
 		}
+	}
 
-		// Prevent infinite loop
-		if c.selectedIdx == originalIdx {
-			break
+	// If no selectable item found below, wrap to the top
+	for i := 0; i < originalIdx; i++ {
+		if c.isSelectable(c.items[i]) {
+			c.selectedIdx = i
+			return
 		}
 	}
 }

+ 45 - 6
packages/tui/internal/components/list/list_test.go

@@ -138,15 +138,18 @@ func TestCtrlNavigation(t *testing.T) {
 func TestNavigationBoundaries(t *testing.T) {
 	list := createTestList()
 
-	// Test up arrow at first item (should stay at 0)
+	// Test up arrow at first item (should wrap to last item)
 	upKey := tea.KeyPressMsg{Code: tea.KeyUp}
 	updatedModel, _ := list.Update(upKey)
 	list = updatedModel.(*listComponent[testItem])
 	_, idx := list.GetSelectedItem()
-	if idx != 0 {
-		t.Errorf("Expected to stay at index 0 when pressing up at first item, got %d", idx)
+	if idx != 2 {
+		t.Errorf("Expected to wrap to index 2 when pressing up at first item, got %d", idx)
 	}
 
+	// Move to first item
+	list.SetSelectedIndex(0)
+
 	// Move to last item
 	downKey := tea.KeyPressMsg{Code: tea.KeyDown}
 	updatedModel, _ = list.Update(downKey)
@@ -158,12 +161,12 @@ func TestNavigationBoundaries(t *testing.T) {
 		t.Errorf("Expected to be at index 2, got %d", idx)
 	}
 
-	// Test down arrow at last item (should stay at 2)
+	// Test down arrow at last item (should wrap to first item)
 	updatedModel, _ = list.Update(downKey)
 	list = updatedModel.(*listComponent[testItem])
 	_, idx = list.GetSelectedItem()
-	if idx != 2 {
-		t.Errorf("Expected to stay at index 2 when pressing down at last item, got %d", idx)
+	if idx != 0 {
+		t.Errorf("Expected to wrap to index 0 when pressing down at last item, got %d", idx)
 	}
 }
 
@@ -208,3 +211,39 @@ func TestEmptyList(t *testing.T) {
 		t.Error("Expected IsEmpty() to return true for empty list")
 	}
 }
+
+func TestWrapAroundNavigation(t *testing.T) {
+	list := createTestList()
+
+	// Start at first item (index 0)
+	_, idx := list.GetSelectedItem()
+	if idx != 0 {
+		t.Errorf("Expected to start at index 0, got %d", idx)
+	}
+
+	// Press up arrow - should wrap to last item (index 2)
+	upKey := tea.KeyPressMsg{Code: tea.KeyUp}
+	updatedModel, _ := list.Update(upKey)
+	list = updatedModel.(*listComponent[testItem])
+	_, idx = list.GetSelectedItem()
+	if idx != 2 {
+		t.Errorf("Expected to wrap to index 2 when pressing up from first item, got %d", idx)
+	}
+
+	// Press down arrow - should wrap to first item (index 0)
+	downKey := tea.KeyPressMsg{Code: tea.KeyDown}
+	updatedModel, _ = list.Update(downKey)
+	list = updatedModel.(*listComponent[testItem])
+	_, idx = list.GetSelectedItem()
+	if idx != 0 {
+		t.Errorf("Expected to wrap to index 0 when pressing down from last item, got %d", idx)
+	}
+
+	// Navigate to middle and verify normal navigation still works
+	updatedModel, _ = list.Update(downKey)
+	list = updatedModel.(*listComponent[testItem])
+	_, idx = list.GetSelectedItem()
+	if idx != 1 {
+		t.Errorf("Expected to move to index 1, got %d", idx)
+	}
+}