feat: extended Coordinates mouse reporting & additional buttons support (#594)

* feat(mouse): add extended mouse & shift key support

Support SGR(1006) mouse mode
Support parsing shift key press
Support additional mouse buttons
Report which button was released
Report button motion

* fix: key.go sgr len missing calculation (#841)

* chore(test): add sgr mouse msg detect test

---------

Co-authored-by: robinsamuel <96998379+robin-samuel@users.noreply.github.com>
This commit is contained in:
Ayman Bagabas 2023-12-04 08:50:59 -08:00 committed by GitHub
parent 2bcb0af2e2
commit a154847611
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1058 additions and 235 deletions

View File

@ -4,21 +4,19 @@ package main
// coordinates and events.
import (
"fmt"
"log"
tea "github.com/charmbracelet/bubbletea"
)
func main() {
p := tea.NewProgram(model{}, tea.WithAltScreen(), tea.WithMouseAllMotion())
p := tea.NewProgram(model{}, tea.WithMouseAllMotion())
if _, err := p.Run(); err != nil {
log.Fatal(err)
}
}
type model struct {
init bool
mouseEvent tea.MouseEvent
}
@ -34,20 +32,14 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
case tea.MouseMsg:
m.init = true
m.mouseEvent = tea.MouseEvent(msg)
return m, tea.Printf("(X: %d, Y: %d) %s", msg.X, msg.Y, tea.MouseEvent(msg))
}
return m, nil
}
func (m model) View() string {
s := "Do mouse stuff. When you're done press q to quit.\n\n"
if m.init {
e := m.mouseEvent
s += fmt.Sprintf("(X: %d, Y: %d) %s", e.X, e.Y, e)
}
s := "Do mouse stuff. When you're done press q to quit.\n"
return s
}

View File

@ -1,3 +1,3 @@
[?25lHi. This program will exit in 10 seconds. To quit sooner press any key
Hi. This program will exit in 9 seconds. To quit sooner press any key.
[?25h[?1002l[?1003l
[?25h[?1002l[?1003l[?1006l

23
key.go
View File

@ -566,7 +566,7 @@ loop:
canHaveMoreData := numBytes == len(buf)
var i, w int
for i, w = 0, 0; i < len(b); i += w {
for i, w = 0, 07; i < len(b); i += w {
var msg Msg
w, msg = detectOneMsg(b[i:], canHaveMoreData)
if w == 0 {
@ -591,13 +591,26 @@ loop:
}
}
var unknownCSIRe = regexp.MustCompile(`^\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]`)
var (
unknownCSIRe = regexp.MustCompile(`^\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]`)
mouseSGRRegex = regexp.MustCompile(`(\d+);(\d+);(\d+)([Mm])`)
)
func detectOneMsg(b []byte, canHaveMoreData bool) (w int, msg Msg) {
// Detect mouse events.
const mouseEventLen = 6
if len(b) >= mouseEventLen && b[0] == '\x1b' && b[1] == '[' && b[2] == 'M' {
return mouseEventLen, MouseMsg(parseX10MouseEvent(b))
// X10 mouse events have a length of 6 bytes
const mouseEventX10Len = 6
if len(b) >= mouseEventX10Len && b[0] == '\x1b' && b[1] == '[' {
switch b[2] {
case 'M':
return mouseEventX10Len, MouseMsg(parseX10MouseEvent(b))
case '<':
if matchIndices := mouseSGRRegex.FindSubmatchIndex(b[3:]); matchIndices != nil {
// SGR mouse events length is the length of the match plus the length of the escape sequence
mouseEventSGRLen := matchIndices[1] + 3
return mouseEventSGRLen, MouseMsg(parseSGRMouseEvent(b))
}
}
}
// Detect escape sequence and control characters other than NUL,

View File

@ -137,7 +137,12 @@ func TestDetectOneMsg(t *testing.T) {
// Mouse event.
seqTest{
[]byte{'\x1b', '[', 'M', byte(32) + 0b0100_0000, byte(65), byte(49)},
MouseMsg{X: 32, Y: 16, Type: MouseWheelUp},
MouseMsg{X: 32, Y: 16, Type: MouseWheelUp, Button: MouseButtonWheelUp, Action: MouseActionPress},
},
// SGR Mouse event.
seqTest{
[]byte("\x1b[<0;33;17M"),
MouseMsg{X: 32, Y: 16, Type: MouseLeft, Button: MouseButtonLeft, Action: MouseActionPress},
},
// Runes.
seqTest{
@ -316,27 +321,33 @@ func TestReadInput(t *testing.T) {
[]byte{'\x1b', '[', 'M', byte(32) + 0b0100_0000, byte(65), byte(49)},
[]Msg{
MouseMsg{
X: 32,
Y: 16,
Type: MouseWheelUp,
X: 32,
Y: 16,
Type: MouseWheelUp,
Button: MouseButtonWheelUp,
Action: MouseActionPress,
},
},
},
{"left release",
{"left motion release",
[]byte{
'\x1b', '[', 'M', byte(32) + 0b0010_0000, byte(32 + 33), byte(16 + 33),
'\x1b', '[', 'M', byte(32) + 0b0000_0011, byte(64 + 33), byte(32 + 33),
},
[]Msg{
MouseMsg(MouseEvent{
X: 32,
Y: 16,
Type: MouseLeft,
X: 32,
Y: 16,
Type: MouseLeft,
Button: MouseButtonLeft,
Action: MouseActionMotion,
}),
MouseMsg(MouseEvent{
X: 64,
Y: 32,
Type: MouseRelease,
X: 64,
Y: 32,
Type: MouseRelease,
Button: MouseButtonNone,
Action: MouseActionRelease,
}),
},
},

290
mouse.go
View File

@ -1,5 +1,7 @@
package tea
import "strconv"
// MouseMsg contains information about a mouse event and are sent to a programs
// update function when mouse activity occurs. Note that the mouse must first
// be enabled in order for the mouse events to be received.
@ -13,11 +15,22 @@ func (m MouseMsg) String() string {
// MouseEvent represents a mouse event, which could be a click, a scroll wheel
// movement, a cursor movement, or a combination.
type MouseEvent struct {
X int
Y int
X int
Y int
Shift bool
Alt bool
Ctrl bool
Action MouseAction
Button MouseButton
// Deprecated: Use MouseAction & MouseButton instead.
Type MouseEventType
Alt bool
Ctrl bool
}
// IsWheel returns true if the mouse event is a wheel event.
func (m MouseEvent) IsWheel() bool {
return m.Button == MouseButtonWheelUp || m.Button == MouseButtonWheelDown ||
m.Button == MouseButtonWheelLeft || m.Button == MouseButtonWheelRight
}
// String returns a string representation of a mouse event.
@ -28,38 +41,170 @@ func (m MouseEvent) String() (s string) {
if m.Alt {
s += "alt+"
}
s += mouseEventTypes[m.Type]
if m.Shift {
s += "shift+"
}
if m.Button == MouseButtonNone {
if m.Action == MouseActionMotion || m.Action == MouseActionRelease {
s += mouseActions[m.Action]
} else {
s += "unknown"
}
} else if m.IsWheel() {
s += mouseButtons[m.Button]
} else {
btn := mouseButtons[m.Button]
if btn != "" {
s += btn
}
act := mouseActions[m.Action]
if act != "" {
s += " " + act
}
}
return s
}
// MouseAction represents the action that occurred during a mouse event.
type MouseAction int
// Mouse event actions.
const (
MouseActionPress MouseAction = iota
MouseActionRelease
MouseActionMotion
)
var mouseActions = map[MouseAction]string{
MouseActionPress: "press",
MouseActionRelease: "release",
MouseActionMotion: "motion",
}
// MouseButton represents the button that was pressed during a mouse event.
type MouseButton int
// Mouse event buttons
//
// This is based on X11 mouse button codes.
//
// 1 = left button
// 2 = middle button (pressing the scroll wheel)
// 3 = right button
// 4 = turn scroll wheel up
// 5 = turn scroll wheel down
// 6 = push scroll wheel left
// 7 = push scroll wheel right
// 8 = 4th button (aka browser backward button)
// 9 = 5th button (aka browser forward button)
// 10
// 11
//
// Other buttons are not supported.
const (
MouseButtonNone MouseButton = iota
MouseButtonLeft
MouseButtonMiddle
MouseButtonRight
MouseButtonWheelUp
MouseButtonWheelDown
MouseButtonWheelLeft
MouseButtonWheelRight
MouseButtonBackward
MouseButtonForward
MouseButton10
MouseButton11
)
var mouseButtons = map[MouseButton]string{
MouseButtonNone: "none",
MouseButtonLeft: "left",
MouseButtonMiddle: "middle",
MouseButtonRight: "right",
MouseButtonWheelUp: "wheel up",
MouseButtonWheelDown: "wheel down",
MouseButtonWheelLeft: "wheel left",
MouseButtonWheelRight: "wheel right",
MouseButtonBackward: "backward",
MouseButtonForward: "forward",
MouseButton10: "button 10",
MouseButton11: "button 11",
}
// MouseEventType indicates the type of mouse event occurring.
//
// Deprecated: Use MouseAction & MouseButton instead.
type MouseEventType int
// Mouse event types.
//
// Deprecated: Use MouseAction & MouseButton instead.
const (
MouseUnknown MouseEventType = iota
MouseLeft
MouseRight
MouseMiddle
MouseRelease
MouseRelease // mouse button release (X10 only)
MouseWheelUp
MouseWheelDown
MouseWheelLeft
MouseWheelRight
MouseBackward
MouseForward
MouseMotion
)
var mouseEventTypes = map[MouseEventType]string{
MouseUnknown: "unknown",
MouseLeft: "left",
MouseRight: "right",
MouseMiddle: "middle",
MouseRelease: "release",
MouseWheelUp: "wheel up",
MouseWheelDown: "wheel down",
MouseMotion: "motion",
// Parse SGR-encoded mouse events; SGR extended mouse events. SGR mouse events
// look like:
//
// ESC [ < Cb ; Cx ; Cy (M or m)
//
// where:
//
// Cb is the encoded button code
// Cx is the x-coordinate of the mouse
// Cy is the y-coordinate of the mouse
// M is for button press, m is for button release
//
// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Extended-coordinates
func parseSGRMouseEvent(buf []byte) MouseEvent {
str := string(buf[3:])
matches := mouseSGRRegex.FindStringSubmatch(str)
if len(matches) != 5 {
// Unreachable, we already checked the regex in `detectOneMsg`.
panic("invalid mouse event")
}
b, _ := strconv.Atoi(matches[1])
px := matches[2]
py := matches[3]
release := matches[4] == "m"
m := parseMouseButton(b, true)
// Wheel buttons don't have release events
// Motion can be reported as a release event in some terminals (Windows Terminal)
if m.Action != MouseActionMotion && !m.IsWheel() && release {
m.Action = MouseActionRelease
m.Type = MouseRelease
}
x, _ := strconv.Atoi(px)
y, _ := strconv.Atoi(py)
// (1,1) is the upper left. We subtract 1 to normalize it to (0,0).
m.X = x - 1
m.Y = y - 1
return m
}
const x10MouseByteOffset = 32
// Parse X10-encoded mouse events; the simplest kind. The last release of X10
// was December 1986, by the way.
// was December 1986, by the way. The original X10 mouse protocol limits the Cx
// and Cy coordinates to 223 (=255-032).
//
// X10 mouse events look like:
//
@ -68,9 +213,22 @@ var mouseEventTypes = map[MouseEventType]string{
// See: http://www.xfree86.org/current/ctlseqs.html#Mouse%20Tracking
func parseX10MouseEvent(buf []byte) MouseEvent {
v := buf[3:6]
m := parseMouseButton(int(v[0]), false)
// (1,1) is the upper left. We subtract 1 to normalize it to (0,0).
m.X = int(v[1]) - x10MouseByteOffset - 1
m.Y = int(v[2]) - x10MouseByteOffset - 1
return m
}
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Extended-coordinates
func parseMouseButton(b int, isSGR bool) MouseEvent {
var m MouseEvent
const byteOffset = 32
e := v[0] - byteOffset
e := b
if !isSGR {
e -= x10MouseByteOffset
}
const (
bitShift = 0b0000_0100
@ -78,55 +236,73 @@ func parseX10MouseEvent(buf []byte) MouseEvent {
bitCtrl = 0b0001_0000
bitMotion = 0b0010_0000
bitWheel = 0b0100_0000
bitAdd = 0b1000_0000 // additional buttons 8-11
bitsMask = 0b0000_0011
bitsLeft = 0b0000_0000
bitsMiddle = 0b0000_0001
bitsRight = 0b0000_0010
bitsRelease = 0b0000_0011
bitsWheelUp = 0b0000_0000
bitsWheelDown = 0b0000_0001
)
if e&bitWheel != 0 {
// Check the low two bits.
switch e & bitsMask {
case bitsWheelUp:
m.Type = MouseWheelUp
case bitsWheelDown:
m.Type = MouseWheelDown
}
if e&bitAdd != 0 {
m.Button = MouseButtonBackward + MouseButton(e&bitsMask)
} else if e&bitWheel != 0 {
m.Button = MouseButtonWheelUp + MouseButton(e&bitsMask)
} else {
// Check the low two bits.
// We do not separate clicking and dragging.
switch e & bitsMask {
case bitsLeft:
m.Type = MouseLeft
case bitsMiddle:
m.Type = MouseMiddle
case bitsRight:
m.Type = MouseRight
case bitsRelease:
if e&bitMotion != 0 {
m.Type = MouseMotion
} else {
m.Type = MouseRelease
}
m.Button = MouseButtonLeft + MouseButton(e&bitsMask)
// X10 reports a button release as 0b0000_0011 (3)
if e&bitsMask == bitsMask {
m.Action = MouseActionRelease
m.Button = MouseButtonNone
}
}
if e&bitAlt != 0 {
m.Alt = true
}
if e&bitCtrl != 0 {
m.Ctrl = true
// Motion bit doesn't get reported for wheel events.
if e&bitMotion != 0 && !m.IsWheel() {
m.Action = MouseActionMotion
}
// (1,1) is the upper left. We subtract 1 to normalize it to (0,0).
m.X = int(v[1]) - byteOffset - 1
m.Y = int(v[2]) - byteOffset - 1
// Modifiers
m.Alt = e&bitAlt != 0
m.Ctrl = e&bitCtrl != 0
m.Shift = e&bitShift != 0
// backward compatibility
switch {
case m.Button == MouseButtonLeft && m.Action == MouseActionPress:
m.Type = MouseLeft
case m.Button == MouseButtonMiddle && m.Action == MouseActionPress:
m.Type = MouseMiddle
case m.Button == MouseButtonRight && m.Action == MouseActionPress:
m.Type = MouseRight
case m.Button == MouseButtonNone && m.Action == MouseActionRelease:
m.Type = MouseRelease
case m.Button == MouseButtonWheelUp && m.Action == MouseActionPress:
m.Type = MouseWheelUp
case m.Button == MouseButtonWheelDown && m.Action == MouseActionPress:
m.Type = MouseWheelDown
case m.Button == MouseButtonWheelLeft && m.Action == MouseActionPress:
m.Type = MouseWheelLeft
case m.Button == MouseButtonWheelRight && m.Action == MouseActionPress:
m.Type = MouseWheelRight
case m.Button == MouseButtonBackward && m.Action == MouseActionPress:
m.Type = MouseBackward
case m.Button == MouseButtonForward && m.Action == MouseActionPress:
m.Type = MouseForward
case m.Action == MouseActionMotion:
m.Type = MouseMotion
switch m.Button {
case MouseButtonLeft:
m.Type = MouseLeft
case MouseButtonMiddle:
m.Type = MouseMiddle
case MouseButtonRight:
m.Type = MouseRight
case MouseButtonBackward:
m.Type = MouseBackward
case MouseButtonForward:
m.Type = MouseForward
}
default:
m.Type = MouseUnknown
}
return m
}

File diff suppressed because it is too large Load Diff

View File

@ -17,3 +17,5 @@ func (n nilRenderer) enableMouseCellMotion() {}
func (n nilRenderer) disableMouseCellMotion() {}
func (n nilRenderer) enableMouseAllMotion() {}
func (n nilRenderer) disableMouseAllMotion() {}
func (n nilRenderer) enableMouseSGRMode() {}
func (n nilRenderer) disableMouseSGRMode() {}

View File

@ -108,6 +108,9 @@ func WithAltScreen() ProgramOption {
// movement events are also captured if a mouse button is pressed (i.e., drag
// events). Cell motion mode is better supported than all motion mode.
//
// This will try to enable the mouse in extended mode (SGR), if that is not
// supported by the terminal it will fall back to normal mode (X10).
//
// To enable mouse cell motion once the program has already started running use
// the EnableMouseCellMotion command. To disable the mouse when the program is
// running use the DisableMouse command.
@ -127,6 +130,9 @@ func WithMouseCellMotion() ProgramOption {
// wheel, and motion events, which are delivered regardless of whether a mouse
// button is pressed, effectively enabling support for hover interactions.
//
// This will try to enable the mouse in extended mode (SGR), if that is not
// supported by the terminal it will fall back to normal mode (X10).
//
// Many modern terminals support this, but not all. If in doubt, use
// EnableMouseCellMotion instead.
//

View File

@ -40,16 +40,22 @@ type renderer interface {
// events if a mouse button is pressed (i.e., drag events).
enableMouseCellMotion()
// DisableMouseCellMotion disables Mouse Cell Motion tracking.
// disableMouseCellMotion disables Mouse Cell Motion tracking.
disableMouseCellMotion()
// EnableMouseAllMotion enables mouse click, release, wheel and motion
// enableMouseAllMotion enables mouse click, release, wheel and motion
// events, regardless of whether a mouse button is pressed. Many modern
// terminals support this, but not all.
enableMouseAllMotion()
// DisableMouseAllMotion disables All Motion mouse tracking.
// disableMouseAllMotion disables All Motion mouse tracking.
disableMouseAllMotion()
// enableMouseSGRMode enables mouse extended mode (SGR).
enableMouseSGRMode()
// disableMouseSGRMode disables mouse extended mode (SGR).
disableMouseSGRMode()
}
// repaintMsg forces a full repaint.

View File

@ -14,42 +14,42 @@ func TestClearMsg(t *testing.T) {
{
name: "clear_screen",
cmds: []Cmd{ClearScreen},
expected: "\x1b[?25l\x1b[2J\x1b[1;1H\x1b[1;1Hsuccess\r\n\x1b[0D\x1b[2K\x1b[?25h\x1b[?1002l\x1b[?1003l",
expected: "\x1b[?25l\x1b[2J\x1b[1;1H\x1b[1;1Hsuccess\r\n\x1b[0D\x1b[2K\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l",
},
{
name: "altscreen",
cmds: []Cmd{EnterAltScreen, ExitAltScreen},
expected: "\x1b[?25l\x1b[?1049h\x1b[2J\x1b[1;1H\x1b[1;1H\x1b[?25l\x1b[?1049l\x1b[?25lsuccess\r\n\x1b[0D\x1b[2K\x1b[?25h\x1b[?1002l\x1b[?1003l",
expected: "\x1b[?25l\x1b[?1049h\x1b[2J\x1b[1;1H\x1b[1;1H\x1b[?25l\x1b[?1049l\x1b[?25lsuccess\r\n\x1b[0D\x1b[2K\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l",
},
{
name: "altscreen_autoexit",
cmds: []Cmd{EnterAltScreen},
expected: "\x1b[?25l\x1b[?1049h\x1b[2J\x1b[1;1H\x1b[1;1H\x1b[?25lsuccess\r\n\x1b[2;0H\x1b[2K\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1049l\x1b[?25h",
expected: "\x1b[?25l\x1b[?1049h\x1b[2J\x1b[1;1H\x1b[1;1H\x1b[?25lsuccess\r\n\x1b[2;0H\x1b[2K\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l\x1b[?1049l\x1b[?25h",
},
{
name: "mouse_cellmotion",
cmds: []Cmd{EnableMouseCellMotion},
expected: "\x1b[?25l\x1b[?1002hsuccess\r\n\x1b[0D\x1b[2K\x1b[?25h\x1b[?1002l\x1b[?1003l",
expected: "\x1b[?25l\x1b[?1002h\x1b[?1006hsuccess\r\n\x1b[0D\x1b[2K\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l",
},
{
name: "mouse_allmotion",
cmds: []Cmd{EnableMouseAllMotion},
expected: "\x1b[?25l\x1b[?1003hsuccess\r\n\x1b[0D\x1b[2K\x1b[?25h\x1b[?1002l\x1b[?1003l",
expected: "\x1b[?25l\x1b[?1003h\x1b[?1006hsuccess\r\n\x1b[0D\x1b[2K\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l",
},
{
name: "mouse_disable",
cmds: []Cmd{EnableMouseAllMotion, DisableMouse},
expected: "\x1b[?25l\x1b[?1003h\x1b[?1002l\x1b[?1003lsuccess\r\n\x1b[0D\x1b[2K\x1b[?25h\x1b[?1002l\x1b[?1003l",
expected: "\x1b[?25l\x1b[?1003h\x1b[?1006h\x1b[?1002l\x1b[?1003l\x1b[?1006lsuccess\r\n\x1b[0D\x1b[2K\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l",
},
{
name: "cursor_hide",
cmds: []Cmd{HideCursor},
expected: "\x1b[?25l\x1b[?25lsuccess\r\n\x1b[0D\x1b[2K\x1b[?25h\x1b[?1002l\x1b[?1003l",
expected: "\x1b[?25l\x1b[?25lsuccess\r\n\x1b[0D\x1b[2K\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l",
},
{
name: "cursor_hideshow",
cmds: []Cmd{HideCursor, ShowCursor},
expected: "\x1b[?25l\x1b[?25l\x1b[?25hsuccess\r\n\x1b[0D\x1b[2K\x1b[?25h\x1b[?1002l\x1b[?1003l",
expected: "\x1b[?25l\x1b[?25l\x1b[?25hsuccess\r\n\x1b[0D\x1b[2K\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l",
},
}

View File

@ -396,6 +396,20 @@ func (r *standardRenderer) disableMouseAllMotion() {
r.out.DisableMouseAllMotion()
}
func (r *standardRenderer) enableMouseSGRMode() {
r.mtx.Lock()
defer r.mtx.Unlock()
r.out.EnableMouseExtendedMode()
}
func (r *standardRenderer) disableMouseSGRMode() {
r.mtx.Lock()
defer r.mtx.Unlock()
r.out.DisableMouseExtendedMode()
}
// setIgnoredLines specifies lines not to be touched by the standard Bubble Tea
// renderer.
func (r *standardRenderer) setIgnoredLines(from int, to int) {

25
tea.go
View File

@ -301,6 +301,12 @@ func (p *Program) handleCommands(cmds chan Cmd) chan struct{} {
return ch
}
func (p *Program) disableMouse() {
p.renderer.disableMouseCellMotion()
p.renderer.disableMouseAllMotion()
p.renderer.disableMouseSGRMode()
}
// eventLoop is the central message loop. It receives and handles the default
// Bubble Tea messages, update the model and triggers redraws.
func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
@ -335,15 +341,18 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
case exitAltScreenMsg:
p.renderer.exitAltScreen()
case enableMouseCellMotionMsg:
p.renderer.enableMouseCellMotion()
case enableMouseAllMotionMsg:
p.renderer.enableMouseAllMotion()
case enableMouseCellMotionMsg, enableMouseAllMotionMsg:
switch msg.(type) {
case enableMouseCellMotionMsg:
p.renderer.enableMouseCellMotion()
case enableMouseAllMotionMsg:
p.renderer.enableMouseAllMotion()
}
// mouse mode (1006) is a no-op if the terminal doesn't support it.
p.renderer.enableMouseSGRMode()
case disableMouseMsg:
p.renderer.disableMouseCellMotion()
p.renderer.disableMouseAllMotion()
p.disableMouse()
case showCursorMsg:
p.renderer.showCursor()
@ -489,8 +498,10 @@ func (p *Program) Run() (Model, error) {
}
if p.startupOptions&withMouseCellMotion != 0 {
p.renderer.enableMouseCellMotion()
p.renderer.enableMouseSGRMode()
} else if p.startupOptions&withMouseAllMotion != 0 {
p.renderer.enableMouseAllMotion()
p.renderer.enableMouseSGRMode()
}
// Initialize the program.

3
tty.go
View File

@ -35,8 +35,7 @@ func (p *Program) initTerminal() error {
func (p *Program) restoreTerminalState() error {
if p.renderer != nil {
p.renderer.showCursor()
p.renderer.disableMouseCellMotion()
p.renderer.disableMouseAllMotion()
p.disableMouse()
if p.renderer.altScreen() {
p.renderer.exitAltScreen()