refactor(omnibox): simplify handleKeyPress with helper methods
- commit
- 1d6b2aa6bdefd84dfcb7276b7036f304b1f55a05
- author
- brice · · 2026-01-23 17:12 UTC
- committer
- bnema · · 2026-01-23 18:07 UTC
- parent
- df1aa18
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +36 -61
M internal/application/usecase/autocomplete.go +1 -1
| @@ -165,7 +165,7 @@ func (uc *AutocompleteUseCase) findBangSuggestion(ctx context.Context, input str | |||
| 165 | 165 | ||
| 166 | 166 | // GetSuggestionForURL returns an autocomplete suggestion specifically for a URL. | |
| 167 | 167 | // This is used when the user selects a row from the list to update ghost text. | |
| 168 | - | func (uc *AutocompleteUseCase) GetSuggestionForURL(ctx context.Context, input, targetURL string) *GetSuggestionOutput { | |
| 168 | + | func (*AutocompleteUseCase) GetSuggestionForURL(_ context.Context, input, targetURL string) *GetSuggestionOutput { | |
| 169 | 169 | if input == "" || targetURL == "" { | |
| 170 | 170 | return &GetSuggestionOutput{Found: false} | |
| 171 | 171 | } | |
| @@ -165,7 +165,7 @@ func (uc *AutocompleteUseCase) findBangSuggestion(ctx context.Context, input str | |||||
| 165 | 165 | ||||
| 166 | // GetSuggestionForURL returns an autocomplete suggestion specifically for a URL. | 166 | // GetSuggestionForURL returns an autocomplete suggestion specifically for a URL. | ||
| 167 | // This is used when the user selects a row from the list to update ghost text. | 167 | // This is used when the user selects a row from the list to update ghost text. | ||
| 168 | - | func (uc *AutocompleteUseCase) GetSuggestionForURL(ctx context.Context, input, targetURL string) *GetSuggestionOutput { | 168 | + | func (*AutocompleteUseCase) GetSuggestionForURL(_ context.Context, input, targetURL string) *GetSuggestionOutput { |
| 169 | if input == "" || targetURL == "" { | 169 | if input == "" || targetURL == "" { | ||
| 170 | return &GetSuggestionOutput{Found: false} | 170 | return &GetSuggestionOutput{Found: false} | ||
| 171 | } | 171 | } | ||
M internal/domain/autocomplete/autocomplete.go +1 -1
| @@ -53,7 +53,7 @@ func StripProtocol(url string) string { | |||
| 53 | 53 | // ComputeURLCompletionSuffix computes the completion suffix for URLs, | |
| 54 | 54 | // handling protocol stripping for better matching. | |
| 55 | 55 | // It tries matching with and without protocol prefixes. | |
| 56 | - | func ComputeURLCompletionSuffix(input, fullURL string) (suffix string, matchedURL string, ok bool) { | |
| 56 | + | func ComputeURLCompletionSuffix(input, fullURL string) (suffix, matchedURL string, ok bool) { | |
| 57 | 57 | // First try direct match | |
| 58 | 58 | if suffix, ok := ComputeCompletionSuffix(input, fullURL); ok { | |
| 59 | 59 | return suffix, fullURL, true | |
| @@ -53,7 +53,7 @@ func StripProtocol(url string) string { | |||||
| 53 | // ComputeURLCompletionSuffix computes the completion suffix for URLs, | 53 | // ComputeURLCompletionSuffix computes the completion suffix for URLs, | ||
| 54 | // handling protocol stripping for better matching. | 54 | // handling protocol stripping for better matching. | ||
| 55 | // It tries matching with and without protocol prefixes. | 55 | // It tries matching with and without protocol prefixes. | ||
| 56 | - | func ComputeURLCompletionSuffix(input, fullURL string) (suffix string, matchedURL string, ok bool) { | 56 | + | func ComputeURLCompletionSuffix(input, fullURL string) (suffix, matchedURL string, ok bool) { |
| 57 | // First try direct match | 57 | // First try direct match | ||
| 58 | if suffix, ok := ComputeCompletionSuffix(input, fullURL); ok { | 58 | if suffix, ok := ComputeCompletionSuffix(input, fullURL); ok { | ||
| 59 | return suffix, fullURL, true | 59 | return suffix, fullURL, true | ||
M internal/ui/component/omnibox.go +34 -59
| @@ -21,8 +21,9 @@ import ( | |||
| 21 | 21 | ) | |
| 22 | 22 | ||
| 23 | 23 | const ( | |
| 24 | - | debounceDelayMs = 50 | |
| 25 | - | endBoxSpacing = 6 | |
| 24 | + | debounceDelayMs = 50 | |
| 25 | + | endBoxSpacing = 6 | |
| 26 | + | ghostIconOffsetBase = 18 // Base icon offset for ghost text positioning (scaled by uiScale) | |
| 26 | 27 | ) | |
| 27 | 28 | ||
| 28 | 29 | // ViewMode distinguishes history search from favorites display. | |
| @@ -570,6 +571,20 @@ func (o *Omnibox) setupFocusCallbacks(onFocusIn func(*gtk.SearchEntry), onFocusO | |||
| 570 | 571 | o.entry.AddController(&focusController.EventController) | |
| 571 | 572 | } | |
| 572 | 573 | ||
| 574 | + | // hasGhost returns whether ghost text is currently visible. | |
| 575 | + | func (o *Omnibox) hasGhost() bool { | |
| 576 | + | o.mu.RLock() | |
| 577 | + | defer o.mu.RUnlock() | |
| 578 | + | return o.hasGhostText | |
| 579 | + | } | |
| 580 | + | ||
| 581 | + | // hasUserNavigated returns whether user has navigated with arrow keys. | |
| 582 | + | func (o *Omnibox) hasUserNavigated() bool { | |
| 583 | + | o.mu.RLock() | |
| 584 | + | defer o.mu.RUnlock() | |
| 585 | + | return o.hasNavigated | |
| 586 | + | } | |
| 587 | + | ||
| 573 | 588 | // handleKeyPress processes keyboard events. | |
| 574 | 589 | // Returns true if the event was handled. | |
| 575 | 590 | func (o *Omnibox) handleKeyPress(keyval, keycode uint, state gdk.ModifierType) bool { | |
| @@ -608,10 +623,7 @@ func (o *Omnibox) handleKeyPress(keyval, keycode uint, state gdk.ModifierType) b | |||
| 608 | 623 | ||
| 609 | 624 | case uint(gdk.KEY_Tab): | |
| 610 | 625 | // Tab accepts ghost text if present, otherwise toggles view mode | |
| 611 | - | o.mu.RLock() | |
| 612 | - | hasGhost := o.hasGhostText | |
| 613 | - | o.mu.RUnlock() | |
| 614 | - | if hasGhost && o.cursorAtEnd() { | |
| 626 | + | if o.hasGhost() { | |
| 615 | 627 | o.acceptGhostCompletion() | |
| 616 | 628 | return true | |
| 617 | 629 | } | |
| @@ -619,11 +631,8 @@ func (o *Omnibox) handleKeyPress(keyval, keycode uint, state gdk.ModifierType) b | |||
| 619 | 631 | return true | |
| 620 | 632 | ||
| 621 | 633 | case uint(gdk.KEY_Right): | |
| 622 | - | // Arrow Right accepts ghost text if present and cursor at end | |
| 623 | - | o.mu.RLock() | |
| 624 | - | hasGhost := o.hasGhostText | |
| 625 | - | o.mu.RUnlock() | |
| 626 | - | if hasGhost && o.cursorAtEnd() { | |
| 634 | + | // Arrow Right accepts ghost text if present | |
| 635 | + | if o.hasGhost() { | |
| 627 | 636 | o.acceptGhostCompletion() | |
| 628 | 637 | return true | |
| 629 | 638 | } | |
| @@ -631,11 +640,7 @@ func (o *Omnibox) handleKeyPress(keyval, keycode uint, state gdk.ModifierType) b | |||
| 631 | 640 | ||
| 632 | 641 | case uint(gdk.KEY_space): | |
| 633 | 642 | // Space toggles favorite only if user has navigated with arrow keys | |
| 634 | - | // Otherwise, let space pass through to entry for typing | |
| 635 | - | o.mu.RLock() | |
| 636 | - | navigated := o.hasNavigated | |
| 637 | - | o.mu.RUnlock() | |
| 638 | - | if navigated { | |
| 643 | + | if o.hasUserNavigated() { | |
| 639 | 644 | o.toggleFavorite() | |
| 640 | 645 | return true | |
| 641 | 646 | } | |
| @@ -643,10 +648,7 @@ func (o *Omnibox) handleKeyPress(keyval, keycode uint, state gdk.ModifierType) b | |||
| 643 | 648 | ||
| 644 | 649 | case uint(gdk.KEY_y): | |
| 645 | 650 | // 'y' yanks (copies) the selected URL to clipboard when navigating | |
| 646 | - | o.mu.RLock() | |
| 647 | - | navigated := o.hasNavigated | |
| 648 | - | o.mu.RUnlock() | |
| 649 | - | if navigated { | |
| 651 | + | if o.hasUserNavigated() { | |
| 650 | 652 | o.yankSelectedURL() | |
| 651 | 653 | return true | |
| 652 | 654 | } | |
| @@ -770,8 +772,8 @@ func (o *Omnibox) setGhostText(originalInput, suffix, fullText string) { | |||
| 770 | 772 | ||
| 771 | 773 | // Account for the search icon inside the entry | |
| 772 | 774 | // Ghost label already has same margin/padding as entry via CSS | |
| 773 | - | // Scale the icon offset by UI scale factor (base ~18px at scale 1.0) | |
| 774 | - | iconOffset := int(18.0 * o.uiScale) | |
| 775 | + | // Scale the icon offset by UI scale factor | |
| 776 | + | iconOffset := int(float64(ghostIconOffsetBase) * o.uiScale) | |
| 775 | 777 | marginStart := widthPx + iconOffset | |
| 776 | 778 | ||
| 777 | 779 | // Set the ghost label text (just the suffix) and position it | |
| @@ -816,12 +818,11 @@ func (o *Omnibox) clearGhostText() { | |||
| 816 | 818 | } | |
| 817 | 819 | ||
| 818 | 820 | // acceptGhostCompletion accepts the ghost text and fills the input. | |
| 819 | - | // Returns true if ghost text was accepted. | |
| 820 | - | func (o *Omnibox) acceptGhostCompletion() bool { | |
| 821 | + | func (o *Omnibox) acceptGhostCompletion() { | |
| 821 | 822 | o.mu.Lock() | |
| 822 | 823 | if !o.hasGhostText || o.ghostFullText == "" { | |
| 823 | 824 | o.mu.Unlock() | |
| 824 | - | return false | |
| 825 | + | return | |
| 825 | 826 | } | |
| 826 | 827 | fullText := o.ghostFullText | |
| 827 | 828 | o.isAcceptingGhost = true | |
| @@ -842,22 +843,6 @@ func (o *Omnibox) acceptGhostCompletion() bool { | |||
| 842 | 843 | o.mu.Lock() | |
| 843 | 844 | o.isAcceptingGhost = false | |
| 844 | 845 | o.mu.Unlock() | |
| 845 | - | ||
| 846 | - | return true | |
| 847 | - | } | |
| 848 | - | ||
| 849 | - | // cursorAtEnd returns true if the entry cursor is at the end of the text. | |
| 850 | - | func (o *Omnibox) cursorAtEnd() bool { | |
| 851 | - | text := o.entry.GetText() | |
| 852 | - | // If entry is empty, cursor is trivially "at end" - allow ghost acceptance | |
| 853 | - | // This handles the case where user navigates with arrows from empty input | |
| 854 | - | if len(text) == 0 { | |
| 855 | - | return true | |
| 856 | - | } | |
| 857 | - | // SearchEntry doesn't have GetPosition directly, but we can use the editable interface | |
| 858 | - | // For simplicity, we'll assume cursor is at end after any input | |
| 859 | - | // This works because Tab/ArrowRight are handled in capture phase before entry processes them | |
| 860 | - | return true | |
| 861 | 846 | } | |
| 862 | 847 | ||
| 863 | 848 | // updateGhostFromSuggestion updates ghost text based on the current autocomplete suggestion. | |
| @@ -867,24 +852,24 @@ func (o *Omnibox) updateGhostFromSuggestion() { | |||
| 867 | 852 | } | |
| 868 | 853 | ||
| 869 | 854 | o.mu.RLock() | |
| 870 | - | input := o.realInput | |
| 855 | + | userInput := o.realInput | |
| 871 | 856 | bangMode := o.bangMode | |
| 872 | 857 | o.mu.RUnlock() | |
| 873 | 858 | ||
| 874 | 859 | // Don't show ghost text in bang mode (it has its own completion) | |
| 875 | - | if bangMode || input == "" { | |
| 860 | + | if bangMode || userInput == "" { | |
| 876 | 861 | o.clearGhostText() | |
| 877 | 862 | return | |
| 878 | 863 | } | |
| 879 | 864 | ||
| 880 | 865 | go func() { | |
| 881 | - | output := o.autocompleteUC.GetSuggestion(o.ctx, usecase.GetSuggestionInput{Input: input}) | |
| 866 | + | output := o.autocompleteUC.GetSuggestion(o.ctx, usecase.GetSuggestionInput{Input: userInput}) | |
| 882 | 867 | if !output.Found || output.Suggestion == nil { | |
| 883 | 868 | o.clearGhostText() | |
| 884 | 869 | return | |
| 885 | 870 | } | |
| 886 | 871 | // Pass the original input so setGhostText can verify it's still current | |
| 887 | - | o.setGhostText(input, output.Suggestion.Suffix, output.Suggestion.FullText) | |
| 872 | + | o.setGhostText(userInput, output.Suggestion.Suffix, output.Suggestion.FullText) | |
| 888 | 873 | }() | |
| 889 | 874 | } | |
| 890 | 875 | ||
| @@ -898,7 +883,7 @@ func (o *Omnibox) updateGhostFromURL(targetURL string) { | |||
| 898 | 883 | } | |
| 899 | 884 | ||
| 900 | 885 | o.mu.RLock() | |
| 901 | - | input := o.realInput | |
| 886 | + | userInput := o.realInput | |
| 902 | 887 | o.mu.RUnlock() | |
| 903 | 888 | ||
| 904 | 889 | // Strip protocol from URL for cleaner display | |
| @@ -910,10 +895,10 @@ func (o *Omnibox) updateGhostFromURL(targetURL string) { | |||
| 910 | 895 | } | |
| 911 | 896 | ||
| 912 | 897 | // Try to compute proper suffix if input is a prefix of the URL | |
| 913 | - | if o.autocompleteUC != nil && input != "" { | |
| 914 | - | output := o.autocompleteUC.GetSuggestionForURL(o.ctx, input, targetURL) | |
| 898 | + | if o.autocompleteUC != nil && userInput != "" { | |
| 899 | + | output := o.autocompleteUC.GetSuggestionForURL(o.ctx, userInput, targetURL) | |
| 915 | 900 | if output.Found && output.Suggestion != nil { | |
| 916 | - | o.setGhostText(input, output.Suggestion.Suffix, output.Suggestion.FullText) | |
| 901 | + | o.setGhostText(userInput, output.Suggestion.Suffix, output.Suggestion.FullText) | |
| 917 | 902 | return | |
| 918 | 903 | } | |
| 919 | 904 | } | |
| @@ -957,16 +942,6 @@ func (o *Omnibox) updateGhostFromSelection() { | |||
| 957 | 942 | o.updateGhostFromURL(targetURL) | |
| 958 | 943 | } | |
| 959 | 944 | ||
| 960 | - | // escapeMarkup escapes special XML/Pango markup characters. | |
| 961 | - | func escapeMarkup(s string) string { | |
| 962 | - | s = strings.ReplaceAll(s, "&", "&") | |
| 963 | - | s = strings.ReplaceAll(s, "<", "<") | |
| 964 | - | s = strings.ReplaceAll(s, ">", ">") | |
| 965 | - | s = strings.ReplaceAll(s, "'", "'") | |
| 966 | - | s = strings.ReplaceAll(s, "\"", """) | |
| 967 | - | return s | |
| 968 | - | } | |
| 969 | - | ||
| 970 | 945 | // performSearch executes the search based on current view mode and query. | |
| 971 | 946 | func (o *Omnibox) performSearch() { | |
| 972 | 947 | o.mu.RLock() | |
| @@ -21,8 +21,9 @@ import ( | |||||
| 21 | ) | 21 | ) | ||
| 22 | 22 | ||||
| 23 | const ( | 23 | const ( | ||
| 24 | - | debounceDelayMs = 50 | 24 | + | debounceDelayMs = 50 |
| 25 | - | endBoxSpacing = 6 | 25 | + | endBoxSpacing = 6 |
| 26 | + | ghostIconOffsetBase = 18 // Base icon offset for ghost text positioning (scaled by uiScale) | |||
| 26 | ) | 27 | ) | ||
| 27 | 28 | ||||
| 28 | // ViewMode distinguishes history search from favorites display. | 29 | // ViewMode distinguishes history search from favorites display. | ||
| @@ -570,6 +571,20 @@ func (o *Omnibox) setupFocusCallbacks(onFocusIn func(*gtk.SearchEntry), onFocusO | |||||
| 570 | o.entry.AddController(&focusController.EventController) | 571 | o.entry.AddController(&focusController.EventController) | ||
| 571 | } | 572 | } | ||
| 572 | 573 | ||||
| 574 | + | // hasGhost returns whether ghost text is currently visible. | |||
| 575 | + | func (o *Omnibox) hasGhost() bool { | |||
| 576 | + | o.mu.RLock() | |||
| 577 | + | defer o.mu.RUnlock() | |||
| 578 | + | return o.hasGhostText | |||
| 579 | + | } | |||
| 580 | + | ||||
| 581 | + | // hasUserNavigated returns whether user has navigated with arrow keys. | |||
| 582 | + | func (o *Omnibox) hasUserNavigated() bool { | |||
| 583 | + | o.mu.RLock() | |||
| 584 | + | defer o.mu.RUnlock() | |||
| 585 | + | return o.hasNavigated | |||
| 586 | + | } | |||
| 587 | + | ||||
| 573 | // handleKeyPress processes keyboard events. | 588 | // handleKeyPress processes keyboard events. | ||
| 574 | // Returns true if the event was handled. | 589 | // Returns true if the event was handled. | ||
| 575 | func (o *Omnibox) handleKeyPress(keyval, keycode uint, state gdk.ModifierType) bool { | 590 | func (o *Omnibox) handleKeyPress(keyval, keycode uint, state gdk.ModifierType) bool { | ||
| @@ -608,10 +623,7 @@ func (o *Omnibox) handleKeyPress(keyval, keycode uint, state gdk.ModifierType) b | |||||
| 608 | 623 | ||||
| 609 | case uint(gdk.KEY_Tab): | 624 | case uint(gdk.KEY_Tab): | ||
| 610 | // Tab accepts ghost text if present, otherwise toggles view mode | 625 | // Tab accepts ghost text if present, otherwise toggles view mode | ||
| 611 | - | o.mu.RLock() | 626 | + | if o.hasGhost() { |
| 612 | - | hasGhost := o.hasGhostText | |||
| 613 | - | o.mu.RUnlock() | |||
| 614 | - | if hasGhost && o.cursorAtEnd() { | |||
| 615 | o.acceptGhostCompletion() | 627 | o.acceptGhostCompletion() | ||
| 616 | return true | 628 | return true | ||
| 617 | } | 629 | } | ||
| @@ -619,11 +631,8 @@ func (o *Omnibox) handleKeyPress(keyval, keycode uint, state gdk.ModifierType) b | |||||
| 619 | return true | 631 | return true | ||
| 620 | 632 | ||||
| 621 | case uint(gdk.KEY_Right): | 633 | case uint(gdk.KEY_Right): | ||
| 622 | - | // Arrow Right accepts ghost text if present and cursor at end | 634 | + | // Arrow Right accepts ghost text if present |
| 623 | - | o.mu.RLock() | 635 | + | if o.hasGhost() { |
| 624 | - | hasGhost := o.hasGhostText | |||
| 625 | - | o.mu.RUnlock() | |||
| 626 | - | if hasGhost && o.cursorAtEnd() { | |||
| 627 | o.acceptGhostCompletion() | 636 | o.acceptGhostCompletion() | ||
| 628 | return true | 637 | return true | ||
| 629 | } | 638 | } | ||
| @@ -631,11 +640,7 @@ func (o *Omnibox) handleKeyPress(keyval, keycode uint, state gdk.ModifierType) b | |||||
| 631 | 640 | ||||
| 632 | case uint(gdk.KEY_space): | 641 | case uint(gdk.KEY_space): | ||
| 633 | // Space toggles favorite only if user has navigated with arrow keys | 642 | // Space toggles favorite only if user has navigated with arrow keys | ||
| 634 | - | // Otherwise, let space pass through to entry for typing | 643 | + | if o.hasUserNavigated() { |
| 635 | - | o.mu.RLock() | |||
| 636 | - | navigated := o.hasNavigated | |||
| 637 | - | o.mu.RUnlock() | |||
| 638 | - | if navigated { | |||
| 639 | o.toggleFavorite() | 644 | o.toggleFavorite() | ||
| 640 | return true | 645 | return true | ||
| 641 | } | 646 | } | ||
| @@ -643,10 +648,7 @@ func (o *Omnibox) handleKeyPress(keyval, keycode uint, state gdk.ModifierType) b | |||||
| 643 | 648 | ||||
| 644 | case uint(gdk.KEY_y): | 649 | case uint(gdk.KEY_y): | ||
| 645 | // 'y' yanks (copies) the selected URL to clipboard when navigating | 650 | // 'y' yanks (copies) the selected URL to clipboard when navigating | ||
| 646 | - | o.mu.RLock() | 651 | + | if o.hasUserNavigated() { |
| 647 | - | navigated := o.hasNavigated | |||
| 648 | - | o.mu.RUnlock() | |||
| 649 | - | if navigated { | |||
| 650 | o.yankSelectedURL() | 652 | o.yankSelectedURL() | ||
| 651 | return true | 653 | return true | ||
| 652 | } | 654 | } | ||
| @@ -770,8 +772,8 @@ func (o *Omnibox) setGhostText(originalInput, suffix, fullText string) { | |||||
| 770 | 772 | ||||
| 771 | // Account for the search icon inside the entry | 773 | // Account for the search icon inside the entry | ||
| 772 | // Ghost label already has same margin/padding as entry via CSS | 774 | // Ghost label already has same margin/padding as entry via CSS | ||
| 773 | - | // Scale the icon offset by UI scale factor (base ~18px at scale 1.0) | 775 | + | // Scale the icon offset by UI scale factor |
| 774 | - | iconOffset := int(18.0 * o.uiScale) | 776 | + | iconOffset := int(float64(ghostIconOffsetBase) * o.uiScale) |
| 775 | marginStart := widthPx + iconOffset | 777 | marginStart := widthPx + iconOffset | ||
| 776 | 778 | ||||
| 777 | // Set the ghost label text (just the suffix) and position it | 779 | // Set the ghost label text (just the suffix) and position it | ||
| @@ -816,12 +818,11 @@ func (o *Omnibox) clearGhostText() { | |||||
| 816 | } | 818 | } | ||
| 817 | 819 | ||||
| 818 | // acceptGhostCompletion accepts the ghost text and fills the input. | 820 | // acceptGhostCompletion accepts the ghost text and fills the input. | ||
| 819 | - | // Returns true if ghost text was accepted. | 821 | + | func (o *Omnibox) acceptGhostCompletion() { |
| 820 | - | func (o *Omnibox) acceptGhostCompletion() bool { | |||
| 821 | o.mu.Lock() | 822 | o.mu.Lock() | ||
| 822 | if !o.hasGhostText || o.ghostFullText == "" { | 823 | if !o.hasGhostText || o.ghostFullText == "" { | ||
| 823 | o.mu.Unlock() | 824 | o.mu.Unlock() | ||
| 824 | - | return false | 825 | + | return |
| 825 | } | 826 | } | ||
| 826 | fullText := o.ghostFullText | 827 | fullText := o.ghostFullText | ||
| 827 | o.isAcceptingGhost = true | 828 | o.isAcceptingGhost = true | ||
| @@ -842,22 +843,6 @@ func (o *Omnibox) acceptGhostCompletion() bool { | |||||
| 842 | o.mu.Lock() | 843 | o.mu.Lock() | ||
| 843 | o.isAcceptingGhost = false | 844 | o.isAcceptingGhost = false | ||
| 844 | o.mu.Unlock() | 845 | o.mu.Unlock() | ||
| 845 | - | ||||
| 846 | - | return true | |||
| 847 | - | } | |||
| 848 | - | ||||
| 849 | - | // cursorAtEnd returns true if the entry cursor is at the end of the text. | |||
| 850 | - | func (o *Omnibox) cursorAtEnd() bool { | |||
| 851 | - | text := o.entry.GetText() | |||
| 852 | - | // If entry is empty, cursor is trivially "at end" - allow ghost acceptance | |||
| 853 | - | // This handles the case where user navigates with arrows from empty input | |||
| 854 | - | if len(text) == 0 { | |||
| 855 | - | return true | |||
| 856 | - | } | |||
| 857 | - | // SearchEntry doesn't have GetPosition directly, but we can use the editable interface | |||
| 858 | - | // For simplicity, we'll assume cursor is at end after any input | |||
| 859 | - | // This works because Tab/ArrowRight are handled in capture phase before entry processes them | |||
| 860 | - | return true | |||
| 861 | } | 846 | } | ||
| 862 | 847 | ||||
| 863 | // updateGhostFromSuggestion updates ghost text based on the current autocomplete suggestion. | 848 | // updateGhostFromSuggestion updates ghost text based on the current autocomplete suggestion. | ||
| @@ -867,24 +852,24 @@ func (o *Omnibox) updateGhostFromSuggestion() { | |||||
| 867 | } | 852 | } | ||
| 868 | 853 | ||||
| 869 | o.mu.RLock() | 854 | o.mu.RLock() | ||
| 870 | - | input := o.realInput | 855 | + | userInput := o.realInput |
| 871 | bangMode := o.bangMode | 856 | bangMode := o.bangMode | ||
| 872 | o.mu.RUnlock() | 857 | o.mu.RUnlock() | ||
| 873 | 858 | ||||
| 874 | // Don't show ghost text in bang mode (it has its own completion) | 859 | // Don't show ghost text in bang mode (it has its own completion) | ||
| 875 | - | if bangMode || input == "" { | 860 | + | if bangMode || userInput == "" { |
| 876 | o.clearGhostText() | 861 | o.clearGhostText() | ||
| 877 | return | 862 | return | ||
| 878 | } | 863 | } | ||
| 879 | 864 | ||||
| 880 | go func() { | 865 | go func() { | ||
| 881 | - | output := o.autocompleteUC.GetSuggestion(o.ctx, usecase.GetSuggestionInput{Input: input}) | 866 | + | output := o.autocompleteUC.GetSuggestion(o.ctx, usecase.GetSuggestionInput{Input: userInput}) |
| 882 | if !output.Found || output.Suggestion == nil { | 867 | if !output.Found || output.Suggestion == nil { | ||
| 883 | o.clearGhostText() | 868 | o.clearGhostText() | ||
| 884 | return | 869 | return | ||
| 885 | } | 870 | } | ||
| 886 | // Pass the original input so setGhostText can verify it's still current | 871 | // Pass the original input so setGhostText can verify it's still current | ||
| 887 | - | o.setGhostText(input, output.Suggestion.Suffix, output.Suggestion.FullText) | 872 | + | o.setGhostText(userInput, output.Suggestion.Suffix, output.Suggestion.FullText) |
| 888 | }() | 873 | }() | ||
| 889 | } | 874 | } | ||
| 890 | 875 | ||||
| @@ -898,7 +883,7 @@ func (o *Omnibox) updateGhostFromURL(targetURL string) { | |||||
| 898 | } | 883 | } | ||
| 899 | 884 | ||||
| 900 | o.mu.RLock() | 885 | o.mu.RLock() | ||
| 901 | - | input := o.realInput | 886 | + | userInput := o.realInput |
| 902 | o.mu.RUnlock() | 887 | o.mu.RUnlock() | ||
| 903 | 888 | ||||
| 904 | // Strip protocol from URL for cleaner display | 889 | // Strip protocol from URL for cleaner display | ||
| @@ -910,10 +895,10 @@ func (o *Omnibox) updateGhostFromURL(targetURL string) { | |||||
| 910 | } | 895 | } | ||
| 911 | 896 | ||||
| 912 | // Try to compute proper suffix if input is a prefix of the URL | 897 | // Try to compute proper suffix if input is a prefix of the URL | ||
| 913 | - | if o.autocompleteUC != nil && input != "" { | 898 | + | if o.autocompleteUC != nil && userInput != "" { |
| 914 | - | output := o.autocompleteUC.GetSuggestionForURL(o.ctx, input, targetURL) | 899 | + | output := o.autocompleteUC.GetSuggestionForURL(o.ctx, userInput, targetURL) |
| 915 | if output.Found && output.Suggestion != nil { | 900 | if output.Found && output.Suggestion != nil { | ||
| 916 | - | o.setGhostText(input, output.Suggestion.Suffix, output.Suggestion.FullText) | 901 | + | o.setGhostText(userInput, output.Suggestion.Suffix, output.Suggestion.FullText) |
| 917 | return | 902 | return | ||
| 918 | } | 903 | } | ||
| 919 | } | 904 | } | ||
| @@ -957,16 +942,6 @@ func (o *Omnibox) updateGhostFromSelection() { | |||||
| 957 | o.updateGhostFromURL(targetURL) | 942 | o.updateGhostFromURL(targetURL) | ||
| 958 | } | 943 | } | ||
| 959 | 944 | ||||
| 960 | - | // escapeMarkup escapes special XML/Pango markup characters. | |||
| 961 | - | func escapeMarkup(s string) string { | |||
| 962 | - | s = strings.ReplaceAll(s, "&", "&") | |||
| 963 | - | s = strings.ReplaceAll(s, "<", "<") | |||
| 964 | - | s = strings.ReplaceAll(s, ">", ">") | |||
| 965 | - | s = strings.ReplaceAll(s, "'", "'") | |||
| 966 | - | s = strings.ReplaceAll(s, "\"", """) | |||
| 967 | - | return s | |||
| 968 | - | } | |||
| 969 | - | ||||
| 970 | // performSearch executes the search based on current view mode and query. | 945 | // performSearch executes the search based on current view mode and query. | ||
| 971 | func (o *Omnibox) performSearch() { | 946 | func (o *Omnibox) performSearch() { | ||
| 972 | o.mu.RLock() | 947 | o.mu.RLock() | ||