fix: align omnibox URL detection with review cases
Add localhost/IP navigation edge cases in omnibox tests, simplify ghost suppression state helper signature, and treat bracketed IPv6/IP hosts as URL-like inputs so typed host targets are navigated directly.
- commit
- 2dbb951e9d3b3019e332d76df90f3964d04388e0
- author
- brice · · 2026-02-17 18:04 UTC
- parent
- 25ff751
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +21 -3
M internal/domain/url/normalize.go +3 -0
| @@ -140,6 +140,9 @@ func LooksLikeURL(input string) bool { | |||
| 140 | 140 | if input == "localhost" || strings.HasPrefix(input, "localhost:") || strings.HasPrefix(input, "localhost/") { | |
| 141 | 141 | return true | |
| 142 | 142 | } | |
| 143 | + | if looksLikeIPAddressWithOptionalPortOrPath(input) { | |
| 144 | + | return true | |
| 145 | + | } | |
| 143 | 146 | return strings.Contains(input, ".") && !strings.Contains(input, " ") | |
| 144 | 147 | } | |
| 145 | 148 | ||
| @@ -140,6 +140,9 @@ func LooksLikeURL(input string) bool { | |||||
| 140 | if input == "localhost" || strings.HasPrefix(input, "localhost:") || strings.HasPrefix(input, "localhost/") { | 140 | if input == "localhost" || strings.HasPrefix(input, "localhost:") || strings.HasPrefix(input, "localhost/") { | ||
| 141 | return true | 141 | return true | ||
| 142 | } | 142 | } | ||
| 143 | + | if looksLikeIPAddressWithOptionalPortOrPath(input) { | |||
| 144 | + | return true | |||
| 145 | + | } | |||
| 143 | return strings.Contains(input, ".") && !strings.Contains(input, " ") | 146 | return strings.Contains(input, ".") && !strings.Contains(input, " ") | ||
| 144 | } | 147 | } | ||
| 145 | 148 | ||||
M internal/domain/url/normalize_test.go +10 -0
| @@ -395,6 +395,16 @@ func TestLooksLikeURL(t *testing.T) { | |||
| 395 | 395 | input: "100.64.0.10:3000", | |
| 396 | 396 | want: true, | |
| 397 | 397 | }, | |
| 398 | + | { | |
| 399 | + | name: "ipv4", | |
| 400 | + | input: "127.0.0.1", | |
| 401 | + | want: true, | |
| 402 | + | }, | |
| 403 | + | { | |
| 404 | + | name: "ipv6 bracketed", | |
| 405 | + | input: "[::1]", | |
| 406 | + | want: true, | |
| 407 | + | }, | |
| 398 | 408 | } | |
| 399 | 409 | ||
| 400 | 410 | for _, tt := range tests { | |
| @@ -395,6 +395,16 @@ func TestLooksLikeURL(t *testing.T) { | |||||
| 395 | input: "100.64.0.10:3000", | 395 | input: "100.64.0.10:3000", | ||
| 396 | want: true, | 396 | want: true, | ||
| 397 | }, | 397 | }, | ||
| 398 | + | { | |||
| 399 | + | name: "ipv4", | |||
| 400 | + | input: "127.0.0.1", | |||
| 401 | + | want: true, | |||
| 402 | + | }, | |||
| 403 | + | { | |||
| 404 | + | name: "ipv6 bracketed", | |||
| 405 | + | input: "[::1]", | |||
| 406 | + | want: true, | |||
| 407 | + | }, | |||
| 398 | } | 408 | } | ||
| 399 | 409 | ||||
| 400 | for _, tt := range tests { | 410 | for _, tt := range tests { | ||
M internal/ui/component/omnibox.go +2 -2
| @@ -748,13 +748,13 @@ func (o *Omnibox) handleKeyPress(keyval, keycode uint, state gdk.ModifierType) b | |||
| 748 | 748 | return false // Let entry handle the key | |
| 749 | 749 | } | |
| 750 | 750 | ||
| 751 | - | func nextGhostSuppressionState(_ bool, keyval uint) bool { | |
| 751 | + | func nextGhostSuppressionState(keyval uint) bool { | |
| 752 | 752 | return isDeletionKey(keyval) | |
| 753 | 753 | } | |
| 754 | 754 | ||
| 755 | 755 | func (o *Omnibox) updateGhostSuppressionFromKey(keyval uint) { | |
| 756 | 756 | o.mu.Lock() | |
| 757 | - | o.suppressGhost = nextGhostSuppressionState(o.suppressGhost, keyval) | |
| 757 | + | o.suppressGhost = nextGhostSuppressionState(keyval) | |
| 758 | 758 | o.mu.Unlock() | |
| 759 | 759 | } | |
| 760 | 760 | ||
| @@ -748,13 +748,13 @@ func (o *Omnibox) handleKeyPress(keyval, keycode uint, state gdk.ModifierType) b | |||||
| 748 | return false // Let entry handle the key | 748 | return false // Let entry handle the key | ||
| 749 | } | 749 | } | ||
| 750 | 750 | ||||
| 751 | - | func nextGhostSuppressionState(_ bool, keyval uint) bool { | 751 | + | func nextGhostSuppressionState(keyval uint) bool { |
| 752 | return isDeletionKey(keyval) | 752 | return isDeletionKey(keyval) | ||
| 753 | } | 753 | } | ||
| 754 | 754 | ||||
| 755 | func (o *Omnibox) updateGhostSuppressionFromKey(keyval uint) { | 755 | func (o *Omnibox) updateGhostSuppressionFromKey(keyval uint) { | ||
| 756 | o.mu.Lock() | 756 | o.mu.Lock() | ||
| 757 | - | o.suppressGhost = nextGhostSuppressionState(o.suppressGhost, keyval) | 757 | + | o.suppressGhost = nextGhostSuppressionState(keyval) |
| 758 | o.mu.Unlock() | 758 | o.mu.Unlock() | ||
| 759 | } | 759 | } | ||
| 760 | 760 | ||||
M internal/ui/component/omnibox_navigation_state_test.go +6 -1
| @@ -15,6 +15,11 @@ func TestShouldPreferTypedURLNavigation(t *testing.T) { | |||
| 15 | 15 | {name: "domain", input: "x.com", want: true}, | |
| 16 | 16 | {name: "domain with path", input: "github.com/bnema", want: true}, | |
| 17 | 17 | {name: "http url", input: "http://example.com", want: true}, | |
| 18 | + | {name: "localhost", input: "localhost", want: true}, | |
| 19 | + | {name: "ipv4", input: "127.0.0.1", want: true}, | |
| 20 | + | {name: "ipv4 with port", input: "192.168.1.1:8080", want: true}, | |
| 21 | + | {name: "ipv6 bracketed", input: "[::1]", want: true}, | |
| 22 | + | {name: "http ipv6 bracketed", input: "http://[::1]", want: true}, | |
| 18 | 23 | {name: "search query", input: "hello world", want: false}, | |
| 19 | 24 | {name: "empty", input: "", want: false}, | |
| 20 | 25 | } | |
| @@ -82,7 +87,7 @@ func TestNextGhostSuppressionState(t *testing.T) { | |||
| 82 | 87 | ||
| 83 | 88 | for _, tt := range tests { | |
| 84 | 89 | t.Run(tt.name, func(t *testing.T) { | |
| 85 | - | if got := nextGhostSuppressionState(tt.current, tt.key); got != tt.want { | |
| 90 | + | if got := nextGhostSuppressionState(tt.key); got != tt.want { | |
| 86 | 91 | t.Fatalf("nextGhostSuppressionState(%v, %d) = %v, want %v", tt.current, tt.key, got, tt.want) | |
| 87 | 92 | } | |
| 88 | 93 | }) | |
| @@ -15,6 +15,11 @@ func TestShouldPreferTypedURLNavigation(t *testing.T) { | |||||
| 15 | {name: "domain", input: "x.com", want: true}, | 15 | {name: "domain", input: "x.com", want: true}, | ||
| 16 | {name: "domain with path", input: "github.com/bnema", want: true}, | 16 | {name: "domain with path", input: "github.com/bnema", want: true}, | ||
| 17 | {name: "http url", input: "http://example.com", want: true}, | 17 | {name: "http url", input: "http://example.com", want: true}, | ||
| 18 | + | {name: "localhost", input: "localhost", want: true}, | |||
| 19 | + | {name: "ipv4", input: "127.0.0.1", want: true}, | |||
| 20 | + | {name: "ipv4 with port", input: "192.168.1.1:8080", want: true}, | |||
| 21 | + | {name: "ipv6 bracketed", input: "[::1]", want: true}, | |||
| 22 | + | {name: "http ipv6 bracketed", input: "http://[::1]", want: true}, | |||
| 18 | {name: "search query", input: "hello world", want: false}, | 23 | {name: "search query", input: "hello world", want: false}, | ||
| 19 | {name: "empty", input: "", want: false}, | 24 | {name: "empty", input: "", want: false}, | ||
| 20 | } | 25 | } | ||
| @@ -82,7 +87,7 @@ func TestNextGhostSuppressionState(t *testing.T) { | |||||
| 82 | 87 | ||||
| 83 | for _, tt := range tests { | 88 | for _, tt := range tests { | ||
| 84 | t.Run(tt.name, func(t *testing.T) { | 89 | t.Run(tt.name, func(t *testing.T) { | ||
| 85 | - | if got := nextGhostSuppressionState(tt.current, tt.key); got != tt.want { | 90 | + | if got := nextGhostSuppressionState(tt.key); got != tt.want { |
| 86 | t.Fatalf("nextGhostSuppressionState(%v, %d) = %v, want %v", tt.current, tt.key, got, tt.want) | 91 | t.Fatalf("nextGhostSuppressionState(%v, %d) = %v, want %v", tt.current, tt.key, got, tt.want) | ||
| 87 | } | 92 | } | ||
| 88 | }) | 93 | }) | ||