diff --git a/examples/go.mod b/examples/go.mod index 71a272838b..2c57c939a1 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -24,7 +24,7 @@ require ( github.com/aymerick/douceur v0.2.0 // indirect github.com/charmbracelet/x/ansi v0.4.5 // indirect github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b // indirect - github.com/charmbracelet/x/term v0.2.0 // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect github.com/dlclark/regexp2 v1.11.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect @@ -41,8 +41,8 @@ require ( github.com/yuin/goldmark v1.7.4 // indirect github.com/yuin/goldmark-emoji v1.0.3 // indirect golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.26.0 // indirect + golang.org/x/sync v0.9.0 // indirect + golang.org/x/sys v0.27.0 // indirect golang.org/x/term v0.22.0 // indirect golang.org/x/text v0.19.0 // indirect ) diff --git a/examples/go.sum b/examples/go.sum index 3b70b3c3ef..cef8c200a0 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -28,8 +28,8 @@ github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b h1:MnAM github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/exp/teatest v0.0.0-20240521184646-23081fb03b28 h1:sOWKNRjt8uOEVgPiJVIJCse1+mUDM2F/vYY6W0Go640= github.com/charmbracelet/x/exp/teatest v0.0.0-20240521184646-23081fb03b28/go.mod h1:l1w+LTJZCCozeGzMEWGxRw6Mo2DfcZUvupz8HGubdes= -github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0= -github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -76,12 +76,12 @@ github.com/yuin/goldmark-emoji v1.0.3 h1:aLRkLHOuBR2czCY4R8olwMjID+tENfhyFDMCRhb github.com/yuin/goldmark-emoji v1.0.3/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U= golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= diff --git a/standard_renderer.go b/standard_renderer.go index 2585bccad6..f30fc3592e 100644 --- a/standard_renderer.go +++ b/standard_renderer.go @@ -34,6 +34,7 @@ type standardRenderer struct { ticker *time.Ticker done chan struct{} lastRender string + lastRenderedLines []string linesRendered int useANSICompressor bool once sync.Once @@ -174,7 +175,6 @@ func (r *standardRenderer) flush() { } newLines := strings.Split(r.buf.String(), "\n") - oldLines := strings.Split(r.lastRender, "\n") // If we know the output's height, we can use it to determine how many // lines we can render. We drop lines from the top of the render buffer if @@ -184,7 +184,6 @@ func (r *standardRenderer) flush() { newLines = newLines[len(newLines)-r.height:] } - numLinesThisFlush := len(newLines) flushQueuedMessages := len(r.queuedMessageLines) > 0 && !r.altScreenActive if flushQueuedMessages { @@ -210,7 +209,7 @@ func (r *standardRenderer) flush() { // Paint new lines. for i := 0; i < len(newLines); i++ { canSkip := !flushQueuedMessages && // Queuing messages triggers repaint -> we don't have access to previous frame content. - len(oldLines) > i && oldLines[i] == newLines[i] // Previously rendered line is the same. + len(r.lastRenderedLines) > i && r.lastRenderedLines[i] == newLines[i] // Previously rendered line is the same. if _, ignore := r.ignoreLines[i]; ignore || canSkip { // Unless this is the last line, move the cursor down. @@ -257,11 +256,11 @@ func (r *standardRenderer) flush() { } // Clearing left over content from last render. - if r.linesRendered > numLinesThisFlush { + if r.linesRendered > len(newLines) { buf.WriteString(ansi.EraseScreenBelow) } - r.linesRendered = numLinesThisFlush + r.linesRendered = len(newLines) // Make sure the cursor is at the start of the last line to keep rendering // behavior consistent. @@ -276,6 +275,11 @@ func (r *standardRenderer) flush() { _, _ = r.out.Write(buf.Bytes()) r.lastRender = r.buf.String() + + // Save previously rendered lines for comparison in the next render. If we + // don't do this, we can't skip rendering lines that haven't changed. + // See https://github.com/charmbracelet/bubbletea/pull/1233 + r.lastRenderedLines = newLines r.buf.Reset() } @@ -299,6 +303,7 @@ func (r *standardRenderer) write(s string) { func (r *standardRenderer) repaint() { r.lastRender = "" + r.lastRenderedLines = nil } func (r *standardRenderer) clearScreen() {