refactor: remove redundant hasGhostText field and simplify ghost state
Remove hasGhostText bool — ghostSuffix != "" serves the same purpose. Defer logger allocation past early-return guards in onEntryChanged. Remove leftover local replace directive for puregotk.
- commit
- cb471676a8bb848fcbba1dab83fd03ae58b21e40
- author
- brice · · 2026-03-27 19:39 UTC
- parent
- 7eb8e31
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +17 -22
M go.mod +0 -2
| @@ -25,8 +25,6 @@ require ( | |||
| 25 | 25 | ||
| 26 | 26 | replace github.com/ebitengine/purego => github.com/bnema/purego v0.0.0-20260323095903-91aa177a2704 | |
| 27 | 27 | ||
| 28 | - | replace github.com/bnema/puregotk => /home/brice/dev/projects/puregotk | |
| 29 | - | ||
| 30 | 28 | require ( | |
| 31 | 29 | github.com/atotto/clipboard v0.1.4 // indirect | |
| 32 | 30 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect | |
| @@ -25,8 +25,6 @@ require ( | |||||
| 25 | 25 | ||||
| 26 | replace github.com/ebitengine/purego => github.com/bnema/purego v0.0.0-20260323095903-91aa177a2704 | 26 | replace github.com/ebitengine/purego => github.com/bnema/purego v0.0.0-20260323095903-91aa177a2704 | ||
| 27 | 27 | ||||
| 28 | - | replace github.com/bnema/puregotk => /home/brice/dev/projects/puregotk | |||
| 29 | - | ||||
| 30 | require ( | 28 | require ( | ||
| 31 | github.com/atotto/clipboard v0.1.4 // indirect | 29 | github.com/atotto/clipboard v0.1.4 // indirect | ||
| 32 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect | 30 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect | ||
M go.sum +2 -0 generated file: collapsed
M internal/ui/component/omnibox.go +15 -20
| @@ -96,7 +96,6 @@ type Omnibox struct { | |||
| 96 | 96 | // Ghost text state | |
| 97 | 97 | realInput string // What user actually typed (without ghost suffix) | |
| 98 | 98 | ghostSuffix string // Completion suffix currently displayed | |
| 99 | - | hasGhostText bool // Quick check for Tab behavior | |
| 100 | 99 | insertCompletion bool // True on character insert, false on delete — gates ghost completion | |
| 101 | 100 | isSettingGhost bool // Guard flag: skip onEntryChanged during programmatic SetText | |
| 102 | 101 | ||
| @@ -678,7 +677,7 @@ func (o *Omnibox) setupFocusCallbacks(onFocusIn func(*gtk.SearchEntry), onFocusO | |||
| 678 | 677 | func (o *Omnibox) hasGhost() bool { | |
| 679 | 678 | o.mu.RLock() | |
| 680 | 679 | defer o.mu.RUnlock() | |
| 681 | - | return o.hasGhostText | |
| 680 | + | return o.ghostSuffix != "" | |
| 682 | 681 | } | |
| 683 | 682 | ||
| 684 | 683 | // hasUserNavigated returns whether user has navigated with arrow keys. | |
| @@ -836,14 +835,10 @@ func (o *Omnibox) handleCtrlNumberShortcut(keyval, keycode uint, ctrl bool) bool | |||
| 836 | 835 | // selected and the user typed, the selection was replaced naturally by GTK. | |
| 837 | 836 | // We must NOT call clearGhostText() here (that would delete real user text). | |
| 838 | 837 | func (o *Omnibox) onEntryChanged() { | |
| 839 | - | log := logging.FromContext(o.ctx) | |
| 840 | - | ||
| 841 | - | // Skip if we're programmatically setting ghost text (synchronous guard). | |
| 842 | 838 | o.mu.RLock() | |
| 843 | 839 | isSettingGhost := o.isSettingGhost | |
| 844 | 840 | o.mu.RUnlock() | |
| 845 | 841 | if isSettingGhost { | |
| 846 | - | log.Debug().Msg("ghost: onEntryChanged skipped (isSettingGhost)") | |
| 847 | 842 | return | |
| 848 | 843 | } | |
| 849 | 844 | ||
| @@ -851,15 +846,15 @@ func (o *Omnibox) onEntryChanged() { | |||
| 851 | 846 | ||
| 852 | 847 | // Detect debounced echo from our own SetText in setGhostText. | |
| 853 | 848 | // search-changed is debounced by GtkSearchEntry, so it fires AFTER | |
| 854 | - | // the isSettingGhost guard is already off. Recognize it by checking | |
| 855 | - | // if the entry text matches realInput + ghostSuffix exactly. | |
| 849 | + | // the isSettingGhost guard is already off. | |
| 856 | 850 | o.mu.RLock() | |
| 857 | - | if o.hasGhostText && o.ghostSuffix != "" && entryText == o.realInput+o.ghostSuffix { | |
| 851 | + | if o.ghostSuffix != "" && entryText == o.realInput+o.ghostSuffix { | |
| 858 | 852 | o.mu.RUnlock() | |
| 859 | - | log.Debug().Str("entryText", entryText).Msg("ghost: onEntryChanged skipped (debounced echo)") | |
| 860 | 853 | return | |
| 861 | 854 | } | |
| 862 | 855 | o.mu.RUnlock() | |
| 856 | + | ||
| 857 | + | log := logging.FromContext(o.ctx) | |
| 863 | 858 | if trimmed := url.TrimLeadingSpacesIfURL(entryText); trimmed != entryText { | |
| 864 | 859 | o.withGhostGuard(func() { | |
| 865 | 860 | o.entry.SetText(trimmed) | |
| @@ -872,7 +867,7 @@ func (o *Omnibox) onEntryChanged() { | |||
| 872 | 867 | o.mu.Lock() | |
| 873 | 868 | oldGhost := o.ghostSuffix | |
| 874 | 869 | o.hasNavigated = false | |
| 875 | - | o.hasGhostText = false | |
| 870 | + | ||
| 876 | 871 | o.ghostSuffix = "" | |
| 877 | 872 | o.realInput = entryText | |
| 878 | 873 | shouldComplete := o.insertCompletion | |
| @@ -941,7 +936,7 @@ func (o *Omnibox) setGhostText(originalInput, suffix string) { | |||
| 941 | 936 | o.mu.Lock() | |
| 942 | 937 | o.realInput = originalInput | |
| 943 | 938 | o.ghostSuffix = suffix | |
| 944 | - | o.hasGhostText = true | |
| 939 | + | ||
| 945 | 940 | o.mu.Unlock() | |
| 946 | 941 | ||
| 947 | 942 | o.withGhostGuard(func() { | |
| @@ -956,11 +951,11 @@ func (o *Omnibox) setGhostText(originalInput, suffix string) { | |||
| 956 | 951 | func (o *Omnibox) clearGhostText() { | |
| 957 | 952 | log := logging.FromContext(o.ctx) | |
| 958 | 953 | o.mu.Lock() | |
| 959 | - | hadGhost := o.hasGhostText | |
| 954 | + | hadGhost := o.ghostSuffix != "" | |
| 960 | 955 | ghostSuffix := o.ghostSuffix | |
| 961 | 956 | realInput := o.realInput | |
| 962 | 957 | o.ghostSuffix = "" | |
| 963 | - | o.hasGhostText = false | |
| 958 | + | ||
| 964 | 959 | o.mu.Unlock() | |
| 965 | 960 | ||
| 966 | 961 | if !hadGhost || ghostSuffix == "" || o.entry == nil { | |
| @@ -1007,13 +1002,13 @@ func (o *Omnibox) clearGhostTextIfInput(expectedInput string) { | |||
| 1007 | 1002 | func (o *Omnibox) acceptGhostCompletion() { | |
| 1008 | 1003 | log := logging.FromContext(o.ctx) | |
| 1009 | 1004 | o.mu.Lock() | |
| 1010 | - | if !o.hasGhostText || o.ghostSuffix == "" { | |
| 1005 | + | if o.ghostSuffix == "" { | |
| 1011 | 1006 | o.mu.Unlock() | |
| 1012 | 1007 | log.Debug().Msg("ghost: acceptGhostCompletion — no ghost to accept") | |
| 1013 | 1008 | return | |
| 1014 | 1009 | } | |
| 1015 | 1010 | suffix := o.ghostSuffix | |
| 1016 | - | o.hasGhostText = false | |
| 1011 | + | ||
| 1017 | 1012 | o.ghostSuffix = "" | |
| 1018 | 1013 | o.mu.Unlock() | |
| 1019 | 1014 | log.Debug().Str("suffix", suffix).Msg("ghost: acceptGhostCompletion") | |
| @@ -1069,7 +1064,7 @@ func (o *Omnibox) updateGhostFromURL(userInput, targetURL string) bool { | |||
| 1069 | 1064 | func (o *Omnibox) updateGhostFromSelection() { | |
| 1070 | 1065 | o.mu.RLock() | |
| 1071 | 1066 | realInput := o.realInput | |
| 1072 | - | hasGhost := o.hasGhostText | |
| 1067 | + | hasGhost := o.ghostSuffix != "" | |
| 1073 | 1068 | o.mu.RUnlock() | |
| 1074 | 1069 | ||
| 1075 | 1070 | entryText := "" | |
| @@ -1287,7 +1282,7 @@ func (o *Omnibox) performSearch() { | |||
| 1287 | 1282 | visible := o.visible | |
| 1288 | 1283 | mode := o.viewMode | |
| 1289 | 1284 | realInput := o.realInput | |
| 1290 | - | hasGhost := o.hasGhostText | |
| 1285 | + | hasGhost := o.ghostSuffix != "" | |
| 1291 | 1286 | o.mu.RUnlock() | |
| 1292 | 1287 | ||
| 1293 | 1288 | // Skip search if omnibox is hidden | |
| @@ -1849,7 +1844,7 @@ func (o *Omnibox) attachRowHoverSelection(row *gtk.ListBoxRow, index int) { | |||
| 1849 | 1844 | enterCb := func(_ gtk.EventControllerMotion, _ float64, _ float64) { | |
| 1850 | 1845 | o.mu.RLock() | |
| 1851 | 1846 | realInput := o.realInput | |
| 1852 | - | hasGhostText := o.hasGhostText | |
| 1847 | + | hasGhostText := o.ghostSuffix != "" | |
| 1853 | 1848 | hasNavigated := o.hasNavigated | |
| 1854 | 1849 | o.mu.RUnlock() | |
| 1855 | 1850 | ||
| @@ -2314,7 +2309,7 @@ func (o *Omnibox) Show(ctx context.Context, query string) { | |||
| 2314 | 2309 | // Initialize ghost text state | |
| 2315 | 2310 | o.realInput = query | |
| 2316 | 2311 | o.ghostSuffix = "" | |
| 2317 | - | o.hasGhostText = false | |
| 2312 | + | ||
| 2318 | 2313 | o.insertCompletion = false | |
| 2319 | 2314 | o.mu.Unlock() | |
| 2320 | 2315 | ||
| @@ -96,7 +96,6 @@ type Omnibox struct { | |||||
| 96 | // Ghost text state | 96 | // Ghost text state | ||
| 97 | realInput string // What user actually typed (without ghost suffix) | 97 | realInput string // What user actually typed (without ghost suffix) | ||
| 98 | ghostSuffix string // Completion suffix currently displayed | 98 | ghostSuffix string // Completion suffix currently displayed | ||
| 99 | - | hasGhostText bool // Quick check for Tab behavior | |||
| 100 | insertCompletion bool // True on character insert, false on delete — gates ghost completion | 99 | insertCompletion bool // True on character insert, false on delete — gates ghost completion | ||
| 101 | isSettingGhost bool // Guard flag: skip onEntryChanged during programmatic SetText | 100 | isSettingGhost bool // Guard flag: skip onEntryChanged during programmatic SetText | ||
| 102 | 101 | ||||
| @@ -678,7 +677,7 @@ func (o *Omnibox) setupFocusCallbacks(onFocusIn func(*gtk.SearchEntry), onFocusO | |||||
| 678 | func (o *Omnibox) hasGhost() bool { | 677 | func (o *Omnibox) hasGhost() bool { | ||
| 679 | o.mu.RLock() | 678 | o.mu.RLock() | ||
| 680 | defer o.mu.RUnlock() | 679 | defer o.mu.RUnlock() | ||
| 681 | - | return o.hasGhostText | 680 | + | return o.ghostSuffix != "" |
| 682 | } | 681 | } | ||
| 683 | 682 | ||||
| 684 | // hasUserNavigated returns whether user has navigated with arrow keys. | 683 | // hasUserNavigated returns whether user has navigated with arrow keys. | ||
| @@ -836,14 +835,10 @@ func (o *Omnibox) handleCtrlNumberShortcut(keyval, keycode uint, ctrl bool) bool | |||||
| 836 | // selected and the user typed, the selection was replaced naturally by GTK. | 835 | // selected and the user typed, the selection was replaced naturally by GTK. | ||
| 837 | // We must NOT call clearGhostText() here (that would delete real user text). | 836 | // We must NOT call clearGhostText() here (that would delete real user text). | ||
| 838 | func (o *Omnibox) onEntryChanged() { | 837 | func (o *Omnibox) onEntryChanged() { | ||
| 839 | - | log := logging.FromContext(o.ctx) | |||
| 840 | - | ||||
| 841 | - | // Skip if we're programmatically setting ghost text (synchronous guard). | |||
| 842 | o.mu.RLock() | 838 | o.mu.RLock() | ||
| 843 | isSettingGhost := o.isSettingGhost | 839 | isSettingGhost := o.isSettingGhost | ||
| 844 | o.mu.RUnlock() | 840 | o.mu.RUnlock() | ||
| 845 | if isSettingGhost { | 841 | if isSettingGhost { | ||
| 846 | - | log.Debug().Msg("ghost: onEntryChanged skipped (isSettingGhost)") | |||
| 847 | return | 842 | return | ||
| 848 | } | 843 | } | ||
| 849 | 844 | ||||
| @@ -851,15 +846,15 @@ func (o *Omnibox) onEntryChanged() { | |||||
| 851 | 846 | ||||
| 852 | // Detect debounced echo from our own SetText in setGhostText. | 847 | // Detect debounced echo from our own SetText in setGhostText. | ||
| 853 | // search-changed is debounced by GtkSearchEntry, so it fires AFTER | 848 | // search-changed is debounced by GtkSearchEntry, so it fires AFTER | ||
| 854 | - | // the isSettingGhost guard is already off. Recognize it by checking | 849 | + | // the isSettingGhost guard is already off. |
| 855 | - | // if the entry text matches realInput + ghostSuffix exactly. | |||
| 856 | o.mu.RLock() | 850 | o.mu.RLock() | ||
| 857 | - | if o.hasGhostText && o.ghostSuffix != "" && entryText == o.realInput+o.ghostSuffix { | 851 | + | if o.ghostSuffix != "" && entryText == o.realInput+o.ghostSuffix { |
| 858 | o.mu.RUnlock() | 852 | o.mu.RUnlock() | ||
| 859 | - | log.Debug().Str("entryText", entryText).Msg("ghost: onEntryChanged skipped (debounced echo)") | |||
| 860 | return | 853 | return | ||
| 861 | } | 854 | } | ||
| 862 | o.mu.RUnlock() | 855 | o.mu.RUnlock() | ||
| 856 | + | ||||
| 857 | + | log := logging.FromContext(o.ctx) | |||
| 863 | if trimmed := url.TrimLeadingSpacesIfURL(entryText); trimmed != entryText { | 858 | if trimmed := url.TrimLeadingSpacesIfURL(entryText); trimmed != entryText { | ||
| 864 | o.withGhostGuard(func() { | 859 | o.withGhostGuard(func() { | ||
| 865 | o.entry.SetText(trimmed) | 860 | o.entry.SetText(trimmed) | ||
| @@ -872,7 +867,7 @@ func (o *Omnibox) onEntryChanged() { | |||||
| 872 | o.mu.Lock() | 867 | o.mu.Lock() | ||
| 873 | oldGhost := o.ghostSuffix | 868 | oldGhost := o.ghostSuffix | ||
| 874 | o.hasNavigated = false | 869 | o.hasNavigated = false | ||
| 875 | - | o.hasGhostText = false | 870 | + | |
| 876 | o.ghostSuffix = "" | 871 | o.ghostSuffix = "" | ||
| 877 | o.realInput = entryText | 872 | o.realInput = entryText | ||
| 878 | shouldComplete := o.insertCompletion | 873 | shouldComplete := o.insertCompletion | ||
| @@ -941,7 +936,7 @@ func (o *Omnibox) setGhostText(originalInput, suffix string) { | |||||
| 941 | o.mu.Lock() | 936 | o.mu.Lock() | ||
| 942 | o.realInput = originalInput | 937 | o.realInput = originalInput | ||
| 943 | o.ghostSuffix = suffix | 938 | o.ghostSuffix = suffix | ||
| 944 | - | o.hasGhostText = true | 939 | + | |
| 945 | o.mu.Unlock() | 940 | o.mu.Unlock() | ||
| 946 | 941 | ||||
| 947 | o.withGhostGuard(func() { | 942 | o.withGhostGuard(func() { | ||
| @@ -956,11 +951,11 @@ func (o *Omnibox) setGhostText(originalInput, suffix string) { | |||||
| 956 | func (o *Omnibox) clearGhostText() { | 951 | func (o *Omnibox) clearGhostText() { | ||
| 957 | log := logging.FromContext(o.ctx) | 952 | log := logging.FromContext(o.ctx) | ||
| 958 | o.mu.Lock() | 953 | o.mu.Lock() | ||
| 959 | - | hadGhost := o.hasGhostText | 954 | + | hadGhost := o.ghostSuffix != "" |
| 960 | ghostSuffix := o.ghostSuffix | 955 | ghostSuffix := o.ghostSuffix | ||
| 961 | realInput := o.realInput | 956 | realInput := o.realInput | ||
| 962 | o.ghostSuffix = "" | 957 | o.ghostSuffix = "" | ||
| 963 | - | o.hasGhostText = false | 958 | + | |
| 964 | o.mu.Unlock() | 959 | o.mu.Unlock() | ||
| 965 | 960 | ||||
| 966 | if !hadGhost || ghostSuffix == "" || o.entry == nil { | 961 | if !hadGhost || ghostSuffix == "" || o.entry == nil { | ||
| @@ -1007,13 +1002,13 @@ func (o *Omnibox) clearGhostTextIfInput(expectedInput string) { | |||||
| 1007 | func (o *Omnibox) acceptGhostCompletion() { | 1002 | func (o *Omnibox) acceptGhostCompletion() { | ||
| 1008 | log := logging.FromContext(o.ctx) | 1003 | log := logging.FromContext(o.ctx) | ||
| 1009 | o.mu.Lock() | 1004 | o.mu.Lock() | ||
| 1010 | - | if !o.hasGhostText || o.ghostSuffix == "" { | 1005 | + | if o.ghostSuffix == "" { |
| 1011 | o.mu.Unlock() | 1006 | o.mu.Unlock() | ||
| 1012 | log.Debug().Msg("ghost: acceptGhostCompletion — no ghost to accept") | 1007 | log.Debug().Msg("ghost: acceptGhostCompletion — no ghost to accept") | ||
| 1013 | return | 1008 | return | ||
| 1014 | } | 1009 | } | ||
| 1015 | suffix := o.ghostSuffix | 1010 | suffix := o.ghostSuffix | ||
| 1016 | - | o.hasGhostText = false | 1011 | + | |
| 1017 | o.ghostSuffix = "" | 1012 | o.ghostSuffix = "" | ||
| 1018 | o.mu.Unlock() | 1013 | o.mu.Unlock() | ||
| 1019 | log.Debug().Str("suffix", suffix).Msg("ghost: acceptGhostCompletion") | 1014 | log.Debug().Str("suffix", suffix).Msg("ghost: acceptGhostCompletion") | ||
| @@ -1069,7 +1064,7 @@ func (o *Omnibox) updateGhostFromURL(userInput, targetURL string) bool { | |||||
| 1069 | func (o *Omnibox) updateGhostFromSelection() { | 1064 | func (o *Omnibox) updateGhostFromSelection() { | ||
| 1070 | o.mu.RLock() | 1065 | o.mu.RLock() | ||
| 1071 | realInput := o.realInput | 1066 | realInput := o.realInput | ||
| 1072 | - | hasGhost := o.hasGhostText | 1067 | + | hasGhost := o.ghostSuffix != "" |
| 1073 | o.mu.RUnlock() | 1068 | o.mu.RUnlock() | ||
| 1074 | 1069 | ||||
| 1075 | entryText := "" | 1070 | entryText := "" | ||
| @@ -1287,7 +1282,7 @@ func (o *Omnibox) performSearch() { | |||||
| 1287 | visible := o.visible | 1282 | visible := o.visible | ||
| 1288 | mode := o.viewMode | 1283 | mode := o.viewMode | ||
| 1289 | realInput := o.realInput | 1284 | realInput := o.realInput | ||
| 1290 | - | hasGhost := o.hasGhostText | 1285 | + | hasGhost := o.ghostSuffix != "" |
| 1291 | o.mu.RUnlock() | 1286 | o.mu.RUnlock() | ||
| 1292 | 1287 | ||||
| 1293 | // Skip search if omnibox is hidden | 1288 | // Skip search if omnibox is hidden | ||
| @@ -1849,7 +1844,7 @@ func (o *Omnibox) attachRowHoverSelection(row *gtk.ListBoxRow, index int) { | |||||
| 1849 | enterCb := func(_ gtk.EventControllerMotion, _ float64, _ float64) { | 1844 | enterCb := func(_ gtk.EventControllerMotion, _ float64, _ float64) { | ||
| 1850 | o.mu.RLock() | 1845 | o.mu.RLock() | ||
| 1851 | realInput := o.realInput | 1846 | realInput := o.realInput | ||
| 1852 | - | hasGhostText := o.hasGhostText | 1847 | + | hasGhostText := o.ghostSuffix != "" |
| 1853 | hasNavigated := o.hasNavigated | 1848 | hasNavigated := o.hasNavigated | ||
| 1854 | o.mu.RUnlock() | 1849 | o.mu.RUnlock() | ||
| 1855 | 1850 | ||||
| @@ -2314,7 +2309,7 @@ func (o *Omnibox) Show(ctx context.Context, query string) { | |||||
| 2314 | // Initialize ghost text state | 2309 | // Initialize ghost text state | ||
| 2315 | o.realInput = query | 2310 | o.realInput = query | ||
| 2316 | o.ghostSuffix = "" | 2311 | o.ghostSuffix = "" | ||
| 2317 | - | o.hasGhostText = false | 2312 | + | |
| 2318 | o.insertCompletion = false | 2313 | o.insertCompletion = false | ||
| 2319 | o.mu.Unlock() | 2314 | o.mu.Unlock() | ||
| 2320 | 2315 | ||||