Skip to content

Commit

Permalink
Merge branch 'master' into ext-mouse
Browse files Browse the repository at this point in the history
  • Loading branch information
aymanbagabas committed Nov 15, 2023
2 parents abbf90f + a6f07b8 commit 86bd6fe
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 26 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
run: |
(cd ./examples && go mod tidy)
(cd ./tutorials && go mod tidy)
- uses: stefanzweifel/git-auto-commit-action@v4
- uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "chore: go mod tidy tutorials and examples"
branch: master
Expand Down
12 changes: 6 additions & 6 deletions examples/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/charmbracelet/bubbletea v0.24.2
github.com/charmbracelet/glamour v0.6.0
github.com/charmbracelet/harmonica v0.2.0
github.com/charmbracelet/lipgloss v0.7.1
github.com/charmbracelet/lipgloss v0.9.1
github.com/charmbracelet/x/exp/teatest v0.0.0-20230508155401-2bd6fa14c46a
github.com/fogleman/ease v0.0.0-20170301025033-8da417bf1776
github.com/lucasb-eyer/go-colorful v1.2.0
Expand All @@ -27,7 +27,7 @@ require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/microcosm-cc/bluemonday v1.0.21 // indirect
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
Expand All @@ -36,11 +36,11 @@ require (
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f // indirect
github.com/yuin/goldmark v1.5.2 // indirect
github.com/yuin/goldmark-emoji v1.0.1 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/term v0.6.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/term v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
)

replace github.com/charmbracelet/bubbletea => ../
28 changes: 21 additions & 7 deletions examples/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM2
github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc=
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E=
github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c=
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
github.com/charmbracelet/x/exp/teatest v0.0.0-20230508155401-2bd6fa14c46a h1:GpNt24LKE8sH5G0SZUpu4Tg15sP5XSt1mnfIqE7fW34=
github.com/charmbracelet/x/exp/teatest v0.0.0-20230508155401-2bd6fa14c46a/go.mod h1:dpMo6PfGlhavn+ofggWhfFQmK9sZB2Ewljiz9bZtKVI=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
Expand Down Expand Up @@ -47,8 +48,9 @@ github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+Ei
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/microcosm-cc/bluemonday v1.0.21 h1:dNH3e4PSyE4vNX+KlRGHT5KrSvjeUkoNPwEORjffHJg=
github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
Expand Down Expand Up @@ -81,13 +83,17 @@ github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18W
github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
Expand All @@ -103,22 +109,30 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
Expand Down
43 changes: 38 additions & 5 deletions key.go
Original file line number Diff line number Diff line change
Expand Up @@ -543,18 +543,40 @@ var spaceRunes = []rune{' '}
func readInputs(ctx context.Context, msgs chan<- Msg, input io.Reader) error {
var buf [256]byte

var leftOverFromPrevIteration []byte
loop:
for {
// Read and block.
numBytes, err := input.Read(buf[:])
if err != nil {
return fmt.Errorf("error reading input: %w", err)
}
b := buf[:numBytes]
if leftOverFromPrevIteration != nil {
b = append(leftOverFromPrevIteration, b...)
}

// If we had a short read (numBytes < len(buf)), we're sure that
// the end of this read is an event boundary, so there is no doubt
// if we are encountering the end of the buffer while parsing a message.
// However, if we've succeeded in filling up the buffer, there may
// be more data in the OS buffer ready to be read in, to complete
// the last message in the input. In that case, we will retry with
// the left over data in the next iteration.
canHaveMoreData := numBytes == len(buf)

var i, w int
for i, w = 0, 07; i < len(b); i += w {
var msg Msg
w, msg = detectOneMsg(b[i:])
w, msg = detectOneMsg(b[i:], canHaveMoreData)
if w == 0 {
// Expecting more bytes beyond the current buffer. Try waiting
// for more input.
leftOverFromPrevIteration = make([]byte, 0, len(b[i:])+len(buf))
leftOverFromPrevIteration = append(leftOverFromPrevIteration, b[i:]...)
continue loop
}

select {
case msgs <- msg:
case <-ctx.Done():
Expand All @@ -565,14 +587,16 @@ func readInputs(ctx context.Context, msgs chan<- Msg, input io.Reader) error {
return err
}
}
leftOverFromPrevIteration = nil
}
}

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

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

func detectOneMsg(b []byte) (w int, msg Msg) {
func detectOneMsg(b []byte, canHaveMoreData bool) (w int, msg Msg) {
// Detect mouse events.
// X10 mouse events have a length of 6 bytes
const mouseEventX10Len = 6
Expand Down Expand Up @@ -630,6 +654,15 @@ func detectOneMsg(b []byte) (w int, msg Msg) {
break
}
}
if i >= len(b) && canHaveMoreData {
// We have encountered the end of the input buffer. Alas, we can't
// be sure whether the data in the remainder of the buffer is
// complete (maybe there was a short read). Instead of sending anything
// dumb to the message channel, do a short read. The outer loop will
// handle this case by extending the buffer as necessary.
return 0, nil
}

// If we found at least one rune, we report the bunch of them as
// a single KeyRunes or KeySpace event.
if len(runes) > 0 {
Expand Down
21 changes: 20 additions & 1 deletion key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ func TestDetectOneMsg(t *testing.T) {

for _, tc := range td {
t.Run(fmt.Sprintf("%q", string(tc.seq)), func(t *testing.T) {
width, msg := detectOneMsg(tc.seq)
width, msg := detectOneMsg(tc.seq, false /* canHaveMoreData */)
if width != len(tc.seq) {
t.Errorf("parser did not consume the entire input: got %d, expected %d", width, len(tc.seq))
}
Expand All @@ -216,6 +216,25 @@ func TestDetectOneMsg(t *testing.T) {
}
}

func TestReadLongInput(t *testing.T) {
input := strings.Repeat("a", 1000)
msgs := testReadInputs(t, bytes.NewReader([]byte(input)))
if len(msgs) != 1 {
t.Errorf("expected 1 messages, got %d", len(msgs))
}
km := msgs[0]
k := Key(km.(KeyMsg))
if k.Type != KeyRunes {
t.Errorf("expected key runes, got %d", k.Type)
}
if len(k.Runes) != 1000 || !reflect.DeepEqual(k.Runes, []rune(input)) {
t.Errorf("unexpected runes: %+v", k)
}
if k.Alt {
t.Errorf("unexpected alt")
}
}

func TestReadInput(t *testing.T) {
type test struct {
keyname string
Expand Down
3 changes: 2 additions & 1 deletion options.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package tea
import (
"context"
"io"
"sync/atomic"

"github.com/muesli/termenv"
)
Expand Down Expand Up @@ -76,7 +77,7 @@ func WithoutCatchPanics() ProgramOption {
// This is mainly useful for testing.
func WithoutSignals() ProgramOption {
return func(p *Program) {
p.ignoreSignals = true
atomic.StoreUint32(&p.ignoreSignals, 1)
}
}

Expand Down
3 changes: 2 additions & 1 deletion options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package tea

import (
"bytes"
"sync/atomic"
"testing"
)

Expand Down Expand Up @@ -37,7 +38,7 @@ func TestOptions(t *testing.T) {

t.Run("without signals", func(t *testing.T) {
p := NewProgram(nil, WithoutSignals())
if !p.ignoreSignals {
if atomic.LoadUint32(&p.ignoreSignals) == 0 {
t.Errorf("ignore signals should have been set")
}
})
Expand Down
9 changes: 5 additions & 4 deletions tea.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"os/signal"
"runtime/debug"
"sync"
"sync/atomic"
"syscall"

"github.com/containerd/console"
Expand Down Expand Up @@ -153,7 +154,7 @@ type Program struct {

// was the altscreen active before releasing the terminal?
altScreenWasActive bool
ignoreSignals bool
ignoreSignals uint32

// Stores the original reference to stdin for cases where input is not a
// TTY on windows and we've automatically opened CONIN$ to receive input.
Expand Down Expand Up @@ -238,7 +239,7 @@ func (p *Program) handleSignals() chan struct{} {
return

case <-sig:
if !p.ignoreSignals {
if atomic.LoadUint32(&p.ignoreSignals) == 0 {
p.msgs <- QuitMsg{}
return
}
Expand Down Expand Up @@ -643,7 +644,7 @@ func (p *Program) shutdown(kill bool) {
// ReleaseTerminal restores the original terminal state and cancels the input
// reader. You can return control to the Program with RestoreTerminal.
func (p *Program) ReleaseTerminal() error {
p.ignoreSignals = true
atomic.StoreUint32(&p.ignoreSignals, 1)
p.cancelReader.Cancel()
p.waitForReadLoop()

Expand All @@ -659,7 +660,7 @@ func (p *Program) ReleaseTerminal() error {
// terminal to the former state when the program was running, and repaints.
// Use it to reinitialize a Program after running ReleaseTerminal.
func (p *Program) RestoreTerminal() error {
p.ignoreSignals = false
atomic.StoreUint32(&p.ignoreSignals, 0)

if err := p.initTerminal(); err != nil {
return err
Expand Down

0 comments on commit 86bd6fe

Please sign in to comment.