Skip to content

Commit

Permalink
feat(mouse): add extended mouse & shift key support
Browse files Browse the repository at this point in the history
Support SGR(1006) mouse mode
Support parsing shift key press
Support additional mouse buttons
Report which button was released
Report button motion
  • Loading branch information
aymanbagabas committed Oct 4, 2023
1 parent 99d7d4b commit 19d760d
Show file tree
Hide file tree
Showing 13 changed files with 1,042 additions and 227 deletions.
14 changes: 3 additions & 11 deletions examples/mouse/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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
}
2 changes: 1 addition & 1 deletion examples/simple/testdata/TestApp.golden
Original file line number Diff line number Diff line change
@@ -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
16 changes: 13 additions & 3 deletions key.go
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ func readInputs(ctx context.Context, msgs chan<- Msg, input io.Reader) error {
b := buf[:numBytes]

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:])
select {
Expand All @@ -570,11 +570,21 @@ func readInputs(ctx context.Context, msgs chan<- Msg, input io.Reader) error {

var unknownCSIRe = regexp.MustCompile(`^\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]`)

var mouseSGRRegex = regexp.MustCompile(`(\d+);(\d+);(\d+)([Mm])`)

func detectOneMsg(b []byte) (w int, msg Msg) {
// Detect mouse events.
// X10 mouse events have a length of 6 bytes
const mouseEventLen = 6
if len(b) >= mouseEventLen && b[0] == '\x1b' && b[1] == '[' && b[2] == 'M' {
return mouseEventLen, MouseMsg(parseX10MouseEvent(b))
if len(b) >= mouseEventLen && b[0] == '\x1b' && b[1] == '[' {
switch b[2] {
case 'M':
return mouseEventLen, MouseMsg(parseX10MouseEvent(b))
case '<':
if mouseSGRRegex.Match(b[3:]) {
return mouseEventLen, MouseMsg(parseSGRMouseEvent(b))
}
}
}

// Detect escape sequence and control characters other than NUL,
Expand Down
28 changes: 17 additions & 11 deletions key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ 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},
},
// Runes.
seqTest{
Expand Down Expand Up @@ -297,27 +297,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,
}),
},
},
Expand Down
Loading

0 comments on commit 19d760d

Please sign in to comment.