detached 334966d

fix(omnibox): avoid per-row hover callback churn

commit
334966de976e5ee7d487c9ca193c2cb62c2b943e
author
brice · · 2026-05-19 17:19 UTC
parent
c927059
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +41 -24
M internal/ui/component/omnibox.go +41 -24
@@ -779,9 +779,50 @@ func (o *Omnibox) initList() error {
779779 o.retainedCallbacks = append(o.retainedCallbacks, rowActivatedCb)
780780 o.listBox.ConnectRowActivated(&rowActivatedCb)
781781
782+ o.setupListHoverSelection()
783+
782784 return nil
783785 }
784786
787+func (o *Omnibox) setupListHoverSelection() {
788+ motionCtrl := gtk.NewEventControllerMotion()
789+ if motionCtrl == nil {
790+ logging.FromContext(o.ctx).Error().Msg("failed to create motion event controller")
791+ return
792+ }
793+
794+ motionCb := func(_ gtk.EventControllerMotion, _ float64, y float64) {
795+ o.handleListHoverSelection(y)
796+ }
797+ o.retainedCallbacks = append(o.retainedCallbacks, motionCb)
798+ motionCtrl.ConnectMotion(&motionCb)
799+ o.listBox.AddController(&motionCtrl.EventController)
800+}
801+
802+func (o *Omnibox) handleListHoverSelection(y float64) {
803+ o.mu.RLock()
804+ realInput := o.realInput
805+ hasGhostText := o.ghostSuffix != ""
806+ hasNavigated := o.hasNavigated
807+ selectedIndex := o.selectedIndex
808+ o.mu.RUnlock()
809+
810+ if !shouldPromoteHoverSelection(realInput, hasGhostText, hasNavigated) {
811+ return
812+ }
813+
814+ row := o.listBox.GetRowAtY(int(y))
815+ if row == nil {
816+ return
817+ }
818+
819+ index := row.GetIndex()
820+ if index < 0 || index == selectedIndex {
821+ return
822+ }
823+ o.selectIndex(index)
824+}
825+
785826 func (o *Omnibox) assembleWidgets() {
786827 o.headerBox.Append(&o.historyBtn.Widget)
787828 o.headerBox.Append(&o.favoritesBtn.Widget)
@@ -2087,7 +2128,6 @@ func (o *Omnibox) createRowWithFaviconURL(
20872128 return nil
20882129 }
20892130 row.AddCssClass("omnibox-row")
2090- o.attachRowHoverSelection(row, index)
20912131
20922132 const rowSpacing = 8
20932133 hbox := gtk.NewBox(gtk.OrientationHorizontalValue, rowSpacing)
@@ -2119,29 +2159,6 @@ func (o *Omnibox) createRowWithFaviconURL(
21192159 return row
21202160 }
21212161
2122-func (o *Omnibox) attachRowHoverSelection(row *gtk.ListBoxRow, index int) {
2123- motionCtrl := gtk.NewEventControllerMotion()
2124- if motionCtrl == nil {
2125- return
2126- }
2127-
2128- enterCb := func(_ gtk.EventControllerMotion, _ float64, _ float64) {
2129- o.mu.RLock()
2130- realInput := o.realInput
2131- hasGhostText := o.ghostSuffix != ""
2132- hasNavigated := o.hasNavigated
2133- o.mu.RUnlock()
2134-
2135- if !shouldPromoteHoverSelection(realInput, hasGhostText, hasNavigated) {
2136- return
2137- }
2138- o.selectIndex(index)
2139- }
2140- o.retainedCallbacks = append(o.retainedCallbacks, enterCb)
2141- motionCtrl.ConnectEnter(&enterCb)
2142- row.AddController(&motionCtrl.EventController)
2143-}
2144-
21452162 func shouldPromoteHoverSelection(realInput string, hasGhostText, hasNavigated bool) bool {
21462163 return realInput == "" && !hasGhostText && !hasNavigated
21472164 }
@@ -779,9 +779,50 @@ func (o *Omnibox) initList() error {
779 o.retainedCallbacks = append(o.retainedCallbacks, rowActivatedCb)779 o.retainedCallbacks = append(o.retainedCallbacks, rowActivatedCb)
780 o.listBox.ConnectRowActivated(&rowActivatedCb)780 o.listBox.ConnectRowActivated(&rowActivatedCb)
781 781
782+ o.setupListHoverSelection()
783+
782 return nil784 return nil
783 }785 }
784 786
787+func (o *Omnibox) setupListHoverSelection() {
788+ motionCtrl := gtk.NewEventControllerMotion()
789+ if motionCtrl == nil {
790+ logging.FromContext(o.ctx).Error().Msg("failed to create motion event controller")
791+ return
792+ }
793+
794+ motionCb := func(_ gtk.EventControllerMotion, _ float64, y float64) {
795+ o.handleListHoverSelection(y)
796+ }
797+ o.retainedCallbacks = append(o.retainedCallbacks, motionCb)
798+ motionCtrl.ConnectMotion(&motionCb)
799+ o.listBox.AddController(&motionCtrl.EventController)
800+}
801+
802+func (o *Omnibox) handleListHoverSelection(y float64) {
803+ o.mu.RLock()
804+ realInput := o.realInput
805+ hasGhostText := o.ghostSuffix != ""
806+ hasNavigated := o.hasNavigated
807+ selectedIndex := o.selectedIndex
808+ o.mu.RUnlock()
809+
810+ if !shouldPromoteHoverSelection(realInput, hasGhostText, hasNavigated) {
811+ return
812+ }
813+
814+ row := o.listBox.GetRowAtY(int(y))
815+ if row == nil {
816+ return
817+ }
818+
819+ index := row.GetIndex()
820+ if index < 0 || index == selectedIndex {
821+ return
822+ }
823+ o.selectIndex(index)
824+}
825+
785 func (o *Omnibox) assembleWidgets() {826 func (o *Omnibox) assembleWidgets() {
786 o.headerBox.Append(&o.historyBtn.Widget)827 o.headerBox.Append(&o.historyBtn.Widget)
787 o.headerBox.Append(&o.favoritesBtn.Widget)828 o.headerBox.Append(&o.favoritesBtn.Widget)
@@ -2087,7 +2128,6 @@ func (o *Omnibox) createRowWithFaviconURL(
2087 return nil2128 return nil
2088 }2129 }
2089 row.AddCssClass("omnibox-row")2130 row.AddCssClass("omnibox-row")
2090- o.attachRowHoverSelection(row, index)
2091 2131
2092 const rowSpacing = 82132 const rowSpacing = 8
2093 hbox := gtk.NewBox(gtk.OrientationHorizontalValue, rowSpacing)2133 hbox := gtk.NewBox(gtk.OrientationHorizontalValue, rowSpacing)
@@ -2119,29 +2159,6 @@ func (o *Omnibox) createRowWithFaviconURL(
2119 return row2159 return row
2120 }2160 }
2121 2161
2122-func (o *Omnibox) attachRowHoverSelection(row *gtk.ListBoxRow, index int) {
2123- motionCtrl := gtk.NewEventControllerMotion()
2124- if motionCtrl == nil {
2125- return
2126- }
2127-
2128- enterCb := func(_ gtk.EventControllerMotion, _ float64, _ float64) {
2129- o.mu.RLock()
2130- realInput := o.realInput
2131- hasGhostText := o.ghostSuffix != ""
2132- hasNavigated := o.hasNavigated
2133- o.mu.RUnlock()
2134-
2135- if !shouldPromoteHoverSelection(realInput, hasGhostText, hasNavigated) {
2136- return
2137- }
2138- o.selectIndex(index)
2139- }
2140- o.retainedCallbacks = append(o.retainedCallbacks, enterCb)
2141- motionCtrl.ConnectEnter(&enterCb)
2142- row.AddController(&motionCtrl.EventController)
2143-}
2144-
2145 func shouldPromoteHoverSelection(realInput string, hasGhostText, hasNavigated bool) bool {2162 func shouldPromoteHoverSelection(realInput string, hasGhostText, hasNavigated bool) bool {
2146 return realInput == "" && !hasGhostText && !hasNavigated2163 return realInput == "" && !hasGhostText && !hasNavigated
2147 }2164 }

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