Skip to content

Commit 43b379f

Browse files
authored
Detect double-clicks properly (#4725)
- **PR Description** Previously, any click on an already selected line in a list view was treated as a double click, and triggered the same action as pressing either enter or space, depending on the view. Improve this by doing proper double-click detection based on how much time has passed between clicks. Fixes #2526.
2 parents c2aab81 + 37197b8 commit 43b379f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+506
-153
lines changed

go.mod

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ require (
1515
github.com/integrii/flaggy v1.4.0
1616
github.com/jesseduffield/generics v0.0.0-20250517122708-b0b4a53a6f5c
1717
github.com/jesseduffield/go-git/v5 v5.14.1-0.20250407170251-e1a013310ccd
18-
github.com/jesseduffield/gocui v0.3.1-0.20250605111917-fc5387961412
18+
github.com/jesseduffield/gocui v0.3.1-0.20250711082438-4aa4fd0b4d22
1919
github.com/jesseduffield/kill v0.0.0-20250101124109-e216ddbe133a
2020
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
2121
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
@@ -37,7 +37,7 @@ require (
3737
github.com/stretchr/testify v1.10.0
3838
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778
3939
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
40-
golang.org/x/sync v0.14.0
40+
golang.org/x/sync v0.16.0
4141
gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0
4242
gopkg.in/yaml.v3 v3.0.1
4343
)
@@ -78,9 +78,9 @@ require (
7878
github.com/xanzy/ssh-agent v0.3.3 // indirect
7979
golang.org/x/crypto v0.37.0 // indirect
8080
golang.org/x/net v0.39.0 // indirect
81-
golang.org/x/sys v0.33.0 // indirect
82-
golang.org/x/term v0.32.0 // indirect
83-
golang.org/x/text v0.25.0 // indirect
81+
golang.org/x/sys v0.34.0 // indirect
82+
golang.org/x/term v0.33.0 // indirect
83+
golang.org/x/text v0.27.0 // indirect
8484
gopkg.in/fsnotify.v1 v1.4.7 // indirect
8585
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
8686
gopkg.in/warnings.v0 v0.1.2 // indirect

go.sum

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,8 @@ github.com/jesseduffield/generics v0.0.0-20250517122708-b0b4a53a6f5c h1:tC2Paiis
194194
github.com/jesseduffield/generics v0.0.0-20250517122708-b0b4a53a6f5c/go.mod h1:F2fEBk0ddf6ixrBrJjY7phfQ3hL9rXG0uSjvwYe50bE=
195195
github.com/jesseduffield/go-git/v5 v5.14.1-0.20250407170251-e1a013310ccd h1:ViKj6qth8FgcIWizn9KiACWwPemWSymx62OPN0tHT+Q=
196196
github.com/jesseduffield/go-git/v5 v5.14.1-0.20250407170251-e1a013310ccd/go.mod h1:lRhCiBr6XjQrvcQVa+UYsy/99d3wMXn/a0nSQlhnhlA=
197-
github.com/jesseduffield/gocui v0.3.1-0.20250605111917-fc5387961412 h1:8z1CpdCy9nzdj47lSLbDbCVmR5MgXsknYsuuHpzYk5M=
198-
github.com/jesseduffield/gocui v0.3.1-0.20250605111917-fc5387961412/go.mod h1:sLIyZ2J42R6idGdtemZzsiR3xY5EF0KsvYEGh3dQv3s=
197+
github.com/jesseduffield/gocui v0.3.1-0.20250711082438-4aa4fd0b4d22 h1:vhMwEsLlMtuKKo9/z3Qcggycgad8oV7+siwOZEnJDOs=
198+
github.com/jesseduffield/gocui v0.3.1-0.20250711082438-4aa4fd0b4d22/go.mod h1:sLIyZ2J42R6idGdtemZzsiR3xY5EF0KsvYEGh3dQv3s=
199199
github.com/jesseduffield/kill v0.0.0-20250101124109-e216ddbe133a h1:UDeJ3EBk04bXDLOPvuqM3on8HvyJfISw0+UMqW+0a4g=
200200
github.com/jesseduffield/kill v0.0.0-20250101124109-e216ddbe133a/go.mod h1:FSWDLKT0NQpntbDd1H3lbz51fhCVlMzy/J0S6nM727Q=
201201
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
@@ -436,8 +436,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
436436
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
437437
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
438438
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
439-
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
440-
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
439+
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
440+
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
441441
golang.org/x/sys v0.0.0-20170407050850-f3918c30c5c2/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
442442
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
443443
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -490,8 +490,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
490490
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
491491
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
492492
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
493-
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
494-
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
493+
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
494+
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
495495
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
496496
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
497497
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -501,8 +501,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
501501
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
502502
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
503503
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
504-
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
505-
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
504+
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
505+
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
506506
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
507507
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
508508
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -517,8 +517,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
517517
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
518518
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
519519
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
520-
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
521-
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
520+
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
521+
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
522522
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
523523
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
524524
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

pkg/gui/context/base_context.go

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ type BaseContext struct {
1818
onClickFn func() error
1919
onClickFocusedMainViewFn onClickFocusedMainViewFn
2020
onRenderToMainFn func()
21-
onFocusFn onFocusFn
22-
onFocusLostFn onFocusLostFn
21+
onFocusFns []onFocusFn
22+
onFocusLostFns []onFocusLostFn
2323

2424
focusable bool
2525
transient bool
@@ -135,19 +135,30 @@ func (self *BaseContext) AddMouseKeybindingsFn(fn types.MouseKeybindingsFn) {
135135
self.mouseKeybindingsFns = append(self.mouseKeybindingsFns, fn)
136136
}
137137

138-
func (self *BaseContext) ClearAllBindingsFn() {
139-
self.keybindingsFns = []types.KeybindingsFn{}
140-
self.mouseKeybindingsFns = []types.MouseKeybindingsFn{}
138+
func (self *BaseContext) ClearAllAttachedControllerFunctions() {
139+
self.keybindingsFns = nil
140+
self.mouseKeybindingsFns = nil
141+
self.onFocusFns = nil
142+
self.onFocusLostFns = nil
143+
self.onClickFn = nil
144+
self.onClickFocusedMainViewFn = nil
145+
self.onRenderToMainFn = nil
141146
}
142147

143148
func (self *BaseContext) AddOnClickFn(fn func() error) {
144149
if fn != nil {
150+
if self.onClickFn != nil {
151+
panic("only one controller is allowed to set an onClickFn")
152+
}
145153
self.onClickFn = fn
146154
}
147155
}
148156

149157
func (self *BaseContext) AddOnClickFocusedMainViewFn(fn onClickFocusedMainViewFn) {
150158
if fn != nil {
159+
if self.onClickFocusedMainViewFn != nil {
160+
panic("only one controller is allowed to set an onClickFocusedMainViewFn")
161+
}
151162
self.onClickFocusedMainViewFn = fn
152163
}
153164
}
@@ -162,34 +173,25 @@ func (self *BaseContext) GetOnClickFocusedMainView() onClickFocusedMainViewFn {
162173

163174
func (self *BaseContext) AddOnRenderToMainFn(fn func()) {
164175
if fn != nil {
176+
if self.onRenderToMainFn != nil {
177+
panic("only one controller is allowed to set an onRenderToMainFn")
178+
}
165179
self.onRenderToMainFn = fn
166180
}
167181
}
168182

169-
func (self *BaseContext) GetOnRenderToMain() func() {
170-
return self.onRenderToMainFn
171-
}
172-
173183
func (self *BaseContext) AddOnFocusFn(fn onFocusFn) {
174184
if fn != nil {
175-
self.onFocusFn = fn
185+
self.onFocusFns = append(self.onFocusFns, fn)
176186
}
177187
}
178188

179-
func (self *BaseContext) GetOnFocus() onFocusFn {
180-
return self.onFocusFn
181-
}
182-
183189
func (self *BaseContext) AddOnFocusLostFn(fn onFocusLostFn) {
184190
if fn != nil {
185-
self.onFocusLostFn = fn
191+
self.onFocusLostFns = append(self.onFocusLostFns, fn)
186192
}
187193
}
188194

189-
func (self *BaseContext) GetOnFocusLost() onFocusLostFn {
190-
return self.onFocusLostFn
191-
}
192-
193195
func (self *BaseContext) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
194196
bindings := []*gocui.ViewMouseBinding{}
195197
for i := range self.mouseKeybindingsFns {

pkg/gui/context/simple_context.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ func (self *SimpleContext) HandleFocus(opts types.OnFocusOpts) {
3737
self.GetViewTrait().SetHighlight(true)
3838
}
3939

40-
if self.onFocusFn != nil {
41-
self.onFocusFn(opts)
40+
for _, fn := range self.onFocusFns {
41+
fn(opts)
4242
}
4343

4444
if self.onRenderToMainFn != nil {
@@ -49,8 +49,8 @@ func (self *SimpleContext) HandleFocus(opts types.OnFocusOpts) {
4949
func (self *SimpleContext) HandleFocusLost(opts types.OnFocusLostOpts) {
5050
self.GetViewTrait().SetHighlight(false)
5151
self.view.SetOriginX(0)
52-
if self.onFocusLostFn != nil {
53-
self.onFocusLostFn(opts)
52+
for _, fn := range self.onFocusLostFns {
53+
fn(opts)
5454
}
5555
}
5656

pkg/gui/controllers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func (gui *Gui) Helpers() *helpers.Helpers {
2121
// the lower in the list the keybindings will appear.
2222
func (gui *Gui) resetHelpersAndControllers() {
2323
for _, context := range gui.Contexts().Flatten() {
24-
context.ClearAllBindingsFn()
24+
context.ClearAllAttachedControllerFunctions()
2525
}
2626

2727
helperCommon := gui.c

pkg/gui/controllers/list_controller.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,6 @@ func (self *ListController) HandleRangeSelectUp() error {
162162
}
163163

164164
func (self *ListController) HandleClick(opts gocui.ViewMouseBindingOpts) error {
165-
prevSelectedLineIdx := self.context.GetList().GetSelectedLineIdx()
166165
newSelectedLineIdx := self.context.ViewIndexToModelIndex(opts.Y)
167166
alreadyFocused := self.isFocused()
168167

@@ -176,7 +175,7 @@ func (self *ListController) HandleClick(opts gocui.ViewMouseBindingOpts) error {
176175

177176
self.context.GetList().SetSelection(newSelectedLineIdx)
178177

179-
if prevSelectedLineIdx == newSelectedLineIdx && alreadyFocused && self.context.GetOnClick() != nil {
178+
if opts.IsDoubleClick && alreadyFocused && self.context.GetOnClick() != nil {
180179
return self.context.GetOnClick()()
181180
}
182181
self.context.HandleFocus(types.OnFocusOpts{})

pkg/gui/types/context.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ type IBaseContext interface {
8888

8989
AddKeybindingsFn(KeybindingsFn)
9090
AddMouseKeybindingsFn(MouseKeybindingsFn)
91-
ClearAllBindingsFn()
91+
ClearAllAttachedControllerFunctions()
9292

9393
// This is a bit of a hack at the moment: we currently only set an onclick function so that
9494
// our list controller can come along and wrap it in a list-specific click handler.
@@ -245,14 +245,15 @@ type HasKeybindings interface {
245245
GetMouseKeybindings(opts KeybindingsOpts) []*gocui.ViewMouseBinding
246246
GetOnClick() func() error
247247
GetOnClickFocusedMainView() func(mainViewName string, clickedLineIdx int) error
248-
GetOnRenderToMain() func()
249-
GetOnFocus() func(OnFocusOpts)
250-
GetOnFocusLost() func(OnFocusLostOpts)
251248
}
252249

253250
type IController interface {
254251
HasKeybindings
255252
Context() Context
253+
254+
GetOnRenderToMain() func()
255+
GetOnFocus() func(OnFocusOpts)
256+
GetOnFocusLost() func(OnFocusLostOpts)
256257
}
257258

258259
type IList interface {

vendor/github.com/jesseduffield/gocui/gui.go

Lines changed: 46 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)