Skip to content

Commit

Permalink
🚀 Improve textarea performance by caching the wrap results
Browse files Browse the repository at this point in the history
  • Loading branch information
wesen committed Oct 16, 2023
1 parent 167e906 commit 30a4341
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 12 deletions.
11 changes: 6 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/charmbracelet/bubbles

go 1.17
go 1.18

require (
github.com/atotto/clipboard v0.1.4
Expand All @@ -18,14 +18,15 @@ require (
require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/go-go-golems/clay v0.0.27 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
golang.org/x/sync v0.1.0 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/term v0.6.0 // indirect
golang.org/x/text v0.3.8 // indirect
golang.org/x/term v0.12.0 // indirect
golang.org/x/text v0.13.0 // indirect
)
14 changes: 10 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ 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/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/go-go-golems/clay v0.0.27 h1:F+beQkhMBEAlTnFve+J18M76ALUhtqBGH+Qi52Qb0Ig=
github.com/go-go-golems/clay v0.0.27/go.mod h1:KX8IIKGpt9FvbwgcEBVOZfPB22LcdjRhVpjk8sB0SBs=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
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=
Expand All @@ -39,8 +41,9 @@ github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4AN
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=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
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=
Expand All @@ -53,8 +56,9 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
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/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
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=
Expand All @@ -69,13 +73,15 @@ golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.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=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
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/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
Expand Down
38 changes: 35 additions & 3 deletions textarea/textarea.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package textarea

Check failure on line 1 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint

: # github.com/charmbracelet/bubbles/textarea

Check failure on line 1 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint-soft

: # github.com/charmbracelet/bubbles/textarea

import (
"crypto/sha256"
"fmt"
"github.com/go-go-golems/clay/pkg/memoization"
"strings"
"unicode"

Expand Down Expand Up @@ -129,11 +131,24 @@ type Style struct {
Text lipgloss.Style
}

type WrapInput struct {
Runes []rune
Width int
}

func (w WrapInput) Hash() string {
// compute runes hash

v := fmt.Sprintf("%s:%d", string(w.Runes), w.Width)
return fmt.Sprintf("%x", sha256.Sum256([]byte(v)))
}

// Model is the Bubble Tea model for this text area element.
type Model struct {
Err error

// General settings.
cache *memoization.MemoCache[WrapInput, [][]rune]

// Prompt is printed at the beginning of each line.
//
Expand Down Expand Up @@ -242,6 +257,7 @@ func New() Model {
style: &blurredStyle,
FocusedStyle: focusedStyle,
BlurredStyle: blurredStyle,
cache: memoization.NewMemoCache[WrapInput, [][]rune](100),
EndOfBufferCharacter: '~',
ShowLineNumbers: true,
Cursor: cur,
Expand Down Expand Up @@ -768,7 +784,7 @@ func (m *Model) capitalizeRight() {
// LineInfo returns the number of characters from the start of the
// (soft-wrapped) line and the (soft-wrapped) line width.
func (m Model) LineInfo() LineInfo {
grid := wrap(m.value[m.row], m.width)
grid := m.memoizedWrap(m.value[m.row], m.width)

// Find out which line we are currently on. This can be determined by the
// m.col and counting the number of runes that we need to skip.
Expand Down Expand Up @@ -1046,7 +1062,7 @@ func (m Model) View() string {

displayLine := 0
for l, line := range m.value {
wrappedLines := wrap(line, m.width)
wrappedLines := m.memoizedWrap(line, m.width)

if m.row == l {
style = m.style.CursorLine
Expand Down Expand Up @@ -1181,14 +1197,29 @@ func Blink() tea.Msg {
return cursor.Blink()
}

func (m Model) memoizedWrap(runes []rune, width int) [][]rune {
input := WrapInput{
Runes: runes,
Width: width,
}
if v, ok := m.cache.Get(input); ok {

Check failure on line 1205 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint

assignment mismatch: 2 variables but m.cache.Get returns 1 value (typecheck)

Check failure on line 1205 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint-soft

assignment mismatch: 2 variables but m.cache.Get returns 1 value (typecheck)

Check failure on line 1205 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / coverage (^1, ubuntu-latest)

assignment mismatch: 2 variables but m.cache.Get returns 1 value

Check failure on line 1205 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / test (^1, ubuntu-latest)

assignment mismatch: 2 variables but m.cache.Get returns 1 value
return v
}

v := wrap(runes, width)
m.cache.Set(input, v)

return v
}

// cursorLineNumber returns the line number that the cursor is on.
// This accounts for soft wrapped lines.
func (m Model) cursorLineNumber() int {
line := 0
for i := 0; i < m.row; i++ {
// Calculate the number of lines that the current line will be split
// into.
line += len(wrap(m.value[i], m.width))
line += len(m.memoizedWrap(m.value[i], m.width))
}
line += m.LineInfo().RowOffset
return line
Expand Down Expand Up @@ -1264,6 +1295,7 @@ func Paste() tea.Msg {
}

func wrap(runes []rune, width int) [][]rune {

var (
lines = [][]rune{{}}
word = []rune{}
Expand Down

0 comments on commit 30a4341

Please sign in to comment.