mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-11-06 13:00:59 -04:00
Time double clicks by when the clicks were read
gocui decides whether two clicks form a double click by the time it handles them. If handling the first click keeps the main loop busy for more than 500ms, a quick second click doesn't count as a double click. The other way round, two separate clicks that wait in the event queue while the main loop is busy count as a double click. Compare the times at which tcell read the clicks instead. For events replayed by integration tests, this is the time the test created them. This way, a DoubleClick in a test no longer depends on how fast lazygit handles the first click. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
5bc855415a
commit
94a3070f02
@@ -100,9 +100,6 @@ func TestDoubleClicksAreTimedByWhenTheClicksWereRead(t *testing.T) {
|
||||
click(readAt.Add(DOUBLE_CLICK_THRESHOLD + 100*time.Millisecond))
|
||||
|
||||
assert.Equal(t,
|
||||
/* EXPECTED:
|
||||
[]bool{false, false, true},
|
||||
ACTUAL: */
|
||||
[]bool{false, true, true},
|
||||
doubleClicks)
|
||||
}
|
||||
|
||||
+10
-8
@@ -1825,7 +1825,7 @@ func (g *Gui) onKey(ev *GocuiEvent) error {
|
||||
if IsMouseKey(ev.Key) {
|
||||
mouseOpts = ViewMouseBindingOpts{
|
||||
X: newX, Y: newY, Key: ev.Key.KeyName(),
|
||||
IsDoubleClick: g.isDoubleClick(newX, newY, ev.Key.KeyName(), v),
|
||||
IsDoubleClick: g.isDoubleClick(newX, newY, ev.Key.KeyName(), v, ev.When),
|
||||
}
|
||||
|
||||
// Dispatch bindings that opt into firing while a popup panel is focused
|
||||
@@ -1835,7 +1835,7 @@ func (g *Gui) onKey(ev *GocuiEvent) error {
|
||||
return err
|
||||
}
|
||||
if matched {
|
||||
g.recordClickInfo(newX, newY, ev.Key.KeyName(), v)
|
||||
g.recordClickInfo(newX, newY, ev.Key.KeyName(), v, ev.When)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1899,7 +1899,7 @@ func (g *Gui) onKey(ev *GocuiEvent) error {
|
||||
}
|
||||
|
||||
if IsMouseKey(ev.Key) {
|
||||
g.recordClickInfo(newX, newY, ev.Key.KeyName(), v)
|
||||
g.recordClickInfo(newX, newY, ev.Key.KeyName(), v, ev.When)
|
||||
matched, err := g.execMouseKeybindings(v, ev, mouseOpts, false)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1933,8 +1933,10 @@ func (g *Gui) onKey(ev *GocuiEvent) error {
|
||||
}
|
||||
|
||||
// isDoubleClick reports whether this click follows one just like it, closely
|
||||
// enough in time to count as a double click.
|
||||
func (g *Gui) isDoubleClick(x, y int, key KeyName, v *View) bool {
|
||||
// enough in time to count as a double click. It goes by when the clicks were
|
||||
// read. The second click may wait in the event queue while the first one is
|
||||
// handled, and that wait doesn't count.
|
||||
func (g *Gui) isDoubleClick(x, y int, key KeyName, v *View, when time.Time) bool {
|
||||
return g.lastClick != nil &&
|
||||
!IsMouseScrollKey(key) &&
|
||||
key != MouseRelease &&
|
||||
@@ -1942,13 +1944,13 @@ func (g *Gui) isDoubleClick(x, y int, key KeyName, v *View) bool {
|
||||
y == g.lastClick.y &&
|
||||
key == g.lastClick.key &&
|
||||
v.Name() == g.lastClick.viewName &&
|
||||
time.Now().Before(g.lastClick.time.Add(DOUBLE_CLICK_THRESHOLD))
|
||||
when.Before(g.lastClick.time.Add(DOUBLE_CLICK_THRESHOLD))
|
||||
}
|
||||
|
||||
// recordClickInfo remembers this click as the one a following click is compared
|
||||
// against. Only the clicks that reach a binding are recorded, so a click the
|
||||
// client rejects leaves double-click detection where it was.
|
||||
func (g *Gui) recordClickInfo(x, y int, key KeyName, v *View) {
|
||||
func (g *Gui) recordClickInfo(x, y int, key KeyName, v *View, when time.Time) {
|
||||
if IsMouseScrollKey(key) {
|
||||
g.lastClick = nil
|
||||
return
|
||||
@@ -1965,7 +1967,7 @@ func (g *Gui) recordClickInfo(x, y int, key KeyName, v *View) {
|
||||
y: y,
|
||||
key: key,
|
||||
viewName: v.Name(),
|
||||
time: time.Now(),
|
||||
time: when,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
package gocui
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gdamore/tcell/v3"
|
||||
"github.com/gdamore/tcell/v3/vt"
|
||||
)
|
||||
@@ -163,6 +165,8 @@ type gocuiEventType uint8
|
||||
//
|
||||
// The 'Mod', 'Key' and 'Ch' fields are valid if 'Type' is 'eventKey'.
|
||||
// The 'MouseX' and 'MouseY' fields are valid if 'Type' is 'eventMouse'.
|
||||
// The 'When' field is valid if 'Type' is 'eventMouse'. It is the time the
|
||||
// input was read, which can be well before the main loop handles the event.
|
||||
// The 'Width' and 'Height' fields are valid if 'Type' is 'eventResize'.
|
||||
// The 'Focused' field is valid if 'Type' is 'eventFocus'.
|
||||
// The 'Start' field is valid if 'Type' is 'eventPaste'. It is true for the
|
||||
@@ -176,6 +180,7 @@ type GocuiEvent struct {
|
||||
Err error
|
||||
MouseX int
|
||||
MouseY int
|
||||
When time.Time
|
||||
Focused bool
|
||||
Start bool
|
||||
N int
|
||||
@@ -241,6 +246,7 @@ type TcellMouseEventWrapper struct {
|
||||
Y int
|
||||
ButtonMask tcell.ButtonMask
|
||||
ModMask tcell.ModMask
|
||||
When time.Time
|
||||
|
||||
task Task // see GocuiEvent.task
|
||||
}
|
||||
@@ -252,11 +258,14 @@ func NewTcellMouseEventWrapper(event *tcell.EventMouse) *TcellMouseEventWrapper
|
||||
Y: y,
|
||||
ButtonMask: event.Buttons(),
|
||||
ModMask: event.Modifiers(),
|
||||
When: event.When(),
|
||||
}
|
||||
}
|
||||
|
||||
func (wrapper TcellMouseEventWrapper) toTcellEvent() tcell.Event {
|
||||
return tcell.NewEventMouse(wrapper.X, wrapper.Y, wrapper.ButtonMask, wrapper.ModMask)
|
||||
event := tcell.NewEventMouse(wrapper.X, wrapper.Y, wrapper.ButtonMask, wrapper.ModMask)
|
||||
event.SetEventTime(wrapper.When)
|
||||
return event
|
||||
}
|
||||
|
||||
type TcellResizeEventWrapper struct {
|
||||
@@ -446,6 +455,7 @@ func gocuiEventFromTcellEvent(tev tcell.Event) GocuiEvent {
|
||||
MouseX: x,
|
||||
MouseY: y,
|
||||
Key: NewKey(mouseKey, "", mouseMod),
|
||||
When: tev.When(),
|
||||
}
|
||||
case *tcell.EventFocus:
|
||||
return GocuiEvent{
|
||||
|
||||
Reference in New Issue
Block a user