From ffda424360cada5943c4baf68fca4e5a2c60f197 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Mon, 4 Mar 2024 17:29:07 -0500 Subject: [PATCH] refactor: use x/exp/term for renderer sequences This replaces termenv.Output with raw sequences and a bytes.Buffer to handle vt screen and cursor events. --- examples/go.mod | 5 +- examples/go.sum | 14 +-- examples/simple/testdata/TestApp.golden | 2 +- exec.go | 2 +- go.mod | 9 +- go.sum | 23 ++--- nil_renderer.go | 1 + options.go | 8 +- options_test.go | 6 +- renderer.go | 3 + screen.go | 2 +- screen_test.go | 18 ++-- standard_renderer.go | 123 ++++++++++++------------ tea.go | 31 +++--- tty.go | 15 +-- tty_unix.go | 8 +- tty_windows.go | 28 +++++- tutorials/go.mod | 10 +- tutorials/go.sum | 27 ++---- 19 files changed, 171 insertions(+), 164 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index ee3928c6be..9411314ecd 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -23,6 +23,7 @@ require ( github.com/aymanbagabas/go-udiff v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/charmbracelet/x/exp/golden v0.0.0-20240222125807-0344fda748f8 // indirect + github.com/charmbracelet/x/exp/term v0.0.0-20240322170634-ebda89b611f2 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect @@ -33,13 +34,13 @@ require ( github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/rivo/uniseg v0.4.6 // indirect + github.com/rivo/uniseg v0.4.7 // indirect 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.17.0 // indirect golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.17.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/term v0.17.0 // indirect golang.org/x/text v0.13.0 // indirect ) diff --git a/examples/go.sum b/examples/go.sum index ac642c5ed3..5741328364 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -21,6 +21,8 @@ github.com/charmbracelet/x/exp/golden v0.0.0-20240222125807-0344fda748f8 h1:kyT+ github.com/charmbracelet/x/exp/golden v0.0.0-20240222125807-0344fda748f8/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/exp/teatest v0.0.0-20240229115032-4b79243a3516 h1:7IZFEUZpEgjlTSd7P1MRRhGXs7t4F6mENeMw17TxnQs= github.com/charmbracelet/x/exp/teatest v0.0.0-20240229115032-4b79243a3516/go.mod h1:SG24wGkG/mix5V2dZLXfQ6Bod43HGvk9CkTDxATwKN4= +github.com/charmbracelet/x/exp/term v0.0.0-20240322170634-ebda89b611f2 h1:53tB77HCI013H68JTiH1kX6rtSJQPB6Cu4AmXj3cCzs= +github.com/charmbracelet/x/exp/term v0.0.0-20240322170634-ebda89b611f2/go.mod h1:madZtB2OVDOG+ZnLruGITVZceYy047W+BLQ1MNQzbWg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -64,13 +66,13 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg= -github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f h1:MvTmaQdww/z0Q4wrYjDSCcZ78NoftLQyHBSLW/Cx79Y= github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.5.2 h1:ALmeCk/px5FSm1MAcFBAsVKZjDuMVj8Tm7FFIlMJnqU= github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= @@ -86,8 +88,8 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= @@ -96,5 +98,5 @@ 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= 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= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/examples/simple/testdata/TestApp.golden b/examples/simple/testdata/TestApp.golden index 52f68184c9..bd9e48987c 100644 --- a/examples/simple/testdata/TestApp.golden +++ b/examples/simple/testdata/TestApp.golden @@ -1,3 +1,3 @@ [?25l[?2004hHi. 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. +Hi. This program will exit in 9 seconds. To quit sooner press any key. [?2004l[?25h[?1002l[?1003l[?1006l \ No newline at end of file diff --git a/exec.go b/exec.go index fb6d91ed15..7a14d2a778 100644 --- a/exec.go +++ b/exec.go @@ -109,7 +109,7 @@ func (p *Program) exec(c ExecCommand, fn ExecCallback) { } c.SetStdin(p.input) - c.SetStdout(p.output.TTY()) + c.SetStdout(p.output) c.SetStderr(os.Stderr) // Execute system command. diff --git a/go.mod b/go.mod index 53eb8ae820..1172502394 100644 --- a/go.mod +++ b/go.mod @@ -3,22 +3,19 @@ module github.com/charmbracelet/bubbletea go 1.18 require ( + github.com/charmbracelet/x/exp/term v0.0.0-20240322170634-ebda89b611f2 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f github.com/mattn/go-localereader v0.0.1 github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 github.com/muesli/cancelreader v0.2.2 github.com/muesli/reflow v0.3.0 - github.com/muesli/termenv v0.15.2 golang.org/x/sync v0.6.0 - golang.org/x/sys v0.17.0 + golang.org/x/sys v0.18.0 golang.org/x/term v0.17.0 ) require ( - github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/mattn/go-isatty v0.0.18 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/rivo/uniseg v0.4.6 // indirect + github.com/rivo/uniseg v0.4.7 // indirect golang.org/x/text v0.3.8 // indirect ) diff --git a/go.sum b/go.sum index 39b77ddb1b..23759e68aa 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,8 @@ -github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= -github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/charmbracelet/x/exp/term v0.0.0-20240322170634-ebda89b611f2 h1:53tB77HCI013H68JTiH1kX6rtSJQPB6Cu4AmXj3cCzs= +github.com/charmbracelet/x/exp/term v0.0.0-20240322170634-ebda89b611f2/go.mod h1:madZtB2OVDOG+ZnLruGITVZceYy047W+BLQ1MNQzbWg= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= -github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= -github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= @@ -17,19 +14,19 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= -github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg= -github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/nil_renderer.go b/nil_renderer.go index b9edeac134..f4a83b6bc4 100644 --- a/nil_renderer.go +++ b/nil_renderer.go @@ -22,3 +22,4 @@ func (n nilRenderer) disableBracketedPaste() {} func (n nilRenderer) enableMouseSGRMode() {} func (n nilRenderer) disableMouseSGRMode() {} func (n nilRenderer) bracketedPasteActive() bool { return false } +func (n nilRenderer) setWindowTitle(_ string) {} diff --git a/options.go b/options.go index e78a048740..8aee6da6be 100644 --- a/options.go +++ b/options.go @@ -4,8 +4,6 @@ import ( "context" "io" "sync/atomic" - - "github.com/muesli/termenv" ) // ProgramOption is used to set options when initializing a Program. Program can @@ -29,11 +27,7 @@ func WithContext(ctx context.Context) ProgramOption { // won't need to use this. func WithOutput(output io.Writer) ProgramOption { return func(p *Program) { - if o, ok := output.(*termenv.Output); ok { - p.output = o - } else { - p.output = termenv.NewOutput(output, termenv.WithColorCache(true)) - } + p.output = output } } diff --git a/options_test.go b/options_test.go index dd51982cdf..ce8d41795a 100644 --- a/options_test.go +++ b/options_test.go @@ -2,6 +2,7 @@ package tea import ( "bytes" + "os" "sync/atomic" "testing" ) @@ -10,8 +11,8 @@ func TestOptions(t *testing.T) { t.Run("output", func(t *testing.T) { var b bytes.Buffer p := NewProgram(nil, WithOutput(&b)) - if p.output.TTY() != nil { - t.Errorf("expected output to custom, got %v", p.output.TTY().Fd()) + if f, ok := p.output.(*os.File); ok { + t.Errorf("expected output to custom, got %v", f.Fd()) } }) @@ -66,7 +67,6 @@ func TestOptions(t *testing.T) { var b bytes.Buffer exercise(t, WithInput(&b), customInput) }) - }) t.Run("startup options", func(t *testing.T) { diff --git a/renderer.go b/renderer.go index 65c2ae6dfa..de3936e73b 100644 --- a/renderer.go +++ b/renderer.go @@ -67,6 +67,9 @@ type renderer interface { // bracketedPasteActive reports whether bracketed paste mode is // currently enabled. bracketedPasteActive() bool + + // setWindowTitle sets the terminal window title. + setWindowTitle(string) } // repaintMsg forces a full repaint. diff --git a/screen.go b/screen.go index b34af56d7b..c17e5b6c69 100644 --- a/screen.go +++ b/screen.go @@ -198,5 +198,5 @@ func (p *Program) DisableMouseAllMotion() { // SetWindowTitle sets the terminal window title. func (p *Program) SetWindowTitle(title string) { - p.output.SetWindowTitle(title) + p.renderer.setWindowTitle(title) } diff --git a/screen_test.go b/screen_test.go index 907a8d9a84..5519baf7f7 100644 --- a/screen_test.go +++ b/screen_test.go @@ -14,47 +14,47 @@ func TestClearMsg(t *testing.T) { { name: "clear_screen", cmds: []Cmd{ClearScreen}, - expected: "\x1b[?25l\x1b[?2004h\x1b[2J\x1b[1;1H\x1b[1;1Hsuccess\r\n\x1b[0D\x1b[2K\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l", + expected: "\x1b[?25l\x1b[?2004h\x1b[2J\x1b[1;1Hsuccess\r\n\x1b[D\x1b[2K\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l", }, { name: "altscreen", cmds: []Cmd{EnterAltScreen, ExitAltScreen}, - expected: "\x1b[?25l\x1b[?2004h\x1b[?1049h\x1b[2J\x1b[1;1H\x1b[1;1H\x1b[?25l\x1b[?1049l\x1b[?25lsuccess\r\n\x1b[0D\x1b[2K\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l", + expected: "\x1b[?25l\x1b[?2004h\x1b[?1049h\x1b[2J\x1b[1;1H\x1b[?25l\x1b[?1049l\x1b[?25lsuccess\r\n\x1b[D\x1b[2K\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l", }, { name: "altscreen_autoexit", cmds: []Cmd{EnterAltScreen}, - expected: "\x1b[?25l\x1b[?2004h\x1b[?1049h\x1b[2J\x1b[1;1H\x1b[1;1H\x1b[?25lsuccess\r\n\x1b[2;0H\x1b[2K\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l\x1b[?1049l\x1b[?25h", + expected: "\x1b[?25l\x1b[?2004h\x1b[?1049h\x1b[2J\x1b[1;1H\x1b[?25lsuccess\r\n\x1b[2;0H\x1b[2K\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l\x1b[?1049l\x1b[?25h", }, { name: "mouse_cellmotion", cmds: []Cmd{EnableMouseCellMotion}, - expected: "\x1b[?25l\x1b[?2004h\x1b[?1002h\x1b[?1006hsuccess\r\n\x1b[0D\x1b[2K\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l", + expected: "\x1b[?25l\x1b[?2004h\x1b[?1002h\x1b[?1006hsuccess\r\n\x1b[D\x1b[2K\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l", }, { name: "mouse_allmotion", cmds: []Cmd{EnableMouseAllMotion}, - expected: "\x1b[?25l\x1b[?2004h\x1b[?1003h\x1b[?1006hsuccess\r\n\x1b[0D\x1b[2K\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l", + expected: "\x1b[?25l\x1b[?2004h\x1b[?1003h\x1b[?1006hsuccess\r\n\x1b[D\x1b[2K\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l", }, { name: "mouse_disable", cmds: []Cmd{EnableMouseAllMotion, DisableMouse}, - expected: "\x1b[?25l\x1b[?2004h\x1b[?1003h\x1b[?1006h\x1b[?1002l\x1b[?1003l\x1b[?1006lsuccess\r\n\x1b[0D\x1b[2K\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l", + expected: "\x1b[?25l\x1b[?2004h\x1b[?1003h\x1b[?1006h\x1b[?1002l\x1b[?1003l\x1b[?1006lsuccess\r\n\x1b[D\x1b[2K\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l", }, { name: "cursor_hide", cmds: []Cmd{HideCursor}, - expected: "\x1b[?25l\x1b[?2004h\x1b[?25lsuccess\r\n\x1b[0D\x1b[2K\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l", + expected: "\x1b[?25l\x1b[?2004h\x1b[?25lsuccess\r\n\x1b[D\x1b[2K\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l", }, { name: "cursor_hideshow", cmds: []Cmd{HideCursor, ShowCursor}, - expected: "\x1b[?25l\x1b[?2004h\x1b[?25l\x1b[?25hsuccess\r\n\x1b[0D\x1b[2K\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l", + expected: "\x1b[?25l\x1b[?2004h\x1b[?25l\x1b[?25hsuccess\r\n\x1b[D\x1b[2K\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l", }, { name: "bp_stop_start", cmds: []Cmd{DisableBracketedPaste, EnableBracketedPaste}, - expected: "\x1b[?25l\x1b[?2004h\x1b[?2004l\x1b[?2004hsuccess\r\n\x1b[0D\x1b[2K\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l", + expected: "\x1b[?25l\x1b[?2004h\x1b[?2004l\x1b[?2004hsuccess\r\n\x1b[D\x1b[2K\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l", }, } diff --git a/standard_renderer.go b/standard_renderer.go index 4e63fd6ecc..4f72e350a5 100644 --- a/standard_renderer.go +++ b/standard_renderer.go @@ -8,9 +8,8 @@ import ( "sync" "time" + "github.com/charmbracelet/x/exp/term/ansi" "github.com/muesli/ansi/compressor" - "github.com/muesli/reflow/truncate" - "github.com/muesli/termenv" ) const ( @@ -27,7 +26,7 @@ const ( // to exclude ranges of lines, allowing them to be written to directly. type standardRenderer struct { mtx *sync.Mutex - out *termenv.Output + out io.Writer buf bytes.Buffer queuedMessageLines []string @@ -58,7 +57,7 @@ type standardRenderer struct { // newRenderer creates a new renderer. Normally you'll want to initialize it // with os.Stdout as the first argument. -func newRenderer(out *termenv.Output, useANSICompressor bool, fps int) renderer { +func newRenderer(out io.Writer, useANSICompressor bool, fps int) renderer { if fps < 1 { fps = defaultFPS } else if fps > maxFPS { @@ -73,7 +72,7 @@ func newRenderer(out *termenv.Output, useANSICompressor bool, fps int) renderer queuedMessageLines: []string{}, } if r.useANSICompressor { - r.out = termenv.NewOutput(&compressor.Writer{Forward: out}) + r.out = &compressor.Writer{Forward: out} } return r } @@ -108,15 +107,20 @@ func (r *standardRenderer) stop() { r.mtx.Lock() defer r.mtx.Unlock() - r.out.ClearLine() + r.execute(ansi.EraseEntireLine) if r.useANSICompressor { - if w, ok := r.out.TTY().(io.WriteCloser); ok { + if w, ok := r.out.(io.WriteCloser); ok { _ = w.Close() } } } +// execute writes a sequence to the terminal. +func (r *standardRenderer) execute(seq string) { + _, _ = io.WriteString(r.out, seq) +} + // kill halts the renderer. The final frame will not be rendered. func (r *standardRenderer) kill() { // Stop the renderer before acquiring the mutex to avoid a deadlock. @@ -127,7 +131,7 @@ func (r *standardRenderer) kill() { r.mtx.Lock() defer r.mtx.Unlock() - r.out.ClearLine() + r.execute(ansi.EraseEntireLine) } // listen waits for ticks on the ticker, or a signal to stop the renderer. @@ -156,7 +160,6 @@ func (r *standardRenderer) flush() { // Output buffer buf := &bytes.Buffer{} - out := termenv.NewOutput(buf) newLines := strings.Split(r.buf.String(), "\n") @@ -180,17 +183,17 @@ func (r *standardRenderer) flush() { // printing messages allows for native terminal word-wrap, we // don't have control over the queued lines if flushQueuedMessages { - out.ClearLine() + buf.WriteString(ansi.EraseEntireLine) } else if (len(newLines) <= len(oldLines)) && (len(newLines) > i && len(oldLines) > i) && (newLines[i] == oldLines[i]) { // If the number of lines we want to render hasn't increased and // new line is the same as the old line we can skip rendering for // this line as a performance optimization. skipLines[i] = struct{}{} } else if _, exists := r.ignoreLines[i]; !exists { - out.ClearLine() + buf.WriteString(ansi.EraseEntireLine) } - out.CursorUp(1) + buf.WriteString(ansi.CursorUp1) } if _, exists := r.ignoreLines[0]; !exists { @@ -203,8 +206,8 @@ func (r *standardRenderer) flush() { // standard (whereas others are proprietary to, say, VT100/VT52). // If cursor previous line (ESC[ + + F) were better supported // we could use that above to eliminate this step. - out.CursorBack(r.width) - out.ClearLine() + buf.WriteString(ansi.CursorLeft(r.width)) + buf.WriteString(ansi.EraseEntireLine) } } @@ -217,8 +220,8 @@ func (r *standardRenderer) flush() { if flushQueuedMessages { // Dump the lines we've queued up for printing for _, line := range r.queuedMessageLines { - _, _ = out.WriteString(line) - _, _ = out.WriteString("\r\n") + _, _ = buf.WriteString(line) + _, _ = buf.WriteString("\r\n") } // clear the queued message lines r.queuedMessageLines = []string{} @@ -229,7 +232,7 @@ func (r *standardRenderer) flush() { if _, skip := skipLines[i]; skip { // Unless this is the last line, move the cursor down. if i < len(newLines)-1 { - out.CursorDown(1) + buf.WriteString(ansi.CursorDown1) } } else { line := newLines[i] @@ -242,13 +245,13 @@ func (r *standardRenderer) flush() { // program initialization, so after a resize this won't perform // correctly (signal SIGWINCH is not supported on Windows). if r.width > 0 { - line = truncate.String(line, uint(r.width)) + line = ansi.Truncate(line, r.width, "") } - _, _ = out.WriteString(line) + _, _ = buf.WriteString(line) if i < len(newLines)-1 { - _, _ = out.WriteString("\r\n") + _, _ = buf.WriteString("\r\n") } } } @@ -260,9 +263,9 @@ func (r *standardRenderer) flush() { // This case fixes a bug in macOS terminal. In other terminals the // other case seems to do the job regardless of whether or not we're // using the full terminal window. - out.MoveCursor(r.linesRendered, 0) + buf.WriteString(ansi.MoveCursor(r.linesRendered, 0)) } else { - out.CursorBack(r.width) + buf.WriteString(ansi.CursorLeft(r.width)) } _, _ = r.out.Write(buf.Bytes()) @@ -296,8 +299,8 @@ func (r *standardRenderer) clearScreen() { r.mtx.Lock() defer r.mtx.Unlock() - r.out.ClearScreen() - r.out.MoveCursor(1, 1) + r.execute(ansi.EraseEntireDisplay) + r.execute(ansi.MoveCursorZero) r.repaint() } @@ -318,7 +321,7 @@ func (r *standardRenderer) enterAltScreen() { } r.altScreenActive = true - r.out.AltScreen() + r.execute(ansi.EnableAltScreenBuffer) // Ensure that the terminal is cleared, even when it doesn't support // alt screen (or alt screen support is disabled, like GNU screen by @@ -326,16 +329,16 @@ func (r *standardRenderer) enterAltScreen() { // // Note: we can't use r.clearScreen() here because the mutex is already // locked. - r.out.ClearScreen() - r.out.MoveCursor(1, 1) + r.execute(ansi.EraseEntireDisplay) + r.execute(ansi.MoveCursorZero) // cmd.exe and other terminals keep separate cursor states for the AltScreen // and the main buffer. We have to explicitly reset the cursor visibility // whenever we enter AltScreen. if r.cursorHidden { - r.out.HideCursor() + r.execute(ansi.HideCursor) } else { - r.out.ShowCursor() + r.execute(ansi.ShowCursor) } r.repaint() @@ -350,15 +353,15 @@ func (r *standardRenderer) exitAltScreen() { } r.altScreenActive = false - r.out.ExitAltScreen() + r.execute(ansi.DisableAltScreenBuffer) // cmd.exe and other terminals keep separate cursor states for the AltScreen // and the main buffer. We have to explicitly reset the cursor visibility // whenever we exit AltScreen. if r.cursorHidden { - r.out.HideCursor() + r.execute(ansi.HideCursor) } else { - r.out.ShowCursor() + r.execute(ansi.ShowCursor) } r.repaint() @@ -369,7 +372,7 @@ func (r *standardRenderer) showCursor() { defer r.mtx.Unlock() r.cursorHidden = false - r.out.ShowCursor() + r.execute(ansi.ShowCursor) } func (r *standardRenderer) hideCursor() { @@ -377,56 +380,56 @@ func (r *standardRenderer) hideCursor() { defer r.mtx.Unlock() r.cursorHidden = true - r.out.HideCursor() + r.execute(ansi.HideCursor) } func (r *standardRenderer) enableMouseCellMotion() { r.mtx.Lock() defer r.mtx.Unlock() - r.out.EnableMouseCellMotion() + r.execute(ansi.EnableMouseCellMotion) } func (r *standardRenderer) disableMouseCellMotion() { r.mtx.Lock() defer r.mtx.Unlock() - r.out.DisableMouseCellMotion() + r.execute(ansi.DisableMouseCellMotion) } func (r *standardRenderer) enableMouseAllMotion() { r.mtx.Lock() defer r.mtx.Unlock() - r.out.EnableMouseAllMotion() + r.execute(ansi.EnableMouseAllMotion) } func (r *standardRenderer) disableMouseAllMotion() { r.mtx.Lock() defer r.mtx.Unlock() - r.out.DisableMouseAllMotion() + r.execute(ansi.DisableMouseAllMotion) } func (r *standardRenderer) enableMouseSGRMode() { r.mtx.Lock() defer r.mtx.Unlock() - r.out.EnableMouseExtendedMode() + r.execute(ansi.EnableMouseSgrExt) } func (r *standardRenderer) disableMouseSGRMode() { r.mtx.Lock() defer r.mtx.Unlock() - r.out.DisableMouseExtendedMode() + r.execute(ansi.DisableMouseSgrExt) } func (r *standardRenderer) enableBracketedPaste() { r.mtx.Lock() defer r.mtx.Unlock() - r.out.EnableBracketedPaste() + r.execute(ansi.EnableBracketedPaste) r.bpActive = true } @@ -434,7 +437,7 @@ func (r *standardRenderer) disableBracketedPaste() { r.mtx.Lock() defer r.mtx.Unlock() - r.out.DisableBracketedPaste() + r.execute(ansi.DisableBracketedPaste) r.bpActive = false } @@ -445,6 +448,11 @@ func (r *standardRenderer) bracketedPasteActive() bool { return r.bpActive } +// setWindowTitle sets the terminal window title. +func (r *standardRenderer) setWindowTitle(title string) { + r.execute(ansi.SetWindowTitle(title)) +} + // setIgnoredLines specifies lines not to be touched by the standard Bubble Tea // renderer. func (r *standardRenderer) setIgnoredLines(from int, to int) { @@ -465,15 +473,14 @@ func (r *standardRenderer) setIgnoredLines(from int, to int) { // Erase ignored lines if r.linesRendered > 0 { buf := &bytes.Buffer{} - out := termenv.NewOutput(buf) for i := r.linesRendered - 1; i >= 0; i-- { if _, exists := r.ignoreLines[i]; exists { - out.ClearLine() + buf.WriteString(ansi.EraseEntireLine) } - out.CursorUp(1) + buf.WriteString(ansi.CursorUp1) } - out.MoveCursor(r.linesRendered, 0) // put cursor back + buf.WriteString(ansi.MoveCursor(r.linesRendered, 0)) // put cursor back _, _ = r.out.Write(buf.Bytes()) } } @@ -508,16 +515,15 @@ func (r *standardRenderer) insertTop(lines []string, topBoundary, bottomBoundary defer r.mtx.Unlock() buf := &bytes.Buffer{} - out := termenv.NewOutput(buf) - out.ChangeScrollingRegion(topBoundary, bottomBoundary) - out.MoveCursor(topBoundary, 0) - out.InsertLines(len(lines)) - _, _ = out.WriteString(strings.Join(lines, "\r\n")) - out.ChangeScrollingRegion(0, r.height) + buf.WriteString(ansi.SetScrollingRegion(topBoundary, bottomBoundary)) + buf.WriteString(ansi.MoveCursor(topBoundary, 0)) + buf.WriteString(ansi.InsertLine(len(lines))) + _, _ = buf.WriteString(strings.Join(lines, "\r\n")) + buf.WriteString(ansi.SetScrollingRegion(0, r.height)) // Move cursor back to where the main rendering routine expects it to be - out.MoveCursor(r.linesRendered, 0) + buf.WriteString(ansi.MoveCursor(r.linesRendered, 0)) _, _ = r.out.Write(buf.Bytes()) } @@ -536,15 +542,14 @@ func (r *standardRenderer) insertBottom(lines []string, topBoundary, bottomBound defer r.mtx.Unlock() buf := &bytes.Buffer{} - out := termenv.NewOutput(buf) - out.ChangeScrollingRegion(topBoundary, bottomBoundary) - out.MoveCursor(bottomBoundary, 0) - _, _ = out.WriteString("\r\n" + strings.Join(lines, "\r\n")) - out.ChangeScrollingRegion(0, r.height) + buf.WriteString(ansi.SetScrollingRegion(topBoundary, bottomBoundary)) + buf.WriteString(ansi.MoveCursor(bottomBoundary, 0)) + _, _ = buf.WriteString("\r\n" + strings.Join(lines, "\r\n")) + buf.WriteString(ansi.SetScrollingRegion(0, r.height)) // Move cursor back to where the main rendering routine expects it to be - out.MoveCursor(r.linesRendered, 0) + buf.WriteString(ansi.MoveCursor(r.linesRendered, 0)) _, _ = r.out.Write(buf.Bytes()) } diff --git a/tea.go b/tea.go index ee3d9cd413..9e9c44c6e9 100644 --- a/tea.go +++ b/tea.go @@ -22,7 +22,6 @@ import ( "syscall" "github.com/muesli/cancelreader" - "github.com/muesli/termenv" "golang.org/x/sync/errgroup" "golang.org/x/term" ) @@ -142,17 +141,19 @@ type Program struct { finished chan struct{} // where to send output, this will usually be os.Stdout. - output *termenv.Output - restoreOutput func() error - renderer renderer + output io.Writer + // ttyOutput is null if output is not a TTY. + ttyOutput *os.File + previousOutputState *term.State + renderer renderer // where to read inputs from, this will usually be os.Stdin. input io.Reader - // tty is null if input is not a TTY. - tty *os.File - previousTtyState *term.State - cancelReader cancelreader.CancelReader - readLoopDone chan struct{} + // ttyInput is null if input is not a TTY. + ttyInput *os.File + previousTtyInputState *term.State + cancelReader cancelreader.CancelReader + readLoopDone chan struct{} // was the altscreen active before releasing the terminal? altScreenWasActive bool @@ -198,14 +199,9 @@ func NewProgram(model Model, opts ...ProgramOption) *Program { // if no output was set, set it to stdout if p.output == nil { - p.output = termenv.DefaultOutput() - - // cache detected color values - termenv.WithColorCache(true)(p.output) + p.output = os.Stdout } - p.restoreOutput, _ = termenv.EnableVirtualTerminalProcessing(p.output) - return p } @@ -249,7 +245,7 @@ func (p *Program) handleSignals() chan struct{} { func (p *Program) handleResize() chan struct{} { ch := make(chan struct{}) - if f, ok := p.output.TTY().(*os.File); ok && term.IsTerminal(int(f.Fd())) { + if p.ttyOutput != nil { // Get the initial terminal size and send it to the program. go p.checkResize() @@ -642,9 +638,6 @@ func (p *Program) shutdown(kill bool) { } _ = p.restoreTerminalState() - if p.restoreOutput != nil { - _ = p.restoreOutput() - } p.finished <- struct{}{} } diff --git a/tty.go b/tty.go index e7b602cf56..879f47ac55 100644 --- a/tty.go +++ b/tty.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "io" - "os" "time" "github.com/muesli/cancelreader" @@ -41,8 +40,13 @@ func (p *Program) restoreTerminalState() error { // restoreInput restores the tty input to its original state. func (p *Program) restoreInput() error { - if p.tty != nil && p.previousTtyState != nil { - if err := term.Restore(int(p.tty.Fd()), p.previousTtyState); err != nil { + if p.ttyInput != nil && p.previousTtyInputState != nil { + if err := term.Restore(int(p.ttyInput.Fd()), p.previousTtyInputState); err != nil { + return fmt.Errorf("error restoring console: %w", err) + } + } + if p.ttyOutput != nil && p.previousOutputState != nil { + if err := term.Restore(int(p.ttyOutput.Fd()), p.previousOutputState); err != nil { return fmt.Errorf("error restoring console: %w", err) } } @@ -89,13 +93,12 @@ func (p *Program) waitForReadLoop() { // checkResize detects the current size of the output and informs the program // via a WindowSizeMsg. func (p *Program) checkResize() { - f, ok := p.output.TTY().(*os.File) - if !ok || !term.IsTerminal(int(f.Fd())) { + if p.ttyOutput == nil { // can't query window size return } - w, h, err := term.GetSize(int(f.Fd())) + w, h, err := term.GetSize(int(p.ttyOutput.Fd())) if err != nil { select { case <-p.ctx.Done(): diff --git a/tty_unix.go b/tty_unix.go index a93444d05c..c28fc5b90e 100644 --- a/tty_unix.go +++ b/tty_unix.go @@ -13,13 +13,17 @@ import ( func (p *Program) initInput() (err error) { // Check if input is a terminal if f, ok := p.input.(*os.File); ok && term.IsTerminal(int(f.Fd())) { - p.tty = f - p.previousTtyState, err = term.MakeRaw(int(p.tty.Fd())) + p.ttyInput = f + p.previousTtyInputState, err = term.MakeRaw(int(p.ttyInput.Fd())) if err != nil { return fmt.Errorf("error entering raw mode: %w", err) } } + if f, ok := p.output.(*os.File); ok && term.IsTerminal(int(f.Fd())) { + p.ttyOutput = f + } + return nil } diff --git a/tty_windows.go b/tty_windows.go index 10b6108766..23fde274d1 100644 --- a/tty_windows.go +++ b/tty_windows.go @@ -13,22 +13,40 @@ import ( func (p *Program) initInput() (err error) { // Save stdin state and enable VT input - // We enable VT processing using Termenv, but we also need to enable VT + // We also need to enable VT // input here. if f, ok := p.input.(*os.File); ok && term.IsTerminal(int(f.Fd())) { - p.tty = f - p.previousTtyState, err = term.MakeRaw(int(p.tty.Fd())) + p.ttyInput = f + p.previousTtyInputState, err = term.MakeRaw(int(p.ttyInput.Fd())) if err != nil { return err } // Enable VT input var mode uint32 - if err := windows.GetConsoleMode(windows.Handle(p.tty.Fd()), &mode); err != nil { + if err := windows.GetConsoleMode(windows.Handle(p.ttyInput.Fd()), &mode); err != nil { return fmt.Errorf("error getting console mode: %w", err) } - if err := windows.SetConsoleMode(windows.Handle(p.tty.Fd()), mode|windows.ENABLE_VIRTUAL_TERMINAL_INPUT); err != nil { + if err := windows.SetConsoleMode(windows.Handle(p.ttyInput.Fd()), mode|windows.ENABLE_VIRTUAL_TERMINAL_INPUT); err != nil { + return fmt.Errorf("error setting console mode: %w", err) + } + } + + // Save output screen buffer state and enable VT processing. + if f, ok := p.output.(*os.File); ok && term.IsTerminal(int(f.Fd())) { + p.ttyOutput = f + p.previousOutputState, err = term.GetState(int(f.Fd())) + if err != nil { + return err + } + + var mode uint32 + if err := windows.GetConsoleMode(windows.Handle(p.ttyOutput.Fd()), &mode); err != nil { + return fmt.Errorf("error getting console mode: %w", err) + } + + if err := windows.SetConsoleMode(windows.Handle(p.ttyOutput.Fd()), mode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err != nil { return fmt.Errorf("error setting console mode: %w", err) } } diff --git a/tutorials/go.mod b/tutorials/go.mod index 80bfc8b032..79edeec48d 100644 --- a/tutorials/go.mod +++ b/tutorials/go.mod @@ -5,19 +5,15 @@ go 1.18 require github.com/charmbracelet/bubbletea v0.25.0 require ( - github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/charmbracelet/x/exp/term v0.0.0-20240322170634-ebda89b611f2 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect - github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect - github.com/muesli/reflow v0.3.0 // indirect - github.com/muesli/termenv v0.15.2 // indirect - github.com/rivo/uniseg v0.4.6 // indirect + github.com/rivo/uniseg v0.4.7 // indirect golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.17.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/term v0.17.0 // indirect golang.org/x/text v0.13.0 // indirect ) diff --git a/tutorials/go.sum b/tutorials/go.sum index 08417d04ef..c33e842eea 100644 --- a/tutorials/go.sum +++ b/tutorials/go.sum @@ -1,35 +1,28 @@ -github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= -github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/charmbracelet/x/exp/term v0.0.0-20240322170634-ebda89b611f2 h1:53tB77HCI013H68JTiH1kX6rtSJQPB6Cu4AmXj3cCzs= +github.com/charmbracelet/x/exp/term v0.0.0-20240322170634-ebda89b611f2/go.mod h1:madZtB2OVDOG+ZnLruGITVZceYy047W+BLQ1MNQzbWg= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= -github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= 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/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= -github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= -github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= -github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg= -github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 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= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=