From 44a2bbeb7c1cd7552a251f3a841688a3b3c8e0d7 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Fri, 24 Jul 2026 08:13:03 +0200 Subject: [PATCH] Deliver mouse release after a drag Releasing a mouse button was delivered as a plain mouse-move (hover) event: the release processing resets dragState to NOT_DRAGGING, after which the event fell into the NOT_DRAGGING branch. Views therefore had no way of telling that a drag gesture ended, which the upcoming drag-based features (range selection, commit reordering) need. Deliver the release as a real mouse event with the MouseRelease key and normalize its modifiers to ModNone, so release bindings also match modified drags. Make recordClickInfo ignore it: a release is the end of a click, not a click of its own, and must not break double-click detection. --- pkg/gocui/gui.go | 6 ++++++ pkg/gocui/tcell_driver.go | 9 ++++----- pkg/gocui/tcell_driver_test.go | 17 +++++++++++++---- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/pkg/gocui/gui.go b/pkg/gocui/gui.go index 57818960c..e5526a109 100644 --- a/pkg/gocui/gui.go +++ b/pkg/gocui/gui.go @@ -1773,6 +1773,12 @@ func (g *Gui) recordClickInfo(x, y int, key KeyName, v *View) bool { g.lastClick = nil return false } + // A release ends a gesture but is not a click of its own; it must leave + // the click info of the press that started it alone, or no double click + // could ever be detected. + if key == MouseRelease { + return false + } clickInfo := &clickInfo{ x: x, diff --git a/pkg/gocui/tcell_driver.go b/pkg/gocui/tcell_driver.go index e70fc3440..745725993 100644 --- a/pkg/gocui/tcell_driver.go +++ b/pkg/gocui/tcell_driver.go @@ -202,7 +202,6 @@ const ( var ( lastMouseKey tcell.ButtonMask = tcell.ButtonNone - lastMouseMod tcell.ModMask = tcell.ModNone dragState = NOT_DRAGGING lastX = 0 lastY = 0 @@ -367,10 +366,10 @@ func gocuiEventFromTcellEvent(tev tcell.Event) GocuiEvent { // process button events (not wheel events) button &= tcell.ButtonMask(0xff) newButtonPress := false + buttonReleased := false if button != tcell.ButtonNone && lastMouseKey == tcell.ButtonNone { newButtonPress = true lastMouseKey = button - lastMouseMod = tev.Modifiers() switch button { case tcell.ButtonPrimary: mouseKey = MouseLeft @@ -388,6 +387,7 @@ func gocuiEventFromTcellEvent(tev tcell.Event) GocuiEvent { switch tev.Buttons() { case tcell.ButtonNone: if lastMouseKey != tcell.ButtonNone { + buttonReleased = true switch lastMouseKey { case tcell.ButtonPrimary: dragState = NOT_DRAGGING @@ -395,14 +395,13 @@ func gocuiEventFromTcellEvent(tev tcell.Event) GocuiEvent { case tcell.ButtonMiddle: default: } - mouseMod = Modifier(lastMouseMod) - lastMouseMod = tcell.ModNone + mouseMod = ModNone lastMouseKey = tcell.ButtonNone } default: } - if !wheeling { + if !wheeling && !buttonReleased { switch dragState { case NOT_DRAGGING: return GocuiEvent{ diff --git a/pkg/gocui/tcell_driver_test.go b/pkg/gocui/tcell_driver_test.go index a5bed98d0..9038e73ce 100644 --- a/pkg/gocui/tcell_driver_test.go +++ b/pkg/gocui/tcell_driver_test.go @@ -32,16 +32,25 @@ func TestMouseReleaseAfterDragIsMouseEvent(t *testing.T) { gocuiEventFromTcellEvent(tcell.NewEventMouse(1, 3, tcell.ButtonPrimary, tcell.ModNone)) releaseEvent := gocuiEventFromTcellEvent(tcell.NewEventMouse(1, 3, tcell.ButtonNone, tcell.ModNone)) - /* EXPECTED: assert.Equal(t, eventMouse, releaseEvent.Type) assert.Equal(t, MouseRelease, releaseEvent.Key.KeyName()) - ACTUAL: */ - assert.Equal(t, eventMouseMove, releaseEvent.Type) +} + +func TestMouseReleaseDoesNotKeepPressModifiers(t *testing.T) { + t.Cleanup(resetMouseState) + resetMouseState() + + gocuiEventFromTcellEvent(tcell.NewEventMouse(1, 2, tcell.ButtonPrimary, tcell.ModAlt)) + gocuiEventFromTcellEvent(tcell.NewEventMouse(1, 3, tcell.ButtonPrimary, tcell.ModAlt)) + releaseEvent := gocuiEventFromTcellEvent(tcell.NewEventMouse(1, 3, tcell.ButtonNone, tcell.ModAlt)) + + assert.Equal(t, eventMouse, releaseEvent.Type) + assert.Equal(t, MouseRelease, releaseEvent.Key.KeyName()) + assert.Equal(t, ModNone, releaseEvent.Key.Mod()) } func resetMouseState() { lastMouseKey = tcell.ButtonNone - lastMouseMod = tcell.ModNone dragState = NOT_DRAGGING lastX = 0 lastY = 0