detached bd7f2a9

fix(omnibox): cap selection to visible results

commit
bd7f2a93ccc32cfef5604fcd19dadae2674e86df
author
brice · · 2026-04-08 11:53 UTC
parent
94b7c39
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · 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 {
636636 return
637637 }
638638
639- targetURL := resolveTargetURLForSelection(mode, idx, suggestions, favorites)
639+ targetURL := resolveTargetURLForSelection(mode, idx, o.effectiveMaxRows(), suggestions, favorites)
640640
641641 if targetURL != "" && o.onNavigate != nil {
642642 o.Hide(o.ctx)
@@ -1141,6 +1141,7 @@ func (o *Omnibox) restoreEntryToRealInput() {
11411141 // updateGhostFromSelectionWithInput updates ghost text based on selected row and input.
11421142 func (o *Omnibox) updateGhostFromSelectionWithInput(entryText string) {
11431143 log := logging.FromContext(o.ctx)
1144+ maxVisible := o.effectiveMaxRows()
11441145 o.mu.RLock()
11451146 idx := o.selectedIndex
11461147 mode := o.viewMode
@@ -1168,10 +1169,10 @@ func (o *Omnibox) updateGhostFromSelectionWithInput(entryText string) {
11681169 return
11691170 }
11701171
1171- targetURL, hasExplicitSelection := selectedTargetURL(mode, idx, suggestions, favorites)
1172+ targetURL, hasExplicitSelection := selectedTargetURL(mode, idx, maxVisible, suggestions, favorites)
11721173
11731174 if entryText != "" {
1174- o.resolveGhostCompletion(entryText, targetURL, hasExplicitSelection, mode, suggestions, favorites)
1175+ o.resolveGhostCompletion(entryText, targetURL, hasExplicitSelection, mode, maxVisible, suggestions, favorites)
11751176 return
11761177 }
11771178
@@ -1193,6 +1194,7 @@ func (o *Omnibox) resolveGhostCompletion(
11931194 entryText, selectedURL string,
11941195 hasExplicitSelection bool,
11951196 mode ViewMode,
1197+ maxVisible int,
11961198 suggestions []Suggestion,
11971199 favorites []Favorite,
11981200 ) {
@@ -1203,7 +1205,7 @@ func (o *Omnibox) resolveGhostCompletion(
12031205 return
12041206 }
12051207
1206- fullText, suffix, found := visibleGhostSuggestion(completionInput, selectedURL, hasExplicitSelection, mode, suggestions, favorites)
1208+ fullText, suffix, found := visibleGhostSuggestion(completionInput, selectedURL, hasExplicitSelection, mode, maxVisible, suggestions, favorites)
12071209 if !found {
12081210 log.Debug().Str("input", completionInput).Msg("ghost: resolveGhost — no match found")
12091211 return
@@ -1275,6 +1277,7 @@ func visibleGhostSuggestion(
12751277 query, selectedURL string,
12761278 hasExplicitSelection bool,
12771279 mode ViewMode,
1280+ maxVisible int,
12781281 suggestions []Suggestion,
12791282 favorites []Favorite,
12801283 ) (fullText, suffix string, ok bool) {
@@ -1283,15 +1286,16 @@ func visibleGhostSuggestion(
12831286 return fullText, suffix, ok
12841287 }
12851288
1286- visibleURLs := visibleURLsForMode(mode, suggestions, favorites)
1289+ visibleURLs := visibleURLsForMode(mode, maxVisible, suggestions, favorites)
12871290 suffix, fullText, ok = autocomplete.BestURLCompletion(query, visibleURLs)
12881291 return fullText, suffix, ok
12891292 }
12901293
1291-func visibleURLsForMode(mode ViewMode, suggestions []Suggestion, favorites []Favorite) []string {
1294+func visibleURLsForMode(mode ViewMode, maxVisible int, suggestions []Suggestion, favorites []Favorite) []string {
12921295 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] {
12951299 if s.URL != "" {
12961300 urls = append(urls, s.URL)
12971301 }
@@ -1299,8 +1303,9 @@ func visibleURLsForMode(mode ViewMode, suggestions []Suggestion, favorites []Fav
12991303 return urls
13001304 }
13011305
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] {
13041309 if f.URL != "" {
13051310 urls = append(urls, f.URL)
13061311 }
@@ -1308,11 +1313,21 @@ func visibleURLsForMode(mode ViewMode, suggestions []Suggestion, favorites []Fav
13081313 return urls
13091314 }
13101315
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) {
13121317 if idx < 0 {
13131318 return "", false
13141319 }
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
13161331 }
13171332
13181333 // performSearch executes the search based on current view mode and query.
@@ -2022,13 +2037,14 @@ func (o *Omnibox) selectNext() {
20222037 current := o.selectedIndex
20232038 mode := o.viewMode
20242039 bangMode := o.bangMode
2040+ maxVisible := o.effectiveMaxRows()
20252041 var maxIndex int
20262042 if bangMode {
2027- maxIndex = len(o.bangSuggestions) - 1
2043+ maxIndex = visibleResultCount(len(o.bangSuggestions), maxVisible) - 1
20282044 } else if mode == ViewModeHistory {
2029- maxIndex = len(o.suggestions) - 1
2045+ maxIndex = visibleResultCount(len(o.suggestions), maxVisible) - 1
20302046 } else {
2031- maxIndex = len(o.favorites) - 1
2047+ maxIndex = visibleResultCount(len(o.favorites), maxVisible) - 1
20322048 }
20332049 o.hasNavigated = true // User is navigating with arrow keys
20342050 o.mu.Unlock()
@@ -2050,13 +2066,14 @@ func (o *Omnibox) selectPrevious() {
20502066 current := o.selectedIndex
20512067 mode := o.viewMode
20522068 bangMode := o.bangMode
2069+ maxVisible := o.effectiveMaxRows()
20532070 var maxIndex int
20542071 if bangMode {
2055- maxIndex = len(o.bangSuggestions) - 1
2072+ maxIndex = visibleResultCount(len(o.bangSuggestions), maxVisible) - 1
20562073 } else if mode == ViewModeHistory {
2057- maxIndex = len(o.suggestions) - 1
2074+ maxIndex = visibleResultCount(len(o.suggestions), maxVisible) - 1
20582075 } else {
2059- maxIndex = len(o.favorites) - 1
2076+ maxIndex = visibleResultCount(len(o.favorites), maxVisible) - 1
20602077 }
20612078 o.hasNavigated = true // User is navigating with arrow keys
20622079 o.mu.Unlock()
@@ -2082,13 +2099,14 @@ func (o *Omnibox) selectAndNavigate(index int) {
20822099 suggestions := o.suggestions
20832100 favorites := o.favorites
20842101 o.mu.RUnlock()
2102+ maxVisible := o.effectiveMaxRows()
20852103
20862104 if bangMode {
20872105 o.navigateToSelected()
20882106 return
20892107 }
20902108
2091- targetURL := resolveTargetURLForSelection(mode, index, suggestions, favorites)
2109+ targetURL := resolveTargetURLForSelection(mode, index, maxVisible, suggestions, favorites)
20922110 if targetURL == "" {
20932111 return
20942112 }
@@ -2142,7 +2160,7 @@ func (o *Omnibox) navigateToSelected() {
21422160 var targetURL string
21432161 if idx >= 0 {
21442162 // 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)
21462164 } else {
21472165 targetURL = o.buildURL(entryText)
21482166 }
@@ -2158,14 +2176,16 @@ func (o *Omnibox) navigateToSelected() {
21582176 }
21592177 }
21602178
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 {
21622180 if mode == ViewModeHistory {
2163- if idx >= 0 && idx < len(suggestions) {
2181+ visibleCount := visibleResultCount(len(suggestions), maxVisible)
2182+ if idx >= 0 && idx < visibleCount {
21642183 return suggestions[idx].URL
21652184 }
21662185 return ""
21672186 }
2168- if idx >= 0 && idx < len(favorites) {
2187+ visibleCount := visibleResultCount(len(favorites), maxVisible)
2188+ if idx >= 0 && idx < visibleCount {
21692189 return favorites[idx].URL
21702190 }
21712191 return ""
@@ -636,7 +636,7 @@ func (o *Omnibox) initList() error {
636 return636 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.selectedIndex1146 idx := o.selectedIndex
1146 mode := o.viewMode1147 mode := o.viewMode
@@ -1168,10 +1169,10 @@ func (o *Omnibox) updateGhostFromSelectionWithInput(entryText string) {
1168 return1169 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 return1176 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 return1205 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 return1211 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, ok1286 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, ok1291 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 urls1303 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 urls1313 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 "", false1318 return "", false
1314 }1319 }
1315- return resolveTargetURLForSelection(mode, idx, suggestions, favorites), true1320+ 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.selectedIndex2037 current := o.selectedIndex
2023 mode := o.viewMode2038 mode := o.viewMode
2024 bangMode := o.bangMode2039 bangMode := o.bangMode
2040+ maxVisible := o.effectiveMaxRows()
2025 var maxIndex int2041 var maxIndex int
2026 if bangMode {2042 if bangMode {
2027- maxIndex = len(o.bangSuggestions) - 12043+ maxIndex = visibleResultCount(len(o.bangSuggestions), maxVisible) - 1
2028 } else if mode == ViewModeHistory {2044 } else if mode == ViewModeHistory {
2029- maxIndex = len(o.suggestions) - 12045+ maxIndex = visibleResultCount(len(o.suggestions), maxVisible) - 1
2030 } else {2046 } else {
2031- maxIndex = len(o.favorites) - 12047+ maxIndex = visibleResultCount(len(o.favorites), maxVisible) - 1
2032 }2048 }
2033 o.hasNavigated = true // User is navigating with arrow keys2049 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.selectedIndex2066 current := o.selectedIndex
2051 mode := o.viewMode2067 mode := o.viewMode
2052 bangMode := o.bangMode2068 bangMode := o.bangMode
2069+ maxVisible := o.effectiveMaxRows()
2053 var maxIndex int2070 var maxIndex int
2054 if bangMode {2071 if bangMode {
2055- maxIndex = len(o.bangSuggestions) - 12072+ maxIndex = visibleResultCount(len(o.bangSuggestions), maxVisible) - 1
2056 } else if mode == ViewModeHistory {2073 } else if mode == ViewModeHistory {
2057- maxIndex = len(o.suggestions) - 12074+ maxIndex = visibleResultCount(len(o.suggestions), maxVisible) - 1
2058 } else {2075 } else {
2059- maxIndex = len(o.favorites) - 12076+ maxIndex = visibleResultCount(len(o.favorites), maxVisible) - 1
2060 }2077 }
2061 o.hasNavigated = true // User is navigating with arrow keys2078 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.suggestions2099 suggestions := o.suggestions
2083 favorites := o.favorites2100 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 return2106 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 return2111 return
2094 }2112 }
@@ -2142,7 +2160,7 @@ func (o *Omnibox) navigateToSelected() {
2142 var targetURL string2160 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].URL2183 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].URL2189 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) {
133133 suggestions := []Suggestion{
134134 {URL: "https://github.com/bnema/dumber"},
135135 {URL: "https://github.com/bnema/dumber/pulls"},
136+ {URL: "https://github.com/bnema/dumber/issues"},
136137 }
137138 favorites := []Favorite{
138139 {URL: "https://dumber.bnema.dev"},
140+ {URL: "https://docs.dumber.bnema.dev"},
139141 }
140142
141143 tests := []struct {
142144 name string
143145 mode ViewMode
144146 index int
147+ limit int
145148 wantURL string
146149 }{
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: ""},
150155 }
151156
152157 for _, tt := range tests {
153158 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)
155160 if got != tt.wantURL {
156161 t.Fatalf("resolveTargetURLForSelection(%s, %d) = %q, want %q", tt.mode, tt.index, got, tt.wantURL)
157162 }
@@ -162,26 +167,31 @@ func TestResolveTargetURLForSelection(t *testing.T) {
162167 func TestSelectedTargetURL(t *testing.T) {
163168 suggestions := []Suggestion{
164169 {URL: "https://github.com/bnema/dumber"},
170+ {URL: "https://github.com/bnema/dumber/pulls"},
165171 }
166172 favorites := []Favorite{
167173 {URL: "https://dumber.bnema.dev"},
174+ {URL: "https://docs.dumber.bnema.dev"},
168175 }
169176
170177 tests := []struct {
171178 name string
172179 mode ViewMode
173180 index int
181+ limit int
174182 wantURL string
175183 wantBool bool
176184 }{
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},
180190 }
181191
182192 for _, tt := range tests {
183193 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)
185195 if gotURL != tt.wantURL || gotBool != tt.wantBool {
186196 t.Fatalf("selectedTargetURL(%s, %d) = (%q, %v), want (%q, %v)", tt.mode, tt.index, gotURL, gotBool, tt.wantURL, tt.wantBool)
187197 }
@@ -236,6 +246,7 @@ func TestVisibleGhostSuggestion(t *testing.T) {
236246 tt.selectedURL,
237247 tt.hasExplicitSelection,
238248 ViewModeHistory,
249+ 10,
239250 suggestions,
240251 nil,
241252 )
@@ -255,6 +266,18 @@ func TestVisibleGhostSuggestion(t *testing.T) {
255266 }
256267 }
257268
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+
258281 func TestShouldPromoteHoverSelection(t *testing.T) {
259282 tests := []struct {
260283 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 string144 name string
143 mode ViewMode145 mode ViewMode
144 index int146 index int
147+ limit int
145 wantURL string148 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 string178 name string
172 mode ViewMode179 mode ViewMode
173 index int180 index int
181+ limit int
174 wantURL string182 wantURL string
175 wantBool bool183 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 string283 name string

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close