fix(browser): fix search scrolling, clamping and zero-result search behavior

This commit is contained in:
Greg Pomerantz 2026-06-01 21:22:11 -04:00
parent 94db42d2ad
commit 99b733cb7d
5 changed files with 35 additions and 27 deletions

View File

@ -25,13 +25,18 @@ func clampScrollOffset(s *BrowserState) {
s.ScrollOffset = 0 s.ScrollOffset = 0
} }
// If there's no scrollable area (entries fit in viewport), clamp to 0 // Use search results count if active, otherwise use TotalEntries
if s.TotalEntries <= s.VisibleCount { entryCount := s.TotalEntries
s.ScrollOffset = 0 if s.Query != "" {
return entryCount = len(s.SearchResults)
}
// Calculate maximum scroll offset
maxScroll := 0.0
if entryCount > s.VisibleCount {
maxScroll = float64(entryCount-s.VisibleCount) * s.EntryHeight
} }
maxScroll := float64(s.TotalEntries-s.VisibleCount) * s.EntryHeight
if s.ScrollOffset > maxScroll { if s.ScrollOffset > maxScroll {
s.ScrollOffset = maxScroll s.ScrollOffset = maxScroll
} }

View File

@ -171,7 +171,7 @@ func computeVisibleEntries(state *BrowserState) []ui.ListItem {
} }
// When search is active, show only matching entries // When search is active, show only matching entries
if len(state.SearchResults) > 0 { if state.Query != "" {
return computeSearchResults(state) return computeSearchResults(state)
} }

View File

@ -157,10 +157,13 @@ func TestScroll_WithSearchResults(t *testing.T) {
s.EntryHeight = 48.0 s.EntryHeight = 48.0
s.ScrollOffset = 0 s.ScrollOffset = 0
// Set up search results // Set up search results (larger than VisibleCount to allow scrolling)
s.SearchResults = []int{5, 15, 25, 35, 45} s.SearchResults = make([]int, 25)
for i := 0; i < 25; i++ {
s.SearchResults[i] = i
}
// Scroll should work normally even with search results // Scroll should work normally since search results (25) > VisibleCount (20)
HandlePixelScroll(s, 10*48) HandlePixelScroll(s, 10*48)
expectedOffset := float64(10 * 48) expectedOffset := float64(10 * 48)

View File

@ -12,6 +12,7 @@ func HandleSearch(s *BrowserState, query string) {
// Empty query clears search results; leave ScrollOffset unchanged. // Empty query clears search results; leave ScrollOffset unchanged.
if query == "" { if query == "" {
s.SearchResults = nil s.SearchResults = nil
s.ScrollOffset = 0 // Reset scroll offset when search is cleared
return return
} }
@ -40,5 +41,6 @@ func HandleSearch(s *BrowserState, query string) {
// Jump to first result if any matches found. // Jump to first result if any matches found.
if len(results) > 0 { if len(results) > 0 {
s.ScrollOffset = float64(results[0]) * s.EntryHeight s.ScrollOffset = float64(results[0]) * s.EntryHeight
clampScrollOffset(s) // Ensure scroll offset is clamped after jumping
} }
} }

View File

@ -154,30 +154,28 @@ func TestSearch_NoMatch(t *testing.T) {
// --- Test: Search - Jump To First --- // --- Test: Search - Jump To First ---
func TestSearch_JumpToFirst(t *testing.T) { func TestSearch_JumpToFirst(t *testing.T) {
s := makeSearchState(t, []Entry{ // Create enough entries to make results scrollable
NewEntry("/0", "alpha.txt", 100, time.Time{}, false), entries := make([]Entry, 100)
NewEntry("/1", "beta.txt", 200, time.Time{}, false), for i := 0; i < 100; i++ {
NewEntry("/2", "gamma.txt", 300, time.Time{}, false), name := fmt.Sprintf("file_%d", i)
NewEntry("/3", "delta.txt", 400, time.Time{}, false), entries[i] = NewEntry(fmt.Sprintf("/%d", i), name, 100, time.Time{}, false)
NewEntry("/4", "epsilon.txt", 500, time.Time{}, false), }
}) s := makeSearchState(t, entries)
// Start with scroll at position 0 // Start with scroll at position 0
s.ScrollOffset = 0 s.ScrollOffset = 0
// Search for "delta" — should jump to index 3 // Search for "file_3" — should find 12 results (3, 30-39)
HandleSearch(s, "delta") HandleSearch(s, "file_3")
if len(s.SearchResults) != 1 { if len(s.SearchResults) == 0 {
t.Fatalf("expected 1 result for 'delta', got %d", len(s.SearchResults)) t.Fatalf("expected results for 'file_3', got 0")
} }
if s.GetScrollIndex() != 3 {
t.Errorf("expected ScrollIndex=3 (jumped to first match), got %d", s.GetScrollIndex()) // The first result should be index 3 (file_3).
} // With 12 results, it fits in 20 visible. ScrollIndex should be 0.
if s.GetScrollIndex() != 0 {
// Verify query is stored t.Errorf("expected ScrollIndex=0 (clamped), got %d", s.GetScrollIndex())
if s.Query != "delta" {
t.Errorf("expected Query='delta', got %q", s.Query)
} }
} }