From 7ba62d4987972d769c1efaeab65c8156607a0f1c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Nov 2022 08:03:14 +0000 Subject: [PATCH 01/89] chore(deps): bump github.com/charmbracelet/glamour in /examples Bumps [github.com/charmbracelet/glamour](https://github.com/charmbracelet/glamour) from 0.5.0 to 0.6.0. - [Release notes](https://github.com/charmbracelet/glamour/releases) - [Commits](https://github.com/charmbracelet/glamour/compare/v0.5.0...v0.6.0) --- updated-dependencies: - dependency-name: github.com/charmbracelet/glamour dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- examples/go.mod | 2 +- examples/go.sum | 24 +++++++++--------------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index 042b22ff68..7465247896 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -5,7 +5,7 @@ go 1.16 require ( github.com/charmbracelet/bubbles v0.13.1-0.20220815142520-649f78e1fd8b github.com/charmbracelet/bubbletea v0.22.0 - github.com/charmbracelet/glamour v0.5.0 + github.com/charmbracelet/glamour v0.6.0 github.com/charmbracelet/lipgloss v0.6.0 github.com/fogleman/ease v0.0.0-20170301025033-8da417bf1776 github.com/lucasb-eyer/go-colorful v1.2.0 diff --git a/examples/go.sum b/examples/go.sum index 65731b4f36..09ac05ee37 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -8,8 +8,8 @@ github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuP github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/charmbracelet/bubbles v0.13.1-0.20220815142520-649f78e1fd8b h1:ppafRlD8VXOEqnUPkMvO7et9rpSsKVjUX+K3QG7tAB0= github.com/charmbracelet/bubbles v0.13.1-0.20220815142520-649f78e1fd8b/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc= -github.com/charmbracelet/glamour v0.5.0 h1:wu15ykPdB7X6chxugG/NNfDUbyyrCLV9XBalj5wdu3g= -github.com/charmbracelet/glamour v0.5.0/go.mod h1:9ZRtG19AUIzcTm7FGLGbq3D5WKQ5UyZBbQsMQN0XIqc= +github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc= +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.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs= @@ -30,7 +30,6 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 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.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -42,8 +41,8 @@ github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRC 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/microcosm-cc/bluemonday v1.0.17 h1:Z1a//hgsQ4yjC+8zEkV8IWySkXnsxmdSY642CTFQb5Y= -github.com/microcosm-cc/bluemonday v1.0.17/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM= +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= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= @@ -51,7 +50,6 @@ github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIf github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= 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.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw= github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0= @@ -69,25 +67,21 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.4 h1:zNWRjYUW32G9KirMXYHQHVNFkXvMI7LpgNW2AgYAoIs= -github.com/yuin/goldmark v1.4.4/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg= +github.com/yuin/goldmark v1.5.2 h1:ALmeCk/px5FSm1MAcFBAsVKZjDuMVj8Tm7FFIlMJnqU= +github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os= github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ= -golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= -golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/net v0.0.0-20221002022538-bcab6841153b h1:6e93nYa3hNqAvLr0pD4PN1fFS+gKzp2zAXqrnTCstqU= +golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/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 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From e079831c31928b17449eedc306fbdca653bea219 Mon Sep 17 00:00:00 2001 From: Austin Vazquez Date: Sun, 30 Oct 2022 20:42:57 -0700 Subject: [PATCH 02/89] Remove references to io/ioutil package Package io/ioutil has been marked deprecated starting in Go 1.16. Signed-off-by: Austin Vazquez --- examples/pager/main.go | 3 +-- examples/tui-daemon-combo/main.go | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/pager/main.go b/examples/pager/main.go index a98a9ec37b..f602ad7c55 100644 --- a/examples/pager/main.go +++ b/examples/pager/main.go @@ -5,7 +5,6 @@ package main import ( "fmt" - "io/ioutil" "os" "strings" @@ -129,7 +128,7 @@ func max(a, b int) int { func main() { // Load some text for our viewport - content, err := ioutil.ReadFile("artichoke.md") + content, err := os.ReadFile("artichoke.md") if err != nil { fmt.Println("could not load file:", err) os.Exit(1) diff --git a/examples/tui-daemon-combo/main.go b/examples/tui-daemon-combo/main.go index cbe779918f..df26034222 100644 --- a/examples/tui-daemon-combo/main.go +++ b/examples/tui-daemon-combo/main.go @@ -3,7 +3,7 @@ package main import ( "flag" "fmt" - "io/ioutil" + "io" "log" "math/rand" "os" @@ -41,7 +41,7 @@ func main() { opts = []tea.ProgramOption{tea.WithoutRenderer()} } else { // If we're in TUI mode, discard log output - log.SetOutput(ioutil.Discard) + log.SetOutput(io.Discard) } p := tea.NewProgram(newModel(), opts...) From 9c20a804d7761caaf312a99eb28921ffa7c92ccf Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 13 Nov 2022 23:45:57 +0100 Subject: [PATCH 03/89] fix: don't close stdin This allows programs to re-use stdin after the tea.Program has finished. Fixes #595. --- tea.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tea.go b/tea.go index 4de805ec0a..03734bc575 100644 --- a/tea.go +++ b/tea.go @@ -345,6 +345,7 @@ func (p *Program) Run() (Model, error) { if err != nil { return p.initialModel, err } + defer f.Close() //nolint:errcheck p.input = f case !p.startupOptions.has(withCustomInput): @@ -364,11 +365,8 @@ func (p *Program) Run() (Model, error) { if err != nil { return p.initialModel, err } - p.input = f - } - - if f, ok := p.input.(io.ReadCloser); ok { defer f.Close() //nolint:errcheck + p.input = f } // Handle signals. From 94d6f5079e07301046e38f6c59cb286dd6856155 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Mon, 14 Nov 2022 13:31:59 -0800 Subject: [PATCH 04/89] docs: update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 06c1c103d3..fe1b5cf5a7 100644 --- a/README.md +++ b/README.md @@ -392,4 +392,4 @@ Part of [Charm](https://charm.sh). The Charm logo -Charm热爱开源 • Charm loves open source +Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة From 79c76c680b1a6bae9cd9bc918c1d8eb336ee4ceb Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 17 Nov 2022 09:22:52 +0100 Subject: [PATCH 05/89] chore: disable dependabot timer --- .github/dependabot.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c622d76ceb..9065bc6290 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,7 +4,6 @@ updates: directory: "/" schedule: interval: "daily" - time: "08:00" labels: - "dependencies" commit-message: @@ -14,7 +13,6 @@ updates: directory: "/examples" schedule: interval: "daily" - time: "08:00" labels: - "dependencies" commit-message: @@ -24,7 +22,6 @@ updates: directory: "/tutorials" schedule: interval: "daily" - time: "08:00" labels: - "dependencies" commit-message: @@ -34,7 +31,6 @@ updates: directory: "/" schedule: interval: "daily" - time: "08:00" labels: - "dependencies" commit-message: From 3765727e659402b7901f8e739f769db567404421 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Mon, 12 Dec 2022 09:52:00 -0500 Subject: [PATCH 06/89] docs(examples): add cellbuffer + harmonica example (#409) --- examples/cellbuffer/main.go | 199 ++++++++++++++++++++++++++++++++++++ examples/go.mod | 1 + 2 files changed, 200 insertions(+) create mode 100644 examples/cellbuffer/main.go diff --git a/examples/cellbuffer/main.go b/examples/cellbuffer/main.go new file mode 100644 index 0000000000..00de39ce3b --- /dev/null +++ b/examples/cellbuffer/main.go @@ -0,0 +1,199 @@ +package main + +// A simple example demonstrating how to draw and animate on a cellular grid. +// Note that the cellbuffer implementation in this example does not support +// double-width runes. + +import ( + "fmt" + "os" + "strings" + "time" + + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/harmonica" +) + +const ( + fps = 60 + frequency = 7.5 + damping = 0.15 +) + +func drawEllipse(cb *cellbuffer, xc, yc, rx, ry float64) { + const c = "*" + var ( + dx, dy, d1, d2 float64 + x float64 = 0 + y = ry + ) + + d1 = ry*ry - rx*rx*ry + 0.25*rx*rx + dx = 2 * ry * ry * x + dy = 2 * rx * rx * y + + for dx < dy { + cb.set(c, int(x+xc), int(y+yc)) + cb.set(c, int(-x+xc), int(y+yc)) + cb.set(c, int(x+xc), int(-y+yc)) + cb.set(c, int(-x+xc), int(-y+yc)) + if d1 < 0 { + x++ + dx = dx + (2 * ry * ry) + d1 = d1 + dx + (ry * ry) + } else { + x++ + y-- + dx = dx + (2 * ry * ry) + dy = dy - (2 * rx * rx) + d1 = d1 + dx - dy + (ry * ry) + } + } + + d2 = ((ry * ry) * ((x + 0.5) * (x + 0.5))) + ((rx * rx) * ((y - 1) * (y - 1))) - (rx * rx * ry * ry) + + for y >= 0 { + cb.set(c, int(x+xc), int(y+yc)) + cb.set(c, int(-x+xc), int(y+yc)) + cb.set(c, int(x+xc), int(-y+yc)) + cb.set(c, int(-x+xc), int(-y+yc)) + if d2 > 0 { + y-- + dy = dy - (2 * rx * rx) + d2 = d2 + (rx * rx) - dy + } else { + y-- + x++ + dx = dx + (2 * ry * ry) + dy = dy - (2 * rx * rx) + d2 = d2 + dx - dy + (rx * rx) + } + } +} + +type cellbuffer struct { + cells []string + stride int +} + +func (c *cellbuffer) init(w, h int) { + if w == 0 { + return + } + c.stride = w + c.cells = make([]string, w*h) + c.wipe() +} + +func (c cellbuffer) set(v string, x, y int) { + i := y*c.stride + x + if i > len(c.cells)-1 || x < 0 || y < 0 || x >= c.width() || y >= c.height() { + return + } + c.cells[i] = v +} + +func (c *cellbuffer) clear(x, y int) { + c.set(" ", x, y) +} + +func (c *cellbuffer) wipe() { + for i := range c.cells { + c.cells[i] = " " + } +} + +func (c cellbuffer) width() int { + return c.stride +} + +func (c cellbuffer) height() int { + h := len(c.cells) / c.stride + if len(c.cells)%c.stride != 0 { + h++ + } + return h +} + +func (c cellbuffer) ready() bool { + return len(c.cells) > 0 +} + +func (c cellbuffer) String() string { + var b strings.Builder + for i := 0; i < len(c.cells); i++ { + if i > 0 && i%c.stride == 0 && i < len(c.cells)-1 { + b.WriteRune('\n') + } + b.WriteString(c.cells[i]) + } + return b.String() +} + +type frameMsg struct{} + +func animate() tea.Cmd { + return tea.Tick(time.Second/fps, func(_ time.Time) tea.Msg { + return frameMsg{} + }) +} + +type model struct { + cells cellbuffer + spring harmonica.Spring + targetX, targetY float64 + x, y float64 + xVelocity, yVelocity float64 +} + +func (m model) Init() tea.Cmd { + return animate() +} + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + return m, tea.Quit + case tea.WindowSizeMsg: + if !m.cells.ready() { + m.targetX, m.targetY = float64(msg.Width)/2, float64(msg.Height)/2 + } + m.cells.init(msg.Width, msg.Height) + return m, nil + case tea.MouseMsg: + if !m.cells.ready() { + return m, nil + } + m.targetX, m.targetY = float64(msg.X), float64(msg.Y) + return m, nil + + case frameMsg: + if !m.cells.ready() { + return m, nil + } + + m.cells.wipe() + m.x, m.xVelocity = m.spring.Update(m.x, m.xVelocity, m.targetX) + m.y, m.yVelocity = m.spring.Update(m.y, m.yVelocity, m.targetY) + drawEllipse(&m.cells, m.x, m.y, 16, 8) + return m, animate() + default: + return m, nil + } +} + +func (m model) View() string { + return m.cells.String() +} + +func main() { + m := model{ + spring: harmonica.NewSpring(harmonica.FPS(fps), frequency, damping), + } + + p := tea.NewProgram(m, tea.WithAltScreen(), tea.WithMouseCellMotion()) + if _, err := p.Run(); err != nil { + fmt.Println("Uh oh:", err) + os.Exit(1) + } +} diff --git a/examples/go.mod b/examples/go.mod index 7465247896..8804897a33 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -6,6 +6,7 @@ require ( github.com/charmbracelet/bubbles v0.13.1-0.20220815142520-649f78e1fd8b github.com/charmbracelet/bubbletea v0.22.0 github.com/charmbracelet/glamour v0.6.0 + github.com/charmbracelet/harmonica v0.2.0 github.com/charmbracelet/lipgloss v0.6.0 github.com/fogleman/ease v0.0.0-20170301025033-8da417bf1776 github.com/lucasb-eyer/go-colorful v1.2.0 From b63a0ae37f412be6e008e02c2a8f9f4e6ebae3c7 Mon Sep 17 00:00:00 2001 From: Solomon White Date: Tue, 13 Dec 2022 13:49:17 -0700 Subject: [PATCH 07/89] docs: add brows to "in the wild" app list --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index fe1b5cf5a7..684d9c19bd 100644 --- a/README.md +++ b/README.md @@ -318,6 +318,7 @@ For some Bubble Tea programs in production, see: * [AT CLI](https://github.com/daskycodes/at_cli): a utility for executing AT Commands via serial port connections * [Aztify](https://github.com/Azure/aztfy): bring Microsoft Azure resources under Terraform +* [brows](https://github.com/rubysolo/brows): A CLI GitHub release browser * [Canard](https://github.com/mrusme/canard): an RSS client * [charm](https://github.com/charmbracelet/charm): the official Charm user account manager * [chezmoi](https://github.com/twpayne/chezmoi): manage your dotfiles across multiple machines, securely From ede4aec24e643134ec418fe48db05470699d485f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Dec 2022 17:03:13 +0000 Subject: [PATCH 08/89] chore(deps): bump github.com/mattn/go-isatty in /examples Bumps [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) from 0.0.16 to 0.0.17. - [Release notes](https://github.com/mattn/go-isatty/releases) - [Commits](https://github.com/mattn/go-isatty/compare/v0.0.16...v0.0.17) --- updated-dependencies: - dependency-name: github.com/mattn/go-isatty dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- examples/go.mod | 2 +- examples/go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index 8804897a33..1ee91e8780 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -10,7 +10,7 @@ require ( github.com/charmbracelet/lipgloss v0.6.0 github.com/fogleman/ease v0.0.0-20170301025033-8da417bf1776 github.com/lucasb-eyer/go-colorful v1.2.0 - github.com/mattn/go-isatty v0.0.16 + github.com/mattn/go-isatty v0.0.17 github.com/muesli/reflow v0.3.0 github.com/muesli/termenv v0.13.0 ) diff --git a/examples/go.sum b/examples/go.sum index 09ac05ee37..08124408eb 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -31,8 +31,9 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+ 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.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 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.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= From b217449c8e1733f366b17e6a78ed261520bcd12d Mon Sep 17 00:00:00 2001 From: Alexander Jung Date: Fri, 20 Jan 2023 11:34:00 +0100 Subject: [PATCH 09/89] fix: Check msg cmd is not nil before invoking GitHub-Fixes: #639 Signed-off-by: Alexander Jung --- tea.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tea.go b/tea.go index 03734bc575..27bc3e3f6e 100644 --- a/tea.go +++ b/tea.go @@ -310,6 +310,10 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) { go func() { // Execute commands one at a time, in order. for _, cmd := range msg { + if cmd == nil { + continue + } + p.Send(cmd()) } }() From 263444cdfb14c70bc33fb513448240b9b51bd77f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=9E=E3=83=AA=E3=82=A6=E3=82=B9?= Date: Tue, 10 Jan 2023 14:44:21 -0500 Subject: [PATCH 10/89] docs: add Neon Modem Overdrive to "in the wild" --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 684d9c19bd..f5bbaf7511 100644 --- a/README.md +++ b/README.md @@ -344,6 +344,7 @@ For some Bubble Tea programs in production, see: * [mandelbrot-cli](https://github.com/MicheleFiladelfia/mandelbrot-cli): Multiplatform terminal mandelbrot set explorer * [mc](https://github.com/minio/mc): the official [MinIO](https://min.io) client * [mergestat](https://github.com/mergestat/mergestat): run SQL queries on git repositories +* [Neon Modem Overdrive](https://github.com/mrusme/neonmodem): BBS-style TUI client for Discourse, Lemmy, Lobsters and Hacker News * [Noted](https://github.com/torbratsberg/noted): Note viewer and manager * [pathos](https://github.com/chip/pathos): pathos - CLI for editing a PATH env variable * [portal](https://github.com/ZinoKader/portal): securely send transfer between computers From fbecc47be2f536ce8eaf1e73eeb2a96f627ffff8 Mon Sep 17 00:00:00 2001 From: Yahya SayadArbabi Date: Thu, 15 Dec 2022 20:10:00 +0300 Subject: [PATCH 11/89] feat(README): add enola to "Bubble Tea in the Wild" list --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f5bbaf7511..ba09cc833d 100644 --- a/README.md +++ b/README.md @@ -326,6 +326,7 @@ For some Bubble Tea programs in production, see: * [clidle](https://github.com/ajeetdsouza/clidle): a Wordle clone for your terminal * [container-canary](https://github.com/NVIDIA/container-canary): a container validator * [dns53](https://github.com/purpleclay/dns53): dynamic DNS with Amazon Route53. Expose your EC2 quickly, securely and privately +* [enola](https://github.com/sherlock-project/enola): 🔎 Hunt down social media accounts by username across social networks * [flapioca](https://github.com/kbrgl/flapioca): Flappy Bird on the CLI! * [fm](https://github.com/knipferrc/fm): a terminal-based file manager * [fork-cleaner](https://github.com/caarlos0/fork-cleaner): cleans up old and inactive forks in your GitHub account From d2a775ef2ef99ba841bb9295c83d98f99e26ff26 Mon Sep 17 00:00:00 2001 From: Petar Jager Date: Wed, 4 Jan 2023 19:11:31 +0100 Subject: [PATCH 12/89] docs: add SlurmCommander to in-the-wild section --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ba09cc833d..2d7354d14c 100644 --- a/README.md +++ b/README.md @@ -352,6 +352,7 @@ For some Bubble Tea programs in production, see: * [redis-viewer](https://github.com/SaltFishPr/redis-viewer): browse Redis databases * [sku](https://github.com/fedeztk/sku): a simple TUI for playing sudoku inside the terminal * [Slides](https://github.com/maaslalani/slides): a markdown-based presentation tool +* [SlurmCommander](https://github.com/CLIP-HPC/SlurmCommander): Slurm workload manager TUI * [Soft Serve](https://github.com/charmbracelet/soft-serve): a command-line-first Git server that runs a TUI over SSH * [StormForge Optimize Controller](https://github.com/thestormforge/optimize-controller): a tool for experimenting with application configurations in Kubernetes * [STTG](https://github.com/wille1101/sttg): teletext client for SVT, Sweden’s national public television station From da44346bd3495f3d4833cff4f1a4e89b91fc557d Mon Sep 17 00:00:00 2001 From: Brian Strauch Date: Sun, 5 Feb 2023 10:41:44 -0800 Subject: [PATCH 13/89] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2d7354d14c..6d1fa03a96 100644 --- a/README.md +++ b/README.md @@ -354,6 +354,7 @@ For some Bubble Tea programs in production, see: * [Slides](https://github.com/maaslalani/slides): a markdown-based presentation tool * [SlurmCommander](https://github.com/CLIP-HPC/SlurmCommander): Slurm workload manager TUI * [Soft Serve](https://github.com/charmbracelet/soft-serve): a command-line-first Git server that runs a TUI over SSH +* [solitaire-tui](https://github.com/brianstrauch/solitaire-tui): Klondike solitaire for the terminal * [StormForge Optimize Controller](https://github.com/thestormforge/optimize-controller): a tool for experimenting with application configurations in Kubernetes * [STTG](https://github.com/wille1101/sttg): teletext client for SVT, Sweden’s national public television station * [sttr](https://github.com/abhimanyu003/sttr): run various text transformations From 45cded1ffc5ccfdf93d7d83d143e674869fa22ef Mon Sep 17 00:00:00 2001 From: Pradeep Chhetri Date: Fri, 20 Jan 2023 15:38:03 +0800 Subject: [PATCH 14/89] Add chtop to the list built with bubbletea Signed-off-by: Pradeep Chhetri --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6d1fa03a96..39746f8641 100644 --- a/README.md +++ b/README.md @@ -322,6 +322,7 @@ For some Bubble Tea programs in production, see: * [Canard](https://github.com/mrusme/canard): an RSS client * [charm](https://github.com/charmbracelet/charm): the official Charm user account manager * [chezmoi](https://github.com/twpayne/chezmoi): manage your dotfiles across multiple machines, securely +* [chtop](https://github.com/chhetripradeep/chtop): monitor your clickhouse node without leaving terminal * [circumflex](https://github.com/bensadeh/circumflex): read Hacker News in your terminal * [clidle](https://github.com/ajeetdsouza/clidle): a Wordle clone for your terminal * [container-canary](https://github.com/NVIDIA/container-canary): a container validator From d7bc68326018b29b41482acbff30d3b0ab1b1fe9 Mon Sep 17 00:00:00 2001 From: BoilingSoup <84747244+BoilingSoup@users.noreply.github.com> Date: Fri, 3 Feb 2023 13:10:06 -0800 Subject: [PATCH 15/89] fix typo --- tutorials/commands/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorials/commands/README.md b/tutorials/commands/README.md index c1d81e3bb2..701f1e24c7 100644 --- a/tutorials/commands/README.md +++ b/tutorials/commands/README.md @@ -176,7 +176,7 @@ func main() { } ``` -And that's that. There's one more thing you that is helpful to know about +And that's that. There's one more thing that is helpful to know about `Cmd`s, though. ## One More Thing About Commands From a3dc5611040824232d6d9de0d24f58d1962a6575 Mon Sep 17 00:00:00 2001 From: koki-develop Date: Mon, 16 Jan 2023 06:21:08 +0900 Subject: [PATCH 16/89] docs: add cLive to "Bubble Tea in the Wild" --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 39746f8641..a13c7d0a69 100644 --- a/README.md +++ b/README.md @@ -325,6 +325,7 @@ For some Bubble Tea programs in production, see: * [chtop](https://github.com/chhetripradeep/chtop): monitor your clickhouse node without leaving terminal * [circumflex](https://github.com/bensadeh/circumflex): read Hacker News in your terminal * [clidle](https://github.com/ajeetdsouza/clidle): a Wordle clone for your terminal +* [cLive](https://github.com/koki-develop/clive): Automates terminal operations and lets you view them live via a browser * [container-canary](https://github.com/NVIDIA/container-canary): a container validator * [dns53](https://github.com/purpleclay/dns53): dynamic DNS with Amazon Route53. Expose your EC2 quickly, securely and privately * [enola](https://github.com/sherlock-project/enola): 🔎 Hunt down social media accounts by username across social networks From e95e1a0db5878c1483ce522da156da6f6e906f3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoffer=20=C3=85str=C3=B6m?= Date: Mon, 6 Feb 2023 20:44:53 +0100 Subject: [PATCH 17/89] feat: `sequence` support `BatchMsg` --- examples/go.sum | 5 +++++ examples/sequence/main.go | 9 ++++++--- go.mod | 1 + go.sum | 3 +++ tea.go | 19 +++++++++++++++++-- tea_test.go | 24 ++++++++++++++++++++++++ tutorials/go.sum | 3 +++ 7 files changed, 59 insertions(+), 5 deletions(-) diff --git a/examples/go.sum b/examples/go.sum index 08124408eb..8b5c580274 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -64,6 +64,7 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 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= @@ -74,6 +75,8 @@ 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/net v0.0.0-20221002022538-bcab6841153b h1:6e93nYa3hNqAvLr0pD4PN1fFS+gKzp2zAXqrnTCstqU= golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -85,7 +88,9 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 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= diff --git a/examples/sequence/main.go b/examples/sequence/main.go index 200b738167..ea46dcc62f 100644 --- a/examples/sequence/main.go +++ b/examples/sequence/main.go @@ -13,9 +13,12 @@ type model struct{} func (m model) Init() tea.Cmd { return tea.Sequence( - tea.Println("A"), - tea.Println("B"), - tea.Println("C"), + tea.Batch( + tea.Println("A"), + tea.Println("B"), + tea.Println("C"), + ), + tea.Println("Z"), tea.Quit, ) } diff --git a/go.mod b/go.mod index d3a0c9746a..26a9114524 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/muesli/cancelreader v0.2.2 github.com/muesli/reflow v0.3.0 github.com/muesli/termenv v0.13.0 + golang.org/x/sync v0.1.0 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 golang.org/x/text v0.3.7 // indirect ) diff --git a/go.sum b/go.sum index 76e0059e1d..cdeb039033 100644 --- a/go.sum +++ b/go.sum @@ -23,6 +23,8 @@ github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4Y github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -32,4 +34,5 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/tea.go b/tea.go index 27bc3e3f6e..f5f452bf56 100644 --- a/tea.go +++ b/tea.go @@ -24,6 +24,7 @@ import ( isatty "github.com/mattn/go-isatty" "github.com/muesli/cancelreader" "github.com/muesli/termenv" + "golang.org/x/sync/errgroup" ) // ErrProgramKilled is returned by [Program.Run] when the program got killed. @@ -313,8 +314,22 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) { if cmd == nil { continue } - - p.Send(cmd()) + msg := cmd() + if batchMsg, ok := msg.(BatchMsg); ok { + g, _ := errgroup.WithContext(p.ctx) + for _, cmd := range batchMsg { + cmd := cmd + g.Go(func() error { + p.Send(cmd()) + return nil + }) + } + //nolint:errcheck + g.Wait() // wait for all commands from batch msg to finish + continue + } else { + p.Send(msg) + } } }() } diff --git a/tea_test.go b/tea_test.go index 142f42df4b..748b4cb7a5 100644 --- a/tea_test.go +++ b/tea_test.go @@ -173,6 +173,30 @@ func TestTeaSequenceMsg(t *testing.T) { } } +func TestTeaSequenceMsgWithBatchMsg(t *testing.T) { + var buf bytes.Buffer + var in bytes.Buffer + + inc := func() Msg { + return incrementMsg{} + } + batch := func() Msg { + return BatchMsg{inc, inc} + } + + m := &testModel{} + p := NewProgram(m, WithInput(&in), WithOutput(&buf)) + go p.Send(sequenceMsg{batch, inc, Quit}) + + if _, err := p.Run(); err != nil { + t.Fatal(err) + } + + if m.counter.Load() != 3 { + t.Fatalf("counter should be 3, got %d", m.counter.Load()) + } +} + func TestTeaSend(t *testing.T) { var buf bytes.Buffer var in bytes.Buffer diff --git a/tutorials/go.sum b/tutorials/go.sum index 76e0059e1d..cdeb039033 100644 --- a/tutorials/go.sum +++ b/tutorials/go.sum @@ -23,6 +23,8 @@ github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4Y github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -32,4 +34,5 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From db1a0954255123989a145ca533d535a7558feb09 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Tue, 7 Feb 2023 10:34:22 -0500 Subject: [PATCH 18/89] chore(docs): cleanup Bubble Tea in the Wild --- README.md | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index a13c7d0a69..d51f9b4ddf 100644 --- a/README.md +++ b/README.md @@ -316,58 +316,58 @@ your program in another window. For some Bubble Tea programs in production, see: -* [AT CLI](https://github.com/daskycodes/at_cli): a utility for executing AT Commands via serial port connections +* [AT CLI](https://github.com/daskycodes/at_cli): execute AT Commands via serial port connections * [Aztify](https://github.com/Azure/aztfy): bring Microsoft Azure resources under Terraform -* [brows](https://github.com/rubysolo/brows): A CLI GitHub release browser +* [brows](https://github.com/rubysolo/brows): a GitHub release browser * [Canard](https://github.com/mrusme/canard): an RSS client * [charm](https://github.com/charmbracelet/charm): the official Charm user account manager -* [chezmoi](https://github.com/twpayne/chezmoi): manage your dotfiles across multiple machines, securely -* [chtop](https://github.com/chhetripradeep/chtop): monitor your clickhouse node without leaving terminal -* [circumflex](https://github.com/bensadeh/circumflex): read Hacker News in your terminal -* [clidle](https://github.com/ajeetdsouza/clidle): a Wordle clone for your terminal -* [cLive](https://github.com/koki-develop/clive): Automates terminal operations and lets you view them live via a browser +* [chezmoi](https://github.com/twpayne/chezmoi): securely manage your dotfiles across multiple machines +* [chtop](https://github.com/chhetripradeep/chtop): monitor your ClickHouse node without leaving terminal +* [circumflex](https://github.com/bensadeh/circumflex): read Hacker News in the terminal +* [clidle](https://github.com/ajeetdsouza/clidle): a Wordle clone +* [cLive](https://github.com/koki-develop/clive): automate terminal operations and view them live in a browser * [container-canary](https://github.com/NVIDIA/container-canary): a container validator -* [dns53](https://github.com/purpleclay/dns53): dynamic DNS with Amazon Route53. Expose your EC2 quickly, securely and privately -* [enola](https://github.com/sherlock-project/enola): 🔎 Hunt down social media accounts by username across social networks +* [dns53](https://github.com/purpleclay/dns53): dynamic DNS with Amazon Route53: expose your EC2 quickly, securely and privately +* [enola](https://github.com/sherlock-project/enola): hunt down social media accounts by username across social networks * [flapioca](https://github.com/kbrgl/flapioca): Flappy Bird on the CLI! * [fm](https://github.com/knipferrc/fm): a terminal-based file manager * [fork-cleaner](https://github.com/caarlos0/fork-cleaner): cleans up old and inactive forks in your GitHub account -* [fztea](https://github.com/jon4hz/fztea): connect to your Flipper's UI over serial or make it accessible via SSH -* [gambit](https://github.com/maaslalani/gambit): play chess in the terminal +* [fztea](https://github.com/jon4hz/fztea): a Flipper Zero TUI +* [gambit](https://github.com/maaslalani/gambit): chess in the terminal * [gembro](https://git.sr.ht/~rafael/gembro): a mouse-driven Gemini browser -* [gh-b](https://github.com/joaom00/gh-b): GitHub CLI extension to easily manage your branches -* [gh-dash](https://www.github.com/dlvhdr/gh-dash): GitHub CLI extension to display a dashboard of PRs and issues +* [gh-b](https://github.com/joaom00/gh-b): a GitHub CLI extension for managing branches +* [gh-dash](https://www.github.com/dlvhdr/gh-dash): a GitHub CLI extension for PRs and issues * [gitflow-toolkit](https://github.com/mritd/gitflow-toolkit): a GitFlow submission tool -* [Glow](https://github.com/charmbracelet/glow): a markdown reader, browser and online markdown stash +* [Glow](https://github.com/charmbracelet/glow): a markdown reader, browser, and online markdown stash * [gocovsh](https://github.com/orlangure/gocovsh): explore Go coverage reports from the CLI * [got](https://github.com/fedeztk/got): a simple translator and text-to-speech app build on top of simplytranslate's APIs * [httpit](https://github.com/gonetx/httpit): a rapid http(s) benchmark tool * [IDNT](https://github.com/r-darwish/idnt): batch software uninstaller * [kboard](https://github.com/CamiloGarciaLaRotta/kboard): a typing game -* [mandelbrot-cli](https://github.com/MicheleFiladelfia/mandelbrot-cli): Multiplatform terminal mandelbrot set explorer +* [mandelbrot-cli](https://github.com/MicheleFiladelfia/mandelbrot-cli): a multiplatform terminal mandelbrot set explorer * [mc](https://github.com/minio/mc): the official [MinIO](https://min.io) client * [mergestat](https://github.com/mergestat/mergestat): run SQL queries on git repositories -* [Neon Modem Overdrive](https://github.com/mrusme/neonmodem): BBS-style TUI client for Discourse, Lemmy, Lobsters and Hacker News -* [Noted](https://github.com/torbratsberg/noted): Note viewer and manager -* [pathos](https://github.com/chip/pathos): pathos - CLI for editing a PATH env variable -* [portal](https://github.com/ZinoKader/portal): securely send transfer between computers -* [redis-viewer](https://github.com/SaltFishPr/redis-viewer): browse Redis databases -* [sku](https://github.com/fedeztk/sku): a simple TUI for playing sudoku inside the terminal +* [Neon Modem Overdrive](https://github.com/mrusme/neonmodem): a BBS-style TUI client for Discourse, Lemmy, Lobste.rs and Hacker News +* [Noted](https://github.com/torbratsberg/noted): a note viewer and manager +* [pathos](https://github.com/chip/pathos): pathos - a PATH env variable editor +* [portal](https://github.com/ZinoKader/portal): secure transfers between computers +* [redis-viewer](https://github.com/SaltFishPr/redis-viewer): a Redis databases browser +* [sku](https://github.com/fedeztk/sku): Sudoku on the CLI * [Slides](https://github.com/maaslalani/slides): a markdown-based presentation tool -* [SlurmCommander](https://github.com/CLIP-HPC/SlurmCommander): Slurm workload manager TUI +* [SlurmCommander](https://github.com/CLIP-HPC/SlurmCommander): a Slurm workload manager TUI * [Soft Serve](https://github.com/charmbracelet/soft-serve): a command-line-first Git server that runs a TUI over SSH -* [solitaire-tui](https://github.com/brianstrauch/solitaire-tui): Klondike solitaire for the terminal +* [solitaire-tui](https://github.com/brianstrauch/solitaire-tui): Klondike Solitaire for the terminal * [StormForge Optimize Controller](https://github.com/thestormforge/optimize-controller): a tool for experimenting with application configurations in Kubernetes * [STTG](https://github.com/wille1101/sttg): teletext client for SVT, Sweden’s national public television station -* [sttr](https://github.com/abhimanyu003/sttr): run various text transformations +* [sttr](https://github.com/abhimanyu003/sttr): a general-purpose text transformer * [tasktimer](https://github.com/caarlos0/tasktimer): a dead-simple task timer * [termdbms](https://github.com/mathaou/termdbms): a keyboard and mouse driven database browser -* [ticker](https://github.com/achannarasappa/ticker): a terminal stock watcher and stock position tracker +* [ticker](https://github.com/achannarasappa/ticker): a terminal stock viewer and stock position tracker * [tran](https://github.com/abdfnx/tran): securely transfer stuff between computers (based on [portal][portal]) * [Typer](https://github.com/maaslalani/typer): a typing test * [tz](https://github.com/oz/tz): an aid for scheduling across multiple time zones * [ugm](https://github.com/ariasmn/ugm): a unix user and group browser -* [wander](https://github.com/robinovitch61/wander): HashiCorp Nomad terminal client +* [wander](https://github.com/robinovitch61/wander): a HashiCorp Nomad terminal client * [wishlist](https://github.com/charmbracelet/wishlist): an SSH directory ## Feedback From b30cca3906f00852b88d78c2ebc1fe2fdc4a182c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Feb 2023 15:12:49 +0000 Subject: [PATCH 19/89] chore(deps): bump github.com/muesli/termenv in /examples Bumps [github.com/muesli/termenv](https://github.com/muesli/termenv) from 0.13.0 to 0.14.0. - [Release notes](https://github.com/muesli/termenv/releases) - [Commits](https://github.com/muesli/termenv/compare/v0.13.0...v0.14.0) --- updated-dependencies: - dependency-name: github.com/muesli/termenv dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- examples/go.mod | 2 +- examples/go.sum | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index 1ee91e8780..0ef3854428 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -12,7 +12,7 @@ require ( github.com/lucasb-eyer/go-colorful v1.2.0 github.com/mattn/go-isatty v0.0.17 github.com/muesli/reflow v0.3.0 - github.com/muesli/termenv v0.13.0 + github.com/muesli/termenv v0.14.0 ) replace github.com/charmbracelet/bubbletea => ../ diff --git a/examples/go.sum b/examples/go.sum index 8b5c580274..83cfc1aa56 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -2,8 +2,9 @@ github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbf github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= -github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg= github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= +github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E= +github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/charmbracelet/bubbles v0.13.1-0.20220815142520-649f78e1fd8b h1:ppafRlD8VXOEqnUPkMvO7et9rpSsKVjUX+K3QG7tAB0= @@ -53,8 +54,9 @@ 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.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= -github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0= github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= +github.com/muesli/termenv v0.14.0 h1:8x9NFfOe8lmIWK4pgy3IfVEy47f+ppe3tUqdPZG2Uy0= +github.com/muesli/termenv v0.14.0/go.mod h1:kG/pF1E7fh949Xhe156crRUrHNyK221IuGO7Ez60Uc8= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -64,7 +66,6 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= -github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 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= @@ -88,9 +89,7 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 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= From cb23938f3fced5bdd4c4976d2a3b380be8423680 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Wed, 8 Feb 2023 09:34:37 -0500 Subject: [PATCH 20/89] chore(docs): additional Bubble Tea in the wild cleanup --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d51f9b4ddf..3bad8385de 100644 --- a/README.md +++ b/README.md @@ -331,7 +331,7 @@ For some Bubble Tea programs in production, see: * [enola](https://github.com/sherlock-project/enola): hunt down social media accounts by username across social networks * [flapioca](https://github.com/kbrgl/flapioca): Flappy Bird on the CLI! * [fm](https://github.com/knipferrc/fm): a terminal-based file manager -* [fork-cleaner](https://github.com/caarlos0/fork-cleaner): cleans up old and inactive forks in your GitHub account +* [fork-cleaner](https://github.com/caarlos0/fork-cleaner): clean up old and inactive forks in your GitHub account * [fztea](https://github.com/jon4hz/fztea): a Flipper Zero TUI * [gambit](https://github.com/maaslalani/gambit): chess in the terminal * [gembro](https://git.sr.ht/~rafael/gembro): a mouse-driven Gemini browser @@ -342,14 +342,14 @@ For some Bubble Tea programs in production, see: * [gocovsh](https://github.com/orlangure/gocovsh): explore Go coverage reports from the CLI * [got](https://github.com/fedeztk/got): a simple translator and text-to-speech app build on top of simplytranslate's APIs * [httpit](https://github.com/gonetx/httpit): a rapid http(s) benchmark tool -* [IDNT](https://github.com/r-darwish/idnt): batch software uninstaller +* [IDNT](https://github.com/r-darwish/idnt): a batch software uninstaller * [kboard](https://github.com/CamiloGarciaLaRotta/kboard): a typing game * [mandelbrot-cli](https://github.com/MicheleFiladelfia/mandelbrot-cli): a multiplatform terminal mandelbrot set explorer * [mc](https://github.com/minio/mc): the official [MinIO](https://min.io) client * [mergestat](https://github.com/mergestat/mergestat): run SQL queries on git repositories * [Neon Modem Overdrive](https://github.com/mrusme/neonmodem): a BBS-style TUI client for Discourse, Lemmy, Lobste.rs and Hacker News * [Noted](https://github.com/torbratsberg/noted): a note viewer and manager -* [pathos](https://github.com/chip/pathos): pathos - a PATH env variable editor +* [pathos](https://github.com/chip/pathos): a PATH env variable editor * [portal](https://github.com/ZinoKader/portal): secure transfers between computers * [redis-viewer](https://github.com/SaltFishPr/redis-viewer): a Redis databases browser * [sku](https://github.com/fedeztk/sku): Sudoku on the CLI @@ -358,12 +358,12 @@ For some Bubble Tea programs in production, see: * [Soft Serve](https://github.com/charmbracelet/soft-serve): a command-line-first Git server that runs a TUI over SSH * [solitaire-tui](https://github.com/brianstrauch/solitaire-tui): Klondike Solitaire for the terminal * [StormForge Optimize Controller](https://github.com/thestormforge/optimize-controller): a tool for experimenting with application configurations in Kubernetes -* [STTG](https://github.com/wille1101/sttg): teletext client for SVT, Sweden’s national public television station +* [STTG](https://github.com/wille1101/sttg): a teletext client for SVT, Sweden’s national public television station * [sttr](https://github.com/abhimanyu003/sttr): a general-purpose text transformer * [tasktimer](https://github.com/caarlos0/tasktimer): a dead-simple task timer * [termdbms](https://github.com/mathaou/termdbms): a keyboard and mouse driven database browser * [ticker](https://github.com/achannarasappa/ticker): a terminal stock viewer and stock position tracker -* [tran](https://github.com/abdfnx/tran): securely transfer stuff between computers (based on [portal][portal]) +* [tran](https://github.com/abdfnx/tran): securely transfer stuff between computers (based on [portal][https://github.com/ZinoKader/portal]) * [Typer](https://github.com/maaslalani/typer): a typing test * [tz](https://github.com/oz/tz): an aid for scheduling across multiple time zones * [ugm](https://github.com/ariasmn/ugm): a unix user and group browser From 1ad9f9c15ce7cba8b0d9025b3510b12be02bbea5 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Wed, 8 Feb 2023 09:56:32 -0500 Subject: [PATCH 21/89] chore(deps): bump termenv and go-isatty Also bump various deps in tutorials and examples --- examples/go.mod | 4 ++-- examples/go.sum | 6 ++---- go.mod | 4 ++-- go.sum | 13 ++++++------- tutorials/go.mod | 2 +- tutorials/go.sum | 13 ++++++------- 6 files changed, 19 insertions(+), 23 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index 0ef3854428..a51a61e327 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -3,8 +3,8 @@ module examples go 1.16 require ( - github.com/charmbracelet/bubbles v0.13.1-0.20220815142520-649f78e1fd8b - github.com/charmbracelet/bubbletea v0.22.0 + github.com/charmbracelet/bubbles v0.15.0 + github.com/charmbracelet/bubbletea v0.23.1 github.com/charmbracelet/glamour v0.6.0 github.com/charmbracelet/harmonica v0.2.0 github.com/charmbracelet/lipgloss v0.6.0 diff --git a/examples/go.sum b/examples/go.sum index 83cfc1aa56..2531ddda77 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -7,13 +7,12 @@ github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTx github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= -github.com/charmbracelet/bubbles v0.13.1-0.20220815142520-649f78e1fd8b h1:ppafRlD8VXOEqnUPkMvO7et9rpSsKVjUX+K3QG7tAB0= -github.com/charmbracelet/bubbles v0.13.1-0.20220815142520-649f78e1fd8b/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc= +github.com/charmbracelet/bubbles v0.15.0 h1:c5vZ3woHV5W2b8YZI1q7v4ZNQaPetfHuoHzx+56Z6TI= +github.com/charmbracelet/bubbles v0.15.0/go.mod h1:Y7gSFbBzlMpUDR/XM9MhZI374Q+1p1kluf1uLl8iK74= github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc= 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.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs= github.com/charmbracelet/lipgloss v0.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87inij9N3wJY= github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= @@ -53,7 +52,6 @@ github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIW 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.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= -github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= github.com/muesli/termenv v0.14.0 h1:8x9NFfOe8lmIWK4pgy3IfVEy47f+ppe3tUqdPZG2Uy0= github.com/muesli/termenv v0.14.0/go.mod h1:kG/pF1E7fh949Xhe156crRUrHNyK221IuGO7Ez60Uc8= diff --git a/go.mod b/go.mod index 26a9114524..069cdf8452 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,12 @@ go 1.16 require ( github.com/containerd/console v1.0.3 - github.com/mattn/go-isatty v0.0.16 + github.com/mattn/go-isatty v0.0.17 github.com/mattn/go-localereader v0.0.1 github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b github.com/muesli/cancelreader v0.2.2 github.com/muesli/reflow v0.3.0 - github.com/muesli/termenv v0.13.0 + github.com/muesli/termenv v0.14.0 golang.org/x/sync v0.1.0 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 golang.org/x/text v0.3.7 // indirect diff --git a/go.sum b/go.sum index cdeb039033..4ea02faef7 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,11 @@ -github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg= -github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= +github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E= +github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= 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.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 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= @@ -18,8 +18,8 @@ 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.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0= -github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= +github.com/muesli/termenv v0.14.0 h1:8x9NFfOe8lmIWK4pgy3IfVEy47f+ppe3tUqdPZG2Uy0= +github.com/muesli/termenv v0.14.0/go.mod h1:kG/pF1E7fh949Xhe156crRUrHNyK221IuGO7Ez60Uc8= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -34,5 +34,4 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/tutorials/go.mod b/tutorials/go.mod index 9f0713f49c..7e09c47294 100644 --- a/tutorials/go.mod +++ b/tutorials/go.mod @@ -2,6 +2,6 @@ module tutorial go 1.16 -require github.com/charmbracelet/bubbletea v0.21.0 +require github.com/charmbracelet/bubbletea v0.23.1 replace github.com/charmbracelet/bubbletea => ../ diff --git a/tutorials/go.sum b/tutorials/go.sum index cdeb039033..4ea02faef7 100644 --- a/tutorials/go.sum +++ b/tutorials/go.sum @@ -1,11 +1,11 @@ -github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg= -github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= +github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E= +github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= 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.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 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= @@ -18,8 +18,8 @@ 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.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0= -github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= +github.com/muesli/termenv v0.14.0 h1:8x9NFfOe8lmIWK4pgy3IfVEy47f+ppe3tUqdPZG2Uy0= +github.com/muesli/termenv v0.14.0/go.mod h1:kG/pF1E7fh949Xhe156crRUrHNyK221IuGO7Ez60Uc8= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -34,5 +34,4 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 331a63bdca9b7815a044294d9ceb15cb6363889e Mon Sep 17 00:00:00 2001 From: Alexander Jung Date: Mon, 30 Jan 2023 11:41:44 +0000 Subject: [PATCH 22/89] fix: Check if program cancelReader is is nil before invoking This commit fixes an issue where a user may provider a nil input via `tea.WithInput(nil)`. This option method does not check if the input is nil and sets the `withCustomInput` attribute with a nil input. This logic is sound since a Tea program may not necessarily want to handle any inputs from users (such as those in non-TTY environments). However, a nil pointer exception is thrown during `tea.Run` because a `cancelReader` is always invoked after the main renderer. However, its instantiation is variable and dependent on whether an input is provided. To mitigate against this, this commit checks if a `cancelReader` is non-nil. Signed-off-by: Alexander Jung --- tea.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tea.go b/tea.go index f5f452bf56..0c29a86b43 100644 --- a/tea.go +++ b/tea.go @@ -474,11 +474,14 @@ func (p *Program) Run() (Model, error) { // Tear down. p.cancel() - // Wait for input loop to finish. - if p.cancelReader.Cancel() { - p.waitForReadLoop() + // Check if the cancel reader has been setup before waiting and closing. + if p.cancelReader != nil { + // Wait for input loop to finish. + if p.cancelReader.Cancel() { + p.waitForReadLoop() + } + _ = p.cancelReader.Close() } - _ = p.cancelReader.Close() // Wait for all handlers to finish. handlers.shutdown() From 76c3c1a221508198cbb2496f8166792834c44511 Mon Sep 17 00:00:00 2001 From: Mark Marryatt <78297942+mjmammoth@users.noreply.github.com> Date: Thu, 16 Feb 2023 16:16:31 +0200 Subject: [PATCH 23/89] docs: fix portal broken markdown URL --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3bad8385de..0e1bc488c8 100644 --- a/README.md +++ b/README.md @@ -363,7 +363,7 @@ For some Bubble Tea programs in production, see: * [tasktimer](https://github.com/caarlos0/tasktimer): a dead-simple task timer * [termdbms](https://github.com/mathaou/termdbms): a keyboard and mouse driven database browser * [ticker](https://github.com/achannarasappa/ticker): a terminal stock viewer and stock position tracker -* [tran](https://github.com/abdfnx/tran): securely transfer stuff between computers (based on [portal][https://github.com/ZinoKader/portal]) +* [tran](https://github.com/abdfnx/tran): securely transfer stuff between computers (based on [portal](https://github.com/ZinoKader/portal)) * [Typer](https://github.com/maaslalani/typer): a typing test * [tz](https://github.com/oz/tz): an aid for scheduling across multiple time zones * [ugm](https://github.com/ariasmn/ugm): a unix user and group browser From 4880cf2a09340b307fd037cefad81f7751fc4430 Mon Sep 17 00:00:00 2001 From: Glenn Gonda Date: Sat, 18 Feb 2023 20:30:17 -0800 Subject: [PATCH 24/89] docs: fix typos and clean up comments --- examples/credit-card-form/main.go | 2 +- examples/help/main.go | 2 +- examples/simple/main.go | 2 +- examples/textarea/main.go | 2 +- examples/tui-daemon-combo/main.go | 2 +- mouse.go | 4 ++-- options.go | 4 ++-- tutorials/basics/README.md | 4 ++-- tutorials/basics/main.go | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/credit-card-form/main.go b/examples/credit-card-form/main.go index 5ac6854da2..ba5b2b3363 100644 --- a/examples/credit-card-form/main.go +++ b/examples/credit-card-form/main.go @@ -71,7 +71,7 @@ func ccnValidator(s string) error { func expValidator(s string) error { // The 3 character should be a slash (/) - // The rest thould be numbers + // The rest should be numbers e := strings.ReplaceAll(s, "/", "") _, err := strconv.ParseInt(e, 10, 64) if err != nil { diff --git a/examples/help/main.go b/examples/help/main.go index caedc9e709..fb382ce26c 100644 --- a/examples/help/main.go +++ b/examples/help/main.go @@ -87,7 +87,7 @@ func (m model) Init() tea.Cmd { func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: - // If we set a width on the help menu it can it can gracefully truncate + // If we set a width on the help menu it can gracefully truncate // its view as needed. m.help.Width = msg.Width diff --git a/examples/simple/main.go b/examples/simple/main.go index 478f95a0f7..f1201fd0ea 100644 --- a/examples/simple/main.go +++ b/examples/simple/main.go @@ -56,7 +56,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } -// Views return a string based on data in the model. That string which will be +// View returns a string based on data in the model. That string which will be // rendered to the terminal. func (m model) View() string { return fmt.Sprintf("Hi. This program will exit in %d seconds. To quit sooner press any key.\n", m) diff --git a/examples/textarea/main.go b/examples/textarea/main.go index 109059c938..68d1097a72 100644 --- a/examples/textarea/main.go +++ b/examples/textarea/main.go @@ -1,6 +1,6 @@ package main -// A simple program demonstrating the text input component from the Bubbles +// A simple program demonstrating the textarea component from the Bubbles // component library. import ( diff --git a/examples/tui-daemon-combo/main.go b/examples/tui-daemon-combo/main.go index df26034222..962da810de 100644 --- a/examples/tui-daemon-combo/main.go +++ b/examples/tui-daemon-combo/main.go @@ -123,7 +123,7 @@ func (m model) View() string { return indent.String(s, 1) } -// processFinishedMsg is send when a pretend process completes. +// processFinishedMsg is sent when a pretend process completes. type processFinishedMsg time.Duration // pretendProcess simulates a long-running process. diff --git a/mouse.go b/mouse.go index 5ff3a2faf0..f918d20695 100644 --- a/mouse.go +++ b/mouse.go @@ -5,9 +5,9 @@ import ( "errors" ) -// MouseMsg contains information about a mouse event and are sent to a programs +// MouseMsg contains information about a mouse event and is sent to a program's // update function when mouse activity occurs. Note that the mouse must first -// be enabled via in order the mouse events to be received. +// be enabled in order for the mouse events to be received. type MouseMsg MouseEvent // MouseEvent represents a mouse event, which could be a click, a scroll wheel diff --git a/options.go b/options.go index d9ce42c7c2..9945924c46 100644 --- a/options.go +++ b/options.go @@ -41,7 +41,7 @@ func WithInput(input io.Reader) ProgramOption { } } -// WithInputTTY open a new TTY for input (or console input device on Windows). +// WithInputTTY opens a new TTY for input (or console input device on Windows). func WithInputTTY() ProgramOption { return func(p *Program) { p.startupOptions |= withInputTTY @@ -144,7 +144,7 @@ func WithoutRenderer() ProgramOption { // WithANSICompressor removes redundant ANSI sequences to produce potentially // smaller output, at the cost of some processing overhead. // -// This feature is provisional, and may be changed removed in a future version +// This feature is provisional, and may be changed or removed in a future version // of this package. func WithANSICompressor() ProgramOption { return func(p *Program) { diff --git a/tutorials/basics/README.md b/tutorials/basics/README.md index 57ccd48150..8e1a26d4c2 100644 --- a/tutorials/basics/README.md +++ b/tutorials/basics/README.md @@ -64,7 +64,7 @@ func initialModel() model { choices: []string{"Buy carrots", "Buy celery", "Buy kohlrabi"}, // A map which indicates which choices are selected. We're using - // the map like a mathematical set. The keys refer to the indexes + // the map like a mathematical set. The keys refer to the indexes // of the `choices` slice, above. selected: make(map[int]struct{}), } @@ -154,7 +154,7 @@ the Bubble Tea runtime to quit, exiting the program. At last, it’s time to render our UI. Of all the methods, the view is the simplest. We look at the model in its current state and use it to return -a `string`. That string is our UI! +a `string`. That string is our UI! Because the view describes the entire UI of your application, you don’t have to worry about redrawing logic and stuff like that. Bubble Tea takes care of it diff --git a/tutorials/basics/main.go b/tutorials/basics/main.go index 0c5b573f8b..4970290d24 100644 --- a/tutorials/basics/main.go +++ b/tutorials/basics/main.go @@ -18,7 +18,7 @@ func initialModel() model { choices: []string{"Buy carrots", "Buy celery", "Buy kohlrabi"}, // A map which indicates which choices are selected. We're using - // the map like a mathematical set. The keys refer to the indexes + // the map like a mathematical set. The keys refer to the indexes // of the `choices` slice, above. selected: make(map[int]struct{}), } From c61e4fd291fe2b5ae480ffea0685aae0d78896b5 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 21 Feb 2023 12:43:58 -0500 Subject: [PATCH 25/89] chore: update deps Fixes: CVE-2022-27664 https://github.com/advisories/GHSA-69cg-p879-7622 --- examples/go.mod | 1 + examples/go.sum | 2 ++ 2 files changed, 3 insertions(+) diff --git a/examples/go.mod b/examples/go.mod index a51a61e327..e2d197f09b 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -13,6 +13,7 @@ require ( github.com/mattn/go-isatty v0.0.17 github.com/muesli/reflow v0.3.0 github.com/muesli/termenv v0.14.0 + golang.org/x/net v0.7.0 // indirect ) replace github.com/charmbracelet/bubbletea => ../ diff --git a/examples/go.sum b/examples/go.sum index 2531ddda77..7306b44a1b 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -74,6 +74,8 @@ 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/net v0.0.0-20221002022538-bcab6841153b h1:6e93nYa3hNqAvLr0pD4PN1fFS+gKzp2zAXqrnTCstqU= 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/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From 388c67d573a408b3c086fac10421e56734c8f4f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Feb 2023 16:29:25 +0000 Subject: [PATCH 26/89] feat(deps): bump golang.org/x/text from 0.3.7 to 0.3.8 (#674) --- .github/workflows/build.yml | 2 +- examples/go.mod | 28 ++++++++++++++++++++++++++-- examples/go.sum | 30 ++++++++++++++++++++++++++---- go.mod | 12 ++++++++++-- go.sum | 22 +++++++++++++++++++++- tutorials/go.mod | 22 ++++++++++++++++++++-- tutorials/go.sum | 22 +++++++++++++++++++++- 7 files changed, 125 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5d086b2df8..b662a60b5d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ jobs: build: strategy: matrix: - go-version: [~1.16, ^1] + go-version: [~1.17, ^1] os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} env: diff --git a/examples/go.mod b/examples/go.mod index e2d197f09b..47bf1abb8d 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -1,10 +1,10 @@ module examples -go 1.16 +go 1.17 require ( github.com/charmbracelet/bubbles v0.15.0 - github.com/charmbracelet/bubbletea v0.23.1 + github.com/charmbracelet/bubbletea v0.23.2 github.com/charmbracelet/glamour v0.6.0 github.com/charmbracelet/harmonica v0.2.0 github.com/charmbracelet/lipgloss v0.6.0 @@ -13,7 +13,31 @@ require ( github.com/mattn/go-isatty v0.0.17 github.com/muesli/reflow v0.3.0 github.com/muesli/termenv v0.14.0 +) + +require ( + github.com/alecthomas/chroma v0.10.0 // indirect + github.com/atotto/clipboard v0.1.4 // indirect + github.com/aymanbagabas/go-osc52 v1.2.1 // indirect + github.com/aymerick/douceur v0.2.0 // indirect + github.com/containerd/console v1.0.3 // indirect + github.com/dlclark/regexp2 v1.4.0 // 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/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 + github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/sahilm/fuzzy v0.1.0 // 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/sync v0.1.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/term v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect ) replace github.com/charmbracelet/bubbletea => ../ diff --git a/examples/go.sum b/examples/go.sum index 7306b44a1b..caa14e9926 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -68,28 +68,50 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.5.2 h1:ALmeCk/px5FSm1MAcFBAsVKZjDuMVj8Tm7FFIlMJnqU= github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os= github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ= -golang.org/x/net v0.0.0-20221002022538-bcab6841153b h1:6e93nYa3hNqAvLr0pD4PN1fFS+gKzp2zAXqrnTCstqU= +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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +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/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= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.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/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +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/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/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= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/go.mod b/go.mod index 069cdf8452..0111e6511f 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/charmbracelet/bubbletea -go 1.16 +go 1.17 require ( github.com/containerd/console v1.0.3 @@ -12,5 +12,13 @@ require ( github.com/muesli/termenv v0.14.0 golang.org/x/sync v0.1.0 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 - golang.org/x/text v0.3.7 // indirect +) + +require ( + github.com/aymanbagabas/go-osc52 v1.2.1 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect + golang.org/x/text v0.3.8 // indirect ) diff --git a/go.sum b/go.sum index 4ea02faef7..e915ca833c 100644 --- a/go.sum +++ b/go.sum @@ -23,15 +23,35 @@ github.com/muesli/termenv v0.14.0/go.mod h1:kG/pF1E7fh949Xhe156crRUrHNyK221IuGO7 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +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/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= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/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 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +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 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 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/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/tutorials/go.mod b/tutorials/go.mod index 7e09c47294..0074938b4e 100644 --- a/tutorials/go.mod +++ b/tutorials/go.mod @@ -1,7 +1,25 @@ module tutorial -go 1.16 +go 1.17 -require github.com/charmbracelet/bubbletea v0.23.1 +require github.com/charmbracelet/bubbletea v0.23.2 + +require ( + github.com/aymanbagabas/go-osc52 v1.2.1 // indirect + github.com/containerd/console v1.0.3 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.14.0 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + golang.org/x/sync v0.1.0 // indirect + golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect + golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect + golang.org/x/text v0.3.8 // indirect +) replace github.com/charmbracelet/bubbletea => ../ diff --git a/tutorials/go.sum b/tutorials/go.sum index 4ea02faef7..e915ca833c 100644 --- a/tutorials/go.sum +++ b/tutorials/go.sum @@ -23,15 +23,35 @@ github.com/muesli/termenv v0.14.0/go.mod h1:kG/pF1E7fh949Xhe156crRUrHNyK221IuGO7 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +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/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= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/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 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +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 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 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/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From ae652b6a57e5f482f0ab931eab049e0234f43675 Mon Sep 17 00:00:00 2001 From: Carlos A Becker Date: Thu, 23 Feb 2023 13:33:07 -0300 Subject: [PATCH 27/89] feat: go 1.17 This was actually done on #674, and merged in 388c67d573a408b3c086fac10421e56734c8f4f2. This commit is just for the sake of complete changelog. Signed-off-by: Carlos A Becker From 32e3027ec19e05e55c987aedd68fb306fab62fa3 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Thu, 23 Feb 2023 13:51:15 -0300 Subject: [PATCH 28/89] feat(ci): auto go mod tidy examples (#561) * feat(ci): auto go mod tidy examples Signed-off-by: Carlos A Becker * fix: tutorials as well * fix: checkout --------- Signed-off-by: Carlos A Becker --- .github/workflows/examples.yml | 38 ++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/examples.yml diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml new file mode 100644 index 0000000000..eed1bbc7bc --- /dev/null +++ b/.github/workflows/examples.yml @@ -0,0 +1,38 @@ +name: examples + +on: + push: + branches: + - 'master' + paths: + - '.github/workflows/examples.yml' + - './examples/go.mod' + - './examples/go.sum' + - './tutorials/go.mod' + - './tutorials/go.sum' + - './go.mod' + - './go.sum' + workflow_dispatch: {} + +jobs: + tidy: + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: '^1' + cache: true + - shell: bash + run: | + (cd ./examples && go mod tidy) + (cd ./tutorials && go mod tidy) + - uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: "chore: go mod tidy tutorials and examples" + branch: master + commit_user_name: actions-user + commit_user_email: actions@github.com + From a136799ed3730f4d162f95f63dfc8e419558945d Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 6 Mar 2023 14:23:51 +0100 Subject: [PATCH 29/89] fix: renderer only stops once Ensure the tea renderer can be stopped and restarted more than once. --- standard_renderer.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/standard_renderer.go b/standard_renderer.go index 17b44e2301..edde56a40f 100644 --- a/standard_renderer.go +++ b/standard_renderer.go @@ -73,7 +73,12 @@ func (r *standardRenderer) start() { if r.ticker == nil { r.ticker = time.NewTicker(r.framerate) } + + // Since the renderer can be restarted after a stop, we need to reset + // the done channel and its corresponding sync.Once. + r.once = sync.Once{} r.done = make(chan struct{}) + go r.listen() } From 90c9124b0ae3156e3ef0a119a9b93c73ea77bee5 Mon Sep 17 00:00:00 2001 From: Maas Lalani Date: Mon, 6 Mar 2023 11:54:26 -0500 Subject: [PATCH 30/89] Filepicker Example (#683) * feat: filepicker example * fix: use new API * docs(examples): update file selection comment in filepicker example * chore: bump bubbles --------- Co-authored-by: Christian Rocha --- examples/file-picker/main.go | 69 ++++++++++++++++++++++++++++++++++++ examples/go.mod | 3 +- examples/go.sum | 10 ++++-- 3 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 examples/file-picker/main.go diff --git a/examples/file-picker/main.go b/examples/file-picker/main.go new file mode 100644 index 0000000000..30ecf01af8 --- /dev/null +++ b/examples/file-picker/main.go @@ -0,0 +1,69 @@ +package main + +import ( + "fmt" + "os" + "strings" + + "github.com/charmbracelet/bubbles/filepicker" + tea "github.com/charmbracelet/bubbletea" +) + +type model struct { + filepicker filepicker.Model + selectedFile string + quitting bool +} + +func (m model) Init() tea.Cmd { + return m.filepicker.Init() +} + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "ctrl+c", "q": + m.quitting = true + return m, tea.Quit + } + } + + var cmd tea.Cmd + m.filepicker, cmd = m.filepicker.Update(msg) + + // Did the user select a file? + if didSelect, path := m.filepicker.DidSelectFile(msg); didSelect { + // Get the path of the selected file. + m.selectedFile = path + } + + return m, cmd +} + +func (m model) View() string { + if m.quitting { + return "" + } + var s strings.Builder + s.WriteString("\n ") + if m.selectedFile == "" { + s.WriteString("Pick a file:") + } else { + s.WriteString("Selected file: " + m.filepicker.Styles.Selected.Render(m.selectedFile)) + } + s.WriteString("\n\n" + m.filepicker.View() + "\n") + return s.String() +} + +func main() { + fp := filepicker.New() + fp.Path, _ = os.UserHomeDir() + + m := model{ + filepicker: fp, + } + tm, _ := tea.NewProgram(&m, tea.WithOutput(os.Stderr)).Run() + mm := tm.(model) + fmt.Println("\n You selected: " + m.filepicker.Styles.Selected.Render(mm.selectedFile) + "\n") +} diff --git a/examples/go.mod b/examples/go.mod index 47bf1abb8d..7f518631d0 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -3,7 +3,7 @@ module examples go 1.17 require ( - github.com/charmbracelet/bubbles v0.15.0 + github.com/charmbracelet/bubbles v0.15.1-0.20230306155959-3372cf1aea2b github.com/charmbracelet/bubbletea v0.23.2 github.com/charmbracelet/glamour v0.6.0 github.com/charmbracelet/harmonica v0.2.0 @@ -22,6 +22,7 @@ require ( github.com/aymerick/douceur v0.2.0 // indirect github.com/containerd/console v1.0.3 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect + 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 diff --git a/examples/go.sum b/examples/go.sum index caa14e9926..729ba0ddff 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -7,8 +7,12 @@ github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTx github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= -github.com/charmbracelet/bubbles v0.15.0 h1:c5vZ3woHV5W2b8YZI1q7v4ZNQaPetfHuoHzx+56Z6TI= -github.com/charmbracelet/bubbles v0.15.0/go.mod h1:Y7gSFbBzlMpUDR/XM9MhZI374Q+1p1kluf1uLl8iK74= +github.com/charmbracelet/bubbles v0.15.1-0.20230303180827-c01b49080882 h1:bhZOZDpFHFKZhROCENdN+cUHNknH3MY8CUE2xJ9a3HM= +github.com/charmbracelet/bubbles v0.15.1-0.20230303180827-c01b49080882/go.mod h1:39HL8bnL0foloiENA/KvD+3mNg5SqWQV2Qh3eY/4ey4= +github.com/charmbracelet/bubbles v0.15.1-0.20230303214459-958a0ea710f1 h1:BcxSgchb4ZV1lJe9W9+LhQj6S3JouC39yYswVI4YOrw= +github.com/charmbracelet/bubbles v0.15.1-0.20230303214459-958a0ea710f1/go.mod h1:39HL8bnL0foloiENA/KvD+3mNg5SqWQV2Qh3eY/4ey4= +github.com/charmbracelet/bubbles v0.15.1-0.20230306155959-3372cf1aea2b h1:K9dWJ2spDhDhIrqnchjG867djPxWWe3mwdk6RdLMfhg= +github.com/charmbracelet/bubbles v0.15.1-0.20230306155959-3372cf1aea2b/go.mod h1:39HL8bnL0foloiENA/KvD+3mNg5SqWQV2Qh3eY/4ey4= github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc= github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc= github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= @@ -22,6 +26,8 @@ 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= github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/fogleman/ease v0.0.0-20170301025033-8da417bf1776 h1:VRIbnDWRmAh5yBdz+J6yFMF5vso1It6vn+WmM/5l7MA= github.com/fogleman/ease v0.0.0-20170301025033-8da417bf1776/go.mod h1:9wvnDu3YOfxzWM9Cst40msBF1C2UdQgDv962oTxSuMs= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= From 402d2b4e2b4c7b71ed5b0c29bea35c339baf8ad7 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 6 Mar 2023 14:39:55 +0100 Subject: [PATCH 31/89] fix: stop renderer before launching a child process. Stops the renderer before starting a child process, which prevents the repaint race condition that writes to non-altscreen. --- standard_renderer.go | 18 ++++++++++-------- tea.go | 7 +++++++ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/standard_renderer.go b/standard_renderer.go index edde56a40f..e4fb61ca9e 100644 --- a/standard_renderer.go +++ b/standard_renderer.go @@ -58,6 +58,7 @@ func newRenderer(out *termenv.Output, useANSICompressor bool) renderer { r := &standardRenderer{ out: out, mtx: &sync.Mutex{}, + done: make(chan struct{}), framerate: defaultFramerate, useANSICompressor: useANSICompressor, queuedMessageLines: []string{}, @@ -72,12 +73,15 @@ func newRenderer(out *termenv.Output, useANSICompressor bool) renderer { func (r *standardRenderer) start() { if r.ticker == nil { r.ticker = time.NewTicker(r.framerate) + } else { + // If the ticker already exists, it has been stopped and we need to + // reset it. + r.ticker.Reset(r.framerate) } // Since the renderer can be restarted after a stop, we need to reset // the done channel and its corresponding sync.Once. r.once = sync.Once{} - r.done = make(chan struct{}) go r.listen() } @@ -92,7 +96,7 @@ func (r *standardRenderer) stop() { r.out.ClearLine() r.once.Do(func() { - close(r.done) + r.done <- struct{}{} }) if r.useANSICompressor { @@ -109,7 +113,7 @@ func (r *standardRenderer) kill() { r.out.ClearLine() r.once.Do(func() { - close(r.done) + r.done <- struct{}{} }) } @@ -117,14 +121,12 @@ func (r *standardRenderer) kill() { func (r *standardRenderer) listen() { for { select { - case <-r.ticker.C: - if r.ticker != nil { - r.flush() - } case <-r.done: r.ticker.Stop() - r.ticker = nil return + + case <-r.ticker.C: + r.flush() } } } diff --git a/tea.go b/tea.go index 0c29a86b43..9132fffe2a 100644 --- a/tea.go +++ b/tea.go @@ -567,6 +567,10 @@ func (p *Program) ReleaseTerminal() error { p.cancelReader.Cancel() p.waitForReadLoop() + if p.renderer != nil { + p.renderer.stop() + } + p.altScreenWasActive = p.renderer.altScreen() return p.restoreTerminalState() } @@ -590,6 +594,9 @@ func (p *Program) RestoreTerminal() error { // entering alt screen already causes a repaint. go p.Send(repaintMsg{}) } + if p.renderer != nil { + p.renderer.start() + } // If the output is a terminal, it may have been resized while another // process was at the foreground, in which case we may not have received From f0389a218d36cc5e8b4bafdce988873ad65778b9 Mon Sep 17 00:00:00 2001 From: Omar kamoun <35850565+Nezz7@users.noreply.github.com> Date: Tue, 7 Mar 2023 20:50:41 +0100 Subject: [PATCH 32/89] feat(README): add eks-node-viewer to "Bubble Tea in the Wild" list (#619) * docs: add awslabs/eks-node-viewer to Bubble Tea in the Wild --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0e1bc488c8..68524a8fee 100644 --- a/README.md +++ b/README.md @@ -327,7 +327,8 @@ For some Bubble Tea programs in production, see: * [clidle](https://github.com/ajeetdsouza/clidle): a Wordle clone * [cLive](https://github.com/koki-develop/clive): automate terminal operations and view them live in a browser * [container-canary](https://github.com/NVIDIA/container-canary): a container validator -* [dns53](https://github.com/purpleclay/dns53): dynamic DNS with Amazon Route53: expose your EC2 quickly, securely and privately +* [dns53](https://github.com/purpleclay/dns53): dynamic DNS with Amazon Route53. Expose your EC2 quickly, securely and privately +* [eks-node-viewer](https://github.com/awslabs/eks-node-viewer): a tool for visualizing dynamic node usage within an eks cluster * [enola](https://github.com/sherlock-project/enola): hunt down social media accounts by username across social networks * [flapioca](https://github.com/kbrgl/flapioca): Flappy Bird on the CLI! * [fm](https://github.com/knipferrc/fm): a terminal-based file manager From 40923b449110566c50f27d4e36a444c7828b77a9 Mon Sep 17 00:00:00 2001 From: Andrian Budantsov Date: Thu, 16 Feb 2023 11:52:33 +0400 Subject: [PATCH 33/89] Add WG Commander to README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 68524a8fee..7f23467d3d 100644 --- a/README.md +++ b/README.md @@ -369,6 +369,7 @@ For some Bubble Tea programs in production, see: * [tz](https://github.com/oz/tz): an aid for scheduling across multiple time zones * [ugm](https://github.com/ariasmn/ugm): a unix user and group browser * [wander](https://github.com/robinovitch61/wander): a HashiCorp Nomad terminal client +* [WG Commander](https://github.com/AndrianBdn/wg-cmd): a TUI for a simple WireGuard VPN setup * [wishlist](https://github.com/charmbracelet/wishlist): an SSH directory ## Feedback From 03060def9f3c122e49de08974c402478724ef130 Mon Sep 17 00:00:00 2001 From: Vernon Miller Date: Sun, 26 Feb 2023 15:49:00 -0700 Subject: [PATCH 34/89] docs(readme): add link to countdown project --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7f23467d3d..0a88caa5fa 100644 --- a/README.md +++ b/README.md @@ -327,6 +327,7 @@ For some Bubble Tea programs in production, see: * [clidle](https://github.com/ajeetdsouza/clidle): a Wordle clone * [cLive](https://github.com/koki-develop/clive): automate terminal operations and view them live in a browser * [container-canary](https://github.com/NVIDIA/container-canary): a container validator +* [countdown](https://github.com/aldernero/countdown): a multi-event countdown timer * [dns53](https://github.com/purpleclay/dns53): dynamic DNS with Amazon Route53. Expose your EC2 quickly, securely and privately * [eks-node-viewer](https://github.com/awslabs/eks-node-viewer): a tool for visualizing dynamic node usage within an eks cluster * [enola](https://github.com/sherlock-project/enola): hunt down social media accounts by username across social networks From ec2c3dd5f262594804ba70a1d6fb9368bae77183 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Wed, 8 Mar 2023 09:51:43 -0300 Subject: [PATCH 35/89] docs: issue template (#389) --- .github/ISSUE_TEMPLATE/bug.yml | 61 +++++++++++++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 5 +++ 2 files changed, 66 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 0000000000..a9b0770050 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,61 @@ +name: Bug Report +description: File a bug report +labels: [bug] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! Please fill the form below. + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Also tell us, what did you expect to happen? + validations: + required: true + - type: textarea + id: reproducible + attributes: + label: How can we reproduce this? + description: | + Please share a code snippet, gist, or public repository that reproduces the issue. + Make sure to make the reproducible as concise as possible, + with only the minimum required code to reproduce the issue. + validations: + required: true + - type: textarea + id: version + attributes: + label: Which version of bubbletea are you using? + description: '' + render: bash + validations: + required: true + - type: textarea + id: terminaal + attributes: + label: Which terminals did you reproduce this with? + description: | + Other helpful information: + was it over SSH? + On tmux? + Which version of said terminal? + validations: + required: true + - type: checkboxes + id: search + attributes: + label: Search + options: + - label: | + I searched for other open and closed issues and pull requests before opening this, + and didn't find anything that seems related. + required: true + - type: textarea + id: ctx + attributes: + label: Additional context + description: Anything else you would like to add + validations: + required: false + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..e45597a79a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: true +contact_links: +- name: Slack + url: https://charm.sh/slack + about: Join our Slack From 02b6aa2e1a18d4ed784ff71e00f3bf248476030e Mon Sep 17 00:00:00 2001 From: Carlos A Becker Date: Wed, 8 Mar 2023 10:19:08 -0300 Subject: [PATCH 36/89] docs: point to discord link --- .github/ISSUE_TEMPLATE/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index e45597a79a..7a97ee87ba 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,5 @@ blank_issues_enabled: true contact_links: -- name: Slack - url: https://charm.sh/slack - about: Join our Slack +- name: Discord + url: https://charm.sh/discord + about: Join our Discord From de6740db2e06beb4eaf968cc7551f6d17c1b9024 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Wed, 8 Mar 2023 09:27:11 -0500 Subject: [PATCH 37/89] chore(meta): tiny copy edit to Discord item in issues --- .github/ISSUE_TEMPLATE/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 7a97ee87ba..897a394e03 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,4 +2,4 @@ blank_issues_enabled: true contact_links: - name: Discord url: https://charm.sh/discord - about: Join our Discord + about: Chat on our Discord. From adb0065256ced56090d0f9854aa094b3dabea7ac Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Thu, 9 Mar 2023 11:46:44 -0300 Subject: [PATCH 38/89] feat: LogToFileWith (#692) Allows to log to file with custom loggers, provided they implement SetOutput and SetPrefix. --- logging.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/logging.go b/logging.go index 97ffcb7097..59258d4c7c 100644 --- a/logging.go +++ b/logging.go @@ -1,6 +1,7 @@ package tea import ( + "io" "log" "os" "unicode" @@ -19,6 +20,18 @@ import ( // } // defer f.Close() func LogToFile(path string, prefix string) (*os.File, error) { + return LogToFileWith(path, prefix, log.Default()) +} + +// LogOptionsSetter is an interface implemented by stdlib's log and charm's log +// libraries. +type LogOptionsSetter interface { + SetOutput(io.Writer) + SetPrefix(string) +} + +// LogToFileWith does allows to call LogToFile with a custom LogOptionsSetter. +func LogToFileWith(path string, prefix string, log LogOptionsSetter) (*os.File, error) { f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644) if err != nil { return nil, err From 35c31f21681d4198888078f9929d5d61d6d7fc20 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 13 Mar 2023 18:02:57 +0100 Subject: [PATCH 39/89] chore: bump console dep --- examples/go.mod | 4 ++-- examples/go.sum | 10 +++------- go.mod | 4 ++-- go.sum | 8 ++++---- tutorials/go.mod | 4 ++-- tutorials/go.sum | 8 ++++---- 6 files changed, 17 insertions(+), 21 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index 7f518631d0..a76cc18089 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -8,6 +8,7 @@ require ( github.com/charmbracelet/glamour v0.6.0 github.com/charmbracelet/harmonica v0.2.0 github.com/charmbracelet/lipgloss v0.6.0 + github.com/dustin/go-humanize v1.0.1 github.com/fogleman/ease v0.0.0-20170301025033-8da417bf1776 github.com/lucasb-eyer/go-colorful v1.2.0 github.com/mattn/go-isatty v0.0.17 @@ -20,9 +21,8 @@ require ( github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52 v1.2.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect - github.com/containerd/console v1.0.3 // indirect + github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect - 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 diff --git a/examples/go.sum b/examples/go.sum index 729ba0ddff..fb495df565 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -7,10 +7,6 @@ github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTx github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= -github.com/charmbracelet/bubbles v0.15.1-0.20230303180827-c01b49080882 h1:bhZOZDpFHFKZhROCENdN+cUHNknH3MY8CUE2xJ9a3HM= -github.com/charmbracelet/bubbles v0.15.1-0.20230303180827-c01b49080882/go.mod h1:39HL8bnL0foloiENA/KvD+3mNg5SqWQV2Qh3eY/4ey4= -github.com/charmbracelet/bubbles v0.15.1-0.20230303214459-958a0ea710f1 h1:BcxSgchb4ZV1lJe9W9+LhQj6S3JouC39yYswVI4YOrw= -github.com/charmbracelet/bubbles v0.15.1-0.20230303214459-958a0ea710f1/go.mod h1:39HL8bnL0foloiENA/KvD+3mNg5SqWQV2Qh3eY/4ey4= github.com/charmbracelet/bubbles v0.15.1-0.20230306155959-3372cf1aea2b h1:K9dWJ2spDhDhIrqnchjG867djPxWWe3mwdk6RdLMfhg= github.com/charmbracelet/bubbles v0.15.1-0.20230306155959-3372cf1aea2b/go.mod h1:39HL8bnL0foloiENA/KvD+3mNg5SqWQV2Qh3eY/4ey4= github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc= @@ -19,8 +15,8 @@ github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= github.com/charmbracelet/lipgloss v0.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87inij9N3wJY= github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk= -github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= -github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= +github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= 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= @@ -94,7 +90,6 @@ golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -102,6 +97,7 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/go.mod b/go.mod index 0111e6511f..f5fa556809 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/charmbracelet/bubbletea go 1.17 require ( - github.com/containerd/console v1.0.3 + github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 github.com/mattn/go-isatty v0.0.17 github.com/mattn/go-localereader v0.0.1 github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b @@ -19,6 +19,6 @@ require ( github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/rivo/uniseg v0.2.0 // indirect - golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect + golang.org/x/sys v0.1.0 // indirect golang.org/x/text v0.3.8 // indirect ) diff --git a/go.sum b/go.sum index e915ca833c..7dd66247ea 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E= github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= -github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= -github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= +github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= 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.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= @@ -36,13 +36,13 @@ golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.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 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/tutorials/go.mod b/tutorials/go.mod index 0074938b4e..ebc9effe9d 100644 --- a/tutorials/go.mod +++ b/tutorials/go.mod @@ -6,7 +6,7 @@ require github.com/charmbracelet/bubbletea v0.23.2 require ( github.com/aymanbagabas/go-osc52 v1.2.1 // indirect - github.com/containerd/console v1.0.3 // indirect + github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-localereader v0.0.1 // indirect @@ -17,7 +17,7 @@ require ( github.com/muesli/termenv v0.14.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect + golang.org/x/sys v0.1.0 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.8 // indirect ) diff --git a/tutorials/go.sum b/tutorials/go.sum index e915ca833c..7dd66247ea 100644 --- a/tutorials/go.sum +++ b/tutorials/go.sum @@ -1,7 +1,7 @@ github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E= github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= -github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= -github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= +github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= 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.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= @@ -36,13 +36,13 @@ golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.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 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= From 9c161cb98b01af3225490465f81573de026f870c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Mar 2023 17:22:41 +0000 Subject: [PATCH 40/89] chore(deps): bump actions/setup-go from 3 to 4 Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3 to 4. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 2 +- .github/workflows/coverage.yml | 2 +- .github/workflows/examples.yml | 2 +- .github/workflows/lint-soft.yml | 2 +- .github/workflows/lint.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b662a60b5d..cef3f37e1e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: GO111MODULE: "on" steps: - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: ${{ matrix.go-version }} diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index bcc6721f12..845096e44c 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -12,7 +12,7 @@ jobs: GO111MODULE: "on" steps: - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: ${{ matrix.go-version }} diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index eed1bbc7bc..f027cf2182 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 + - uses: actions/setup-go@v4 with: go-version: '^1' cache: true diff --git a/.github/workflows/lint-soft.yml b/.github/workflows/lint-soft.yml index 77348b95e0..6230528921 100644 --- a/.github/workflows/lint-soft.yml +++ b/.github/workflows/lint-soft.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: ^1 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 1129ece62e..2f26b2ac2a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: ^1 From 248eb83001a7ab6a11c80534bd4e3e96a6185f4a Mon Sep 17 00:00:00 2001 From: muesli Date: Thu, 16 Mar 2023 10:09:43 +0000 Subject: [PATCH 41/89] chore: go mod tidy tutorials and examples --- examples/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/go.mod b/examples/go.mod index a76cc18089..4aec7b93a9 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -8,7 +8,6 @@ require ( github.com/charmbracelet/glamour v0.6.0 github.com/charmbracelet/harmonica v0.2.0 github.com/charmbracelet/lipgloss v0.6.0 - github.com/dustin/go-humanize v1.0.1 github.com/fogleman/ease v0.0.0-20170301025033-8da417bf1776 github.com/lucasb-eyer/go-colorful v1.2.0 github.com/mattn/go-isatty v0.0.17 @@ -23,6 +22,7 @@ require ( github.com/aymerick/douceur v0.2.0 // indirect github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect + 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 From 6eb0209d13f0dae88e37459a45ae5209eb318786 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Tue, 4 Apr 2023 13:04:19 +0200 Subject: [PATCH 42/89] chore: bump termenv, lipgloss, x/term --- examples/go.mod | 14 +++++++------- examples/go.sum | 18 ++++++++++++------ go.mod | 10 +++++----- go.sum | 17 ++++++++++------- tutorials/go.mod | 10 +++++----- tutorials/go.sum | 15 ++++++++++----- 6 files changed, 49 insertions(+), 35 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index 4aec7b93a9..1183e5ea21 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -7,22 +7,22 @@ require ( github.com/charmbracelet/bubbletea v0.23.2 github.com/charmbracelet/glamour v0.6.0 github.com/charmbracelet/harmonica v0.2.0 - github.com/charmbracelet/lipgloss v0.6.0 + github.com/charmbracelet/lipgloss v0.7.1 + github.com/dustin/go-humanize v1.0.1 github.com/fogleman/ease v0.0.0-20170301025033-8da417bf1776 github.com/lucasb-eyer/go-colorful v1.2.0 - github.com/mattn/go-isatty v0.0.17 + github.com/mattn/go-isatty v0.0.18 github.com/muesli/reflow v0.3.0 - github.com/muesli/termenv v0.14.0 + github.com/muesli/termenv v0.15.1 ) require ( github.com/alecthomas/chroma v0.10.0 // indirect github.com/atotto/clipboard v0.1.4 // indirect - github.com/aymanbagabas/go-osc52 v1.2.1 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect - 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 @@ -36,8 +36,8 @@ require ( github.com/yuin/goldmark-emoji v1.0.1 // indirect golang.org/x/net v0.7.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/term v0.5.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/term v0.6.0 // indirect golang.org/x/text v0.7.0 // indirect ) diff --git a/examples/go.sum b/examples/go.sum index fb495df565..cce9745377 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -5,6 +5,8 @@ github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E= github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= +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/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/charmbracelet/bubbles v0.15.1-0.20230306155959-3372cf1aea2b h1:K9dWJ2spDhDhIrqnchjG867djPxWWe3mwdk6RdLMfhg= @@ -13,8 +15,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.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87inij9N3wJY= github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk= +github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E= +github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c= github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -34,8 +37,9 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69 github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= @@ -55,8 +59,8 @@ 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.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= -github.com/muesli/termenv v0.14.0 h1:8x9NFfOe8lmIWK4pgy3IfVEy47f+ppe3tUqdPZG2Uy0= -github.com/muesli/termenv v0.14.0/go.mod h1:kG/pF1E7fh949Xhe156crRUrHNyK221IuGO7Ez60Uc8= +github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= +github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -98,12 +102,14 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.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 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= 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/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= diff --git a/go.mod b/go.mod index f5fa556809..86f90679aa 100644 --- a/go.mod +++ b/go.mod @@ -4,21 +4,21 @@ go 1.17 require ( github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 - github.com/mattn/go-isatty v0.0.17 + github.com/mattn/go-isatty v0.0.18 github.com/mattn/go-localereader v0.0.1 github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b github.com/muesli/cancelreader v0.2.2 github.com/muesli/reflow v0.3.0 - github.com/muesli/termenv v0.14.0 + github.com/muesli/termenv v0.15.1 golang.org/x/sync v0.1.0 - golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 + golang.org/x/term v0.6.0 ) require ( - github.com/aymanbagabas/go-osc52 v1.2.1 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/rivo/uniseg v0.2.0 // indirect - golang.org/x/sys v0.1.0 // indirect + golang.org/x/sys v0.6.0 // indirect golang.org/x/text v0.3.8 // indirect ) diff --git a/go.sum b/go.sum index 7dd66247ea..77ace75615 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,12 @@ -github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E= -github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= +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/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= 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.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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= @@ -18,8 +19,8 @@ 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.14.0 h1:8x9NFfOe8lmIWK4pgy3IfVEy47f+ppe3tUqdPZG2Uy0= -github.com/muesli/termenv v0.14.0/go.mod h1:kG/pF1E7fh949Xhe156crRUrHNyK221IuGO7Ez60Uc8= +github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= +github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -41,11 +42,13 @@ golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.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 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= 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= diff --git a/tutorials/go.mod b/tutorials/go.mod index ebc9effe9d..5933d123ca 100644 --- a/tutorials/go.mod +++ b/tutorials/go.mod @@ -5,20 +5,20 @@ go 1.17 require github.com/charmbracelet/bubbletea v0.23.2 require ( - github.com/aymanbagabas/go-osc52 v1.2.1 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/reflow v0.3.0 // indirect - github.com/muesli/termenv v0.14.0 // indirect + github.com/muesli/termenv v0.15.1 // indirect github.com/rivo/uniseg v0.2.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.1.0 // indirect - golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/term v0.6.0 // indirect golang.org/x/text v0.3.8 // indirect ) diff --git a/tutorials/go.sum b/tutorials/go.sum index 7dd66247ea..484e442e86 100644 --- a/tutorials/go.sum +++ b/tutorials/go.sum @@ -1,11 +1,14 @@ github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E= github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= +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/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= 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.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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= @@ -18,8 +21,8 @@ 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.14.0 h1:8x9NFfOe8lmIWK4pgy3IfVEy47f+ppe3tUqdPZG2Uy0= -github.com/muesli/termenv v0.14.0/go.mod h1:kG/pF1E7fh949Xhe156crRUrHNyK221IuGO7Ez60Uc8= +github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= +github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -41,11 +44,13 @@ golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.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 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= 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= From 273f59bd1126efddfb4f2481d09e6e344034960f Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Tue, 4 Apr 2023 13:07:04 +0200 Subject: [PATCH 43/89] fix: adapt to lipgloss API change --- examples/list-simple/main.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/list-simple/main.go b/examples/list-simple/main.go index 9208d059fb..8cc42909af 100644 --- a/examples/list-simple/main.go +++ b/examples/list-simple/main.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "os" + "strings" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" @@ -40,8 +41,8 @@ func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list fn := itemStyle.Render if index == m.Index() { - fn = func(s string) string { - return selectedItemStyle.Render("> " + s) + fn = func(s ...string) string { + return selectedItemStyle.Render("> " + strings.Join(s, " ")) } } From 58c5e01d8c90f6788f0aaff7e2f133f6c0fb64ae Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Tue, 4 Apr 2023 13:18:50 +0200 Subject: [PATCH 44/89] chore: fix linter warnings --- nil_renderer.go | 2 +- tea.go | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/nil_renderer.go b/nil_renderer.go index a0226364e7..f5637aa47b 100644 --- a/nil_renderer.go +++ b/nil_renderer.go @@ -5,7 +5,7 @@ type nilRenderer struct{} func (n nilRenderer) start() {} func (n nilRenderer) stop() {} func (n nilRenderer) kill() {} -func (n nilRenderer) write(v string) {} +func (n nilRenderer) write(_ string) {} func (n nilRenderer) repaint() {} func (n nilRenderer) clearScreen() {} func (n nilRenderer) altScreen() bool { return false } diff --git a/tea.go b/tea.go index 9132fffe2a..76c9b3be55 100644 --- a/tea.go +++ b/tea.go @@ -314,6 +314,7 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) { if cmd == nil { continue } + msg := cmd() if batchMsg, ok := msg.(BatchMsg); ok { g, _ := errgroup.WithContext(p.ctx) @@ -324,12 +325,13 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) { return nil }) } + //nolint:errcheck g.Wait() // wait for all commands from batch msg to finish continue - } else { - p.Send(msg) } + + p.Send(msg) } }() } From 5b809a3da86a63d2f07372b1941e8058178dad4d Mon Sep 17 00:00:00 2001 From: bashbunni <15822994+bashbunni@users.noreply.github.com> Date: Thu, 6 Apr 2023 10:34:23 -0700 Subject: [PATCH 45/89] docs: update issue templates (#712) * docs: update issue templates * add source code instructions --- .github/ISSUE_TEMPLATE/bug_report.md | 37 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000..6d77155600 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,37 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**Setup** +Please complete the following information along with version numbers, if applicable. + - OS [e.g. Ubuntu, Mac OS] + - Shell [e.g. zsh, fish] + - Terminal Emulator [e.g. kitty, iterm] + - Terminal Multiplexer [e.g. tmux] + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Source Code** +Please include source code if needed to reproduce the behaviour. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +Add screenshots to help explain your problem. + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000..11fc491ef1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From 8514d90b9e74d6b91358dca5a03dda17e4d862d3 Mon Sep 17 00:00:00 2001 From: bashbunni <15822994+bashbunni@users.noreply.github.com> Date: Tue, 11 Apr 2023 12:48:34 -0700 Subject: [PATCH 46/89] docs: remove british spelling (#719) --- .github/ISSUE_TEMPLATE/bug_report.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 6d77155600..0e7a6cb540 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -12,7 +12,7 @@ A clear and concise description of what the bug is. **Setup** Please complete the following information along with version numbers, if applicable. - - OS [e.g. Ubuntu, Mac OS] + - OS [e.g. Ubuntu, macOS] - Shell [e.g. zsh, fish] - Terminal Emulator [e.g. kitty, iterm] - Terminal Multiplexer [e.g. tmux] @@ -25,7 +25,7 @@ Steps to reproduce the behavior: 4. See error **Source Code** -Please include source code if needed to reproduce the behaviour. +Please include source code if needed to reproduce the behavior. **Expected behavior** A clear and concise description of what you expected to happen. From c56884c0e247cafa6bfa3e222aa512fe6d6a292e Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 17 Apr 2023 22:02:55 +0200 Subject: [PATCH 47/89] feat: add generic event filter (#536) `WithFilter` lets you supply an event filter that will be invoked before Bubble Tea processes a `tea.Msg`. The event filter can return any `tea.Msg` which will then get handled by Bubble Tea instead of the original event. If the event filter returns nil, the event will be ignored and Bubble Tea will not process it. As an example, this could be used to prevent a program from shutting down if there are unsaved changes. Based on the fantastic work by @aschey and supersedes #521. Resolves #472. --- examples/prevent-quit/main.go | 154 ++++++++++++++++++++++++++++++++++ options.go | 35 ++++++++ options_test.go | 7 ++ tea.go | 22 +++-- tea_test.go | 41 +++++++++ 5 files changed, 253 insertions(+), 6 deletions(-) create mode 100644 examples/prevent-quit/main.go diff --git a/examples/prevent-quit/main.go b/examples/prevent-quit/main.go new file mode 100644 index 0000000000..1339393937 --- /dev/null +++ b/examples/prevent-quit/main.go @@ -0,0 +1,154 @@ +package main + +// A program demonstrating how to use the WithFilter option to intercept events. + +import ( + "fmt" + "log" + + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/textarea" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) + +var ( + choiceStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(lipgloss.Color("241")) + saveTextStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("170")) + quitViewStyle = lipgloss.NewStyle().Padding(1).Border(lipgloss.RoundedBorder()).BorderForeground(lipgloss.Color("170")) +) + +func main() { + p := tea.NewProgram(initialModel(), tea.WithFilter(filter)) + + if _, err := p.Run(); err != nil { + log.Fatal(err) + } +} + +func filter(teaModel tea.Model, msg tea.Msg) tea.Msg { + if _, ok := msg.(tea.QuitMsg); !ok { + return msg + } + + m := teaModel.(model) + if m.hasChanges { + return nil + } + + return msg +} + +type model struct { + textarea textarea.Model + help help.Model + keymap keymap + saveText string + hasChanges bool + quitting bool +} + +type keymap struct { + save key.Binding + quit key.Binding +} + +func initialModel() model { + ti := textarea.New() + ti.Placeholder = "Only the best words" + ti.Focus() + + return model{ + textarea: ti, + help: help.NewModel(), + keymap: keymap{ + save: key.NewBinding( + key.WithKeys("ctrl+s"), + key.WithHelp("ctrl+s", "save"), + ), + quit: key.NewBinding( + key.WithKeys("esc", "ctrl+c"), + key.WithHelp("esc", "quit"), + ), + }, + } +} + +func (m model) Init() tea.Cmd { + return textarea.Blink +} + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + if m.quitting { + return m.updatePromptView(msg) + } + + return m.updateTextView(msg) +} + +func (m model) updateTextView(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmds []tea.Cmd + var cmd tea.Cmd + + switch msg := msg.(type) { + case tea.KeyMsg: + m.saveText = "" + switch { + case key.Matches(msg, m.keymap.save): + m.saveText = "Changes saved!" + m.hasChanges = false + case key.Matches(msg, m.keymap.quit): + m.quitting = true + return m, tea.Quit + case msg.Type == tea.KeyRunes: + m.saveText = "" + m.hasChanges = true + fallthrough + default: + if !m.textarea.Focused() { + cmd = m.textarea.Focus() + cmds = append(cmds, cmd) + } + } + } + m.textarea, cmd = m.textarea.Update(msg) + cmds = append(cmds, cmd) + return m, tea.Batch(cmds...) +} + +func (m model) updatePromptView(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + // For simplicity's sake, we'll treat any key besides "y" as "no" + if key.Matches(msg, m.keymap.quit) || msg.String() == "y" { + m.hasChanges = false + return m, tea.Quit + } + m.quitting = false + } + + return m, nil +} + +func (m model) View() string { + if m.quitting { + if m.hasChanges { + text := lipgloss.JoinHorizontal(lipgloss.Top, "You have unsaved changes. Quit without saving?", choiceStyle.Render("[yn]")) + return quitViewStyle.Render(text) + } + return "Very important, thank you\n" + } + + helpView := m.help.ShortHelpView([]key.Binding{ + m.keymap.save, + m.keymap.quit, + }) + + return fmt.Sprintf( + "\nType some important things.\n\n%s\n\n %s\n %s", + m.textarea.View(), + saveTextStyle.Render(m.saveText), + helpView, + ) + "\n\n" +} diff --git a/options.go b/options.go index 9945924c46..17d05ba447 100644 --- a/options.go +++ b/options.go @@ -151,3 +151,38 @@ func WithANSICompressor() ProgramOption { p.startupOptions |= withANSICompressor } } + +// WithFilter supplies an event filter that will be invoked before Bubble Tea +// processes a tea.Msg. The event filter can return any tea.Msg which will then +// get handled by Bubble Tea instead of the original event. If the event filter +// returns nil, the event will be ignored and Bubble Tea will not process it. +// +// As an example, this could be used to prevent a program from shutting down if +// there are unsaved changes. +// +// Example: +// +// func filter(m tea.Model, msg tea.Msg) tea.Msg { +// if _, ok := msg.(tea.QuitMsg); !ok { +// return msg +// } +// +// model := m.(myModel) +// if model.hasChanges { +// return nil +// } +// +// return msg +// } +// +// p := tea.NewProgram(Model{}, tea.WithFilter(filter)); +// +// if _,err := p.Run(); err != nil { +// fmt.Println("Error running program:", err) +// os.Exit(1) +// } +func WithFilter(filter func(Model, Msg) Msg) ProgramOption { + return func(p *Program) { + p.filter = filter + } +} diff --git a/options_test.go b/options_test.go index 7e08f58eee..71a3c6c6d0 100644 --- a/options_test.go +++ b/options_test.go @@ -35,6 +35,13 @@ func TestOptions(t *testing.T) { } }) + t.Run("filter", func(t *testing.T) { + p := NewProgram(nil, WithFilter(func(_ Model, msg Msg) Msg { return msg })) + if p.filter == nil { + t.Errorf("expected filter to be set") + } + }) + t.Run("startup options", func(t *testing.T) { exercise := func(t *testing.T, opt ProgramOption, expect startupOptions) { p := NewProgram(nil, opt) diff --git a/tea.go b/tea.go index 76c9b3be55..f5ae4e9e0e 100644 --- a/tea.go +++ b/tea.go @@ -123,16 +123,18 @@ type Program struct { // as this value only comes into play on Windows, hence the ignore comment // below. windowsStdin *os.File //nolint:golint,structcheck,unused + + filter func(Model, Msg) Msg } // Quit is a special command that tells the Bubble Tea program to exit. func Quit() Msg { - return quitMsg{} + return QuitMsg{} } -// quitMsg in an internal message signals that the program should quit. You can -// send a quitMsg with Quit. -type quitMsg struct{} +// QuitMsg signals that the program should quit. You can send a QuitMsg with +// Quit. +type QuitMsg struct{} // NewProgram creates a new Program. func NewProgram(model Model, opts ...ProgramOption) *Program { @@ -194,7 +196,7 @@ func (p *Program) handleSignals() chan struct{} { case <-sig: if !p.ignoreSignals { - p.msgs <- quitMsg{} + p.msgs <- QuitMsg{} return } } @@ -267,9 +269,17 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) { return model, err case msg := <-p.msgs: + // Filter messages. + if p.filter != nil { + msg = p.filter(model, msg) + } + if msg == nil { + continue + } + // Handle special internal messages. switch msg := msg.(type) { - case quitMsg: + case QuitMsg: return model, nil case clearScreenMsg: diff --git a/tea_test.go b/tea_test.go index 748b4cb7a5..d65f0d7fe3 100644 --- a/tea_test.go +++ b/tea_test.go @@ -77,6 +77,47 @@ func TestTeaQuit(t *testing.T) { } } +func TestTeaWithFilter(t *testing.T) { + testTeaWithFilter(t, 0) + testTeaWithFilter(t, 1) + testTeaWithFilter(t, 2) +} + +func testTeaWithFilter(t *testing.T, preventCount uint32) { + var buf bytes.Buffer + var in bytes.Buffer + + m := &testModel{} + shutdowns := uint32(0) + p := NewProgram(m, + WithInput(&in), + WithOutput(&buf), + WithFilter(func(_ Model, msg Msg) Msg { + if _, ok := msg.(QuitMsg); !ok { + return msg + } + if shutdowns < preventCount { + atomic.AddUint32(&shutdowns, 1) + return nil + } + return msg + })) + + go func() { + for atomic.LoadUint32(&shutdowns) <= preventCount { + time.Sleep(time.Millisecond) + p.Quit() + } + }() + + if err := p.Start(); err != nil { + t.Fatal(err) + } + if shutdowns != preventCount { + t.Errorf("Expected %d prevented shutdowns, got %d", preventCount, shutdowns) + } +} + func TestTeaKill(t *testing.T) { var buf bytes.Buffer var in bytes.Buffer From 29254a08f1458a1ba43db4d2b902982f10758c71 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Wed, 26 Apr 2023 13:04:14 -0700 Subject: [PATCH 48/89] fix(output): reuse termenv output (#715) If the passed io.Writer is a termenv.Output, use it instead of creating a new termenv.Output. --- options.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/options.go b/options.go index 17d05ba447..a0607228c3 100644 --- a/options.go +++ b/options.go @@ -28,7 +28,11 @@ func WithContext(ctx context.Context) ProgramOption { // won't need to use this. func WithOutput(output io.Writer) ProgramOption { return func(p *Program) { - p.output = termenv.NewOutput(output, termenv.WithColorCache(true)) + if o, ok := output.(*termenv.Output); ok { + p.output = o + } else { + p.output = termenv.NewOutput(output, termenv.WithColorCache(true)) + } } } From 9cc3861babc8f54ab963f454caa09b8c1ad0a60b Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Thu, 4 May 2023 16:30:30 -0300 Subject: [PATCH 49/89] feat: tea.Wait (#722) * feat: tea.Wait wait for the underlying context to finish. extract from #352 * fix: wait til the end of shutdown Signed-off-by: Carlos Alexandro Becker --------- Signed-off-by: Carlos Alexandro Becker --- tea.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tea.go b/tea.go index f5ae4e9e0e..e61bb5a6ae 100644 --- a/tea.go +++ b/tea.go @@ -97,8 +97,9 @@ type Program struct { ctx context.Context cancel context.CancelFunc - msgs chan Msg - errs chan error + msgs chan Msg + errs chan error + finished chan struct{} // where to send output, this will usually be os.Stdout. output *termenv.Output @@ -366,6 +367,7 @@ func (p *Program) Run() (Model, error) { handlers := handlers{} cmds := make(chan Cmd) p.errs = make(chan error) + p.finished = make(chan struct{}, 1) defer p.cancel() @@ -555,6 +557,11 @@ func (p *Program) Kill() { p.cancel() } +// Wait waits/blocks until the underlying Program finished shutting down. +func (p *Program) Wait() { + <-p.finished +} + // shutdown performs operations to free up resources and restore the terminal // to its original state. func (p *Program) shutdown(kill bool) { @@ -570,6 +577,7 @@ func (p *Program) shutdown(kill bool) { if p.restoreOutput != nil { _ = p.restoreOutput() } + p.finished <- struct{}{} } // ReleaseTerminal restores the original terminal state and cancels the input From 5326d76c402abcd4cc1ccbd9f354fda295363cd5 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Thu, 4 May 2023 16:33:25 -0300 Subject: [PATCH 50/89] feat: allow to disable signals (#721) Signed-off-by: Carlos Alexandro Becker Signed-off-by: Carlos A Becker --- options.go | 8 ++++++++ options_test.go | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/options.go b/options.go index a0607228c3..7a63c67b1b 100644 --- a/options.go +++ b/options.go @@ -70,6 +70,14 @@ func WithoutCatchPanics() ProgramOption { } } +// WithoutSignals will ignore OS signals. +// This is mainly useful for testing. +func WithoutSignals() ProgramOption { + return func(p *Program) { + p.ignoreSignals = true + } +} + // WithAltScreen starts the program with the alternate screen buffer enabled // (i.e. the program starts in full window mode). Note that the altscreen will // be automatically exited when the program quits. diff --git a/options_test.go b/options_test.go index 71a3c6c6d0..a66483d713 100644 --- a/options_test.go +++ b/options_test.go @@ -35,6 +35,13 @@ func TestOptions(t *testing.T) { } }) + t.Run("without signals", func(t *testing.T) { + p := NewProgram(nil, WithoutSignals()) + if !p.ignoreSignals { + t.Errorf("ignore signals should have been set") + } + }) + t.Run("filter", func(t *testing.T) { p := NewProgram(nil, WithFilter(func(_ Model, msg Msg) Msg { return msg })) if p.filter == nil { From 25022e9789dd7038bb87cb2c37c2d758c7cbbe05 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Fri, 5 May 2023 16:14:26 -0300 Subject: [PATCH 51/89] example: using the x/exp/teatest package (#352) teatest was originally designed in this PR, and was later moved into `github.com/charmbracelet/x`. * docs: example test Signed-off-by: Carlos A Becker * feat: teatest Signed-off-by: Carlos A Becker * fix: improve api Signed-off-by: Carlos A Becker * fix: improve api Signed-off-by: Carlos A Becker * feat: goldenfiles Signed-off-by: Carlos A Becker * chore: minor improvements Signed-off-by: Carlos A Becker * feat: type text, diff Signed-off-by: Carlos A Becker * fix: release terminal Signed-off-by: Carlos A Becker * fix: lint Signed-off-by: Carlos A Becker * fix: update cancelreader Signed-off-by: Carlos A Becker * fix: make it safe Signed-off-by: Carlos A Becker * feat: functional options Signed-off-by: Carlos A Becker * feat: IsQuit and IsQuitMsg * fix: save file Signed-off-by: Carlos A Becker * fix: do not use deprecate func Signed-off-by: Carlos A Becker * fix: make diff not complain about trailing whitespaces Signed-off-by: Carlos A Becker * feat: with term size Signed-off-by: Carlos A Becker * feat: RequireRegexOutput Signed-off-by: Carlos A Becker * fix: update Signed-off-by: Carlos A Becker * chore: rename Signed-off-by: Carlos A Becker * fix: improve reliability * fix: use returned model * fix: making it more predictable, avoid sleeps * fix: remove WithRequiredRegexpOutput Signed-off-by: Carlos A Becker * fix: allow to assert within interactions * feat: added wait for Signed-off-by: Carlos A Becker * fix: optional * feat: improve usage Signed-off-by: Carlos A Becker * fix: use udiff Signed-off-by: Carlos A Becker * feat: tea.Wait wait for the underlying context to finish. extract from #352 * fix: merge Signed-off-by: Carlos A Becker * fix: wait til the end of shutdown Signed-off-by: Carlos Alexandro Becker * fix: final output * feat: use x/exp/teatest Signed-off-by: Carlos Alexandro Becker * chore: go mod tidy Signed-off-by: Carlos Alexandro Becker --------- Signed-off-by: Carlos A Becker Signed-off-by: Carlos Alexandro Becker --- examples/go.mod | 6 +- examples/go.sum | 6 ++ examples/simple/main_test.go | 86 +++++++++++++++++++++++++ examples/simple/testdata/TestApp.golden | 3 + 4 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 examples/simple/main_test.go create mode 100644 examples/simple/testdata/TestApp.golden diff --git a/examples/go.mod b/examples/go.mod index 1183e5ea21..4d12f7637d 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -4,11 +4,11 @@ go 1.17 require ( github.com/charmbracelet/bubbles v0.15.1-0.20230306155959-3372cf1aea2b - github.com/charmbracelet/bubbletea v0.23.2 + github.com/charmbracelet/bubbletea v0.23.3-0.20230504193325-5326d76c402a github.com/charmbracelet/glamour v0.6.0 github.com/charmbracelet/harmonica v0.2.0 github.com/charmbracelet/lipgloss v0.7.1 - github.com/dustin/go-humanize v1.0.1 + github.com/charmbracelet/x/exp/teatest v0.0.0-20230505175841-25a09ea9b713 github.com/fogleman/ease v0.0.0-20170301025033-8da417bf1776 github.com/lucasb-eyer/go-colorful v1.2.0 github.com/mattn/go-isatty v0.0.18 @@ -20,9 +20,11 @@ require ( github.com/alecthomas/chroma v0.10.0 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/aymanbagabas/go-udiff v0.1.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect + 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 diff --git a/examples/go.sum b/examples/go.sum index cce9745377..bc3e500360 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -7,6 +7,8 @@ github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTx github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= 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/aymanbagabas/go-udiff v0.1.0 h1:9Dpklm2oBBhMxIFbMffmPvDaF7vOYfv9B5HXVr42KMU= +github.com/aymanbagabas/go-udiff v0.1.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/charmbracelet/bubbles v0.15.1-0.20230306155959-3372cf1aea2b h1:K9dWJ2spDhDhIrqnchjG867djPxWWe3mwdk6RdLMfhg= @@ -18,6 +20,10 @@ github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJ github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk= github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E= github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c= +github.com/charmbracelet/x/exp/teatest v0.0.0-20230504195420-fee909365d1c h1:3Dha70yq12co31grOoSMOaBIo8POU4ARhvp9W8RsOsw= +github.com/charmbracelet/x/exp/teatest v0.0.0-20230504195420-fee909365d1c/go.mod h1:dpMo6PfGlhavn+ofggWhfFQmK9sZB2Ewljiz9bZtKVI= +github.com/charmbracelet/x/exp/teatest v0.0.0-20230505175841-25a09ea9b713 h1:aRUlesfHrzj9NECuQ1O2h1yBK5M4LFKwLYTQVoEkaQ8= +github.com/charmbracelet/x/exp/teatest v0.0.0-20230505175841-25a09ea9b713/go.mod h1:dpMo6PfGlhavn+ofggWhfFQmK9sZB2Ewljiz9bZtKVI= github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/examples/simple/main_test.go b/examples/simple/main_test.go new file mode 100644 index 0000000000..f736de754b --- /dev/null +++ b/examples/simple/main_test.go @@ -0,0 +1,86 @@ +package main + +import ( + "bytes" + "io" + "regexp" + "testing" + "time" + + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/x/exp/teatest" +) + +func TestApp(t *testing.T) { + m := model(10) + tm := teatest.NewTestModel( + t, m, + teatest.WithInitialTermSize(70, 30), + ) + t.Cleanup(func() { + if err := tm.Quit(); err != nil { + t.Fatal(err) + } + }) + + time.Sleep(time.Second + time.Millisecond*200) + tm.Type("I'm typing things, but it'll be ignored by my program") + tm.Send("ignored msg") + tm.Send(tea.KeyMsg{ + Type: tea.KeyEnter, + }) + + if err := tm.Quit(); err != nil { + t.Fatal(err) + } + + out := readBts(t, tm.FinalOutput()) + if !regexp.MustCompile(`This program will exit in \d+ seconds`).Match(out) { + t.Fatalf("output does not match the given regular expression: %s", string(out)) + } + teatest.RequireEqualOutput(t, out) + + if tm.FinalModel().(model) != 9 { + t.Errorf("expected model to be 10, was %d", m) + } +} + +func TestAppInteractive(t *testing.T) { + m := model(10) + tm := teatest.NewTestModel( + t, m, + teatest.WithInitialTermSize(70, 30), + ) + + time.Sleep(time.Second + time.Millisecond*200) + tm.Send("ignored msg") + + if bts := readBts(t, tm.Output()); !bytes.Contains(bts, []byte("This program will exit in 9 seconds")) { + t.Fatalf("output does not match: expected %q", string(bts)) + } + + teatest.WaitFor(t, tm.Output(), func(out []byte) bool { + return bytes.Contains(out, []byte("This program will exit in 7 seconds")) + }, teatest.WithDuration(5*time.Second)) + + tm.Send(tea.KeyMsg{ + Type: tea.KeyEnter, + }) + + if err := tm.Quit(); err != nil { + t.Fatal(err) + } + + if tm.FinalModel().(model) != 7 { + t.Errorf("expected model to be 7, was %d", m) + } +} + +func readBts(tb testing.TB, r io.Reader) []byte { + tb.Helper() + bts, err := io.ReadAll(r) + if err != nil { + tb.Fatal(err) + } + return bts +} diff --git a/examples/simple/testdata/TestApp.golden b/examples/simple/testdata/TestApp.golden new file mode 100644 index 0000000000..0a973b283e --- /dev/null +++ b/examples/simple/testdata/TestApp.golden @@ -0,0 +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 \ No newline at end of file From fcc805f3da68f535f7c3c78b94b4b14cd22a047a Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Fri, 5 May 2023 14:55:25 -0400 Subject: [PATCH 52/89] chore: make input options mutually exclusive --- options.go | 8 +++++--- options_test.go | 32 +++++++++++++++++++++++++------- tea.go | 32 ++++++++++++++++++++++++++------ 3 files changed, 56 insertions(+), 16 deletions(-) diff --git a/options.go b/options.go index 7a63c67b1b..30f7e6c50b 100644 --- a/options.go +++ b/options.go @@ -37,18 +37,20 @@ func WithOutput(output io.Writer) ProgramOption { } // WithInput sets the input which, by default, is stdin. In most cases you -// won't need to use this. +// won't need to use this. To disable input entirely pass nil. +// +// p := NewProgram(model, WithInput(nil)) func WithInput(input io.Reader) ProgramOption { return func(p *Program) { p.input = input - p.startupOptions |= withCustomInput + p.inputType = customInput } } // WithInputTTY opens a new TTY for input (or console input device on Windows). func WithInputTTY() ProgramOption { return func(p *Program) { - p.startupOptions |= withInputTTY + p.inputType = ttyInput } } diff --git a/options_test.go b/options_test.go index a66483d713..fdbc5b2cc2 100644 --- a/options_test.go +++ b/options_test.go @@ -14,13 +14,13 @@ func TestOptions(t *testing.T) { } }) - t.Run("input", func(t *testing.T) { + t.Run("custom input", func(t *testing.T) { var b bytes.Buffer p := NewProgram(nil, WithInput(&b)) if p.input != &b { t.Errorf("expected input to custom, got %v", p.input) } - if p.startupOptions&withCustomInput == 0 { + if p.inputType != customInput { t.Errorf("expected startup options to have custom input set, got %v", p.input) } }) @@ -49,6 +49,25 @@ func TestOptions(t *testing.T) { } }) + t.Run("input options", func(t *testing.T) { + exercise := func(t *testing.T, opt ProgramOption, expect inputType) { + p := NewProgram(nil, opt) + if p.inputType != expect { + t.Errorf("expected input type %s, got %s", expect, p.inputType) + } + } + + t.Run("tty input", func(t *testing.T) { + exercise(t, WithInputTTY(), ttyInput) + }) + + t.Run("custom input", func(t *testing.T) { + var b bytes.Buffer + exercise(t, WithInput(&b), customInput) + }) + + }) + t.Run("startup options", func(t *testing.T) { exercise := func(t *testing.T, opt ProgramOption, expect startupOptions) { p := NewProgram(nil, opt) @@ -57,10 +76,6 @@ func TestOptions(t *testing.T) { } } - t.Run("input tty", func(t *testing.T) { - exercise(t, WithInputTTY(), withInputTTY) - }) - t.Run("alt screen", func(t *testing.T) { exercise(t, WithAltScreen(), withAltScreen) }) @@ -100,10 +115,13 @@ func TestOptions(t *testing.T) { t.Run("multiple", func(t *testing.T) { p := NewProgram(nil, WithMouseAllMotion(), WithAltScreen(), WithInputTTY()) - for _, opt := range []startupOptions{withMouseAllMotion, withAltScreen, withInputTTY} { + for _, opt := range []startupOptions{withMouseAllMotion, withAltScreen} { if !p.startupOptions.has(opt) { t.Errorf("expected startup options have %v, got %v", opt, p.startupOptions) } + if p.inputType != ttyInput { + t.Errorf("expected input to be %v, got %v", opt, p.startupOptions) + } } }) } diff --git a/tea.go b/tea.go index e61bb5a6ae..ca24eacf3a 100644 --- a/tea.go +++ b/tea.go @@ -60,6 +60,24 @@ type Cmd func() Msg type handlers []chan struct{} +type inputType int + +const ( + defaultInput inputType = iota + ttyInput + customInput +) + +// String implements the stringer interface for [inputType]. It is inteded to +// be used in testing. +func (i inputType) String() string { + return [...]string{ + "default input", + "tty input", + "custom input", + }[i] +} + // Options to customize the program during its initialization. These are // generally set with ProgramOptions. // @@ -74,8 +92,6 @@ const ( withAltScreen startupOptions = 1 << iota withMouseCellMotion withMouseAllMotion - withInputTTY - withCustomInput withANSICompressor withoutSignalHandler @@ -94,6 +110,8 @@ type Program struct { // treated as bits. These options can be set via various ProgramOptions. startupOptions startupOptions + inputType inputType + ctx context.Context cancel context.CancelFunc @@ -141,7 +159,6 @@ type QuitMsg struct{} func NewProgram(model Model, opts ...ProgramOption) *Program { p := &Program{ initialModel: model, - input: os.Stdin, msgs: make(chan Msg), } @@ -371,8 +388,11 @@ func (p *Program) Run() (Model, error) { defer p.cancel() - switch { - case p.startupOptions.has(withInputTTY): + switch p.inputType { + case defaultInput: + p.input = os.Stdin + + case ttyInput: // Open a new TTY, by request f, err := openInputTTY() if err != nil { @@ -381,7 +401,7 @@ func (p *Program) Run() (Model, error) { defer f.Close() //nolint:errcheck p.input = f - case !p.startupOptions.has(withCustomInput): + case customInput: // If the user hasn't set a custom input, and input's not a terminal, // open a TTY so we can capture input as normal. This will allow things // to "just work" in cases where data was piped or redirected into this From 326b0b2df960cdee09bd42061b356d94a9a51b99 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Mon, 8 May 2023 17:46:37 +0000 Subject: [PATCH 53/89] chore: updates Signed-off-by: Carlos Alexandro Becker --- examples/go.mod | 4 ++-- examples/go.sum | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index 4d12f7637d..057df64474 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -4,11 +4,11 @@ go 1.17 require ( github.com/charmbracelet/bubbles v0.15.1-0.20230306155959-3372cf1aea2b - github.com/charmbracelet/bubbletea v0.23.3-0.20230504193325-5326d76c402a + github.com/charmbracelet/bubbletea v0.24.0 github.com/charmbracelet/glamour v0.6.0 github.com/charmbracelet/harmonica v0.2.0 github.com/charmbracelet/lipgloss v0.7.1 - github.com/charmbracelet/x/exp/teatest v0.0.0-20230505175841-25a09ea9b713 + 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 github.com/mattn/go-isatty v0.0.18 diff --git a/examples/go.sum b/examples/go.sum index bc3e500360..107d0e59ea 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -20,10 +20,10 @@ github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJ github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk= github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E= github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c= -github.com/charmbracelet/x/exp/teatest v0.0.0-20230504195420-fee909365d1c h1:3Dha70yq12co31grOoSMOaBIo8POU4ARhvp9W8RsOsw= -github.com/charmbracelet/x/exp/teatest v0.0.0-20230504195420-fee909365d1c/go.mod h1:dpMo6PfGlhavn+ofggWhfFQmK9sZB2Ewljiz9bZtKVI= github.com/charmbracelet/x/exp/teatest v0.0.0-20230505175841-25a09ea9b713 h1:aRUlesfHrzj9NECuQ1O2h1yBK5M4LFKwLYTQVoEkaQ8= github.com/charmbracelet/x/exp/teatest v0.0.0-20230505175841-25a09ea9b713/go.mod h1:dpMo6PfGlhavn+ofggWhfFQmK9sZB2Ewljiz9bZtKVI= +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= github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= From d9df8c3de69d00cebc6ecd3fb3af4ae9d42be704 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Mon, 8 May 2023 17:47:01 +0000 Subject: [PATCH 54/89] chore: go mod tidy Signed-off-by: Carlos Alexandro Becker --- examples/go.sum | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/go.sum b/examples/go.sum index 107d0e59ea..b112ab32dc 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -20,8 +20,6 @@ github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJ github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk= github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E= github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c= -github.com/charmbracelet/x/exp/teatest v0.0.0-20230505175841-25a09ea9b713 h1:aRUlesfHrzj9NECuQ1O2h1yBK5M4LFKwLYTQVoEkaQ8= -github.com/charmbracelet/x/exp/teatest v0.0.0-20230505175841-25a09ea9b713/go.mod h1:dpMo6PfGlhavn+ofggWhfFQmK9sZB2Ewljiz9bZtKVI= 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= From f5a91f0a59389992f7eb24c47000d333321b50c2 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Thu, 11 May 2023 12:37:50 +0000 Subject: [PATCH 55/89] docs: update license Signed-off-by: Carlos Alexandro Becker --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index e5892cfe9d..31d76c1c6e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 Charmbracelet, Inc +Copyright (c) 2020-2023 Charmbracelet, Inc Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From d1a16bd88370daba01ea34b9007e050f56adc97c Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Fri, 12 May 2023 14:15:17 -0400 Subject: [PATCH 56/89] fix(ci): remove soft-serve workflow --- .github/workflows/soft-serve.yml | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 .github/workflows/soft-serve.yml diff --git a/.github/workflows/soft-serve.yml b/.github/workflows/soft-serve.yml deleted file mode 100644 index 8eb3221d2d..0000000000 --- a/.github/workflows/soft-serve.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: soft-serve - -on: - push: - branches: - - master - -jobs: - soft-serve: - uses: charmbracelet/meta/.github/workflows/soft-serve.yml@main - secrets: - ssh-key: "${{ secrets.CHARM_SOFT_SERVE_KEY }}" From c267762438a3b24e9138e4ec02dd43c3b99c71fd Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Wed, 24 May 2023 12:31:00 -0400 Subject: [PATCH 57/89] fix(regression): auto-open a TTY when stdin is not a TTY (#746) The regression was introduced in precisely this revision: fcc805f3da68f535f7c3c78b94b4b14cd22a047a Closes #745. --- tea.go | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/tea.go b/tea.go index ca24eacf3a..2e024ed604 100644 --- a/tea.go +++ b/tea.go @@ -392,20 +392,12 @@ func (p *Program) Run() (Model, error) { case defaultInput: p.input = os.Stdin - case ttyInput: - // Open a new TTY, by request - f, err := openInputTTY() - if err != nil { - return p.initialModel, err - } - defer f.Close() //nolint:errcheck - p.input = f - - case customInput: - // If the user hasn't set a custom input, and input's not a terminal, - // open a TTY so we can capture input as normal. This will allow things - // to "just work" in cases where data was piped or redirected into this - // application. + // The user has not set a custom input, so we need to check whether or + // not standard input is a terminal. If it's not, we open a new TTY for + // input. This will allow things to "just work" in cases where data was + // piped in or redirected to the application. + // + // To disable input entirely pass nil to the [WithInput] program option. f, isFile := p.input.(*os.File) if !isFile { break @@ -420,6 +412,18 @@ func (p *Program) Run() (Model, error) { } defer f.Close() //nolint:errcheck p.input = f + + case ttyInput: + // Open a new TTY, by request + f, err := openInputTTY() + if err != nil { + return p.initialModel, err + } + defer f.Close() //nolint:errcheck + p.input = f + + case customInput: + // (There is nothing extra to do.) } // Handle signals. From 26dc0b5b327b55a1deaece8cb57bda39564ca25f Mon Sep 17 00:00:00 2001 From: Maas Lalani Date: Wed, 31 May 2023 14:08:34 -0400 Subject: [PATCH 58/89] chore(deps): bump bubbles to v0.16.1 --- examples/go.mod | 4 ++-- examples/go.sum | 10 ++-------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index 057df64474..5dad68268c 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -3,8 +3,8 @@ module examples go 1.17 require ( - github.com/charmbracelet/bubbles v0.15.1-0.20230306155959-3372cf1aea2b - github.com/charmbracelet/bubbletea v0.24.0 + github.com/charmbracelet/bubbles v0.16.1 + github.com/charmbracelet/bubbletea v0.24.1 github.com/charmbracelet/glamour v0.6.0 github.com/charmbracelet/harmonica v0.2.0 github.com/charmbracelet/lipgloss v0.7.1 diff --git a/examples/go.sum b/examples/go.sum index b112ab32dc..902315408e 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -11,13 +11,12 @@ github.com/aymanbagabas/go-udiff v0.1.0 h1:9Dpklm2oBBhMxIFbMffmPvDaF7vOYfv9B5HXV github.com/aymanbagabas/go-udiff v0.1.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= -github.com/charmbracelet/bubbles v0.15.1-0.20230306155959-3372cf1aea2b h1:K9dWJ2spDhDhIrqnchjG867djPxWWe3mwdk6RdLMfhg= -github.com/charmbracelet/bubbles v0.15.1-0.20230306155959-3372cf1aea2b/go.mod h1:39HL8bnL0foloiENA/KvD+3mNg5SqWQV2Qh3eY/4ey4= +github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY= +github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc= github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc= 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.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk= github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E= github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c= github.com/charmbracelet/x/exp/teatest v0.0.0-20230508155401-2bd6fa14c46a h1:GpNt24LKE8sH5G0SZUpu4Tg15sP5XSt1mnfIqE7fW34= @@ -39,7 +38,6 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 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.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= @@ -47,7 +45,6 @@ github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D 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.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= 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= @@ -58,10 +55,8 @@ github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTd github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= 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.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= 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.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= @@ -99,7 +94,6 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From 5f7a07b03c73639d86343ac3df00cb698cf24832 Mon Sep 17 00:00:00 2001 From: Maas Lalani Date: Wed, 31 May 2023 14:08:43 -0400 Subject: [PATCH 59/89] chore(deps): bump bubbles to v0.16.1 --- go.mod | 1 + go.sum | 2 ++ 2 files changed, 3 insertions(+) diff --git a/go.mod b/go.mod index 86f90679aa..2e15427694 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/charmbracelet/bubbles v0.16.1 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/rivo/uniseg v0.2.0 // indirect diff --git a/go.sum b/go.sum index 77ace75615..98430888bc 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ 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/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY= +github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc= github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= From 8254e0e4721c62d7b479d1103ad24be4f2bde032 Mon Sep 17 00:00:00 2001 From: Maas Lalani Date: Wed, 31 May 2023 14:20:18 -0400 Subject: [PATCH 60/89] fix(examples/file-picker): use `CurrentDirectory` instead of Path --- examples/file-picker/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/file-picker/main.go b/examples/file-picker/main.go index 30ecf01af8..727b880e8a 100644 --- a/examples/file-picker/main.go +++ b/examples/file-picker/main.go @@ -58,7 +58,7 @@ func (m model) View() string { func main() { fp := filepicker.New() - fp.Path, _ = os.UserHomeDir() + fp.CurrentDirectory, _ = os.UserHomeDir() m := model{ filepicker: fp, From 444e04bbb30f700e3d5d2d7323b5e60b7a2b78c5 Mon Sep 17 00:00:00 2001 From: Maas Lalani Date: Wed, 31 May 2023 16:18:24 -0500 Subject: [PATCH 61/89] docs(examples): filepicker AllowedTypes example (#713) --- examples/file-picker/main.go | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/examples/file-picker/main.go b/examples/file-picker/main.go index 727b880e8a..f0470cd338 100644 --- a/examples/file-picker/main.go +++ b/examples/file-picker/main.go @@ -1,9 +1,11 @@ package main import ( + "errors" "fmt" "os" "strings" + "time" "github.com/charmbracelet/bubbles/filepicker" tea "github.com/charmbracelet/bubbletea" @@ -13,6 +15,15 @@ type model struct { filepicker filepicker.Model selectedFile string quitting bool + err error +} + +type clearErrorMsg struct{} + +func clearErrorAfter(t time.Duration) tea.Cmd { + return tea.Tick(t, func(_ time.Time) tea.Msg { + return clearErrorMsg{} + }) } func (m model) Init() tea.Cmd { @@ -27,6 +38,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.quitting = true return m, tea.Quit } + case clearErrorMsg: + m.err = nil } var cmd tea.Cmd @@ -38,6 +51,15 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.selectedFile = path } + // Did the user select a disabled file? + // This is only necessary to display an error to the user. + if didSelect, path := m.filepicker.DidSelectDisabledFile(msg); didSelect { + // Let's clear the selectedFile and display an error. + m.err = errors.New(path + " is not valid.") + m.selectedFile = "" + return m, tea.Batch(cmd, clearErrorAfter(2*time.Second)) + } + return m, cmd } @@ -47,7 +69,9 @@ func (m model) View() string { } var s strings.Builder s.WriteString("\n ") - if m.selectedFile == "" { + if m.err != nil { + s.WriteString(m.filepicker.Styles.DisabledFile.Render(m.err.Error())) + } else if m.selectedFile == "" { s.WriteString("Pick a file:") } else { s.WriteString("Selected file: " + m.filepicker.Styles.Selected.Render(m.selectedFile)) @@ -58,6 +82,7 @@ func (m model) View() string { func main() { fp := filepicker.New() + fp.AllowedTypes = []string{".mod", ".sum", ".go", ".txt", ".md"} fp.CurrentDirectory, _ = os.UserHomeDir() m := model{ From 44f17fa1c0d9bf4a20d5bc89640d2ebe7a7f3a8b Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 2 Jun 2023 12:05:33 +0200 Subject: [PATCH 62/89] fix: stop renderer before acquiring renderer mutex --- standard_renderer.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/standard_renderer.go b/standard_renderer.go index e4fb61ca9e..0ab9473d80 100644 --- a/standard_renderer.go +++ b/standard_renderer.go @@ -88,6 +88,11 @@ func (r *standardRenderer) start() { // stop permanently halts the renderer, rendering the final frame. func (r *standardRenderer) stop() { + // Stop the renderer before acquiring the mutex to avoid a deadlock. + r.once.Do(func() { + r.done <- struct{}{} + }) + // flush locks the mutex r.flush() @@ -95,9 +100,6 @@ func (r *standardRenderer) stop() { defer r.mtx.Unlock() r.out.ClearLine() - r.once.Do(func() { - r.done <- struct{}{} - }) if r.useANSICompressor { if w, ok := r.out.TTY().(io.WriteCloser); ok { @@ -108,13 +110,15 @@ func (r *standardRenderer) stop() { // 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. + r.once.Do(func() { + r.done <- struct{}{} + }) + r.mtx.Lock() defer r.mtx.Unlock() r.out.ClearLine() - r.once.Do(func() { - r.done <- struct{}{} - }) } // listen waits for ticks on the ticker, or a signal to stop the renderer. From f3e1b676056b478f3994ec894a8aafe761bae67b Mon Sep 17 00:00:00 2001 From: tomfeigin Date: Tue, 6 Jun 2023 18:49:11 +0300 Subject: [PATCH 63/89] (feat): Add option to set max FPS (#578) * (feat): add option for setting FPS on renderer Co-authored-by: Christian Rocha --- options.go | 9 +++++++++ standard_renderer.go | 12 +++++++++--- tea.go | 6 +++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/options.go b/options.go index 30f7e6c50b..249d3bffc9 100644 --- a/options.go +++ b/options.go @@ -200,3 +200,12 @@ func WithFilter(filter func(Model, Msg) Msg) ProgramOption { p.filter = filter } } + +// WithMaxFPS sets a custom maximum FPS at which the renderer should run. If +// less than 1, the default value of 60 will be used. If over 120, the FPS +// will be capped at 120. +func WithFPS(fps int) ProgramOption { + return func(p *Program) { + p.fps = fps + } +} diff --git a/standard_renderer.go b/standard_renderer.go index 0ab9473d80..c6a1b61017 100644 --- a/standard_renderer.go +++ b/standard_renderer.go @@ -16,7 +16,8 @@ import ( const ( // defaultFramerate specifies the maximum interval at which we should // update the view. - defaultFramerate = time.Second / 60 + defaultFPS = 60 + maxFPS = 120 ) // standardRenderer is a framerate-based terminal renderer, updating the view @@ -54,12 +55,17 @@ 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) renderer { +func newRenderer(out *termenv.Output, useANSICompressor bool, fps int) renderer { + if fps < 1 { + fps = defaultFPS + } else if fps > maxFPS { + fps = maxFPS + } r := &standardRenderer{ out: out, mtx: &sync.Mutex{}, done: make(chan struct{}), - framerate: defaultFramerate, + framerate: time.Second / time.Duration(fps), useANSICompressor: useANSICompressor, queuedMessageLines: []string{}, } diff --git a/tea.go b/tea.go index 2e024ed604..c1d8cafd5d 100644 --- a/tea.go +++ b/tea.go @@ -144,6 +144,10 @@ type Program struct { windowsStdin *os.File //nolint:golint,structcheck,unused filter func(Model, Msg) Msg + + // fps is the frames per second we should set on the renderer, if + // applicable, + fps int } // Quit is a special command that tells the Bubble Tea program to exit. @@ -445,7 +449,7 @@ func (p *Program) Run() (Model, error) { // If no renderer is set use the standard one. if p.renderer == nil { - p.renderer = newRenderer(p.output, p.startupOptions.has(withANSICompressor)) + p.renderer = newRenderer(p.output, p.startupOptions.has(withANSICompressor), p.fps) } // Check if output is a TTY before entering raw mode, hiding the cursor and From b2d2ac6504e595df2a895e043f243cfca4cd3fed Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Fri, 9 Jun 2023 09:17:15 -0300 Subject: [PATCH 64/89] chore: update example tests, test on ci (#735) * chore: update example tests, test on ci Signed-off-by: Carlos Alexandro Becker * fix: mark *.golden as binary Signed-off-by: Carlos Alexandro Becker --------- Signed-off-by: Carlos Alexandro Becker --- .gitattributes | 1 + .github/workflows/build.yml | 4 ++++ examples/simple/main_test.go | 12 +++++++++--- examples/simple/testdata/TestApp.golden | 4 ++-- 4 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..6c929d4803 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.golden -text diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cef3f37e1e..2511296056 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,6 +32,10 @@ jobs: run: go build -v ./... working-directory: ./examples + - name: Test examples + run: go test -v ./... + working-directory: ./examples + - name: Build tutorials run: go build -v ./... working-directory: ./tutorials diff --git a/examples/simple/main_test.go b/examples/simple/main_test.go index f736de754b..a2e747f6af 100644 --- a/examples/simple/main_test.go +++ b/examples/simple/main_test.go @@ -8,9 +8,15 @@ import ( "time" tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/x/exp/teatest" + "github.com/muesli/termenv" ) +func init() { + lipgloss.SetColorProfile(termenv.Ascii) +} + func TestApp(t *testing.T) { m := model(10) tm := teatest.NewTestModel( @@ -34,13 +40,13 @@ func TestApp(t *testing.T) { t.Fatal(err) } - out := readBts(t, tm.FinalOutput()) + out := readBts(t, tm.FinalOutput(t)) if !regexp.MustCompile(`This program will exit in \d+ seconds`).Match(out) { t.Fatalf("output does not match the given regular expression: %s", string(out)) } teatest.RequireEqualOutput(t, out) - if tm.FinalModel().(model) != 9 { + if tm.FinalModel(t).(model) != 9 { t.Errorf("expected model to be 10, was %d", m) } } @@ -71,7 +77,7 @@ func TestAppInteractive(t *testing.T) { t.Fatal(err) } - if tm.FinalModel().(model) != 7 { + if tm.FinalModel(t).(model) != 7 { t.Errorf("expected model to be 7, was %d", m) } } diff --git a/examples/simple/testdata/TestApp.golden b/examples/simple/testdata/TestApp.golden index 0a973b283e..b0b6c3dc97 100644 --- a/examples/simple/testdata/TestApp.golden +++ b/examples/simple/testdata/TestApp.golden @@ -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. +[?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 \ No newline at end of file From b80eb8303bba8fe2a8ea89a3962a01a0ee5320b8 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Fri, 9 Jun 2023 13:33:53 -0300 Subject: [PATCH 65/89] docs: fix lint issues in examples and tutorials (#759) Signed-off-by: Carlos Alexandro Becker --- examples/cellbuffer/main.go | 30 ++++++++++++---------------- examples/composable-views/main.go | 2 +- examples/help/main.go | 6 +++--- examples/http/main.go | 2 +- examples/list-simple/main.go | 6 +++--- examples/package-manager/main.go | 4 +--- examples/package-manager/packages.go | 2 +- examples/pipe/main.go | 2 +- examples/prevent-quit/main.go | 2 +- examples/progress-download/main.go | 6 +++--- examples/realtime/main.go | 6 ++---- examples/send-msg/main.go | 8 +++----- examples/stopwatch/main.go | 2 +- examples/textinputs/main.go | 11 +++++----- examples/timer/main.go | 2 +- examples/tui-daemon-combo/main.go | 8 +++----- examples/views/main.go | 12 ++--------- tutorials/commands/main.go | 2 +- 18 files changed, 47 insertions(+), 66 deletions(-) diff --git a/examples/cellbuffer/main.go b/examples/cellbuffer/main.go index 00de39ce3b..456089e3e4 100644 --- a/examples/cellbuffer/main.go +++ b/examples/cellbuffer/main.go @@ -18,14 +18,14 @@ const ( fps = 60 frequency = 7.5 damping = 0.15 + asterisk = "*" ) func drawEllipse(cb *cellbuffer, xc, yc, rx, ry float64) { - const c = "*" var ( dx, dy, d1, d2 float64 - x float64 = 0 - y = ry + x float64 + y = ry ) d1 = ry*ry - rx*rx*ry + 0.25*rx*rx @@ -33,10 +33,10 @@ func drawEllipse(cb *cellbuffer, xc, yc, rx, ry float64) { dy = 2 * rx * rx * y for dx < dy { - cb.set(c, int(x+xc), int(y+yc)) - cb.set(c, int(-x+xc), int(y+yc)) - cb.set(c, int(x+xc), int(-y+yc)) - cb.set(c, int(-x+xc), int(-y+yc)) + cb.set(int(x+xc), int(y+yc)) + cb.set(int(-x+xc), int(y+yc)) + cb.set(int(x+xc), int(-y+yc)) + cb.set(int(-x+xc), int(-y+yc)) if d1 < 0 { x++ dx = dx + (2 * ry * ry) @@ -53,10 +53,10 @@ func drawEllipse(cb *cellbuffer, xc, yc, rx, ry float64) { d2 = ((ry * ry) * ((x + 0.5) * (x + 0.5))) + ((rx * rx) * ((y - 1) * (y - 1))) - (rx * rx * ry * ry) for y >= 0 { - cb.set(c, int(x+xc), int(y+yc)) - cb.set(c, int(-x+xc), int(y+yc)) - cb.set(c, int(x+xc), int(-y+yc)) - cb.set(c, int(-x+xc), int(-y+yc)) + cb.set(int(x+xc), int(y+yc)) + cb.set(int(-x+xc), int(y+yc)) + cb.set(int(x+xc), int(-y+yc)) + cb.set(int(-x+xc), int(-y+yc)) if d2 > 0 { y-- dy = dy - (2 * rx * rx) @@ -85,16 +85,12 @@ func (c *cellbuffer) init(w, h int) { c.wipe() } -func (c cellbuffer) set(v string, x, y int) { +func (c cellbuffer) set(x, y int) { i := y*c.stride + x if i > len(c.cells)-1 || x < 0 || y < 0 || x >= c.width() || y >= c.height() { return } - c.cells[i] = v -} - -func (c *cellbuffer) clear(x, y int) { - c.set(" ", x, y) + c.cells[i] = asterisk } func (c *cellbuffer) wipe() { diff --git a/examples/composable-views/main.go b/examples/composable-views/main.go index f81ce6ac8e..4352a7c5ad 100644 --- a/examples/composable-views/main.go +++ b/examples/composable-views/main.go @@ -98,7 +98,7 @@ func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } else { m.Next() m.resetSpinner() - cmds = append(cmds, spinner.Tick) + cmds = append(cmds, m.spinner.Tick) } } switch m.state { diff --git a/examples/help/main.go b/examples/help/main.go index fb382ce26c..67e5a4f480 100644 --- a/examples/help/main.go +++ b/examples/help/main.go @@ -132,12 +132,12 @@ func (m model) View() string { func main() { if os.Getenv("HELP_DEBUG") != "" { - if f, err := tea.LogToFile("debug.log", "help"); err != nil { + f, err := tea.LogToFile("debug.log", "help") + if err != nil { fmt.Println("Couldn't open a file for logging:", err) os.Exit(1) - } else { - defer f.Close() } + defer f.Close() // nolint:errcheck } if _, err := tea.NewProgram(newModel()).Run(); err != nil { diff --git a/examples/http/main.go b/examples/http/main.go index 1b6a1e6228..dc2842de0e 100644 --- a/examples/http/main.go +++ b/examples/http/main.go @@ -76,7 +76,7 @@ func checkServer() tea.Msg { if err != nil { return errMsg{err} } - defer res.Body.Close() + defer res.Body.Close() // nolint:errcheck return statusMsg(res.StatusCode) } diff --git a/examples/list-simple/main.go b/examples/list-simple/main.go index 8cc42909af..f8f85ed100 100644 --- a/examples/list-simple/main.go +++ b/examples/list-simple/main.go @@ -28,9 +28,9 @@ func (i item) FilterValue() string { return "" } type itemDelegate struct{} -func (d itemDelegate) Height() int { return 1 } -func (d itemDelegate) Spacing() int { return 0 } -func (d itemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil } +func (d itemDelegate) Height() int { return 1 } +func (d itemDelegate) Spacing() int { return 0 } +func (d itemDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil } func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { i, ok := listItem.(item) if !ok { diff --git a/examples/package-manager/main.go b/examples/package-manager/main.go index ced7c773dc..eb6f69ad2d 100644 --- a/examples/package-manager/main.go +++ b/examples/package-manager/main.go @@ -115,7 +115,7 @@ type installedPkgMsg string func downloadAndInstall(pkg string) tea.Cmd { // This is where you'd do i/o stuff to download and install packages. In // our case we're just pausing for a moment to simulate the process. - d := time.Millisecond * time.Duration(rand.Intn(500)) + d := time.Millisecond * time.Duration(rand.Intn(500)) //nolint:gosec return tea.Tick(d, func(t time.Time) tea.Msg { return installedPkgMsg(pkg) }) @@ -129,8 +129,6 @@ func max(a, b int) int { } func main() { - rand.Seed(time.Now().Unix()) - if _, err := tea.NewProgram(newModel()).Run(); err != nil { fmt.Println("Error running program:", err) os.Exit(1) diff --git a/examples/package-manager/packages.go b/examples/package-manager/packages.go index 7ed8478a3c..40425fca2b 100644 --- a/examples/package-manager/packages.go +++ b/examples/package-manager/packages.go @@ -46,7 +46,7 @@ func getPackages() []string { }) for k := range pkgs { - pkgs[k] += fmt.Sprintf("-%d.%d.%d", rand.Intn(10), rand.Intn(10), rand.Intn(10)) + pkgs[k] += fmt.Sprintf("-%d.%d.%d", rand.Intn(10), rand.Intn(10), rand.Intn(10)) //nolint:gosec } return pkgs } diff --git a/examples/pipe/main.go b/examples/pipe/main.go index 1fce2fce84..a309566327 100644 --- a/examples/pipe/main.go +++ b/examples/pipe/main.go @@ -58,7 +58,7 @@ type model struct { func newModel(initialValue string) (m model) { i := textinput.New() i.Prompt = "" - i.CursorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("63")) + i.Cursor.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("63")) i.Width = 48 i.SetValue(initialValue) i.CursorEnd() diff --git a/examples/prevent-quit/main.go b/examples/prevent-quit/main.go index 1339393937..7399d3088b 100644 --- a/examples/prevent-quit/main.go +++ b/examples/prevent-quit/main.go @@ -61,7 +61,7 @@ func initialModel() model { return model{ textarea: ti, - help: help.NewModel(), + help: help.New(), keymap: keymap{ save: key.NewBinding( key.WithKeys("ctrl+s"), diff --git a/examples/progress-download/main.go b/examples/progress-download/main.go index 4639aa1b20..018b29f14f 100644 --- a/examples/progress-download/main.go +++ b/examples/progress-download/main.go @@ -40,7 +40,7 @@ func (pw *progressWriter) Write(p []byte) (int, error) { } func getResponse(url string) (*http.Response, error) { - resp, err := http.Get(url) + resp, err := http.Get(url) // nolint:gosec if err != nil { log.Fatal(err) } @@ -64,7 +64,7 @@ func main() { fmt.Println("could not get response", err) os.Exit(1) } - defer resp.Body.Close() + defer resp.Body.Close() // nolint:errcheck // Don't add TUI if the header doesn't include content size // it's impossible see progress without total @@ -79,7 +79,7 @@ func main() { fmt.Println("could not create file:", err) os.Exit(1) } - defer file.Close() + defer file.Close() // nolint:errcheck pw := &progressWriter{ total: int(resp.ContentLength), diff --git a/examples/realtime/main.go b/examples/realtime/main.go index 516fcd662e..4abddd3b6f 100644 --- a/examples/realtime/main.go +++ b/examples/realtime/main.go @@ -24,7 +24,7 @@ type responseMsg struct{} func listenForActivity(sub chan struct{}) tea.Cmd { return func() tea.Msg { for { - time.Sleep(time.Millisecond * time.Duration(rand.Int63n(900)+100)) + time.Sleep(time.Millisecond * time.Duration(rand.Int63n(900)+100)) // nolint:gosec sub <- struct{}{} } } @@ -46,7 +46,7 @@ type model struct { func (m model) Init() tea.Cmd { return tea.Batch( - spinner.Tick, + m.spinner.Tick, listenForActivity(m.sub), // generate activity waitForActivity(m.sub), // wait for activity ) @@ -78,8 +78,6 @@ func (m model) View() string { } func main() { - rand.Seed(time.Now().UTC().UnixNano()) - p := tea.NewProgram(model{ sub: make(chan struct{}), spinner: spinner.New(), diff --git a/examples/send-msg/main.go b/examples/send-msg/main.go index 7badf6a485..5ace43bcb7 100644 --- a/examples/send-msg/main.go +++ b/examples/send-msg/main.go @@ -53,7 +53,7 @@ func newModel() model { } func (m model) Init() tea.Cmd { - return spinner.Tick + return m.spinner.Tick } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { @@ -100,14 +100,12 @@ func (m model) View() string { } func main() { - rand.Seed(time.Now().UTC().UnixNano()) - p := tea.NewProgram(newModel()) // Simulate activity go func() { for { - pause := time.Duration(rand.Int63n(899)+100) * time.Millisecond + pause := time.Duration(rand.Int63n(899)+100) * time.Millisecond // nolint:gosec time.Sleep(pause) // Send the Bubble Tea program a message from outside the @@ -129,5 +127,5 @@ func randomFood() string { "a kohlrabi", "some spaghetti", "tacos", "a currywurst", "some curry", "a sandwich", "some peanut butter", "some cashews", "some ramen", } - return string(food[rand.Intn(len(food))]) + return food[rand.Intn(len(food))] // nolint:gosec } diff --git a/examples/stopwatch/main.go b/examples/stopwatch/main.go index 69884b1e11..2b1a4d4586 100644 --- a/examples/stopwatch/main.go +++ b/examples/stopwatch/main.go @@ -91,7 +91,7 @@ func main() { key.WithHelp("q", "quit"), ), }, - help: help.NewModel(), + help: help.New(), } m.keymap.start.SetEnabled(false) diff --git a/examples/textinputs/main.go b/examples/textinputs/main.go index 2620bfc3de..5882858aad 100644 --- a/examples/textinputs/main.go +++ b/examples/textinputs/main.go @@ -8,6 +8,7 @@ import ( "os" "strings" + "github.com/charmbracelet/bubbles/cursor" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" @@ -28,7 +29,7 @@ var ( type model struct { focusIndex int inputs []textinput.Model - cursorMode textinput.CursorMode + cursorMode cursor.Mode } func initialModel() model { @@ -39,7 +40,7 @@ func initialModel() model { var t textinput.Model for i := range m.inputs { t = textinput.New() - t.CursorStyle = cursorStyle + t.Cursor.Style = cursorStyle t.CharLimit = 32 switch i { @@ -77,12 +78,12 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // Change cursor mode case "ctrl+r": m.cursorMode++ - if m.cursorMode > textinput.CursorHide { - m.cursorMode = textinput.CursorBlink + if m.cursorMode > cursor.CursorHide { + m.cursorMode = cursor.CursorBlink } cmds := make([]tea.Cmd, len(m.inputs)) for i := range m.inputs { - cmds[i] = m.inputs[i].SetCursorMode(m.cursorMode) + cmds[i] = m.inputs[i].Cursor.SetMode(m.cursorMode) } return m, tea.Batch(cmds...) diff --git a/examples/timer/main.go b/examples/timer/main.go index 7ee4f7ddac..42d980c7bd 100644 --- a/examples/timer/main.go +++ b/examples/timer/main.go @@ -111,7 +111,7 @@ func main() { key.WithHelp("q", "quit"), ), }, - help: help.NewModel(), + help: help.New(), } m.keymap.start.SetEnabled(false) diff --git a/examples/tui-daemon-combo/main.go b/examples/tui-daemon-combo/main.go index 962da810de..07364065b9 100644 --- a/examples/tui-daemon-combo/main.go +++ b/examples/tui-daemon-combo/main.go @@ -19,8 +19,6 @@ import ( var helpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("241")).Render func main() { - rand.Seed(time.Now().UTC().UnixNano()) - var ( daemonMode bool showHelp bool @@ -77,7 +75,7 @@ func newModel() model { func (m model) Init() tea.Cmd { log.Println("Starting work...") return tea.Batch( - spinner.Tick, + m.spinner.Tick, runPretendProcess, ) } @@ -128,12 +126,12 @@ type processFinishedMsg time.Duration // pretendProcess simulates a long-running process. func runPretendProcess() tea.Msg { - pause := time.Duration(rand.Int63n(899)+100) * time.Millisecond + pause := time.Duration(rand.Int63n(899)+100) * time.Millisecond // nolint:gosec time.Sleep(pause) return processFinishedMsg(pause) } func randomEmoji() string { emojis := []rune("🍦🧋🍡🤠👾😭🦊🐯🦆🥨🎏🍔🍒🍥🎮📦🦁🐶🐸🍕🥐🧲🚒🥇🏆🌽") - return string(emojis[rand.Intn(len(emojis))]) + return string(emojis[rand.Intn(len(emojis))]) // nolint:gosec } diff --git a/examples/views/main.go b/examples/views/main.go index a06b59248f..da3c4ec943 100644 --- a/examples/views/main.go +++ b/examples/views/main.go @@ -216,7 +216,7 @@ func chosenView(m model) string { label = fmt.Sprintf("Downloaded. Exiting in %s seconds...", colorFg(strconv.Itoa(m.Ticks), "79")) } - return msg + "\n\n" + label + "\n" + progressbar(80, m.Progress) + "%" + return msg + "\n\n" + label + "\n" + progressbar(m.Progress) + "%" } func checkbox(label string, checked bool) string { @@ -226,7 +226,7 @@ func checkbox(label string, checked bool) string { return fmt.Sprintf("[ ] %s", label) } -func progressbar(width int, percent float64) string { +func progressbar(percent float64) string { w := float64(progressBarWidth) fullSize := int(math.Round(w * percent)) @@ -253,14 +253,6 @@ func makeFgStyle(color string) func(string) string { return termenv.Style{}.Foreground(term.Color(color)).Styled } -// Color a string's foreground and background with the given value. -func makeFgBgStyle(fg, bg string) func(string) string { - return termenv.Style{}. - Foreground(term.Color(fg)). - Background(term.Color(bg)). - Styled -} - // Generate a blend of colors. func makeRamp(colorA, colorB string, steps float64) (s []string) { cA, _ := colorful.Hex(colorA) diff --git a/tutorials/commands/main.go b/tutorials/commands/main.go index 8ed68b7f5a..37a62f0b4c 100644 --- a/tutorials/commands/main.go +++ b/tutorials/commands/main.go @@ -22,7 +22,7 @@ func checkServer() tea.Msg { if err != nil { return errMsg{err} } - defer res.Body.Close() + defer res.Body.Close() // nolint:errcheck return statusMsg(res.StatusCode) } From c0cc6aa1fb4f2e19554d1f75fd20e9266d200c90 Mon Sep 17 00:00:00 2001 From: Lukas Bloznelis <33397865+bloznelis@users.noreply.github.com> Date: Wed, 14 Jun 2023 17:25:09 +0300 Subject: [PATCH 66/89] chore(docs): add typioca to Bubble Tea in the Wild (#763) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0a88caa5fa..395b65c172 100644 --- a/README.md +++ b/README.md @@ -367,6 +367,7 @@ For some Bubble Tea programs in production, see: * [ticker](https://github.com/achannarasappa/ticker): a terminal stock viewer and stock position tracker * [tran](https://github.com/abdfnx/tran): securely transfer stuff between computers (based on [portal](https://github.com/ZinoKader/portal)) * [Typer](https://github.com/maaslalani/typer): a typing test +* [typioca](https://github.com/bloznelis/typioca): Cozy typing speed tester in terminal * [tz](https://github.com/oz/tz): an aid for scheduling across multiple time zones * [ugm](https://github.com/ariasmn/ugm): a unix user and group browser * [wander](https://github.com/robinovitch61/wander): a HashiCorp Nomad terminal client From d9c675138c8f2a48522d0f0f043609baae30ce4e Mon Sep 17 00:00:00 2001 From: Raphael 'kena' Poss Date: Fri, 21 Oct 2022 17:00:39 +0200 Subject: [PATCH 67/89] fix(key),test: simplify the input analysis code --- key.go | 352 +++++++++++++++++------------------- key_sequences.go | 71 ++++++++ key_test.go | 457 +++++++++++++++++++++++++++++++++++++++++++---- mouse.go | 143 +++++++-------- mouse_test.go | 302 +++++++++++-------------------- 5 files changed, 821 insertions(+), 504 deletions(-) create mode 100644 key_sequences.go diff --git a/key.go b/key.go index c2e5e3ab01..bbd533cd78 100644 --- a/key.go +++ b/key.go @@ -1,8 +1,9 @@ package tea import ( - "errors" + "fmt" "io" + "regexp" "unicode/utf8" "github.com/mattn/go-localereader" @@ -338,85 +339,73 @@ var keyNames = map[KeyType]string{ // Sequence mappings. var sequences = map[string]Key{ // Arrow keys - "\x1b[A": {Type: KeyUp}, - "\x1b[B": {Type: KeyDown}, - "\x1b[C": {Type: KeyRight}, - "\x1b[D": {Type: KeyLeft}, - "\x1b[1;2A": {Type: KeyShiftUp}, - "\x1b[1;2B": {Type: KeyShiftDown}, - "\x1b[1;2C": {Type: KeyShiftRight}, - "\x1b[1;2D": {Type: KeyShiftLeft}, - "\x1b[OA": {Type: KeyShiftUp}, // DECCKM - "\x1b[OB": {Type: KeyShiftDown}, // DECCKM - "\x1b[OC": {Type: KeyShiftRight}, // DECCKM - "\x1b[OD": {Type: KeyShiftLeft}, // DECCKM - "\x1b[a": {Type: KeyShiftUp}, // urxvt - "\x1b[b": {Type: KeyShiftDown}, // urxvt - "\x1b[c": {Type: KeyShiftRight}, // urxvt - "\x1b[d": {Type: KeyShiftLeft}, // urxvt - "\x1b[1;3A": {Type: KeyUp, Alt: true}, - "\x1b[1;3B": {Type: KeyDown, Alt: true}, - "\x1b[1;3C": {Type: KeyRight, Alt: true}, - "\x1b[1;3D": {Type: KeyLeft, Alt: true}, - "\x1b\x1b[A": {Type: KeyUp, Alt: true}, // urxvt - "\x1b\x1b[B": {Type: KeyDown, Alt: true}, // urxvt - "\x1b\x1b[C": {Type: KeyRight, Alt: true}, // urxvt - "\x1b\x1b[D": {Type: KeyLeft, Alt: true}, // urxvt - "\x1b[1;4A": {Type: KeyShiftUp, Alt: true}, - "\x1b[1;4B": {Type: KeyShiftDown, Alt: true}, - "\x1b[1;4C": {Type: KeyShiftRight, Alt: true}, - "\x1b[1;4D": {Type: KeyShiftLeft, Alt: true}, - "\x1b\x1b[a": {Type: KeyShiftUp, Alt: true}, // urxvt - "\x1b\x1b[b": {Type: KeyShiftDown, Alt: true}, // urxvt - "\x1b\x1b[c": {Type: KeyShiftRight, Alt: true}, // urxvt - "\x1b\x1b[d": {Type: KeyShiftLeft, Alt: true}, // urxvt - "\x1b[1;5A": {Type: KeyCtrlUp}, - "\x1b[1;5B": {Type: KeyCtrlDown}, - "\x1b[1;5C": {Type: KeyCtrlRight}, - "\x1b[1;5D": {Type: KeyCtrlLeft}, - "\x1b[Oa": {Type: KeyCtrlUp, Alt: true}, // urxvt - "\x1b[Ob": {Type: KeyCtrlDown, Alt: true}, // urxvt - "\x1b[Oc": {Type: KeyCtrlRight, Alt: true}, // urxvt - "\x1b[Od": {Type: KeyCtrlLeft, Alt: true}, // urxvt - "\x1b[1;6A": {Type: KeyCtrlShiftUp}, - "\x1b[1;6B": {Type: KeyCtrlShiftDown}, - "\x1b[1;6C": {Type: KeyCtrlShiftRight}, - "\x1b[1;6D": {Type: KeyCtrlShiftLeft}, - "\x1b[1;7A": {Type: KeyCtrlUp, Alt: true}, - "\x1b[1;7B": {Type: KeyCtrlDown, Alt: true}, - "\x1b[1;7C": {Type: KeyCtrlRight, Alt: true}, - "\x1b[1;7D": {Type: KeyCtrlLeft, Alt: true}, - "\x1b[1;8A": {Type: KeyCtrlShiftUp, Alt: true}, - "\x1b[1;8B": {Type: KeyCtrlShiftDown, Alt: true}, - "\x1b[1;8C": {Type: KeyCtrlShiftRight, Alt: true}, - "\x1b[1;8D": {Type: KeyCtrlShiftLeft, Alt: true}, + "\x1b[A": {Type: KeyUp}, + "\x1b[B": {Type: KeyDown}, + "\x1b[C": {Type: KeyRight}, + "\x1b[D": {Type: KeyLeft}, + "\x1b[1;2A": {Type: KeyShiftUp}, + "\x1b[1;2B": {Type: KeyShiftDown}, + "\x1b[1;2C": {Type: KeyShiftRight}, + "\x1b[1;2D": {Type: KeyShiftLeft}, + "\x1b[OA": {Type: KeyShiftUp}, // DECCKM + "\x1b[OB": {Type: KeyShiftDown}, // DECCKM + "\x1b[OC": {Type: KeyShiftRight}, // DECCKM + "\x1b[OD": {Type: KeyShiftLeft}, // DECCKM + "\x1b[a": {Type: KeyShiftUp}, // urxvt + "\x1b[b": {Type: KeyShiftDown}, // urxvt + "\x1b[c": {Type: KeyShiftRight}, // urxvt + "\x1b[d": {Type: KeyShiftLeft}, // urxvt + "\x1b[1;3A": {Type: KeyUp, Alt: true}, + "\x1b[1;3B": {Type: KeyDown, Alt: true}, + "\x1b[1;3C": {Type: KeyRight, Alt: true}, + "\x1b[1;3D": {Type: KeyLeft, Alt: true}, + + "\x1b[1;4A": {Type: KeyShiftUp, Alt: true}, + "\x1b[1;4B": {Type: KeyShiftDown, Alt: true}, + "\x1b[1;4C": {Type: KeyShiftRight, Alt: true}, + "\x1b[1;4D": {Type: KeyShiftLeft, Alt: true}, + + "\x1b[1;5A": {Type: KeyCtrlUp}, + "\x1b[1;5B": {Type: KeyCtrlDown}, + "\x1b[1;5C": {Type: KeyCtrlRight}, + "\x1b[1;5D": {Type: KeyCtrlLeft}, + "\x1b[Oa": {Type: KeyCtrlUp, Alt: true}, // urxvt + "\x1b[Ob": {Type: KeyCtrlDown, Alt: true}, // urxvt + "\x1b[Oc": {Type: KeyCtrlRight, Alt: true}, // urxvt + "\x1b[Od": {Type: KeyCtrlLeft, Alt: true}, // urxvt + "\x1b[1;6A": {Type: KeyCtrlShiftUp}, + "\x1b[1;6B": {Type: KeyCtrlShiftDown}, + "\x1b[1;6C": {Type: KeyCtrlShiftRight}, + "\x1b[1;6D": {Type: KeyCtrlShiftLeft}, + "\x1b[1;7A": {Type: KeyCtrlUp, Alt: true}, + "\x1b[1;7B": {Type: KeyCtrlDown, Alt: true}, + "\x1b[1;7C": {Type: KeyCtrlRight, Alt: true}, + "\x1b[1;7D": {Type: KeyCtrlLeft, Alt: true}, + "\x1b[1;8A": {Type: KeyCtrlShiftUp, Alt: true}, + "\x1b[1;8B": {Type: KeyCtrlShiftDown, Alt: true}, + "\x1b[1;8C": {Type: KeyCtrlShiftRight, Alt: true}, + "\x1b[1;8D": {Type: KeyCtrlShiftLeft, Alt: true}, // Miscellaneous keys "\x1b[Z": {Type: KeyShiftTab}, - "\x1b[2~": {Type: KeyInsert}, - "\x1b[3;2~": {Type: KeyInsert, Alt: true}, - "\x1b\x1b[2~": {Type: KeyInsert, Alt: true}, // urxvt - - "\x1b[3~": {Type: KeyDelete}, - "\x1b[3;3~": {Type: KeyDelete, Alt: true}, - "\x1b\x1b[3~": {Type: KeyDelete, Alt: true}, // urxvt - - "\x1b[5~": {Type: KeyPgUp}, - "\x1b[5;3~": {Type: KeyPgUp, Alt: true}, - "\x1b\x1b[5~": {Type: KeyPgUp, Alt: true}, // urxvt - "\x1b[5;5~": {Type: KeyCtrlPgUp}, - "\x1b[5^": {Type: KeyCtrlPgUp}, // urxvt - "\x1b[5;7~": {Type: KeyCtrlPgUp, Alt: true}, - "\x1b\x1b[5^": {Type: KeyCtrlPgUp, Alt: true}, // urxvt - - "\x1b[6~": {Type: KeyPgDown}, - "\x1b[6;3~": {Type: KeyPgDown, Alt: true}, - "\x1b\x1b[6~": {Type: KeyPgDown, Alt: true}, // urxvt - "\x1b[6;5~": {Type: KeyCtrlPgDown}, - "\x1b[6^": {Type: KeyCtrlPgDown}, // urxvt - "\x1b[6;7~": {Type: KeyCtrlPgDown, Alt: true}, - "\x1b\x1b[6^": {Type: KeyCtrlPgDown, Alt: true}, // urxvt + "\x1b[2~": {Type: KeyInsert}, + "\x1b[3;2~": {Type: KeyInsert, Alt: true}, + + "\x1b[3~": {Type: KeyDelete}, + "\x1b[3;3~": {Type: KeyDelete, Alt: true}, + + "\x1b[5~": {Type: KeyPgUp}, + "\x1b[5;3~": {Type: KeyPgUp, Alt: true}, + "\x1b[5;5~": {Type: KeyCtrlPgUp}, + "\x1b[5^": {Type: KeyCtrlPgUp}, // urxvt + "\x1b[5;7~": {Type: KeyCtrlPgUp, Alt: true}, + + "\x1b[6~": {Type: KeyPgDown}, + "\x1b[6;3~": {Type: KeyPgDown, Alt: true}, + "\x1b[6;5~": {Type: KeyCtrlPgDown}, + "\x1b[6^": {Type: KeyCtrlPgDown}, // urxvt + "\x1b[6;7~": {Type: KeyCtrlPgDown, Alt: true}, "\x1b[1~": {Type: KeyHome}, "\x1b[H": {Type: KeyHome}, // xterm, lxterm @@ -438,23 +427,15 @@ var sequences = map[string]Key{ "\x1b[1;6F": {Type: KeyCtrlShiftEnd}, // xterm, lxterm "\x1b[1;8F": {Type: KeyCtrlShiftEnd, Alt: true}, // xterm, lxterm - "\x1b[7~": {Type: KeyHome}, // urxvt - "\x1b\x1b[7~": {Type: KeyHome, Alt: true}, // urxvt - "\x1b[7^": {Type: KeyCtrlHome}, // urxvt - "\x1b\x1b[7^": {Type: KeyCtrlHome, Alt: true}, // urxvt - "\x1b[7$": {Type: KeyShiftHome}, // urxvt - "\x1b\x1b[7$": {Type: KeyShiftHome, Alt: true}, // urxvt - "\x1b[7@": {Type: KeyCtrlShiftHome}, // urxvt - "\x1b\x1b[7@": {Type: KeyCtrlShiftHome, Alt: true}, // urxvt - - "\x1b[8~": {Type: KeyEnd}, // urxvt - "\x1b\x1b[8~": {Type: KeyEnd, Alt: true}, // urxvt - "\x1b[8^": {Type: KeyCtrlEnd}, // urxvt - "\x1b\x1b[8^": {Type: KeyCtrlEnd, Alt: true}, // urxvt - "\x1b[8$": {Type: KeyShiftEnd}, // urxvt - "\x1b\x1b[8$": {Type: KeyShiftEnd, Alt: true}, // urxvt - "\x1b[8@": {Type: KeyCtrlShiftEnd}, // urxvt - "\x1b\x1b[8@": {Type: KeyCtrlShiftEnd, Alt: true}, // urxvt + "\x1b[7~": {Type: KeyHome}, // urxvt + "\x1b[7^": {Type: KeyCtrlHome}, // urxvt + "\x1b[7$": {Type: KeyShiftHome}, // urxvt + "\x1b[7@": {Type: KeyCtrlShiftHome}, // urxvt + + "\x1b[8~": {Type: KeyEnd}, // urxvt + "\x1b[8^": {Type: KeyCtrlEnd}, // urxvt + "\x1b[8$": {Type: KeyShiftEnd}, // urxvt + "\x1b[8@": {Type: KeyCtrlShiftEnd}, // urxvt // Function keys, Linux console "\x1b[[A": {Type: KeyF1}, // linux console @@ -479,29 +460,16 @@ var sequences = map[string]Key{ "\x1b[13~": {Type: KeyF3}, // urxvt "\x1b[14~": {Type: KeyF4}, // urxvt - "\x1b\x1b[11~": {Type: KeyF1, Alt: true}, // urxvt - "\x1b\x1b[12~": {Type: KeyF2, Alt: true}, // urxvt - "\x1b\x1b[13~": {Type: KeyF3, Alt: true}, // urxvt - "\x1b\x1b[14~": {Type: KeyF4, Alt: true}, // urxvt - "\x1b[15~": {Type: KeyF5}, // vt100, xterm, also urxvt "\x1b[15;3~": {Type: KeyF5, Alt: true}, // vt100, xterm, also urxvt - "\x1b\x1b[15~": {Type: KeyF5, Alt: true}, // urxvt - "\x1b[17~": {Type: KeyF6}, // vt100, xterm, also urxvt "\x1b[18~": {Type: KeyF7}, // vt100, xterm, also urxvt "\x1b[19~": {Type: KeyF8}, // vt100, xterm, also urxvt "\x1b[20~": {Type: KeyF9}, // vt100, xterm, also urxvt "\x1b[21~": {Type: KeyF10}, // vt100, xterm, also urxvt - "\x1b\x1b[17~": {Type: KeyF6, Alt: true}, // urxvt - "\x1b\x1b[18~": {Type: KeyF7, Alt: true}, // urxvt - "\x1b\x1b[19~": {Type: KeyF8, Alt: true}, // urxvt - "\x1b\x1b[20~": {Type: KeyF9, Alt: true}, // urxvt - "\x1b\x1b[21~": {Type: KeyF10, Alt: true}, // urxvt - "\x1b[17;3~": {Type: KeyF6, Alt: true}, // vt100, xterm "\x1b[18;3~": {Type: KeyF7, Alt: true}, // vt100, xterm "\x1b[19;3~": {Type: KeyF8, Alt: true}, // vt100, xterm @@ -514,9 +482,6 @@ var sequences = map[string]Key{ "\x1b[23;3~": {Type: KeyF11, Alt: true}, // vt100, xterm "\x1b[24;3~": {Type: KeyF12, Alt: true}, // vt100, xterm - "\x1b\x1b[23~": {Type: KeyF11, Alt: true}, // urxvt - "\x1b\x1b[24~": {Type: KeyF12, Alt: true}, // urxvt - "\x1b[1;2P": {Type: KeyF13}, "\x1b[1;2Q": {Type: KeyF14}, @@ -526,9 +491,6 @@ var sequences = map[string]Key{ "\x1b[25;3~": {Type: KeyF13, Alt: true}, // vt100, xterm "\x1b[26;3~": {Type: KeyF14, Alt: true}, // vt100, xterm - "\x1b\x1b[25~": {Type: KeyF13, Alt: true}, // urxvt - "\x1b\x1b[26~": {Type: KeyF14, Alt: true}, // urxvt - "\x1b[1;2R": {Type: KeyF15}, "\x1b[1;2S": {Type: KeyF16}, @@ -538,9 +500,6 @@ var sequences = map[string]Key{ "\x1b[28;3~": {Type: KeyF15, Alt: true}, // vt100, xterm "\x1b[29;3~": {Type: KeyF16, Alt: true}, // vt100, xterm - "\x1b\x1b[28~": {Type: KeyF15, Alt: true}, // urxvt - "\x1b\x1b[29~": {Type: KeyF16, Alt: true}, // urxvt - "\x1b[15;2~": {Type: KeyF17}, "\x1b[17;2~": {Type: KeyF18}, "\x1b[18;2~": {Type: KeyF19}, @@ -551,11 +510,6 @@ var sequences = map[string]Key{ "\x1b[33~": {Type: KeyF19}, "\x1b[34~": {Type: KeyF20}, - "\x1b\x1b[31~": {Type: KeyF17, Alt: true}, // urxvt - "\x1b\x1b[32~": {Type: KeyF18, Alt: true}, // urxvt - "\x1b\x1b[33~": {Type: KeyF19, Alt: true}, // urxvt - "\x1b\x1b[34~": {Type: KeyF20, Alt: true}, // urxvt - // Powershell sequences. "\x1bOA": {Type: KeyUp, Alt: false}, "\x1bOB": {Type: KeyDown, Alt: false}, @@ -563,102 +517,118 @@ var sequences = map[string]Key{ "\x1bOD": {Type: KeyLeft, Alt: false}, } +// unknownInputByteMsg is reported by the input reader when an invalid +// utf-8 byte is detected on the input. Currently, it is not handled +// further by bubbletea. However, having this event makes it possible +// to troubleshoot invalid inputs. +type unknownInputByteMsg byte + +func (u unknownInputByteMsg) String() string { + return fmt.Sprintf("?%#02x?", int(u)) +} + +// unknownCSISequenceMsg is reported by the input reader when an +// unrecognized CSI sequence is detected on the input. Currently, it +// is not handled further by bubbletea. However, having this event +// makes it possible to troubleshoot invalid inputs. +type unknownCSISequenceMsg []byte + +func (u unknownCSISequenceMsg) String() string { + return fmt.Sprintf("?CSI%+v?", []byte(u)[2:]) +} + +var spaceRunes = []rune{' '} + // readInputs reads keypress and mouse inputs from a TTY and returns messages // containing information about the key or mouse events accordingly. func readInputs(input io.Reader) ([]Msg, error) { var buf [256]byte + input = localereader.NewReader(input) + // Read and block numBytes, err := input.Read(buf[:]) if err != nil { return nil, err } b := buf[:numBytes] - b, err = localereader.UTF8(b) - if err != nil { - return nil, err - } - // Check if it's a mouse event. For now we're parsing X10-type mouse events - // only. - mouseEvent, err := parseX10MouseEvents(b) - if err == nil { - var m []Msg - for _, v := range mouseEvent { - m = append(m, MouseMsg(v)) - } - return m, nil + var msgs []Msg + for i, w := 0, 0; i < len(b); i += w { + var msg Msg + w, msg = detectOneMsg(b[i:]) + msgs = append(msgs, msg) } + return msgs, nil +} - var runeSets [][]rune - var runes []rune +var unknownCSIRe = regexp.MustCompile(`^\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]`) - // Translate input into runes. In most cases we'll receive exactly one - // rune, but there are cases, particularly when an input method editor is - // used, where we can receive multiple runes at once. - for i, w := 0, 0; i < len(b); i += w { - r, width := utf8.DecodeRune(b[i:]) - if r == utf8.RuneError { - return nil, errors.New("could not decode rune") - } +func detectOneMsg(b []byte) (w int, msg Msg) { + // Detect mouse events. + if len(b) >= 6 && b[0] == '\x1b' && b[1] == '[' && b[2] == 'M' { + return 6, MouseMsg(parseX10MouseEvent(b)) + } - if r == '\x1b' && len(runes) > 1 { - // a new key sequence has started - runeSets = append(runeSets, runes) - runes = []rune{} - } + // Detect escape sequence and control characters other than NUL, + // possibly with an escape character in front to mark the Alt + // modifier. + var foundSeq bool + foundSeq, w, msg = detectSequence(b) + if foundSeq { + return + } - runes = append(runes, r) - w = width + // No non-NUL control character or escape sequence. + // If we are seeing at least an escape character, remember it for later below. + alt := false + i := 0 + if b[0] == '\x1b' { + alt = true + i++ } - // add the final set of runes we decoded - runeSets = append(runeSets, runes) - if len(runeSets) == 0 { - return nil, errors.New("received 0 runes from input") + // Are we seeing a standalone NUL? This is not handled by detectSequence(). + if i < len(b) && b[i] == 0 { + return i + 1, KeyMsg{Type: keyNUL, Alt: alt} } - var msgs []Msg - for _, runes := range runeSets { - // Is it a sequence, like an arrow key? - if k, ok := sequences[string(runes)]; ok { - msgs = append(msgs, KeyMsg(k)) - continue + // Find the longest sequence of runes that are not control + // characters from this point. + var runes []rune + for rw := 0; i < len(b); i += rw { + var r rune + r, rw = utf8.DecodeRune(b[i:]) + if r == utf8.RuneError || r <= rune(keyUS) || r == rune(keyDEL) || r == ' ' { + // Rune errors are handled below; control characters and spaces will + // be handled by detectSequence in the next call to detectOneMsg. + break } - - // Is this an unrecognized CSI sequence? If so, ignore it. - if len(runes) > 2 && runes[0] == 0x1b && (runes[1] == '[' || - (len(runes) > 3 && runes[1] == 0x1b && runes[2] == '[')) { - continue + runes = append(runes, r) + if alt { + // We only support a single rune after an escape alt modifier. + i += rw + break } - - // Is the alt key pressed? If so, the buffer will be prefixed with an - // escape. - alt := false - if len(runes) > 1 && runes[0] == 0x1b { - alt = true - runes = runes[1:] + } + // If we found at least one rune, we report the bunch of them as + // a single KeyRunes or KeySpace event. + if len(runes) > 0 { + k := Key{Type: KeyRunes, Runes: runes, Alt: alt} + if len(runes) == 1 && runes[0] == ' ' { + k.Type = KeySpace } + return i, KeyMsg(k) + } - for _, v := range runes { - // Is the first rune a control character? - r := KeyType(v) - if r <= keyUS || r == keyDEL { - msgs = append(msgs, KeyMsg(Key{Type: r, Alt: alt})) - continue - } - - // If it's a space, override the type with KeySpace (but still include - // the rune). - if r == ' ' { - msgs = append(msgs, KeyMsg(Key{Type: KeySpace, Runes: []rune{v}, Alt: alt})) - continue - } - - // Welp, just regular, ol' runes. - msgs = append(msgs, KeyMsg(Key{Type: KeyRunes, Runes: []rune{v}, Alt: alt})) - } + // We didn't find an escape sequence, nor a valid rune. Was this a + // lone escape character at the end of the input? + if alt && len(b) == 1 { + return 1, KeyMsg(Key{Type: KeyEscape}) } - return msgs, nil + // The character at the current position is neither an escape + // sequence, a valid rune start or a sole escape character. Report + // it as an invalid byte. + return 1, unknownInputByteMsg(b[0]) } diff --git a/key_sequences.go b/key_sequences.go new file mode 100644 index 0000000000..cc200f8d02 --- /dev/null +++ b/key_sequences.go @@ -0,0 +1,71 @@ +package tea + +import "sort" + +// extSequences is used by the map-based algorithm below. It contains +// the sequences plus their alternatives with an escape character +// prefixed, plus the control chars, plus the space. +// It does not contain the NUL character, which is handled specially +// by detectOneMsg. +var extSequences = func() map[string]Key { + s := map[string]Key{} + for seq, key := range sequences { + key := key + s[seq] = key + if !key.Alt { + key.Alt = true + s["\x1b"+seq] = key + } + } + for i := keyNUL + 1; i <= keyDEL; i++ { + if i == keyESC { + continue + } + s[string([]byte{byte(i)})] = Key{Type: i} + s[string([]byte{'\x1b', byte(i)})] = Key{Type: i, Alt: true} + if i == keyUS { + i = keyDEL - 1 + } + } + s[" "] = Key{Type: KeySpace, Runes: spaceRunes} + s["\x1b "] = Key{Type: KeySpace, Alt: true, Runes: spaceRunes} + s["\x1b\x1b"] = Key{Type: KeyEscape, Alt: true} + return s +}() + +// seqLengths is the sizes of valid sequences, starting with the +// largest size. +var seqLengths = func() []int { + sizes := map[int]struct{}{} + for seq := range extSequences { + sizes[len(seq)] = struct{}{} + } + lsizes := make([]int, 0, len(sizes)) + for sz := range sizes { + lsizes = append(lsizes, sz) + } + sort.Slice(lsizes, func(i, j int) bool { return lsizes[i] > lsizes[j] }) + return lsizes +}() + +// detectSequence uses a longest prefix match over the input +// sequence and a hash map. +func detectSequence(input []byte) (hasSeq bool, width int, msg Msg) { + seqs := extSequences + for _, sz := range seqLengths { + if sz > len(input) { + continue + } + prefix := input[:sz] + key, ok := seqs[string(prefix)] + if ok { + return true, sz, KeyMsg(key) + } + } + // Is this an unknown CSI sequence? + if loc := unknownCSIRe.FindIndex(input); loc != nil { + return true, loc[1], unknownCSISequenceMsg(input[:loc[1]]) + } + + return false, 0, nil +} diff --git a/key_test.go b/key_test.go index 07c2743c65..1af6a3e9ae 100644 --- a/key_test.go +++ b/key_test.go @@ -2,8 +2,15 @@ package tea import ( "bytes" + "flag" "fmt" + "math/rand" + "reflect" + "runtime" + "sort" + "strings" "testing" + "time" ) func TestKeyString(t *testing.T) { @@ -48,13 +55,165 @@ func TestKeyTypeString(t *testing.T) { }) } +type seqTest struct { + seq []byte + msg Msg +} + +// buildBaseSeqTests returns sequence tests that are valid for the +// detectSequence() function. +func buildBaseSeqTests() []seqTest { + td := []seqTest{} + for seq, key := range sequences { + key := key + td = append(td, seqTest{[]byte(seq), KeyMsg(key)}) + if !key.Alt { + key.Alt = true + td = append(td, seqTest{[]byte("\x1b" + seq), KeyMsg(key)}) + } + } + // Add all the control characters. + for i := keyNUL + 1; i <= keyDEL; i++ { + if i == keyESC { + // Not handled in detectSequence(), so not part of the base test + // suite. + continue + } + td = append(td, seqTest{[]byte{byte(i)}, KeyMsg{Type: i}}) + td = append(td, seqTest{[]byte{'\x1b', byte(i)}, KeyMsg{Type: i, Alt: true}}) + if i == keyUS { + i = keyDEL - 1 + } + } + + // Additional special cases. + td = append(td, + // Unrecognized CSI sequence. + seqTest{ + []byte{'\x1b', '[', '-', '-', '-', '-', 'X'}, + unknownCSISequenceMsg([]byte{'\x1b', '[', '-', '-', '-', '-', 'X'}), + }, + // A lone space character. + seqTest{ + []byte{' '}, + KeyMsg{Type: KeySpace, Runes: []rune(" ")}, + }, + // An escape character with the alt modifier. + seqTest{ + []byte{'\x1b', ' '}, + KeyMsg{Type: KeySpace, Runes: []rune(" "), Alt: true}, + }, + ) + return td +} + +func TestDetectSequence(t *testing.T) { + td := buildBaseSeqTests() + for _, tc := range td { + t.Run(fmt.Sprintf("%q", string(tc.seq)), func(t *testing.T) { + hasSeq, width, msg := detectSequence(tc.seq) + if !hasSeq { + t.Fatalf("no sequence found") + } + if width != len(tc.seq) { + t.Errorf("parser did not consume the entire input: got %d, expected %d", width, len(tc.seq)) + } + if !reflect.DeepEqual(tc.msg, msg) { + t.Errorf("expected event %#v (%T), got %#v (%T)", tc.msg, tc.msg, msg, msg) + } + }) + } +} + +func TestDetectOneMsg(t *testing.T) { + td := buildBaseSeqTests() + // Add tests for the inputs that detectOneMsg() can parse, but + // detectSequence() cannot. + td = append(td, + // Mouse event. + seqTest{ + []byte{'\x1b', '[', 'M', byte(32) + 0b0100_0000, byte(65), byte(49)}, + MouseMsg{X: 32, Y: 16, Type: MouseWheelUp}, + }, + // Runes. + seqTest{ + []byte{'a'}, + KeyMsg{Type: KeyRunes, Runes: []rune("a")}, + }, + seqTest{ + []byte{'\x1b', 'a'}, + KeyMsg{Type: KeyRunes, Runes: []rune("a"), Alt: true}, + }, + seqTest{ + []byte{'a', 'a', 'a'}, + KeyMsg{Type: KeyRunes, Runes: []rune("aaa")}, + }, + // Multi-byte rune. + seqTest{ + []byte("☃"), + KeyMsg{Type: KeyRunes, Runes: []rune("☃")}, + }, + seqTest{ + []byte("\x1b☃"), + KeyMsg{Type: KeyRunes, Runes: []rune("☃"), Alt: true}, + }, + // Standalone control chacters. + seqTest{ + []byte{'\x1b'}, + KeyMsg{Type: KeyEscape}, + }, + seqTest{ + []byte{byte(keySOH)}, + KeyMsg{Type: KeyCtrlA}, + }, + seqTest{ + []byte{'\x1b', byte(keySOH)}, + KeyMsg{Type: KeyCtrlA, Alt: true}, + }, + seqTest{ + []byte{byte(keyNUL)}, + KeyMsg{Type: KeyCtrlAt}, + }, + seqTest{ + []byte{'\x1b', byte(keyNUL)}, + KeyMsg{Type: KeyCtrlAt, Alt: true}, + }, + // Invalid characters. + seqTest{ + []byte{'\x80'}, + unknownInputByteMsg(0x80), + }, + ) + + if runtime.GOOS != "windows" { + // Sadly, utf8.DecodeRune([]byte(0xfe)) returns a valid rune on windows. + // This is incorrect, but it makes our test fail if we try it out. + td = append(td, seqTest{ + []byte{'\xfe'}, + unknownInputByteMsg(0xfe), + }) + } + + for _, tc := range td { + t.Run(fmt.Sprintf("%q", string(tc.seq)), func(t *testing.T) { + width, msg := detectOneMsg(tc.seq) + if width != len(tc.seq) { + t.Errorf("parser did not consume the entire input: got %d, expected %d", width, len(tc.seq)) + } + if !reflect.DeepEqual(tc.msg, msg) { + t.Errorf("expected event %#v (%T), got %#v (%T)", tc.msg, tc.msg, msg, msg) + } + }) + } +} + func TestReadInput(t *testing.T) { type test struct { keyname string in []byte out []Msg } - for i, td := range []test{ + testData := []test{ {"a", []byte{'a'}, []Msg{ @@ -73,6 +232,21 @@ func TestReadInput(t *testing.T) { }, }, }, + {"a alt+a", + []byte{'a', '\x1b', 'a'}, + []Msg{ + KeyMsg{Type: KeyRunes, Runes: []rune{'a'}}, + KeyMsg{Type: KeyRunes, Runes: []rune{'a'}, Alt: true}, + }, + }, + {"a alt+a a", + []byte{'a', '\x1b', 'a', 'a'}, + []Msg{ + KeyMsg{Type: KeyRunes, Runes: []rune{'a'}}, + KeyMsg{Type: KeyRunes, Runes: []rune{'a'}, Alt: true}, + KeyMsg{Type: KeyRunes, Runes: []rune{'a'}}, + }, + }, {"ctrl+a", []byte{byte(keySOH)}, []Msg{ @@ -81,6 +255,13 @@ func TestReadInput(t *testing.T) { }, }, }, + {"ctrl+a ctrl+b", + []byte{byte(keySOH), byte(keySTX)}, + []Msg{ + KeyMsg{Type: KeyCtrlA}, + KeyMsg{Type: KeyCtrlB}, + }, + }, {"alt+a", []byte{byte(0x1b), 'a'}, []Msg{ @@ -96,19 +277,7 @@ func TestReadInput(t *testing.T) { []Msg{ KeyMsg{ Type: KeyRunes, - Runes: []rune{'a'}, - }, - KeyMsg{ - Type: KeyRunes, - Runes: []rune{'b'}, - }, - KeyMsg{ - Type: KeyRunes, - Runes: []rune{'c'}, - }, - KeyMsg{ - Type: KeyRunes, - Runes: []rune{'d'}, + Runes: []rune{'a', 'b', 'c', 'd'}, }, }, }, @@ -124,10 +293,30 @@ func TestReadInput(t *testing.T) { []byte{'\x1b', '[', 'M', byte(32) + 0b0100_0000, byte(65), byte(49)}, []Msg{ MouseMsg{ + X: 32, + Y: 16, Type: MouseWheelUp, }, }, }, + {"left 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, + }), + MouseMsg(MouseEvent{ + X: 64, + Y: 32, + Type: MouseRelease, + }), + }, + }, {"shift+tab", []byte{'\x1b', '[', 'Z'}, []Msg{ @@ -136,6 +325,10 @@ func TestReadInput(t *testing.T) { }, }, }, + {"enter", + []byte{'\r'}, + []Msg{KeyMsg{Type: KeyEnter}}, + }, {"alt+enter", []byte{'\x1b', '\r'}, []Msg{ @@ -162,9 +355,9 @@ func TestReadInput(t *testing.T) { }, }, }, - {"unrecognized CSI", + {"?CSI[45 45 45 45 88]?", []byte{'\x1b', '[', '-', '-', '-', '-', 'X'}, - []Msg{}, + []Msg{unknownCSISequenceMsg([]byte{'\x1b', '[', '-', '-', '-', '-', 'X'})}, }, // Powershell sequences. {"up", @@ -191,34 +384,236 @@ func TestReadInput(t *testing.T) { []byte{'\x1b', '\x7f'}, []Msg{KeyMsg{Type: KeyBackspace, Alt: true}}, }, - } { + {"ctrl+@", + []byte{'\x00'}, + []Msg{KeyMsg{Type: KeyCtrlAt}}, + }, + {"alt+ctrl+@", + []byte{'\x1b', '\x00'}, + []Msg{KeyMsg{Type: KeyCtrlAt, Alt: true}}, + }, + {"esc", + []byte{'\x1b'}, + []Msg{KeyMsg{Type: KeyEsc}}, + }, + {"alt+esc", + []byte{'\x1b', '\x1b'}, + []Msg{KeyMsg{Type: KeyEsc, Alt: true}}, + }, + // Bracketed paste does not work yet. + {"?CSI[50 48 48 126]? a b ?CSI[50 48 49 126]?", + []byte{ + '\x1b', '[', '2', '0', '0', '~', + 'a', ' ', 'b', + '\x1b', '[', '2', '0', '1', '~'}, + []Msg{ + // What we expect once bracketed paste is recognized properly: + // + // KeyMsg{Type: KeyRunes, Runes: []rune("a b")}, + // + // What we get instead (for now): + unknownCSISequenceMsg{0x1b, 0x5b, 0x32, 0x30, 0x30, 0x7e}, + KeyMsg{Type: KeyRunes, Runes: []rune{'a'}}, + KeyMsg{Type: KeySpace, Runes: []rune{' '}}, + KeyMsg{Type: KeyRunes, Runes: []rune{'b'}}, + unknownCSISequenceMsg{0x1b, 0x5b, 0x32, 0x30, 0x31, 0x7e}, + }, + }, + } + if runtime.GOOS != "windows" { + // Sadly, utf8.DecodeRune([]byte(0xfe)) returns a valid rune on windows. + // This is incorrect, but it makes our test fail if we try it out. + testData = append(testData, + test{"?0xfe?", + []byte{'\xfe'}, + []Msg{unknownInputByteMsg(0xfe)}, + }, + test{"a ?0xfe? b", + []byte{'a', '\xfe', ' ', 'b'}, + []Msg{ + KeyMsg{Type: KeyRunes, Runes: []rune{'a'}}, + unknownInputByteMsg(0xfe), + KeyMsg{Type: KeySpace, Runes: []rune{' '}}, + KeyMsg{Type: KeyRunes, Runes: []rune{'b'}}, + }, + }, + ) + } + + for i, td := range testData { t.Run(fmt.Sprintf("%d: %s", i, td.keyname), func(t *testing.T) { msgs, err := readInputs(bytes.NewReader(td.in)) if err != nil { t.Fatalf("unexpected error: %v", err) } + + // Compute the title for the event sequence. + var buf strings.Builder + for i, msg := range msgs { + if i > 0 { + buf.WriteByte(' ') + } + if s, ok := msg.(fmt.Stringer); ok { + buf.WriteString(s.String()) + } else { + fmt.Fprintf(&buf, "%#v:%T", msg, msg) + } + } + title := buf.String() + if title != td.keyname { + t.Errorf("expected message titles:\n %s\ngot:\n %s", td.keyname, title) + } + if len(msgs) != len(td.out) { - t.Fatalf("unexpected message list length") + t.Fatalf("unexpected message list length: got %d, expected %d\n%#v", len(msgs), len(td.out), msgs) } - if len(msgs) == 1 { - if m, ok := msgs[0].(KeyMsg); ok && m.String() != td.keyname { - t.Fatalf(`expected a keymsg %q, got %q`, td.keyname, m) - } + if !reflect.DeepEqual(td.out, msgs) { + t.Fatalf("expected:\n%#v\ngot:\n%#v", td.out, msgs) } + }) + } +} - for i, v := range msgs { - if m, ok := v.(KeyMsg); ok && - m.String() != td.out[i].(KeyMsg).String() { - t.Fatalf(`expected a keymsg %q, got %q`, td.out[i].(KeyMsg), m) +// randTest defines the test input and expected output for a sequence +// of interleaved control sequences and control characters. +type randTest struct { + data []byte + lengths []int + names []string +} + +// seed is the random seed to randomize the input. This helps check +// that all the sequences get ultimately exercised. +var seed = flag.Int64("seed", 0, "random seed (0 to autoselect)") + +// genRandomData generates a randomized test, with a random seed unless +// the seed flag was set. +func genRandomData(logfn func(int64), length int) randTest { + // We'll use a random source. However, we give the user the option + // to override it to a specific value for reproduceability. + s := *seed + if s == 0 { + s = time.Now().UnixNano() + } + // Inform the user so they know what to reuse to get the same data. + logfn(s) + return genRandomDataWithSeed(s, length) +} + +// genRandomDataWithSeed generates a randomized test with a fixed seed. +func genRandomDataWithSeed(s int64, length int) randTest { + src := rand.NewSource(s) + r := rand.New(src) + + // allseqs contains all the sequences, in sorted order. We sort + // to make the test deterministic (when the seed is also fixed). + type seqpair struct { + seq string + name string + } + var allseqs []seqpair + for seq, key := range sequences { + allseqs = append(allseqs, seqpair{seq, key.String()}) + } + sort.Slice(allseqs, func(i, j int) bool { return allseqs[i].seq < allseqs[j].seq }) + + // res contains the computed test. + var res randTest + + for len(res.data) < length { + alt := r.Intn(2) + prefix := "" + esclen := 0 + if alt == 1 { + prefix = "alt+" + esclen = 1 + } + kind := r.Intn(3) + switch kind { + case 0: + // A control character. + if alt == 1 { + res.data = append(res.data, '\x1b') + } + res.data = append(res.data, 1) + res.names = append(res.names, prefix+"ctrl+a") + res.lengths = append(res.lengths, 1+esclen) + + case 1, 2: + // A sequence. + seqi := r.Intn(len(allseqs)) + s := allseqs[seqi] + if strings.HasPrefix(s.name, "alt+") { + esclen = 0 + prefix = "" + alt = 0 + } + if alt == 1 { + res.data = append(res.data, '\x1b') + } + res.data = append(res.data, s.seq...) + res.names = append(res.names, prefix+s.name) + res.lengths = append(res.lengths, len(s.seq)+esclen) + } + } + return res +} + +// TestDetectRandomSequencesLex checks that the lex-generated sequence +// detector works over concatenations of random sequences. +func TestDetectRandomSequencesLex(t *testing.T) { + runTestDetectSequence(t, detectSequence) +} + +func runTestDetectSequence( + t *testing.T, detectSequence func(input []byte) (hasSeq bool, width int, msg Msg), +) { + for i := 0; i < 10; i++ { + t.Run("", func(t *testing.T) { + td := genRandomData(func(s int64) { t.Logf("using random seed: %d", s) }, 1000) + + t.Logf("%#v", td) + + // tn is the event number in td. + // i is the cursor in the input data. + // w is the length of the last sequence detected. + for tn, i, w := 0, 0, 0; i < len(td.data); tn, i = tn+1, i+w { + hasSequence, width, msg := detectSequence(td.data[i:]) + if !hasSequence { + t.Fatalf("at %d (ev %d): failed to find sequence", i, tn) } - if m, ok := v.(MouseMsg); ok && - (mouseEventTypes[m.Type] != td.keyname || m.Type != td.out[i].(MouseMsg).Type) { - t.Fatalf(`expected a mousemsg %q, got %q`, - td.keyname, - mouseEventTypes[td.out[i].(MouseMsg).Type]) + if width != td.lengths[tn] { + t.Errorf("at %d (ev %d): expected width %d, got %d", i, tn, td.lengths[tn], width) + } + w = width + + s, ok := msg.(fmt.Stringer) + if !ok { + t.Errorf("at %d (ev %d): expected stringer event, got %T", i, tn, msg) + } else { + if td.names[tn] != s.String() { + t.Errorf("at %d (ev %d): expected event %q, got %q", i, tn, td.names[tn], s.String()) + } } } }) } } + +// TestDetectRandomSequencesLex checks that the map-based sequence +// detector works over concatenations of random sequences. +func TestDetectRandomSequencesMap(t *testing.T) { + runTestDetectSequence(t, detectSequence) +} + +// BenchmarkDetectSequenceMap benchmarks the map-based sequence +// detector. +func BenchmarkDetectSequenceMap(b *testing.B) { + td := genRandomDataWithSeed(123, 10000) + for i := 0; i < b.N; i++ { + for j, w := 0, 0; j < len(td.data); j += w { + _, w, _ = detectSequence(td.data[j:]) + } + } +} diff --git a/mouse.go b/mouse.go index f918d20695..fc66691cc6 100644 --- a/mouse.go +++ b/mouse.go @@ -1,15 +1,15 @@ package tea -import ( - "bytes" - "errors" -) - -// MouseMsg contains information about a mouse event and is sent to a program's +// 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. type MouseMsg MouseEvent +// String returns a string representation of a mouse event. +func (m MouseMsg) String() string { + return MouseEvent(m).String() +} + // MouseEvent represents a mouse event, which could be a click, a scroll wheel // movement, a cursor movement, or a combination. type MouseEvent struct { @@ -66,84 +66,67 @@ var mouseEventTypes = map[MouseEventType]string{ // ESC [M Cb Cx Cy // // See: http://www.xfree86.org/current/ctlseqs.html#Mouse%20Tracking -func parseX10MouseEvents(buf []byte) ([]MouseEvent, error) { - var r []MouseEvent - - seq := []byte("\x1b[M") - if !bytes.Contains(buf, seq) { - return r, errors.New("not an X10 mouse event") - } - - for _, v := range bytes.Split(buf, seq) { - if len(v) == 0 { - continue - } - if len(v) != 3 { - return r, errors.New("not an X10 mouse event") +func parseX10MouseEvent(buf []byte) MouseEvent { + v := buf[3:6] + var m MouseEvent + const byteOffset = 32 + e := v[0] - byteOffset + + const ( + bitShift = 0b0000_0100 + bitAlt = 0b0000_1000 + bitCtrl = 0b0001_0000 + bitMotion = 0b0010_0000 + bitWheel = 0b0100_0000 + + 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 } - - var m MouseEvent - const byteOffset = 32 - e := v[0] - byteOffset - - const ( - bitShift = 0b0000_0100 - bitAlt = 0b0000_1000 - bitCtrl = 0b0001_0000 - bitMotion = 0b0010_0000 - bitWheel = 0b0100_0000 - - 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 - } - } 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 - } + } 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 } } + } - if e&bitAlt != 0 { - m.Alt = true - } - if e&bitCtrl != 0 { - m.Ctrl = true - } - - // (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 - - r = append(r, m) + if e&bitAlt != 0 { + m.Alt = true } + if e&bitCtrl != 0 { + m.Ctrl = true + } + + // (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 - return r, nil + return m } diff --git a/mouse_test.go b/mouse_test.go index d9c108f2f7..a64a2e302d 100644 --- a/mouse_test.go +++ b/mouse_test.go @@ -122,268 +122,209 @@ func TestParseX10MouseEvent(t *testing.T) { tt := []struct { name string buf []byte - expected []MouseEvent + expected MouseEvent }{ // Position. { name: "zero position", buf: encode(0b0010_0000, 0, 0), - expected: []MouseEvent{ - { - X: 0, - Y: 0, - Type: MouseLeft, - }, + expected: MouseEvent{ + X: 0, + Y: 0, + Type: MouseLeft, }, }, { name: "max position", buf: encode(0b0010_0000, 222, 222), // Because 255 (max int8) - 32 - 1. - expected: []MouseEvent{ - { - X: 222, - Y: 222, - Type: MouseLeft, - }, + expected: MouseEvent{ + X: 222, + Y: 222, + Type: MouseLeft, }, }, // Simple. { name: "left", buf: encode(0b0000_0000, 32, 16), - expected: []MouseEvent{ - { - X: 32, - Y: 16, - Type: MouseLeft, - }, + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseLeft, }, }, { name: "left in motion", buf: encode(0b0010_0000, 32, 16), - expected: []MouseEvent{ - { - X: 32, - Y: 16, - Type: MouseLeft, - }, + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseLeft, }, }, { name: "middle", buf: encode(0b0000_0001, 32, 16), - expected: []MouseEvent{ - { - X: 32, - Y: 16, - Type: MouseMiddle, - }, + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseMiddle, }, }, { name: "middle in motion", buf: encode(0b0010_0001, 32, 16), - expected: []MouseEvent{ - { - X: 32, - Y: 16, - Type: MouseMiddle, - }, + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseMiddle, }, }, { name: "right", buf: encode(0b0000_0010, 32, 16), - expected: []MouseEvent{ - { - X: 32, - Y: 16, - Type: MouseRight, - }, + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseRight, }, }, { name: "right in motion", buf: encode(0b0010_0010, 32, 16), - expected: []MouseEvent{ - { - X: 32, - Y: 16, - Type: MouseRight, - }, + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseRight, }, }, { name: "motion", buf: encode(0b0010_0011, 32, 16), - expected: []MouseEvent{ - { - X: 32, - Y: 16, - Type: MouseMotion, - }, + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseMotion, }, }, { name: "wheel up", buf: encode(0b0100_0000, 32, 16), - expected: []MouseEvent{ - { - X: 32, - Y: 16, - Type: MouseWheelUp, - }, + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseWheelUp, }, }, { name: "wheel down", buf: encode(0b0100_0001, 32, 16), - expected: []MouseEvent{ - { - X: 32, - Y: 16, - Type: MouseWheelDown, - }, + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseWheelDown, }, }, { name: "release", buf: encode(0b0000_0011, 32, 16), - expected: []MouseEvent{ - { - X: 32, - Y: 16, - Type: MouseRelease, - }, + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseRelease, }, }, // Combinations. { name: "alt+right", buf: encode(0b0010_1010, 32, 16), - expected: []MouseEvent{ - { - X: 32, - Y: 16, - Type: MouseRight, - Alt: true, - }, + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseRight, + Alt: true, }, }, { name: "ctrl+right", buf: encode(0b0011_0010, 32, 16), - expected: []MouseEvent{ - { - X: 32, - Y: 16, - Type: MouseRight, - Ctrl: true, - }, + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseRight, + Ctrl: true, }, }, { name: "ctrl+alt+right", buf: encode(0b0011_1010, 32, 16), - expected: []MouseEvent{ - { - X: 32, - Y: 16, - Type: MouseRight, - Alt: true, - Ctrl: true, - }, + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseRight, + Alt: true, + Ctrl: true, }, }, { name: "alt+wheel down", buf: encode(0b0100_1001, 32, 16), - expected: []MouseEvent{ - { - X: 32, - Y: 16, - Type: MouseWheelDown, - Alt: true, - }, + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseWheelDown, + Alt: true, }, }, { name: "ctrl+wheel down", buf: encode(0b0101_0001, 32, 16), - expected: []MouseEvent{ - { - X: 32, - Y: 16, - Type: MouseWheelDown, - Ctrl: true, - }, + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseWheelDown, + Ctrl: true, }, }, { name: "ctrl+alt+wheel down", buf: encode(0b0101_1001, 32, 16), - expected: []MouseEvent{ - { - X: 32, - Y: 16, - Type: MouseWheelDown, - Alt: true, - Ctrl: true, - }, + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseWheelDown, + Alt: true, + Ctrl: true, }, }, // Unknown. { name: "wheel with unknown bit", buf: encode(0b0100_0010, 32, 16), - expected: []MouseEvent{ - { - X: 32, - Y: 16, - Type: MouseUnknown, - }, + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseUnknown, }, }, { name: "unknown with modifier", buf: encode(0b0100_1010, 32, 16), - expected: []MouseEvent{ - { - X: 32, - Y: 16, - Type: MouseUnknown, - Alt: true, - }, + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseUnknown, + Alt: true, }, }, // Overflow position. { name: "overflow position", buf: encode(0b0010_0000, 250, 223), // Because 255 (max int8) - 32 - 1. - expected: []MouseEvent{ - { - X: -6, - Y: -33, - Type: MouseLeft, - }, - }, - }, - // Batched events. - { - name: "batched events", - buf: append(encode(0b0010_0000, 32, 16), encode(0b0000_0011, 64, 32)...), - expected: []MouseEvent{ - { - X: 32, - Y: 16, - Type: MouseLeft, - }, - { - X: 64, - Y: 32, - Type: MouseRelease, - }, + expected: MouseEvent{ + X: -6, + Y: -33, + Type: MouseLeft, }, }, } @@ -392,56 +333,13 @@ func TestParseX10MouseEvent(t *testing.T) { tc := tt[i] t.Run(tc.name, func(t *testing.T) { - actual, err := parseX10MouseEvents(tc.buf) - if err != nil { - t.Fatalf("unexpected error for test: %v", - err, - ) - } - - for i := range tc.expected { - if tc.expected[i] != actual[i] { - t.Fatalf("expected %#v but got %#v", - tc.expected[i], - actual[i], - ) - } - } - }) - } -} - -func TestParseX10MouseEvent_error(t *testing.T) { - tt := []struct { - name string - buf []byte - }{ - { - name: "empty buf", - buf: nil, - }, - { - name: "wrong high bit", - buf: []byte("\x1a[M@A1"), - }, - { - name: "short buf", - buf: []byte("\x1b[M@A"), - }, - { - name: "long buf", - buf: []byte("\x1b[M@A11"), - }, - } - - for i := range tt { - tc := tt[i] - - t.Run(tc.name, func(t *testing.T) { - _, err := parseX10MouseEvents(tc.buf) + actual := parseX10MouseEvent(tc.buf) - if err == nil { - t.Fatalf("expected error but got nil") + if tc.expected != actual { + t.Fatalf("expected %#v but got %#v", + tc.expected, + actual, + ) } }) } From ed4f2ec1ca0b47156fece1b368ee1f68f2020478 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Mon, 19 Jun 2023 17:30:27 -0400 Subject: [PATCH 68/89] chore: go mod tidy to remove rogue bubbles dep --- go.mod | 1 - go.sum | 2 -- 2 files changed, 3 deletions(-) diff --git a/go.mod b/go.mod index 2e15427694..86f90679aa 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,6 @@ require ( require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/charmbracelet/bubbles v0.16.1 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/rivo/uniseg v0.2.0 // indirect diff --git a/go.sum b/go.sum index 98430888bc..77ace75615 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ 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/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY= -github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc= github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= From b1e7f42ab0625c298c70f3dcd1f04da979a2e8be Mon Sep 17 00:00:00 2001 From: Raphael 'kena' Poss Date: Fri, 21 Oct 2022 18:18:56 +0200 Subject: [PATCH 69/89] fix(key): invert the control loop Instead of reading messages in an array and then sending them into a channel, this version of key.go writes to the channel directly. --- key.go | 40 ++++++++++++++++++++------------------ key_test.go | 55 +++++++++++++++++++++++++++++++++++++++++++++++------ tty.go | 26 +++++++------------------ 3 files changed, 77 insertions(+), 44 deletions(-) diff --git a/key.go b/key.go index bbd533cd78..74f85ec405 100644 --- a/key.go +++ b/key.go @@ -1,12 +1,11 @@ package tea import ( + "context" "fmt" "io" "regexp" "unicode/utf8" - - "github.com/mattn/go-localereader" ) // KeyMsg contains information about a keypress. KeyMsgs are always sent to @@ -539,27 +538,30 @@ func (u unknownCSISequenceMsg) String() string { var spaceRunes = []rune{' '} -// readInputs reads keypress and mouse inputs from a TTY and returns messages +// readInputs reads keypress and mouse inputs from a TTY and produces messages // containing information about the key or mouse events accordingly. -func readInputs(input io.Reader) ([]Msg, error) { +func readInputs(ctx context.Context, msgs chan<- Msg, input io.Reader) error { var buf [256]byte - input = localereader.NewReader(input) - - // Read and block - numBytes, err := input.Read(buf[:]) - if err != nil { - return nil, err - } - b := buf[:numBytes] - - var msgs []Msg - for i, w := 0, 0; i < len(b); i += w { - var msg Msg - w, msg = detectOneMsg(b[i:]) - msgs = append(msgs, msg) + for { + // Read and block. + numBytes, err := input.Read(buf[:]) + if err != nil { + return err + } + b := buf[:numBytes] + + var i, w int + for i, w = 0, 0; i < len(b); i += w { + var msg Msg + w, msg = detectOneMsg(b[i:]) + select { + case msgs <- msg: + case <-ctx.Done(): + return ctx.Err() + } + } } - return msgs, nil } var unknownCSIRe = regexp.MustCompile(`^\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]`) diff --git a/key_test.go b/key_test.go index 1af6a3e9ae..d466b056e3 100644 --- a/key_test.go +++ b/key_test.go @@ -2,13 +2,17 @@ package tea import ( "bytes" + "context" + "errors" "flag" "fmt" + "io" "math/rand" "reflect" "runtime" "sort" "strings" + "sync" "testing" "time" ) @@ -442,12 +446,7 @@ func TestReadInput(t *testing.T) { for i, td := range testData { t.Run(fmt.Sprintf("%d: %s", i, td.keyname), func(t *testing.T) { - msgs, err := readInputs(bytes.NewReader(td.in)) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - - // Compute the title for the event sequence. + msgs := testReadInputs(t, bytes.NewReader(td.in)) var buf strings.Builder for i, msg := range msgs { if i > 0 { @@ -459,6 +458,7 @@ func TestReadInput(t *testing.T) { fmt.Fprintf(&buf, "%#v:%T", msg, msg) } } + title := buf.String() if title != td.keyname { t.Errorf("expected message titles:\n %s\ngot:\n %s", td.keyname, title) @@ -475,6 +475,49 @@ func TestReadInput(t *testing.T) { } } +func testReadInputs(t *testing.T, input io.Reader) []Msg { + // We'll check that the input reader finishes at the end + // without error. + var wg sync.WaitGroup + var inputErr error + ctx, cancel := context.WithCancel(context.Background()) + defer func() { + cancel() + wg.Wait() + if inputErr != nil && !errors.Is(inputErr, io.EOF) { + t.Fatalf("unexpected input error: %v", inputErr) + } + }() + + // The messages we're consuming. + msgsC := make(chan Msg) + + // Start the reader in the background. + wg.Add(1) + go func() { + defer wg.Done() + inputErr = readInputs(ctx, msgsC, input) + msgsC <- nil + }() + + var msgs []Msg +loop: + for { + select { + case msg := <-msgsC: + if msg == nil { + // end of input marker for the test. + break loop + } + msgs = append(msgs, msg) + case <-time.After(2 * time.Second): + t.Errorf("timeout waiting for input event") + break loop + } + } + return msgs +} + // randTest defines the test input and expected output for a sequence // of interleaved control sequences and control characters. type randTest struct { diff --git a/tty.go b/tty.go index 3ab6639b75..715d542356 100644 --- a/tty.go +++ b/tty.go @@ -7,6 +7,7 @@ import ( "time" isatty "github.com/mattn/go-isatty" + localereader "github.com/mattn/go-localereader" "github.com/muesli/cancelreader" "golang.org/x/term" ) @@ -71,25 +72,12 @@ func (p *Program) initCancelReader() error { func (p *Program) readLoop() { defer close(p.readLoopDone) - for { - if p.ctx.Err() != nil { - return - } - - msgs, err := readInputs(p.cancelReader) - if err != nil { - if !errors.Is(err, io.EOF) && !errors.Is(err, cancelreader.ErrCanceled) { - select { - case <-p.ctx.Done(): - case p.errs <- err: - } - } - - return - } - - for _, msg := range msgs { - p.msgs <- msg + input := localereader.NewReader(p.cancelReader) + err := readInputs(p.ctx, p.msgs, input) + if !errors.Is(err, io.EOF) && !errors.Is(err, cancelreader.ErrCanceled) { + select { + case <-p.ctx.Done(): + case p.errs <- err: } } } From f75684c9862611cfa7f5f5f862330d6769fee757 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Wed, 14 Jun 2023 14:42:04 -0400 Subject: [PATCH 70/89] chore: group handler type and methods together --- tea.go | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/tea.go b/tea.go index c1d8cafd5d..34fb883e59 100644 --- a/tea.go +++ b/tea.go @@ -58,8 +58,6 @@ type Model interface { // update function. type Cmd func() Msg -type handlers []chan struct{} - type inputType int const ( @@ -102,6 +100,29 @@ const ( withoutCatchPanics ) +// handlers manages series of channels returned by various processes. It allows +// us to wait for those processes to terminate before exiting the program. +type handlers []chan struct{} + +// Adds a channel to the list of handlers. We wait for all handlers to terminate +// gracefully on shutdown. +func (h *handlers) add(ch chan struct{}) { + *h = append(*h, ch) +} + +// shutdown waits for all handlers to terminate. +func (h handlers) shutdown() { + var wg sync.WaitGroup + for _, ch := range h { + wg.Add(1) + go func(ch chan struct{}) { + <-ch + wg.Done() + }(ch) + } + wg.Wait() +} + // Program is a terminal user interface. type Program struct { initialModel Model @@ -678,22 +699,3 @@ func (p *Program) Printf(template string, args ...interface{}) { messageBody: fmt.Sprintf(template, args...), } } - -// Adds a handler to the list of handlers. We wait for all handlers to terminate -// gracefully on shutdown. -func (h *handlers) add(ch chan struct{}) { - *h = append(*h, ch) -} - -// Shutdown waits for all handlers to terminate. -func (h handlers) shutdown() { - var wg sync.WaitGroup - for _, ch := range h { - wg.Add(1) - go func(ch chan struct{}) { - <-ch - wg.Done() - }(ch) - } - wg.Wait() -} From cd63c32c730b5b847a97c333b9b8d8e049d9e2e1 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Wed, 28 Jun 2023 11:40:08 -0300 Subject: [PATCH 71/89] feat(deps): update termenv (#768) --- examples/go.mod | 4 ++-- examples/go.sum | 6 ++++-- go.mod | 4 ++-- go.sum | 9 ++++----- tutorials/go.mod | 4 ++-- tutorials/go.sum | 11 ++++------- 6 files changed, 18 insertions(+), 20 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index 5dad68268c..9de58ea074 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -13,7 +13,7 @@ require ( github.com/lucasb-eyer/go-colorful v1.2.0 github.com/mattn/go-isatty v0.0.18 github.com/muesli/reflow v0.3.0 - github.com/muesli/termenv v0.15.1 + github.com/muesli/termenv v0.15.2 ) require ( @@ -38,7 +38,7 @@ require ( github.com/yuin/goldmark-emoji v1.0.1 // indirect golang.org/x/net v0.7.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.6.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 ) diff --git a/examples/go.sum b/examples/go.sum index 902315408e..7b504cd64c 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -58,8 +58,9 @@ github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIf 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.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= -github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -101,8 +102,9 @@ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= 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/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= diff --git a/go.mod b/go.mod index 86f90679aa..b5e9e98cd9 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b github.com/muesli/cancelreader v0.2.2 github.com/muesli/reflow v0.3.0 - github.com/muesli/termenv v0.15.1 + github.com/muesli/termenv v0.15.2 golang.org/x/sync v0.1.0 golang.org/x/term v0.6.0 ) @@ -19,6 +19,6 @@ require ( github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/rivo/uniseg v0.2.0 // indirect - golang.org/x/sys v0.6.0 // indirect + golang.org/x/sys v0.7.0 // indirect golang.org/x/text v0.3.8 // indirect ) diff --git a/go.sum b/go.sum index 77ace75615..9bc66d6357 100644 --- a/go.sum +++ b/go.sum @@ -4,7 +4,6 @@ github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2 github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= 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.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 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= @@ -19,8 +18,8 @@ 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.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= -github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= +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/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -41,10 +40,10 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= 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/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.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= diff --git a/tutorials/go.mod b/tutorials/go.mod index 5933d123ca..085b9d4560 100644 --- a/tutorials/go.mod +++ b/tutorials/go.mod @@ -14,10 +14,10 @@ require ( github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/reflow v0.3.0 // indirect - github.com/muesli/termenv v0.15.1 // indirect + github.com/muesli/termenv v0.15.2 // indirect github.com/rivo/uniseg v0.2.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.6.0 // indirect + golang.org/x/sys v0.7.0 // indirect golang.org/x/term v0.6.0 // indirect golang.org/x/text v0.3.8 // indirect ) diff --git a/tutorials/go.sum b/tutorials/go.sum index 484e442e86..9bc66d6357 100644 --- a/tutorials/go.sum +++ b/tutorials/go.sum @@ -1,12 +1,9 @@ -github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E= -github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= 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/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= 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.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 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= @@ -21,8 +18,8 @@ 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.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= -github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= +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/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -43,10 +40,10 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= 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/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.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= From c1b0b19d64bad50197c2888ad4a72d429206985a Mon Sep 17 00:00:00 2001 From: Roman Leonenkov Date: Fri, 30 Jun 2023 00:37:48 +0100 Subject: [PATCH 72/89] fix: index out of range in examples/credit-card-form when ccn is empty (#770) --- examples/credit-card-form/main.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/credit-card-form/main.go b/examples/credit-card-form/main.go index ba5b2b3363..71b3bf58bf 100644 --- a/examples/credit-card-form/main.go +++ b/examples/credit-card-form/main.go @@ -53,14 +53,15 @@ func ccnValidator(s string) error { return fmt.Errorf("CCN is too long") } + if len(s) == 0 || len(s)%5 != 0 && (s[len(s)-1] < '0' || s[len(s)-1] > '9') { + return fmt.Errorf("CCN is invalid") + } + // The last digit should be a number unless it is a multiple of 4 in which // case it should be a space if len(s)%5 == 0 && s[len(s)-1] != ' ' { return fmt.Errorf("CCN must separate groups with spaces") } - if len(s)%5 != 0 && (s[len(s)-1] < '0' || s[len(s)-1] > '9') { - return fmt.Errorf("CCN is invalid") - } // The remaining digits should be integers c := strings.ReplaceAll(s, " ", "") From ea7ceb7f38f047c54490d077904884a7fae2ac96 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Thu, 6 Jul 2023 13:42:53 -0300 Subject: [PATCH 73/89] build: group dependabot updates (#773) --- .github/dependabot.yml | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 9065bc6290..45a12a0563 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,6 +9,10 @@ updates: commit-message: prefix: "feat" include: "scope" + groups: + gomod: + patterns: + - "*" - package-ecosystem: "gomod" directory: "/examples" schedule: @@ -16,8 +20,12 @@ updates: labels: - "dependencies" commit-message: - prefix: "chore" + prefix: "docs" include: "scope" + groups: + gomod: + patterns: + - "*" - package-ecosystem: "gomod" directory: "/tutorials" schedule: @@ -25,8 +33,12 @@ updates: labels: - "dependencies" commit-message: - prefix: "chore" + prefix: "docs" include: "scope" + groups: + gomod: + patterns: + - "*" - package-ecosystem: "github-actions" directory: "/" schedule: @@ -36,3 +48,7 @@ updates: commit-message: prefix: "chore" include: "scope" + groups: + github-actions: + patterns: + - "*" From 5bc25046903fef4b35d95e01b6541293260770bc Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Thu, 6 Jul 2023 16:20:36 -0400 Subject: [PATCH 74/89] chore(lint): wrap various TTY-related errors --- tty.go | 7 ++++--- tty_unix.go | 7 +++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/tty.go b/tty.go index 715d542356..b804cf0bcb 100644 --- a/tty.go +++ b/tty.go @@ -2,6 +2,7 @@ package tea import ( "errors" + "fmt" "io" "os" "time" @@ -21,7 +22,7 @@ func (p *Program) initTerminal() error { if p.console != nil { err = p.console.SetRaw() if err != nil { - return err + return fmt.Errorf("error entering raw mode: %w", err) } } @@ -48,7 +49,7 @@ func (p *Program) restoreTerminalState() error { if p.console != nil { err := p.console.Reset() if err != nil { - return err + return fmt.Errorf("error restoring terminal state: %w", err) } } @@ -60,7 +61,7 @@ func (p *Program) initCancelReader() error { var err error p.cancelReader, err = cancelreader.NewReader(p.input) if err != nil { - return err + return fmt.Errorf("error creating cancelreader: %w", err) } p.readLoopDone = make(chan struct{}) diff --git a/tty_unix.go b/tty_unix.go index b6f5ffdf43..a3a25b8fa6 100644 --- a/tty_unix.go +++ b/tty_unix.go @@ -4,6 +4,7 @@ package tea import ( + "fmt" "os" "github.com/containerd/console" @@ -28,7 +29,9 @@ func (p *Program) initInput() error { // program exits. func (p *Program) restoreInput() error { if p.console != nil { - return p.console.Reset() + if err := p.console.Reset(); err != nil { + return fmt.Errorf("error restoring console: %w", err) + } } return nil } @@ -36,7 +39,7 @@ func (p *Program) restoreInput() error { func openInputTTY() (*os.File, error) { f, err := os.Open("/dev/tty") if err != nil { - return nil, err + return nil, fmt.Errorf("could not open a new TTY: %w", err) } return f, nil } From b639c9bab238b4be6925ae4766e88945786e84d9 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Fri, 7 Jul 2023 19:17:24 +0000 Subject: [PATCH 75/89] Revert "build: group dependabot updates (#773)" This reverts commit ea7ceb7f38f047c54490d077904884a7fae2ac96. --- .github/dependabot.yml | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 45a12a0563..9065bc6290 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,10 +9,6 @@ updates: commit-message: prefix: "feat" include: "scope" - groups: - gomod: - patterns: - - "*" - package-ecosystem: "gomod" directory: "/examples" schedule: @@ -20,12 +16,8 @@ updates: labels: - "dependencies" commit-message: - prefix: "docs" + prefix: "chore" include: "scope" - groups: - gomod: - patterns: - - "*" - package-ecosystem: "gomod" directory: "/tutorials" schedule: @@ -33,12 +25,8 @@ updates: labels: - "dependencies" commit-message: - prefix: "docs" + prefix: "chore" include: "scope" - groups: - gomod: - patterns: - - "*" - package-ecosystem: "github-actions" directory: "/" schedule: @@ -48,7 +36,3 @@ updates: commit-message: prefix: "chore" include: "scope" - groups: - github-actions: - patterns: - - "*" From ffad6555d58677ea95eeebdc65cfaee77ccd922b Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Fri, 7 Jul 2023 11:34:57 -0400 Subject: [PATCH 76/89] chore(lint): add various nolint directives, where appropriate --- commands.go | 2 +- tty.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/commands.go b/commands.go index 7c30a1216d..3a0520d479 100644 --- a/commands.go +++ b/commands.go @@ -13,7 +13,7 @@ import ( // return tea.Batch(someCommand, someOtherCommand) // } func Batch(cmds ...Cmd) Cmd { - var validCmds []Cmd + var validCmds []Cmd //nolint:prealloc for _, c := range cmds { if c == nil { continue diff --git a/tty.go b/tty.go index b804cf0bcb..bd9717efbf 100644 --- a/tty.go +++ b/tty.go @@ -42,7 +42,7 @@ func (p *Program) restoreTerminalState() error { p.renderer.exitAltScreen() // give the terminal a moment to catch up - time.Sleep(time.Millisecond * 10) + time.Sleep(time.Millisecond * 10) //nolint:gomnd } } @@ -87,7 +87,7 @@ func (p *Program) readLoop() { func (p *Program) waitForReadLoop() { select { case <-p.readLoopDone: - case <-time.After(500 * time.Millisecond): + case <-time.After(500 * time.Millisecond): //nolint:gomnd // The read loop hangs, which means the input // cancelReader's cancel function has returned true even // though it was not able to cancel the read. From 522659d79813db8953b1b34f59c8d2013222660c Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Fri, 7 Jul 2023 11:35:32 -0400 Subject: [PATCH 77/89] chore(lint): wrap various errors --- key.go | 8 ++++++-- logging.go | 5 +++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/key.go b/key.go index 74f85ec405..a49152c3db 100644 --- a/key.go +++ b/key.go @@ -547,7 +547,7 @@ func readInputs(ctx context.Context, msgs chan<- Msg, input io.Reader) error { // Read and block. numBytes, err := input.Read(buf[:]) if err != nil { - return err + return fmt.Errorf("error reading input: %w", err) } b := buf[:numBytes] @@ -558,7 +558,11 @@ func readInputs(ctx context.Context, msgs chan<- Msg, input io.Reader) error { select { case msgs <- msg: case <-ctx.Done(): - return ctx.Err() + err := ctx.Err() + if err != nil { + err = fmt.Errorf("found context error while reading input: %w", err) + } + return err } } } diff --git a/logging.go b/logging.go index 59258d4c7c..e60626994a 100644 --- a/logging.go +++ b/logging.go @@ -1,6 +1,7 @@ package tea import ( + "fmt" "io" "log" "os" @@ -32,9 +33,9 @@ type LogOptionsSetter interface { // LogToFileWith does allows to call LogToFile with a custom LogOptionsSetter. func LogToFileWith(path string, prefix string, log LogOptionsSetter) (*os.File, error) { - f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644) + f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644) //nolint:gomnd if err != nil { - return nil, err + return nil, fmt.Errorf("error opening file for logging: %w", err) } log.SetOutput(f) From c284acad9f18f5563906f98bd4c5d6d27e709599 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Fri, 7 Jul 2023 11:35:46 -0400 Subject: [PATCH 78/89] chore(lint): extract a magic number when parsing X10 mouse events --- key.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/key.go b/key.go index a49152c3db..b71222857b 100644 --- a/key.go +++ b/key.go @@ -572,8 +572,9 @@ var unknownCSIRe = regexp.MustCompile(`^\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e func detectOneMsg(b []byte) (w int, msg Msg) { // Detect mouse events. - if len(b) >= 6 && b[0] == '\x1b' && b[1] == '[' && b[2] == 'M' { - return 6, MouseMsg(parseX10MouseEvent(b)) + const mouseEventLen = 6 + if len(b) >= mouseEventLen && b[0] == '\x1b' && b[1] == '[' && b[2] == 'M' { + return mouseEventLen, MouseMsg(parseX10MouseEvent(b)) } // Detect escape sequence and control characters other than NUL, From c4c83ba757f891a8fbe513790b0176f9f26fc85b Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Mon, 10 Jul 2023 08:52:36 -0400 Subject: [PATCH 79/89] chore: restrict logfile permissions to owner-only --- logging.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logging.go b/logging.go index e60626994a..a53118193c 100644 --- a/logging.go +++ b/logging.go @@ -33,7 +33,7 @@ type LogOptionsSetter interface { // LogToFileWith does allows to call LogToFile with a custom LogOptionsSetter. func LogToFileWith(path string, prefix string, log LogOptionsSetter) (*os.File, error) { - f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644) //nolint:gomnd + f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o600) //nolint:gomnd if err != nil { return nil, fmt.Errorf("error opening file for logging: %w", err) } From 91dd1200733714c4fb7bc7ffb24af2c35cc2f111 Mon Sep 17 00:00:00 2001 From: naglis <827324+naglis@users.noreply.github.com> Date: Mon, 24 Jul 2023 16:07:57 +0300 Subject: [PATCH 80/89] docs: fix `WithFPS` godoc --- options.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/options.go b/options.go index 249d3bffc9..d1c04034b7 100644 --- a/options.go +++ b/options.go @@ -201,7 +201,7 @@ func WithFilter(filter func(Model, Msg) Msg) ProgramOption { } } -// WithMaxFPS sets a custom maximum FPS at which the renderer should run. If +// WithFPS sets a custom maximum FPS at which the renderer should run. If // less than 1, the default value of 60 will be used. If over 120, the FPS // will be capped at 120. func WithFPS(fps int) ProgramOption { From f6f65aef201aa1b56be7d8c4445e739719274c43 Mon Sep 17 00:00:00 2001 From: Gabriel Lopes Date: Mon, 21 Aug 2023 11:28:06 -0400 Subject: [PATCH 81/89] Add storydb to 'Bubble Tea in the Wild' section (#804) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 395b65c172..f55bb5105c 100644 --- a/README.md +++ b/README.md @@ -360,6 +360,7 @@ For some Bubble Tea programs in production, see: * [Soft Serve](https://github.com/charmbracelet/soft-serve): a command-line-first Git server that runs a TUI over SSH * [solitaire-tui](https://github.com/brianstrauch/solitaire-tui): Klondike Solitaire for the terminal * [StormForge Optimize Controller](https://github.com/thestormforge/optimize-controller): a tool for experimenting with application configurations in Kubernetes +* [Storydb](https://github.com/grrlopes/storydb): a bash/zsh ctrl+r improved command history finder. * [STTG](https://github.com/wille1101/sttg): a teletext client for SVT, Sweden’s national public television station * [sttr](https://github.com/abhimanyu003/sttr): a general-purpose text transformer * [tasktimer](https://github.com/caarlos0/tasktimer): a dead-simple task timer From 1ad127782c2af4c772565c2892d472300a395d43 Mon Sep 17 00:00:00 2001 From: Maas Lalani Date: Mon, 21 Aug 2023 11:41:38 -0400 Subject: [PATCH 82/89] feat(textinput): example for autocompletion with charmbracelet repositories (#803) * feat: autocompletion with charmbracelet repositories * chore(deps): bump bubbles --- examples/autocomplete/main.go | 104 ++++++++++++++++++++++++++++++++++ examples/go.mod | 6 +- examples/go.sum | 8 +-- 3 files changed, 111 insertions(+), 7 deletions(-) create mode 100644 examples/autocomplete/main.go diff --git a/examples/autocomplete/main.go b/examples/autocomplete/main.go new file mode 100644 index 0000000000..55795883f9 --- /dev/null +++ b/examples/autocomplete/main.go @@ -0,0 +1,104 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "log" + "net/http" + + "github.com/charmbracelet/bubbles/textinput" + tea "github.com/charmbracelet/bubbletea" +) + +func main() { + p := tea.NewProgram(initialModel()) + if _, err := p.Run(); err != nil { + log.Fatal(err) + } +} + +type gotReposSuccessMsg []repo +type gotReposErrMsg error + +type repo struct { + Name string `json:"name"` +} + +const reposURL = "https://api.github.com/orgs/charmbracelet/repos" + +func getRepos() tea.Msg { + req, err := http.NewRequest(http.MethodGet, reposURL, nil) + if err != nil { + return gotReposErrMsg(err) + } + + req.Header.Add("Accept", "application/vnd.github+json") + req.Header.Add("X-GitHub-Api-Version", "2022-11-28") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return gotReposErrMsg(err) + } + defer resp.Body.Close() // nolint: errcheck + + data, err := io.ReadAll(resp.Body) + if err != nil { + return gotReposErrMsg(err) + } + + var repos []repo + + err = json.Unmarshal(data, &repos) + if err != nil { + return gotReposErrMsg(err) + } + + return gotReposSuccessMsg(repos) +} + +type model struct { + textInput textinput.Model +} + +func initialModel() model { + ti := textinput.New() + ti.Placeholder = "Bubbletea" + ti.Focus() + ti.CharLimit = 50 + ti.Width = 20 + ti.ShowSuggestions = true + return model{textInput: ti} +} + +func (m model) Init() tea.Cmd { + return tea.Batch(getRepos, textinput.Blink) +} + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.Type { + case tea.KeyEnter, tea.KeyCtrlC, tea.KeyEsc: + return m, tea.Quit + } + case gotReposSuccessMsg: + var suggestions []string + for _, r := range msg { + suggestions = append(suggestions, r.Name) + } + m.textInput.SetSuggestions(suggestions) + } + + var cmd tea.Cmd + m.textInput, cmd = m.textInput.Update(msg) + return m, cmd +} + +func (m model) View() string { + return fmt.Sprintf( + "What’s your favorite Charm repository?\n\n%s\n\n%s\n", + m.textInput.View(), + "(esc to quit)", + ) +} diff --git a/examples/go.mod b/examples/go.mod index 9de58ea074..ab5d792b53 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -3,8 +3,8 @@ module examples go 1.17 require ( - github.com/charmbracelet/bubbles v0.16.1 - github.com/charmbracelet/bubbletea v0.24.1 + github.com/charmbracelet/bubbles v0.16.2-0.20230821152602-eda891258c02 + 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 @@ -33,7 +33,7 @@ require ( github.com/muesli/cancelreader v0.2.2 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/rivo/uniseg v0.2.0 // indirect - github.com/sahilm/fuzzy v0.1.0 // 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.7.0 // indirect diff --git a/examples/go.sum b/examples/go.sum index 7b504cd64c..725fddf586 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -11,8 +11,8 @@ github.com/aymanbagabas/go-udiff v0.1.0 h1:9Dpklm2oBBhMxIFbMffmPvDaF7vOYfv9B5HXV github.com/aymanbagabas/go-udiff v0.1.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= -github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY= -github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc= +github.com/charmbracelet/bubbles v0.16.2-0.20230821152602-eda891258c02 h1:MruS04uPbUJHF0MQzwOlh9yco1pHwPDLI7qGMu25IME= +github.com/charmbracelet/bubbles v0.16.2-0.20230821152602-eda891258c02/go.mod h1:XUdibuVUiMfcfKTRla58bmY3TWsdjgF+Rp8pvimQLck= github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc= github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc= github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= @@ -68,8 +68,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= -github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +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= From 5506e9a22e2e8c572ca2c53b85b71ef4b43bd7d2 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Mon, 21 Aug 2023 13:27:53 -0400 Subject: [PATCH 83/89] chore: minor UX edits to autocomplete example (#807) --- examples/autocomplete/main.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/autocomplete/main.go b/examples/autocomplete/main.go index 55795883f9..2aba5d8733 100644 --- a/examples/autocomplete/main.go +++ b/examples/autocomplete/main.go @@ -9,6 +9,7 @@ import ( "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" ) func main() { @@ -63,7 +64,9 @@ type model struct { func initialModel() model { ti := textinput.New() - ti.Placeholder = "Bubbletea" + ti.Prompt = "charmbracelet/" + ti.Placeholder = "repo..." + ti.Cursor.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("63")) ti.Focus() ti.CharLimit = 50 ti.Width = 20 @@ -99,6 +102,6 @@ func (m model) View() string { return fmt.Sprintf( "What’s your favorite Charm repository?\n\n%s\n\n%s\n", m.textInput.View(), - "(esc to quit)", + "(tab to complete, ctrl+n/ctrl+p to cycle through suggestions, esc to quit)", ) } From b5e2519feaddf29998c4eecdecc54f7fa4c008ec Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Tue, 22 Aug 2023 10:21:49 -0400 Subject: [PATCH 84/89] chore(docs): add walk to Bubble Tea in the Wild --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f55bb5105c..092ca4ff75 100644 --- a/README.md +++ b/README.md @@ -371,6 +371,7 @@ For some Bubble Tea programs in production, see: * [typioca](https://github.com/bloznelis/typioca): Cozy typing speed tester in terminal * [tz](https://github.com/oz/tz): an aid for scheduling across multiple time zones * [ugm](https://github.com/ariasmn/ugm): a unix user and group browser +* [walk](https://github.com/antonmedv/walk): a terminal navigator * [wander](https://github.com/robinovitch61/wander): a HashiCorp Nomad terminal client * [WG Commander](https://github.com/AndrianBdn/wg-cmd): a TUI for a simple WireGuard VPN setup * [wishlist](https://github.com/charmbracelet/wishlist): an SSH directory From 12f405723d7b7d2845fe716a8607834996be2a85 Mon Sep 17 00:00:00 2001 From: Eng Zer Jun Date: Fri, 25 Aug 2023 00:17:44 +0800 Subject: [PATCH 85/89] refactor: remove redundant nil check in `flush` (#812) From the Go docs: "If the map is nil, the number of iterations is 0." [1] Therefore, an additional nil check for before the loop is unnecessary. [1]: https://go.dev/ref/spec#For_range Signed-off-by: Eng Zer Jun --- standard_renderer.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/standard_renderer.go b/standard_renderer.go index c6a1b61017..0f282d8bb9 100644 --- a/standard_renderer.go +++ b/standard_renderer.go @@ -208,10 +208,8 @@ func (r *standardRenderer) flush() { // Merge the set of lines we're skipping as a rendering optimization with // the set of lines we've explicitly asked the renderer to ignore. - if r.ignoreLines != nil { - for k, v := range r.ignoreLines { - skipLines[k] = v - } + for k, v := range r.ignoreLines { + skipLines[k] = v } // Paint new lines From bf2ffaf84002e0c45f2d85cc5571902b21264025 Mon Sep 17 00:00:00 2001 From: Maas Lalani Date: Mon, 28 Aug 2023 10:07:18 -0400 Subject: [PATCH 86/89] chore: remove examples/mouse/README.md --- examples/mouse/README.md | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 examples/mouse/README.md diff --git a/examples/mouse/README.md b/examples/mouse/README.md deleted file mode 100644 index 396219d7d5..0000000000 --- a/examples/mouse/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Mouse - - From d55cfec13eae603b3905214eb854539f1331f46c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Sep 2023 17:24:42 +0000 Subject: [PATCH 87/89] chore(deps): bump actions/checkout from 3 to 4 (#820) --- .github/workflows/build.yml | 2 +- .github/workflows/coverage.yml | 2 +- .github/workflows/examples.yml | 2 +- .github/workflows/lint-soft.yml | 2 +- .github/workflows/lint.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2511296056..a14384cdda 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,7 +17,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Download Go modules run: go mod download diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 845096e44c..f6985a684c 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -17,7 +17,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Coverage env: diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index f027cf2182..22051704ab 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -20,7 +20,7 @@ jobs: contents: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v4 with: go-version: '^1' diff --git a/.github/workflows/lint-soft.yml b/.github/workflows/lint-soft.yml index 6230528921..5ce0d37f87 100644 --- a/.github/workflows/lint-soft.yml +++ b/.github/workflows/lint-soft.yml @@ -18,7 +18,7 @@ jobs: with: go-version: ^1 - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2f26b2ac2a..16f38b8a66 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,7 +18,7 @@ jobs: with: go-version: ^1 - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: From afcf4983362a4f15323998da44d1091a17285560 Mon Sep 17 00:00:00 2001 From: David Dworken Date: Sat, 23 Sep 2023 16:52:47 -0700 Subject: [PATCH 88/89] chore(docs): Add hiSHtory to Bubble Tea in the Wild --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 092ca4ff75..0e94e7bc0c 100644 --- a/README.md +++ b/README.md @@ -343,6 +343,7 @@ For some Bubble Tea programs in production, see: * [Glow](https://github.com/charmbracelet/glow): a markdown reader, browser, and online markdown stash * [gocovsh](https://github.com/orlangure/gocovsh): explore Go coverage reports from the CLI * [got](https://github.com/fedeztk/got): a simple translator and text-to-speech app build on top of simplytranslate's APIs +* [hiSHtory](https://github.com/ddworken/hishtory): your shell history in context, synced, and queryable * [httpit](https://github.com/gonetx/httpit): a rapid http(s) benchmark tool * [IDNT](https://github.com/r-darwish/idnt): a batch software uninstaller * [kboard](https://github.com/CamiloGarciaLaRotta/kboard): a typing game From 99d7d4bd17b92a6748d97cf116d0e5b88e420758 Mon Sep 17 00:00:00 2001 From: William Poussier Date: Sun, 1 Oct 2023 22:52:51 +0200 Subject: [PATCH 89/89] docs(README): add wI2L/scrabbler to Bubble Tea in the Wild --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0e94e7bc0c..685c1d31f5 100644 --- a/README.md +++ b/README.md @@ -355,6 +355,7 @@ For some Bubble Tea programs in production, see: * [pathos](https://github.com/chip/pathos): a PATH env variable editor * [portal](https://github.com/ZinoKader/portal): secure transfers between computers * [redis-viewer](https://github.com/SaltFishPr/redis-viewer): a Redis databases browser +* [scrabbler](https://github.com/wI2L/scrabbler): Automatic draw TUI for your duplicate Scrabble games * [sku](https://github.com/fedeztk/sku): Sudoku on the CLI * [Slides](https://github.com/maaslalani/slides): a markdown-based presentation tool * [SlurmCommander](https://github.com/CLIP-HPC/SlurmCommander): a Slurm workload manager TUI