fix(omnibox): cap selection to visible results
- commit
- bd7f2a93ccc32cfef5604fcd19dadae2674e86df
- author
- brice · · 2026-04-08 11:53 UTC
- parent
- 94b7c39
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +74 -31
M internal/ui/component/omnibox.go +43 -23
| @@ -636,7 +636,7 @@ func (o *Omnibox) initList() error { | |||
| 636 | 636 | return | |
| 637 | 637 | } | |
| 638 | 638 | ||
| 639 | - | targetURL := resolveTargetURLForSelection(mode, idx, suggestions, favorites) | |
| 639 | + | targetURL := resolveTargetURLForSelection(mode, idx, o.effectiveMaxRows(), suggestions, favorites) | |
| 640 | 640 | ||
| 641 | 641 | if targetURL != "" && o.onNavigate != nil { | |
| 642 | 642 | o.Hide(o.ctx) | |
| @@ -1141,6 +1141,7 @@ func (o *Omnibox) restoreEntryToRealInput() { | |||
| 1141 | 1141 | // updateGhostFromSelectionWithInput updates ghost text based on selected row and input. | |
| 1142 | 1142 | func (o *Omnibox) updateGhostFromSelectionWithInput(entryText string) { | |
| 1143 | 1143 | log := logging.FromContext(o.ctx) | |
| 1144 | + | maxVisible := o.effectiveMaxRows() | |
| 1144 | 1145 | o.mu.RLock() | |
| 1145 | 1146 | idx := o.selectedIndex | |
| 1146 | 1147 | mode := o.viewMode | |
| @@ -1168,10 +1169,10 @@ func (o *Omnibox) updateGhostFromSelectionWithInput(entryText string) { | |||
| 1168 | 1169 | return | |
| 1169 | 1170 | } | |
| 1170 | 1171 | ||
| 1171 | - | targetURL, hasExplicitSelection := selectedTargetURL(mode, idx, suggestions, favorites) | |
| 1172 | + | targetURL, hasExplicitSelection := selectedTargetURL(mode, idx, maxVisible, suggestions, favorites) | |
| 1172 | 1173 | ||
| 1173 | 1174 | if entryText != "" { | |
| 1174 | - | o.resolveGhostCompletion(entryText, targetURL, hasExplicitSelection, mode, suggestions, favorites) | |
| 1175 | + | o.resolveGhostCompletion(entryText, targetURL, hasExplicitSelection, mode, maxVisible, suggestions, favorites) | |
| 1175 | 1176 | return | |
| 1176 | 1177 | } | |
| 1177 | 1178 | ||
| @@ -1193,6 +1194,7 @@ func (o *Omnibox) resolveGhostCompletion( | |||
| 1193 | 1194 | entryText, selectedURL string, | |
| 1194 | 1195 | hasExplicitSelection bool, | |
| 1195 | 1196 | mode ViewMode, | |
| 1197 | + | maxVisible int, | |
| 1196 | 1198 | suggestions []Suggestion, | |
| 1197 | 1199 | favorites []Favorite, | |
| 1198 | 1200 | ) { | |
| @@ -1203,7 +1205,7 @@ func (o *Omnibox) resolveGhostCompletion( | |||
| 1203 | 1205 | return | |
| 1204 | 1206 | } | |
| 1205 | 1207 | ||
| 1206 | - | fullText, suffix, found := visibleGhostSuggestion(completionInput, selectedURL, hasExplicitSelection, mode, suggestions, favorites) | |
| 1208 | + | fullText, suffix, found := visibleGhostSuggestion(completionInput, selectedURL, hasExplicitSelection, mode, maxVisible, suggestions, favorites) | |
| 1207 | 1209 | if !found { | |
| 1208 | 1210 | log.Debug().Str("input", completionInput).Msg("ghost: resolveGhost — no match found") | |
| 1209 | 1211 | return | |
| @@ -1275,6 +1277,7 @@ func visibleGhostSuggestion( | |||
| 1275 | 1277 | query, selectedURL string, | |
| 1276 | 1278 | hasExplicitSelection bool, | |
| 1277 | 1279 | mode ViewMode, | |
| 1280 | + | maxVisible int, | |
| 1278 | 1281 | suggestions []Suggestion, | |
| 1279 | 1282 | favorites []Favorite, | |
| 1280 | 1283 | ) (fullText, suffix string, ok bool) { | |
| @@ -1283,15 +1286,16 @@ func visibleGhostSuggestion( | |||
| 1283 | 1286 | return fullText, suffix, ok | |
| 1284 | 1287 | } | |
| 1285 | 1288 | ||
| 1286 | - | visibleURLs := visibleURLsForMode(mode, suggestions, favorites) | |
| 1289 | + | visibleURLs := visibleURLsForMode(mode, maxVisible, suggestions, favorites) | |
| 1287 | 1290 | suffix, fullText, ok = autocomplete.BestURLCompletion(query, visibleURLs) | |
| 1288 | 1291 | return fullText, suffix, ok | |
| 1289 | 1292 | } | |
| 1290 | 1293 | ||
| 1291 | - | func visibleURLsForMode(mode ViewMode, suggestions []Suggestion, favorites []Favorite) []string { | |
| 1294 | + | func visibleURLsForMode(mode ViewMode, maxVisible int, suggestions []Suggestion, favorites []Favorite) []string { | |
| 1292 | 1295 | if mode == ViewModeHistory { | |
| 1293 | - | urls := make([]string, 0, len(suggestions)) | |
| 1294 | - | for _, s := range suggestions { | |
| 1296 | + | visibleCount := visibleResultCount(len(suggestions), maxVisible) | |
| 1297 | + | urls := make([]string, 0, visibleCount) | |
| 1298 | + | for _, s := range suggestions[:visibleCount] { | |
| 1295 | 1299 | if s.URL != "" { | |
| 1296 | 1300 | urls = append(urls, s.URL) | |
| 1297 | 1301 | } | |
| @@ -1299,8 +1303,9 @@ func visibleURLsForMode(mode ViewMode, suggestions []Suggestion, favorites []Fav | |||
| 1299 | 1303 | return urls | |
| 1300 | 1304 | } | |
| 1301 | 1305 | ||
| 1302 | - | urls := make([]string, 0, len(favorites)) | |
| 1303 | - | for _, f := range favorites { | |
| 1306 | + | visibleCount := visibleResultCount(len(favorites), maxVisible) | |
| 1307 | + | urls := make([]string, 0, visibleCount) | |
| 1308 | + | for _, f := range favorites[:visibleCount] { | |
| 1304 | 1309 | if f.URL != "" { | |
| 1305 | 1310 | urls = append(urls, f.URL) | |
| 1306 | 1311 | } | |
| @@ -1308,11 +1313,21 @@ func visibleURLsForMode(mode ViewMode, suggestions []Suggestion, favorites []Fav | |||
| 1308 | 1313 | return urls | |
| 1309 | 1314 | } | |
| 1310 | 1315 | ||
| 1311 | - | func selectedTargetURL(mode ViewMode, idx int, suggestions []Suggestion, favorites []Favorite) (string, bool) { | |
| 1316 | + | func selectedTargetURL(mode ViewMode, idx, maxVisible int, suggestions []Suggestion, favorites []Favorite) (string, bool) { | |
| 1312 | 1317 | if idx < 0 { | |
| 1313 | 1318 | return "", false | |
| 1314 | 1319 | } | |
| 1315 | - | return resolveTargetURLForSelection(mode, idx, suggestions, favorites), true | |
| 1320 | + | return resolveTargetURLForSelection(mode, idx, maxVisible, suggestions, favorites), true | |
| 1321 | + | } | |
| 1322 | + | ||
| 1323 | + | func visibleResultCount(total, maxVisible int) int { | |
| 1324 | + | if total <= 0 { | |
| 1325 | + | return 0 | |
| 1326 | + | } | |
| 1327 | + | if maxVisible <= 0 || total < maxVisible { | |
| 1328 | + | return total | |
| 1329 | + | } | |
| 1330 | + | return maxVisible | |
| 1316 | 1331 | } | |
| 1317 | 1332 | ||
| 1318 | 1333 | // performSearch executes the search based on current view mode and query. | |
| @@ -2022,13 +2037,14 @@ func (o *Omnibox) selectNext() { | |||
| 2022 | 2037 | current := o.selectedIndex | |
| 2023 | 2038 | mode := o.viewMode | |
| 2024 | 2039 | bangMode := o.bangMode | |
| 2040 | + | maxVisible := o.effectiveMaxRows() | |
| 2025 | 2041 | var maxIndex int | |
| 2026 | 2042 | if bangMode { | |
| 2027 | - | maxIndex = len(o.bangSuggestions) - 1 | |
| 2043 | + | maxIndex = visibleResultCount(len(o.bangSuggestions), maxVisible) - 1 | |
| 2028 | 2044 | } else if mode == ViewModeHistory { | |
| 2029 | - | maxIndex = len(o.suggestions) - 1 | |
| 2045 | + | maxIndex = visibleResultCount(len(o.suggestions), maxVisible) - 1 | |
| 2030 | 2046 | } else { | |
| 2031 | - | maxIndex = len(o.favorites) - 1 | |
| 2047 | + | maxIndex = visibleResultCount(len(o.favorites), maxVisible) - 1 | |
| 2032 | 2048 | } | |
| 2033 | 2049 | o.hasNavigated = true // User is navigating with arrow keys | |
| 2034 | 2050 | o.mu.Unlock() | |
| @@ -2050,13 +2066,14 @@ func (o *Omnibox) selectPrevious() { | |||
| 2050 | 2066 | current := o.selectedIndex | |
| 2051 | 2067 | mode := o.viewMode | |
| 2052 | 2068 | bangMode := o.bangMode | |
| 2069 | + | maxVisible := o.effectiveMaxRows() | |
| 2053 | 2070 | var maxIndex int | |
| 2054 | 2071 | if bangMode { | |
| 2055 | - | maxIndex = len(o.bangSuggestions) - 1 | |
| 2072 | + | maxIndex = visibleResultCount(len(o.bangSuggestions), maxVisible) - 1 | |
| 2056 | 2073 | } else if mode == ViewModeHistory { | |
| 2057 | - | maxIndex = len(o.suggestions) - 1 | |
| 2074 | + | maxIndex = visibleResultCount(len(o.suggestions), maxVisible) - 1 | |
| 2058 | 2075 | } else { | |
| 2059 | - | maxIndex = len(o.favorites) - 1 | |
| 2076 | + | maxIndex = visibleResultCount(len(o.favorites), maxVisible) - 1 | |
| 2060 | 2077 | } | |
| 2061 | 2078 | o.hasNavigated = true // User is navigating with arrow keys | |
| 2062 | 2079 | o.mu.Unlock() | |
| @@ -2082,13 +2099,14 @@ func (o *Omnibox) selectAndNavigate(index int) { | |||
| 2082 | 2099 | suggestions := o.suggestions | |
| 2083 | 2100 | favorites := o.favorites | |
| 2084 | 2101 | o.mu.RUnlock() | |
| 2102 | + | maxVisible := o.effectiveMaxRows() | |
| 2085 | 2103 | ||
| 2086 | 2104 | if bangMode { | |
| 2087 | 2105 | o.navigateToSelected() | |
| 2088 | 2106 | return | |
| 2089 | 2107 | } | |
| 2090 | 2108 | ||
| 2091 | - | targetURL := resolveTargetURLForSelection(mode, index, suggestions, favorites) | |
| 2109 | + | targetURL := resolveTargetURLForSelection(mode, index, maxVisible, suggestions, favorites) | |
| 2092 | 2110 | if targetURL == "" { | |
| 2093 | 2111 | return | |
| 2094 | 2112 | } | |
| @@ -2142,7 +2160,7 @@ func (o *Omnibox) navigateToSelected() { | |||
| 2142 | 2160 | var targetURL string | |
| 2143 | 2161 | if idx >= 0 { | |
| 2144 | 2162 | // If user has selected a result, navigate to that result. | |
| 2145 | - | targetURL = resolveTargetURLForSelection(mode, idx, suggestions, favorites) | |
| 2163 | + | targetURL = resolveTargetURLForSelection(mode, idx, o.effectiveMaxRows(), suggestions, favorites) | |
| 2146 | 2164 | } else { | |
| 2147 | 2165 | targetURL = o.buildURL(entryText) | |
| 2148 | 2166 | } | |
| @@ -2158,14 +2176,16 @@ func (o *Omnibox) navigateToSelected() { | |||
| 2158 | 2176 | } | |
| 2159 | 2177 | } | |
| 2160 | 2178 | ||
| 2161 | - | func resolveTargetURLForSelection(mode ViewMode, idx int, suggestions []Suggestion, favorites []Favorite) string { | |
| 2179 | + | func resolveTargetURLForSelection(mode ViewMode, idx, maxVisible int, suggestions []Suggestion, favorites []Favorite) string { | |
| 2162 | 2180 | if mode == ViewModeHistory { | |
| 2163 | - | if idx >= 0 && idx < len(suggestions) { | |
| 2181 | + | visibleCount := visibleResultCount(len(suggestions), maxVisible) | |
| 2182 | + | if idx >= 0 && idx < visibleCount { | |
| 2164 | 2183 | return suggestions[idx].URL | |
| 2165 | 2184 | } | |
| 2166 | 2185 | return "" | |
| 2167 | 2186 | } | |
| 2168 | - | if idx >= 0 && idx < len(favorites) { | |
| 2187 | + | visibleCount := visibleResultCount(len(favorites), maxVisible) | |
| 2188 | + | if idx >= 0 && idx < visibleCount { | |
| 2169 | 2189 | return favorites[idx].URL | |
| 2170 | 2190 | } | |
| 2171 | 2191 | return "" | |
| @@ -636,7 +636,7 @@ func (o *Omnibox) initList() error { | |||||
| 636 | return | 636 | return | ||
| 637 | } | 637 | } | ||
| 638 | 638 | ||||
| 639 | - | targetURL := resolveTargetURLForSelection(mode, idx, suggestions, favorites) | 639 | + | targetURL := resolveTargetURLForSelection(mode, idx, o.effectiveMaxRows(), suggestions, favorites) |
| 640 | 640 | ||||
| 641 | if targetURL != "" && o.onNavigate != nil { | 641 | if targetURL != "" && o.onNavigate != nil { | ||
| 642 | o.Hide(o.ctx) | 642 | o.Hide(o.ctx) | ||
| @@ -1141,6 +1141,7 @@ func (o *Omnibox) restoreEntryToRealInput() { | |||||
| 1141 | // updateGhostFromSelectionWithInput updates ghost text based on selected row and input. | 1141 | // updateGhostFromSelectionWithInput updates ghost text based on selected row and input. | ||
| 1142 | func (o *Omnibox) updateGhostFromSelectionWithInput(entryText string) { | 1142 | func (o *Omnibox) updateGhostFromSelectionWithInput(entryText string) { | ||
| 1143 | log := logging.FromContext(o.ctx) | 1143 | log := logging.FromContext(o.ctx) | ||
| 1144 | + | maxVisible := o.effectiveMaxRows() | |||
| 1144 | o.mu.RLock() | 1145 | o.mu.RLock() | ||
| 1145 | idx := o.selectedIndex | 1146 | idx := o.selectedIndex | ||
| 1146 | mode := o.viewMode | 1147 | mode := o.viewMode | ||
| @@ -1168,10 +1169,10 @@ func (o *Omnibox) updateGhostFromSelectionWithInput(entryText string) { | |||||
| 1168 | return | 1169 | return | ||
| 1169 | } | 1170 | } | ||
| 1170 | 1171 | ||||
| 1171 | - | targetURL, hasExplicitSelection := selectedTargetURL(mode, idx, suggestions, favorites) | 1172 | + | targetURL, hasExplicitSelection := selectedTargetURL(mode, idx, maxVisible, suggestions, favorites) |
| 1172 | 1173 | ||||
| 1173 | if entryText != "" { | 1174 | if entryText != "" { | ||
| 1174 | - | o.resolveGhostCompletion(entryText, targetURL, hasExplicitSelection, mode, suggestions, favorites) | 1175 | + | o.resolveGhostCompletion(entryText, targetURL, hasExplicitSelection, mode, maxVisible, suggestions, favorites) |
| 1175 | return | 1176 | return | ||
| 1176 | } | 1177 | } | ||
| 1177 | 1178 | ||||
| @@ -1193,6 +1194,7 @@ func (o *Omnibox) resolveGhostCompletion( | |||||
| 1193 | entryText, selectedURL string, | 1194 | entryText, selectedURL string, | ||
| 1194 | hasExplicitSelection bool, | 1195 | hasExplicitSelection bool, | ||
| 1195 | mode ViewMode, | 1196 | mode ViewMode, | ||
| 1197 | + | maxVisible int, | |||
| 1196 | suggestions []Suggestion, | 1198 | suggestions []Suggestion, | ||
| 1197 | favorites []Favorite, | 1199 | favorites []Favorite, | ||
| 1198 | ) { | 1200 | ) { | ||
| @@ -1203,7 +1205,7 @@ func (o *Omnibox) resolveGhostCompletion( | |||||
| 1203 | return | 1205 | return | ||
| 1204 | } | 1206 | } | ||
| 1205 | 1207 | ||||
| 1206 | - | fullText, suffix, found := visibleGhostSuggestion(completionInput, selectedURL, hasExplicitSelection, mode, suggestions, favorites) | 1208 | + | fullText, suffix, found := visibleGhostSuggestion(completionInput, selectedURL, hasExplicitSelection, mode, maxVisible, suggestions, favorites) |
| 1207 | if !found { | 1209 | if !found { | ||
| 1208 | log.Debug().Str("input", completionInput).Msg("ghost: resolveGhost — no match found") | 1210 | log.Debug().Str("input", completionInput).Msg("ghost: resolveGhost — no match found") | ||
| 1209 | return | 1211 | return | ||
| @@ -1275,6 +1277,7 @@ func visibleGhostSuggestion( | |||||
| 1275 | query, selectedURL string, | 1277 | query, selectedURL string, | ||
| 1276 | hasExplicitSelection bool, | 1278 | hasExplicitSelection bool, | ||
| 1277 | mode ViewMode, | 1279 | mode ViewMode, | ||
| 1280 | + | maxVisible int, | |||
| 1278 | suggestions []Suggestion, | 1281 | suggestions []Suggestion, | ||
| 1279 | favorites []Favorite, | 1282 | favorites []Favorite, | ||
| 1280 | ) (fullText, suffix string, ok bool) { | 1283 | ) (fullText, suffix string, ok bool) { | ||
| @@ -1283,15 +1286,16 @@ func visibleGhostSuggestion( | |||||
| 1283 | return fullText, suffix, ok | 1286 | return fullText, suffix, ok | ||
| 1284 | } | 1287 | } | ||
| 1285 | 1288 | ||||
| 1286 | - | visibleURLs := visibleURLsForMode(mode, suggestions, favorites) | 1289 | + | visibleURLs := visibleURLsForMode(mode, maxVisible, suggestions, favorites) |
| 1287 | suffix, fullText, ok = autocomplete.BestURLCompletion(query, visibleURLs) | 1290 | suffix, fullText, ok = autocomplete.BestURLCompletion(query, visibleURLs) | ||
| 1288 | return fullText, suffix, ok | 1291 | return fullText, suffix, ok | ||
| 1289 | } | 1292 | } | ||
| 1290 | 1293 | ||||
| 1291 | - | func visibleURLsForMode(mode ViewMode, suggestions []Suggestion, favorites []Favorite) []string { | 1294 | + | func visibleURLsForMode(mode ViewMode, maxVisible int, suggestions []Suggestion, favorites []Favorite) []string { |
| 1292 | if mode == ViewModeHistory { | 1295 | if mode == ViewModeHistory { | ||
| 1293 | - | urls := make([]string, 0, len(suggestions)) | 1296 | + | visibleCount := visibleResultCount(len(suggestions), maxVisible) |
| 1294 | - | for _, s := range suggestions { | 1297 | + | urls := make([]string, 0, visibleCount) |
| 1298 | + | for _, s := range suggestions[:visibleCount] { | |||
| 1295 | if s.URL != "" { | 1299 | if s.URL != "" { | ||
| 1296 | urls = append(urls, s.URL) | 1300 | urls = append(urls, s.URL) | ||
| 1297 | } | 1301 | } | ||
| @@ -1299,8 +1303,9 @@ func visibleURLsForMode(mode ViewMode, suggestions []Suggestion, favorites []Fav | |||||
| 1299 | return urls | 1303 | return urls | ||
| 1300 | } | 1304 | } | ||
| 1301 | 1305 | ||||
| 1302 | - | urls := make([]string, 0, len(favorites)) | 1306 | + | visibleCount := visibleResultCount(len(favorites), maxVisible) |
| 1303 | - | for _, f := range favorites { | 1307 | + | urls := make([]string, 0, visibleCount) |
| 1308 | + | for _, f := range favorites[:visibleCount] { | |||
| 1304 | if f.URL != "" { | 1309 | if f.URL != "" { | ||
| 1305 | urls = append(urls, f.URL) | 1310 | urls = append(urls, f.URL) | ||
| 1306 | } | 1311 | } | ||
| @@ -1308,11 +1313,21 @@ func visibleURLsForMode(mode ViewMode, suggestions []Suggestion, favorites []Fav | |||||
| 1308 | return urls | 1313 | return urls | ||
| 1309 | } | 1314 | } | ||
| 1310 | 1315 | ||||
| 1311 | - | func selectedTargetURL(mode ViewMode, idx int, suggestions []Suggestion, favorites []Favorite) (string, bool) { | 1316 | + | func selectedTargetURL(mode ViewMode, idx, maxVisible int, suggestions []Suggestion, favorites []Favorite) (string, bool) { |
| 1312 | if idx < 0 { | 1317 | if idx < 0 { | ||
| 1313 | return "", false | 1318 | return "", false | ||
| 1314 | } | 1319 | } | ||
| 1315 | - | return resolveTargetURLForSelection(mode, idx, suggestions, favorites), true | 1320 | + | return resolveTargetURLForSelection(mode, idx, maxVisible, suggestions, favorites), true |
| 1321 | + | } | |||
| 1322 | + | ||||
| 1323 | + | func visibleResultCount(total, maxVisible int) int { | |||
| 1324 | + | if total <= 0 { | |||
| 1325 | + | return 0 | |||
| 1326 | + | } | |||
| 1327 | + | if maxVisible <= 0 || total < maxVisible { | |||
| 1328 | + | return total | |||
| 1329 | + | } | |||
| 1330 | + | return maxVisible | |||
| 1316 | } | 1331 | } | ||
| 1317 | 1332 | ||||
| 1318 | // performSearch executes the search based on current view mode and query. | 1333 | // performSearch executes the search based on current view mode and query. | ||
| @@ -2022,13 +2037,14 @@ func (o *Omnibox) selectNext() { | |||||
| 2022 | current := o.selectedIndex | 2037 | current := o.selectedIndex | ||
| 2023 | mode := o.viewMode | 2038 | mode := o.viewMode | ||
| 2024 | bangMode := o.bangMode | 2039 | bangMode := o.bangMode | ||
| 2040 | + | maxVisible := o.effectiveMaxRows() | |||
| 2025 | var maxIndex int | 2041 | var maxIndex int | ||
| 2026 | if bangMode { | 2042 | if bangMode { | ||
| 2027 | - | maxIndex = len(o.bangSuggestions) - 1 | 2043 | + | maxIndex = visibleResultCount(len(o.bangSuggestions), maxVisible) - 1 |
| 2028 | } else if mode == ViewModeHistory { | 2044 | } else if mode == ViewModeHistory { | ||
| 2029 | - | maxIndex = len(o.suggestions) - 1 | 2045 | + | maxIndex = visibleResultCount(len(o.suggestions), maxVisible) - 1 |
| 2030 | } else { | 2046 | } else { | ||
| 2031 | - | maxIndex = len(o.favorites) - 1 | 2047 | + | maxIndex = visibleResultCount(len(o.favorites), maxVisible) - 1 |
| 2032 | } | 2048 | } | ||
| 2033 | o.hasNavigated = true // User is navigating with arrow keys | 2049 | o.hasNavigated = true // User is navigating with arrow keys | ||
| 2034 | o.mu.Unlock() | 2050 | o.mu.Unlock() | ||
| @@ -2050,13 +2066,14 @@ func (o *Omnibox) selectPrevious() { | |||||
| 2050 | current := o.selectedIndex | 2066 | current := o.selectedIndex | ||
| 2051 | mode := o.viewMode | 2067 | mode := o.viewMode | ||
| 2052 | bangMode := o.bangMode | 2068 | bangMode := o.bangMode | ||
| 2069 | + | maxVisible := o.effectiveMaxRows() | |||
| 2053 | var maxIndex int | 2070 | var maxIndex int | ||
| 2054 | if bangMode { | 2071 | if bangMode { | ||
| 2055 | - | maxIndex = len(o.bangSuggestions) - 1 | 2072 | + | maxIndex = visibleResultCount(len(o.bangSuggestions), maxVisible) - 1 |
| 2056 | } else if mode == ViewModeHistory { | 2073 | } else if mode == ViewModeHistory { | ||
| 2057 | - | maxIndex = len(o.suggestions) - 1 | 2074 | + | maxIndex = visibleResultCount(len(o.suggestions), maxVisible) - 1 |
| 2058 | } else { | 2075 | } else { | ||
| 2059 | - | maxIndex = len(o.favorites) - 1 | 2076 | + | maxIndex = visibleResultCount(len(o.favorites), maxVisible) - 1 |
| 2060 | } | 2077 | } | ||
| 2061 | o.hasNavigated = true // User is navigating with arrow keys | 2078 | o.hasNavigated = true // User is navigating with arrow keys | ||
| 2062 | o.mu.Unlock() | 2079 | o.mu.Unlock() | ||
| @@ -2082,13 +2099,14 @@ func (o *Omnibox) selectAndNavigate(index int) { | |||||
| 2082 | suggestions := o.suggestions | 2099 | suggestions := o.suggestions | ||
| 2083 | favorites := o.favorites | 2100 | favorites := o.favorites | ||
| 2084 | o.mu.RUnlock() | 2101 | o.mu.RUnlock() | ||
| 2102 | + | maxVisible := o.effectiveMaxRows() | |||
| 2085 | 2103 | ||||
| 2086 | if bangMode { | 2104 | if bangMode { | ||
| 2087 | o.navigateToSelected() | 2105 | o.navigateToSelected() | ||
| 2088 | return | 2106 | return | ||
| 2089 | } | 2107 | } | ||
| 2090 | 2108 | ||||
| 2091 | - | targetURL := resolveTargetURLForSelection(mode, index, suggestions, favorites) | 2109 | + | targetURL := resolveTargetURLForSelection(mode, index, maxVisible, suggestions, favorites) |
| 2092 | if targetURL == "" { | 2110 | if targetURL == "" { | ||
| 2093 | return | 2111 | return | ||
| 2094 | } | 2112 | } | ||
| @@ -2142,7 +2160,7 @@ func (o *Omnibox) navigateToSelected() { | |||||
| 2142 | var targetURL string | 2160 | var targetURL string | ||
| 2143 | if idx >= 0 { | 2161 | if idx >= 0 { | ||
| 2144 | // If user has selected a result, navigate to that result. | 2162 | // If user has selected a result, navigate to that result. | ||
| 2145 | - | targetURL = resolveTargetURLForSelection(mode, idx, suggestions, favorites) | 2163 | + | targetURL = resolveTargetURLForSelection(mode, idx, o.effectiveMaxRows(), suggestions, favorites) |
| 2146 | } else { | 2164 | } else { | ||
| 2147 | targetURL = o.buildURL(entryText) | 2165 | targetURL = o.buildURL(entryText) | ||
| 2148 | } | 2166 | } | ||
| @@ -2158,14 +2176,16 @@ func (o *Omnibox) navigateToSelected() { | |||||
| 2158 | } | 2176 | } | ||
| 2159 | } | 2177 | } | ||
| 2160 | 2178 | ||||
| 2161 | - | func resolveTargetURLForSelection(mode ViewMode, idx int, suggestions []Suggestion, favorites []Favorite) string { | 2179 | + | func resolveTargetURLForSelection(mode ViewMode, idx, maxVisible int, suggestions []Suggestion, favorites []Favorite) string { |
| 2162 | if mode == ViewModeHistory { | 2180 | if mode == ViewModeHistory { | ||
| 2163 | - | if idx >= 0 && idx < len(suggestions) { | 2181 | + | visibleCount := visibleResultCount(len(suggestions), maxVisible) |
| 2182 | + | if idx >= 0 && idx < visibleCount { | |||
| 2164 | return suggestions[idx].URL | 2183 | return suggestions[idx].URL | ||
| 2165 | } | 2184 | } | ||
| 2166 | return "" | 2185 | return "" | ||
| 2167 | } | 2186 | } | ||
| 2168 | - | if idx >= 0 && idx < len(favorites) { | 2187 | + | visibleCount := visibleResultCount(len(favorites), maxVisible) |
| 2188 | + | if idx >= 0 && idx < visibleCount { | |||
| 2169 | return favorites[idx].URL | 2189 | return favorites[idx].URL | ||
| 2170 | } | 2190 | } | ||
| 2171 | return "" | 2191 | return "" | ||
M internal/ui/component/omnibox_navigation_state_test.go +31 -8
| @@ -133,25 +133,30 @@ func TestResolveTargetURLForSelection(t *testing.T) { | |||
| 133 | 133 | suggestions := []Suggestion{ | |
| 134 | 134 | {URL: "https://github.com/bnema/dumber"}, | |
| 135 | 135 | {URL: "https://github.com/bnema/dumber/pulls"}, | |
| 136 | + | {URL: "https://github.com/bnema/dumber/issues"}, | |
| 136 | 137 | } | |
| 137 | 138 | favorites := []Favorite{ | |
| 138 | 139 | {URL: "https://dumber.bnema.dev"}, | |
| 140 | + | {URL: "https://docs.dumber.bnema.dev"}, | |
| 139 | 141 | } | |
| 140 | 142 | ||
| 141 | 143 | tests := []struct { | |
| 142 | 144 | name string | |
| 143 | 145 | mode ViewMode | |
| 144 | 146 | index int | |
| 147 | + | limit int | |
| 145 | 148 | wantURL string | |
| 146 | 149 | }{ | |
| 147 | - | {name: "history index", mode: ViewModeHistory, index: 1, wantURL: "https://github.com/bnema/dumber/pulls"}, | |
| 148 | - | {name: "favorites index", mode: ViewModeFavorites, index: 0, wantURL: "https://dumber.bnema.dev"}, | |
| 149 | - | {name: "invalid index", mode: ViewModeHistory, index: 99, wantURL: ""}, | |
| 150 | + | {name: "history index", mode: ViewModeHistory, index: 1, limit: 10, wantURL: "https://github.com/bnema/dumber/pulls"}, | |
| 151 | + | {name: "favorites index", mode: ViewModeFavorites, index: 0, limit: 10, wantURL: "https://dumber.bnema.dev"}, | |
| 152 | + | {name: "invalid index", mode: ViewModeHistory, index: 99, limit: 10, wantURL: ""}, | |
| 153 | + | {name: "history index beyond visible limit is hidden", mode: ViewModeHistory, index: 2, limit: 2, wantURL: ""}, | |
| 154 | + | {name: "favorites index beyond visible limit is hidden", mode: ViewModeFavorites, index: 1, limit: 1, wantURL: ""}, | |
| 150 | 155 | } | |
| 151 | 156 | ||
| 152 | 157 | for _, tt := range tests { | |
| 153 | 158 | t.Run(tt.name, func(t *testing.T) { | |
| 154 | - | got := resolveTargetURLForSelection(tt.mode, tt.index, suggestions, favorites) | |
| 159 | + | got := resolveTargetURLForSelection(tt.mode, tt.index, tt.limit, suggestions, favorites) | |
| 155 | 160 | if got != tt.wantURL { | |
| 156 | 161 | t.Fatalf("resolveTargetURLForSelection(%s, %d) = %q, want %q", tt.mode, tt.index, got, tt.wantURL) | |
| 157 | 162 | } | |
| @@ -162,26 +167,31 @@ func TestResolveTargetURLForSelection(t *testing.T) { | |||
| 162 | 167 | func TestSelectedTargetURL(t *testing.T) { | |
| 163 | 168 | suggestions := []Suggestion{ | |
| 164 | 169 | {URL: "https://github.com/bnema/dumber"}, | |
| 170 | + | {URL: "https://github.com/bnema/dumber/pulls"}, | |
| 165 | 171 | } | |
| 166 | 172 | favorites := []Favorite{ | |
| 167 | 173 | {URL: "https://dumber.bnema.dev"}, | |
| 174 | + | {URL: "https://docs.dumber.bnema.dev"}, | |
| 168 | 175 | } | |
| 169 | 176 | ||
| 170 | 177 | tests := []struct { | |
| 171 | 178 | name string | |
| 172 | 179 | mode ViewMode | |
| 173 | 180 | index int | |
| 181 | + | limit int | |
| 174 | 182 | wantURL string | |
| 175 | 183 | wantBool bool | |
| 176 | 184 | }{ | |
| 177 | - | {name: "negative index is not explicit selection", mode: ViewModeHistory, index: -1, wantURL: "", wantBool: false}, | |
| 178 | - | {name: "history selection is explicit", mode: ViewModeHistory, index: 0, wantURL: "https://github.com/bnema/dumber", wantBool: true}, | |
| 179 | - | {name: "favorites selection is explicit", mode: ViewModeFavorites, index: 0, wantURL: "https://dumber.bnema.dev", wantBool: true}, | |
| 185 | + | {name: "negative index is not explicit selection", mode: ViewModeHistory, index: -1, limit: 10, wantURL: "", wantBool: false}, | |
| 186 | + | {name: "history selection is explicit", mode: ViewModeHistory, index: 0, limit: 10, wantURL: "https://github.com/bnema/dumber", wantBool: true}, | |
| 187 | + | {name: "favorites selection is explicit", mode: ViewModeFavorites, index: 0, limit: 10, wantURL: "https://dumber.bnema.dev", wantBool: true}, | |
| 188 | + | {name: "hidden history selection is ignored", mode: ViewModeHistory, index: 1, limit: 1, wantURL: "", wantBool: true}, | |
| 189 | + | {name: "hidden favorites selection is ignored", mode: ViewModeFavorites, index: 1, limit: 1, wantURL: "", wantBool: true}, | |
| 180 | 190 | } | |
| 181 | 191 | ||
| 182 | 192 | for _, tt := range tests { | |
| 183 | 193 | t.Run(tt.name, func(t *testing.T) { | |
| 184 | - | gotURL, gotBool := selectedTargetURL(tt.mode, tt.index, suggestions, favorites) | |
| 194 | + | gotURL, gotBool := selectedTargetURL(tt.mode, tt.index, tt.limit, suggestions, favorites) | |
| 185 | 195 | if gotURL != tt.wantURL || gotBool != tt.wantBool { | |
| 186 | 196 | t.Fatalf("selectedTargetURL(%s, %d) = (%q, %v), want (%q, %v)", tt.mode, tt.index, gotURL, gotBool, tt.wantURL, tt.wantBool) | |
| 187 | 197 | } | |
| @@ -236,6 +246,7 @@ func TestVisibleGhostSuggestion(t *testing.T) { | |||
| 236 | 246 | tt.selectedURL, | |
| 237 | 247 | tt.hasExplicitSelection, | |
| 238 | 248 | ViewModeHistory, | |
| 249 | + | 10, | |
| 239 | 250 | suggestions, | |
| 240 | 251 | nil, | |
| 241 | 252 | ) | |
| @@ -255,6 +266,18 @@ func TestVisibleGhostSuggestion(t *testing.T) { | |||
| 255 | 266 | } | |
| 256 | 267 | } | |
| 257 | 268 | ||
| 269 | + | func TestVisibleGhostSuggestionRespectsVisibleLimit(t *testing.T) { | |
| 270 | + | suggestions := []Suggestion{ | |
| 271 | + | {URL: "https://github.com/bnema/dumber"}, | |
| 272 | + | {URL: "https://gitlab.com/team/project"}, | |
| 273 | + | } | |
| 274 | + | ||
| 275 | + | gotFull, gotSuffix, gotOK := visibleGhostSuggestion("gitl", "", false, ViewModeHistory, 1, suggestions, nil) | |
| 276 | + | if gotFull != "" || gotSuffix != "" || gotOK { | |
| 277 | + | t.Fatalf("visibleGhostSuggestion should ignore hidden suggestions, got (%q, %q, %v)", gotFull, gotSuffix, gotOK) | |
| 278 | + | } | |
| 279 | + | } | |
| 280 | + | ||
| 258 | 281 | func TestShouldPromoteHoverSelection(t *testing.T) { | |
| 259 | 282 | tests := []struct { | |
| 260 | 283 | name string | |
| @@ -133,25 +133,30 @@ func TestResolveTargetURLForSelection(t *testing.T) { | |||||
| 133 | suggestions := []Suggestion{ | 133 | suggestions := []Suggestion{ | ||
| 134 | {URL: "https://github.com/bnema/dumber"}, | 134 | {URL: "https://github.com/bnema/dumber"}, | ||
| 135 | {URL: "https://github.com/bnema/dumber/pulls"}, | 135 | {URL: "https://github.com/bnema/dumber/pulls"}, | ||
| 136 | + | {URL: "https://github.com/bnema/dumber/issues"}, | |||
| 136 | } | 137 | } | ||
| 137 | favorites := []Favorite{ | 138 | favorites := []Favorite{ | ||
| 138 | {URL: "https://dumber.bnema.dev"}, | 139 | {URL: "https://dumber.bnema.dev"}, | ||
| 140 | + | {URL: "https://docs.dumber.bnema.dev"}, | |||
| 139 | } | 141 | } | ||
| 140 | 142 | ||||
| 141 | tests := []struct { | 143 | tests := []struct { | ||
| 142 | name string | 144 | name string | ||
| 143 | mode ViewMode | 145 | mode ViewMode | ||
| 144 | index int | 146 | index int | ||
| 147 | + | limit int | |||
| 145 | wantURL string | 148 | wantURL string | ||
| 146 | }{ | 149 | }{ | ||
| 147 | - | {name: "history index", mode: ViewModeHistory, index: 1, wantURL: "https://github.com/bnema/dumber/pulls"}, | 150 | + | {name: "history index", mode: ViewModeHistory, index: 1, limit: 10, wantURL: "https://github.com/bnema/dumber/pulls"}, |
| 148 | - | {name: "favorites index", mode: ViewModeFavorites, index: 0, wantURL: "https://dumber.bnema.dev"}, | 151 | + | {name: "favorites index", mode: ViewModeFavorites, index: 0, limit: 10, wantURL: "https://dumber.bnema.dev"}, |
| 149 | - | {name: "invalid index", mode: ViewModeHistory, index: 99, wantURL: ""}, | 152 | + | {name: "invalid index", mode: ViewModeHistory, index: 99, limit: 10, wantURL: ""}, |
| 153 | + | {name: "history index beyond visible limit is hidden", mode: ViewModeHistory, index: 2, limit: 2, wantURL: ""}, | |||
| 154 | + | {name: "favorites index beyond visible limit is hidden", mode: ViewModeFavorites, index: 1, limit: 1, wantURL: ""}, | |||
| 150 | } | 155 | } | ||
| 151 | 156 | ||||
| 152 | for _, tt := range tests { | 157 | for _, tt := range tests { | ||
| 153 | t.Run(tt.name, func(t *testing.T) { | 158 | t.Run(tt.name, func(t *testing.T) { | ||
| 154 | - | got := resolveTargetURLForSelection(tt.mode, tt.index, suggestions, favorites) | 159 | + | got := resolveTargetURLForSelection(tt.mode, tt.index, tt.limit, suggestions, favorites) |
| 155 | if got != tt.wantURL { | 160 | if got != tt.wantURL { | ||
| 156 | t.Fatalf("resolveTargetURLForSelection(%s, %d) = %q, want %q", tt.mode, tt.index, got, tt.wantURL) | 161 | t.Fatalf("resolveTargetURLForSelection(%s, %d) = %q, want %q", tt.mode, tt.index, got, tt.wantURL) | ||
| 157 | } | 162 | } | ||
| @@ -162,26 +167,31 @@ func TestResolveTargetURLForSelection(t *testing.T) { | |||||
| 162 | func TestSelectedTargetURL(t *testing.T) { | 167 | func TestSelectedTargetURL(t *testing.T) { | ||
| 163 | suggestions := []Suggestion{ | 168 | suggestions := []Suggestion{ | ||
| 164 | {URL: "https://github.com/bnema/dumber"}, | 169 | {URL: "https://github.com/bnema/dumber"}, | ||
| 170 | + | {URL: "https://github.com/bnema/dumber/pulls"}, | |||
| 165 | } | 171 | } | ||
| 166 | favorites := []Favorite{ | 172 | favorites := []Favorite{ | ||
| 167 | {URL: "https://dumber.bnema.dev"}, | 173 | {URL: "https://dumber.bnema.dev"}, | ||
| 174 | + | {URL: "https://docs.dumber.bnema.dev"}, | |||
| 168 | } | 175 | } | ||
| 169 | 176 | ||||
| 170 | tests := []struct { | 177 | tests := []struct { | ||
| 171 | name string | 178 | name string | ||
| 172 | mode ViewMode | 179 | mode ViewMode | ||
| 173 | index int | 180 | index int | ||
| 181 | + | limit int | |||
| 174 | wantURL string | 182 | wantURL string | ||
| 175 | wantBool bool | 183 | wantBool bool | ||
| 176 | }{ | 184 | }{ | ||
| 177 | - | {name: "negative index is not explicit selection", mode: ViewModeHistory, index: -1, wantURL: "", wantBool: false}, | 185 | + | {name: "negative index is not explicit selection", mode: ViewModeHistory, index: -1, limit: 10, wantURL: "", wantBool: false}, |
| 178 | - | {name: "history selection is explicit", mode: ViewModeHistory, index: 0, wantURL: "https://github.com/bnema/dumber", wantBool: true}, | 186 | + | {name: "history selection is explicit", mode: ViewModeHistory, index: 0, limit: 10, wantURL: "https://github.com/bnema/dumber", wantBool: true}, |
| 179 | - | {name: "favorites selection is explicit", mode: ViewModeFavorites, index: 0, wantURL: "https://dumber.bnema.dev", wantBool: true}, | 187 | + | {name: "favorites selection is explicit", mode: ViewModeFavorites, index: 0, limit: 10, wantURL: "https://dumber.bnema.dev", wantBool: true}, |
| 188 | + | {name: "hidden history selection is ignored", mode: ViewModeHistory, index: 1, limit: 1, wantURL: "", wantBool: true}, | |||
| 189 | + | {name: "hidden favorites selection is ignored", mode: ViewModeFavorites, index: 1, limit: 1, wantURL: "", wantBool: true}, | |||
| 180 | } | 190 | } | ||
| 181 | 191 | ||||
| 182 | for _, tt := range tests { | 192 | for _, tt := range tests { | ||
| 183 | t.Run(tt.name, func(t *testing.T) { | 193 | t.Run(tt.name, func(t *testing.T) { | ||
| 184 | - | gotURL, gotBool := selectedTargetURL(tt.mode, tt.index, suggestions, favorites) | 194 | + | gotURL, gotBool := selectedTargetURL(tt.mode, tt.index, tt.limit, suggestions, favorites) |
| 185 | if gotURL != tt.wantURL || gotBool != tt.wantBool { | 195 | if gotURL != tt.wantURL || gotBool != tt.wantBool { | ||
| 186 | t.Fatalf("selectedTargetURL(%s, %d) = (%q, %v), want (%q, %v)", tt.mode, tt.index, gotURL, gotBool, tt.wantURL, tt.wantBool) | 196 | t.Fatalf("selectedTargetURL(%s, %d) = (%q, %v), want (%q, %v)", tt.mode, tt.index, gotURL, gotBool, tt.wantURL, tt.wantBool) | ||
| 187 | } | 197 | } | ||
| @@ -236,6 +246,7 @@ func TestVisibleGhostSuggestion(t *testing.T) { | |||||
| 236 | tt.selectedURL, | 246 | tt.selectedURL, | ||
| 237 | tt.hasExplicitSelection, | 247 | tt.hasExplicitSelection, | ||
| 238 | ViewModeHistory, | 248 | ViewModeHistory, | ||
| 249 | + | 10, | |||
| 239 | suggestions, | 250 | suggestions, | ||
| 240 | nil, | 251 | nil, | ||
| 241 | ) | 252 | ) | ||
| @@ -255,6 +266,18 @@ func TestVisibleGhostSuggestion(t *testing.T) { | |||||
| 255 | } | 266 | } | ||
| 256 | } | 267 | } | ||
| 257 | 268 | ||||
| 269 | + | func TestVisibleGhostSuggestionRespectsVisibleLimit(t *testing.T) { | |||
| 270 | + | suggestions := []Suggestion{ | |||
| 271 | + | {URL: "https://github.com/bnema/dumber"}, | |||
| 272 | + | {URL: "https://gitlab.com/team/project"}, | |||
| 273 | + | } | |||
| 274 | + | ||||
| 275 | + | gotFull, gotSuffix, gotOK := visibleGhostSuggestion("gitl", "", false, ViewModeHistory, 1, suggestions, nil) | |||
| 276 | + | if gotFull != "" || gotSuffix != "" || gotOK { | |||
| 277 | + | t.Fatalf("visibleGhostSuggestion should ignore hidden suggestions, got (%q, %q, %v)", gotFull, gotSuffix, gotOK) | |||
| 278 | + | } | |||
| 279 | + | } | |||
| 280 | + | ||||
| 258 | func TestShouldPromoteHoverSelection(t *testing.T) { | 281 | func TestShouldPromoteHoverSelection(t *testing.T) { | ||
| 259 | tests := []struct { | 282 | tests := []struct { | ||
| 260 | name string | 283 | name string | ||