mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-19 15:27:20 +09:00
Introduce mouse release and mouse drag events
Introduce separate mouse release and mouse drag (move while pressed) events: MouseLeftRelease, MouseLeftDrag, MouseRightRelease etc, to allow binding them to actions independently from mouse press events (MouseLeft, MouseRight etc). This change: - Makes it possible to handle mouse release and drag for arbitrary mouse events and actions (including Lua actions), not just for MouseLeft as in the current code. - Fixes issue #2599 with PastePrimary and MouseMultiCursor actions: selection is pasted not only when pressing MouseMiddle but also when moving mouse with MouseMiddle pressed; similarly, a new multicursor is added not only when pressing Ctrl-MouseLeft but also when moving mouse with Ctrl-MouseLeft pressed. My initial approach was not to introduce new events for mouse release and mouse drag but to pass "mouse released" info to action functions in addition to *tcell.EventMouse to let the action functions do the necessary checks (similarly to what MousePress is already doing). But then I realized it was a bad idea, since we still want to be able also to bind mouse events to regular key actions (such as PastePrimary) which don't care about mouse event info.
This commit is contained in:
@@ -51,53 +51,47 @@ func (h *BufPane) ScrollAdjust() {
|
||||
func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
|
||||
b := h.Buf
|
||||
mx, my := e.Position()
|
||||
// ignore click on the status line
|
||||
if my >= h.BufView().Y+h.BufView().Height {
|
||||
return false
|
||||
}
|
||||
mouseLoc := h.LocFromVisual(buffer.Loc{mx, my})
|
||||
h.Cursor.Loc = mouseLoc
|
||||
if h.mouseReleased {
|
||||
if b.NumCursors() > 1 {
|
||||
b.ClearCursors()
|
||||
h.Relocate()
|
||||
h.Cursor = h.Buf.GetActiveCursor()
|
||||
h.Cursor.Loc = mouseLoc
|
||||
}
|
||||
if time.Since(h.lastClickTime)/time.Millisecond < config.DoubleClickThreshold && (mouseLoc.X == h.lastLoc.X && mouseLoc.Y == h.lastLoc.Y) {
|
||||
if h.doubleClick {
|
||||
// Triple click
|
||||
h.lastClickTime = time.Now()
|
||||
|
||||
h.tripleClick = true
|
||||
h.doubleClick = false
|
||||
|
||||
h.Cursor.SelectLine()
|
||||
h.Cursor.CopySelection(clipboard.PrimaryReg)
|
||||
} else {
|
||||
// Double click
|
||||
h.lastClickTime = time.Now()
|
||||
|
||||
h.doubleClick = true
|
||||
h.tripleClick = false
|
||||
|
||||
h.Cursor.SelectWord()
|
||||
h.Cursor.CopySelection(clipboard.PrimaryReg)
|
||||
}
|
||||
} else {
|
||||
h.doubleClick = false
|
||||
h.tripleClick = false
|
||||
if b.NumCursors() > 1 {
|
||||
b.ClearCursors()
|
||||
h.Relocate()
|
||||
h.Cursor = h.Buf.GetActiveCursor()
|
||||
h.Cursor.Loc = mouseLoc
|
||||
}
|
||||
if time.Since(h.lastClickTime)/time.Millisecond < config.DoubleClickThreshold && (mouseLoc.X == h.lastLoc.X && mouseLoc.Y == h.lastLoc.Y) {
|
||||
if h.doubleClick {
|
||||
// Triple click
|
||||
h.lastClickTime = time.Now()
|
||||
|
||||
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
||||
h.Cursor.CurSelection[0] = h.Cursor.Loc
|
||||
h.Cursor.CurSelection[1] = h.Cursor.Loc
|
||||
}
|
||||
h.mouseReleased = false
|
||||
} else if !h.mouseReleased {
|
||||
if h.tripleClick {
|
||||
h.Cursor.AddLineToSelection()
|
||||
} else if h.doubleClick {
|
||||
h.Cursor.AddWordToSelection()
|
||||
h.tripleClick = true
|
||||
h.doubleClick = false
|
||||
|
||||
h.Cursor.SelectLine()
|
||||
h.Cursor.CopySelection(clipboard.PrimaryReg)
|
||||
} else {
|
||||
h.Cursor.SetSelectionEnd(h.Cursor.Loc)
|
||||
// Double click
|
||||
h.lastClickTime = time.Now()
|
||||
|
||||
h.doubleClick = true
|
||||
h.tripleClick = false
|
||||
|
||||
h.Cursor.SelectWord()
|
||||
h.Cursor.CopySelection(clipboard.PrimaryReg)
|
||||
}
|
||||
} else {
|
||||
h.doubleClick = false
|
||||
h.tripleClick = false
|
||||
h.lastClickTime = time.Now()
|
||||
|
||||
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
||||
h.Cursor.CurSelection[0] = h.Cursor.Loc
|
||||
h.Cursor.CurSelection[1] = h.Cursor.Loc
|
||||
}
|
||||
|
||||
h.Cursor.StoreVisualX()
|
||||
@@ -106,6 +100,45 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *BufPane) MouseDrag(e *tcell.EventMouse) bool {
|
||||
mx, my := e.Position()
|
||||
// ignore drag on the status line
|
||||
if my >= h.BufView().Y+h.BufView().Height {
|
||||
return false
|
||||
}
|
||||
h.Cursor.Loc = h.LocFromVisual(buffer.Loc{mx, my})
|
||||
|
||||
if h.tripleClick {
|
||||
h.Cursor.AddLineToSelection()
|
||||
} else if h.doubleClick {
|
||||
h.Cursor.AddWordToSelection()
|
||||
} else {
|
||||
h.Cursor.SetSelectionEnd(h.Cursor.Loc)
|
||||
}
|
||||
|
||||
h.Cursor.StoreVisualX()
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *BufPane) MouseRelease(e *tcell.EventMouse) bool {
|
||||
// We could finish the selection based on the release location as in the
|
||||
// commented out code below, to allow text selections even in a terminal
|
||||
// that doesn't support mouse motion events. But when the mouse click is
|
||||
// within the scroll margin, that would cause a scroll and selection
|
||||
// even for a simple mouse click, which is not good.
|
||||
// if !h.doubleClick && !h.tripleClick {
|
||||
// mx, my := e.Position()
|
||||
// h.Cursor.Loc = h.LocFromVisual(buffer.Loc{mx, my})
|
||||
// h.Cursor.SetSelectionEnd(h.Cursor.Loc)
|
||||
// }
|
||||
|
||||
if h.Cursor.HasSelection() {
|
||||
h.Cursor.CopySelection(clipboard.PrimaryReg)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ScrollUpAction scrolls the view up
|
||||
func (h *BufPane) ScrollUpAction() bool {
|
||||
h.ScrollUp(util.IntOpt(h.Buf.Settings["scrollspeed"]))
|
||||
@@ -1794,6 +1827,10 @@ func (h *BufPane) SpawnMultiCursorSelect() bool {
|
||||
func (h *BufPane) MouseMultiCursor(e *tcell.EventMouse) bool {
|
||||
b := h.Buf
|
||||
mx, my := e.Position()
|
||||
// ignore click on the status line
|
||||
if my >= h.BufView().Y+h.BufView().Height {
|
||||
return false
|
||||
}
|
||||
mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
|
||||
c := buffer.NewCursor(b, mouseLoc)
|
||||
b.AddCursor(c)
|
||||
|
||||
@@ -201,11 +201,20 @@ modSearch:
|
||||
}, true
|
||||
}
|
||||
|
||||
var mstate MouseState = MousePress
|
||||
if strings.HasSuffix(k, "Drag") {
|
||||
k = k[:len(k)-4]
|
||||
mstate = MouseDrag
|
||||
} else if strings.HasSuffix(k, "Release") {
|
||||
k = k[:len(k)-7]
|
||||
mstate = MouseRelease
|
||||
}
|
||||
// See if we can find the key in bindingMouse
|
||||
if code, ok := mouseEvents[k]; ok {
|
||||
return MouseEvent{
|
||||
btn: code,
|
||||
mod: modifiers,
|
||||
btn: code,
|
||||
mod: modifiers,
|
||||
state: mstate,
|
||||
}, true
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/clipboard"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
@@ -200,11 +199,15 @@ type BufPane struct {
|
||||
// Cursor is the currently active buffer cursor
|
||||
Cursor *buffer.Cursor
|
||||
|
||||
// Since tcell doesn't differentiate between a mouse release event
|
||||
// and a mouse move event with no keys pressed, we need to keep
|
||||
// track of whether or not the mouse was pressed (or not released) last event to determine
|
||||
// mouse release events
|
||||
mouseReleased bool
|
||||
// Since tcell doesn't differentiate between a mouse press event
|
||||
// and a mouse move event with button pressed (nor between a mouse
|
||||
// release event and a mouse move event with no buttons pressed),
|
||||
// we need to keep track of whether or not the mouse was previously
|
||||
// pressed, to determine mouse release and mouse drag events.
|
||||
// Moreover, since in case of a release event tcell doesn't tell us
|
||||
// which button was released, we need to keep track of which
|
||||
// (possibly multiple) buttons were pressed previously.
|
||||
mousePressed map[MouseEvent]bool
|
||||
|
||||
// We need to keep track of insert key press toggle
|
||||
isOverwriteMode bool
|
||||
@@ -250,7 +253,7 @@ func newBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane {
|
||||
h.tab = tab
|
||||
|
||||
h.Cursor = h.Buf.GetActiveCursor()
|
||||
h.mouseReleased = true
|
||||
h.mousePressed = make(map[MouseEvent]bool)
|
||||
|
||||
return h
|
||||
}
|
||||
@@ -332,7 +335,9 @@ func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
|
||||
h.initialRelocate()
|
||||
// Set mouseReleased to true because we assume the mouse is not being
|
||||
// pressed when the editor is opened
|
||||
h.mouseReleased = true
|
||||
for me := range h.mousePressed {
|
||||
delete(h.mousePressed, me)
|
||||
}
|
||||
// Set isOverwriteMode to false, because we assume we are in the default
|
||||
// mode when editor is opened
|
||||
h.isOverwriteMode = false
|
||||
@@ -432,50 +437,31 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
|
||||
h.DoRuneInsert(e.Rune())
|
||||
}
|
||||
case *tcell.EventMouse:
|
||||
cancel := false
|
||||
switch e.Buttons() {
|
||||
case tcell.Button1:
|
||||
_, my := e.Position()
|
||||
if h.Buf.Type.Kind != buffer.BTInfo.Kind && h.Buf.Settings["statusline"].(bool) && my >= h.GetView().Y+h.GetView().Height-1 {
|
||||
cancel = true
|
||||
}
|
||||
case tcell.ButtonNone:
|
||||
// Mouse event with no click
|
||||
if !h.mouseReleased {
|
||||
// Mouse was just released
|
||||
|
||||
// mx, my := e.Position()
|
||||
// mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
|
||||
|
||||
// we could finish the selection based on the release location as described
|
||||
// below but when the mouse click is within the scroll margin this will
|
||||
// cause a scroll and selection even for a simple mouse click which is
|
||||
// not good
|
||||
// for terminals that don't support mouse motion events, selection via
|
||||
// the mouse won't work but this is ok
|
||||
|
||||
// Relocating here isn't really necessary because the cursor will
|
||||
// be in the right place from the last mouse event
|
||||
// However, if we are running in a terminal that doesn't support mouse motion
|
||||
// events, this still allows the user to make selections, except only after they
|
||||
// release the mouse
|
||||
|
||||
// if !h.doubleClick && !h.tripleClick {
|
||||
// h.Cursor.SetSelectionEnd(h.Cursor.Loc)
|
||||
// }
|
||||
if h.Cursor.HasSelection() {
|
||||
h.Cursor.CopySelection(clipboard.PrimaryReg)
|
||||
}
|
||||
h.mouseReleased = true
|
||||
}
|
||||
}
|
||||
|
||||
if !cancel {
|
||||
if e.Buttons() != tcell.ButtonNone {
|
||||
me := MouseEvent{
|
||||
btn: e.Buttons(),
|
||||
mod: metaToAlt(e.Modifiers()),
|
||||
btn: e.Buttons(),
|
||||
mod: metaToAlt(e.Modifiers()),
|
||||
state: MousePress,
|
||||
}
|
||||
if len(h.mousePressed) > 0 {
|
||||
me.state = MouseDrag
|
||||
}
|
||||
|
||||
if e.Buttons() & ^(tcell.WheelUp|tcell.WheelDown|tcell.WheelLeft|tcell.WheelRight) != tcell.ButtonNone {
|
||||
h.mousePressed[me] = true
|
||||
}
|
||||
|
||||
h.DoMouseEvent(me, e)
|
||||
} else {
|
||||
// Mouse event with no click - mouse was just released.
|
||||
// If there were multiple mouse buttons pressed, we don't know which one
|
||||
// was actually released, so we assume they all were released.
|
||||
for me := range h.mousePressed {
|
||||
delete(h.mousePressed, me)
|
||||
|
||||
me.state = MouseRelease
|
||||
h.DoMouseEvent(me, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
h.Buf.MergeCursors()
|
||||
@@ -788,6 +774,8 @@ var BufKeyActions = map[string]BufKeyAction{
|
||||
// BufMouseActions contains the list of all possible mouse actions the bufhandler could execute
|
||||
var BufMouseActions = map[string]BufMouseAction{
|
||||
"MousePress": (*BufPane).MousePress,
|
||||
"MouseDrag": (*BufPane).MouseDrag,
|
||||
"MouseRelease": (*BufPane).MouseRelease,
|
||||
"MouseMultiCursor": (*BufPane).MouseMultiCursor,
|
||||
}
|
||||
|
||||
|
||||
@@ -90,11 +90,13 @@ var bufdefaults = map[string]string{
|
||||
"Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors,UnhighlightSearch",
|
||||
|
||||
// Mouse bindings
|
||||
"MouseWheelUp": "ScrollUp",
|
||||
"MouseWheelDown": "ScrollDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseMiddle": "PastePrimary",
|
||||
"Ctrl-MouseLeft": "MouseMultiCursor",
|
||||
"MouseWheelUp": "ScrollUp",
|
||||
"MouseWheelDown": "ScrollDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseLeftDrag": "MouseDrag",
|
||||
"MouseLeftRelease": "MouseRelease",
|
||||
"MouseMiddle": "PastePrimary",
|
||||
"Ctrl-MouseLeft": "MouseMultiCursor",
|
||||
|
||||
"Alt-n": "SpawnMultiCursor",
|
||||
"AltShiftUp": "SpawnMultiCursorUp",
|
||||
@@ -173,8 +175,10 @@ var infodefaults = map[string]string{
|
||||
"Esc": "AbortCommand",
|
||||
|
||||
// Mouse bindings
|
||||
"MouseWheelUp": "HistoryUp",
|
||||
"MouseWheelDown": "HistoryDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseMiddle": "PastePrimary",
|
||||
"MouseWheelUp": "HistoryUp",
|
||||
"MouseWheelDown": "HistoryDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseLeftDrag": "MouseDrag",
|
||||
"MouseLeftRelease": "MouseRelease",
|
||||
"MouseMiddle": "PastePrimary",
|
||||
}
|
||||
|
||||
@@ -92,11 +92,13 @@ var bufdefaults = map[string]string{
|
||||
"Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors,UnhighlightSearch",
|
||||
|
||||
// Mouse bindings
|
||||
"MouseWheelUp": "ScrollUp",
|
||||
"MouseWheelDown": "ScrollDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseMiddle": "PastePrimary",
|
||||
"Ctrl-MouseLeft": "MouseMultiCursor",
|
||||
"MouseWheelUp": "ScrollUp",
|
||||
"MouseWheelDown": "ScrollDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseLeftDrag": "MouseDrag",
|
||||
"MouseLeftRelease": "MouseRelease",
|
||||
"MouseMiddle": "PastePrimary",
|
||||
"Ctrl-MouseLeft": "MouseMultiCursor",
|
||||
|
||||
"Alt-n": "SpawnMultiCursor",
|
||||
"Alt-m": "SpawnMultiCursorSelect",
|
||||
@@ -175,8 +177,10 @@ var infodefaults = map[string]string{
|
||||
"Esc": "AbortCommand",
|
||||
|
||||
// Mouse bindings
|
||||
"MouseWheelUp": "HistoryUp",
|
||||
"MouseWheelDown": "HistoryDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseMiddle": "PastePrimary",
|
||||
"MouseWheelUp": "HistoryUp",
|
||||
"MouseWheelDown": "HistoryDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseLeftDrag": "MouseDrag",
|
||||
"MouseLeftRelease": "MouseRelease",
|
||||
"MouseMiddle": "PastePrimary",
|
||||
}
|
||||
|
||||
@@ -100,11 +100,20 @@ func (k KeySequenceEvent) Name() string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
type MouseState int
|
||||
|
||||
const (
|
||||
MousePress = iota
|
||||
MouseDrag
|
||||
MouseRelease
|
||||
)
|
||||
|
||||
// MouseEvent is a mouse event with a mouse button and
|
||||
// any possible key modifiers
|
||||
type MouseEvent struct {
|
||||
btn tcell.ButtonMask
|
||||
mod tcell.ModMask
|
||||
btn tcell.ButtonMask
|
||||
mod tcell.ModMask
|
||||
state MouseState
|
||||
}
|
||||
|
||||
func (m MouseEvent) Name() string {
|
||||
@@ -122,9 +131,17 @@ func (m MouseEvent) Name() string {
|
||||
mod = "Ctrl-"
|
||||
}
|
||||
|
||||
state := ""
|
||||
switch m.state {
|
||||
case MouseDrag:
|
||||
state = "Drag"
|
||||
case MouseRelease:
|
||||
state = "Release"
|
||||
}
|
||||
|
||||
for k, v := range mouseEvents {
|
||||
if v == m.btn {
|
||||
return fmt.Sprintf("%s%s", mod, k)
|
||||
return fmt.Sprintf("%s%s%s", mod, k, state)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
|
||||
@@ -407,8 +407,14 @@ mouse actions)
|
||||
|
||||
```
|
||||
MouseLeft
|
||||
MouseLeftDrag
|
||||
MouseLeftRelease
|
||||
MouseMiddle
|
||||
MouseMiddleDrag
|
||||
MouseMiddleRelease
|
||||
MouseRight
|
||||
MouseRightDrag
|
||||
MouseRightRelease
|
||||
MouseWheelUp
|
||||
MouseWheelDown
|
||||
MouseWheelLeft
|
||||
@@ -520,11 +526,13 @@ conventions for text editing defaults.
|
||||
"Esc": "Escape",
|
||||
|
||||
// Mouse bindings
|
||||
"MouseWheelUp": "ScrollUp",
|
||||
"MouseWheelDown": "ScrollDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseMiddle": "PastePrimary",
|
||||
"Ctrl-MouseLeft": "MouseMultiCursor",
|
||||
"MouseWheelUp": "ScrollUp",
|
||||
"MouseWheelDown": "ScrollDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseLeftDrag": "MouseDrag",
|
||||
"MouseLeftRelease": "MouseRelease",
|
||||
"MouseMiddle": "PastePrimary",
|
||||
"Ctrl-MouseLeft": "MouseMultiCursor",
|
||||
|
||||
"Alt-n": "SpawnMultiCursor",
|
||||
"AltShiftUp": "SpawnMultiCursorUp",
|
||||
@@ -629,10 +637,12 @@ are given below:
|
||||
"Esc": "AbortCommand",
|
||||
|
||||
// Mouse bindings
|
||||
"MouseWheelUp": "HistoryUp",
|
||||
"MouseWheelDown": "HistoryDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseMiddle": "PastePrimary"
|
||||
"MouseWheelUp": "HistoryUp",
|
||||
"MouseWheelDown": "HistoryDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseLeftDrag": "MouseDrag",
|
||||
"MouseLeftRelease": "MouseRelease",
|
||||
"MouseMiddle": "PastePrimary"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user