Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extended Coordinates mouse reporting & additional buttons support #594

Merged
merged 4 commits into from
Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
23 changes: 18 additions & 5 deletions key.go
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,7 @@
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 {
Expand All @@ -591,13 +591,26 @@
}
}

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

Check failure on line 610 in key.go

View workflow job for this annotation

GitHub Actions / lint-soft

mnd: Magic number: 3, in <operation> detected (gomnd)

Check failure on line 610 in key.go

View workflow job for this annotation

GitHub Actions / lint-soft

mnd: Magic number: 3, in <operation> detected (gomnd)
return mouseEventSGRLen, MouseMsg(parseSGRMouseEvent(b))
}
}
}

// Detect escape sequence and control characters other than NUL,
Expand All @@ -606,7 +619,7 @@
var foundSeq bool
foundSeq, w, msg = detectSequence(b)
if foundSeq {
return

Check failure on line 622 in key.go

View workflow job for this annotation

GitHub Actions / lint-soft

naked return in func `detectOneMsg` with 87 lines of code (nakedret)
}

// No non-NUL control character or escape sequence.
Expand Down
33 changes: 22 additions & 11 deletions key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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,
}),
},
},
Expand Down
Loading
Loading