Skip to content

fix: performance issues with textarea - #1024

Open
andrinoff wants to merge 1 commit into
charmbracelet:mainfrom
andrinoff:fix/textarea-performance
Open

fix: performance issues with textarea#1024
andrinoff wants to merge 1 commit into
charmbracelet:mainfrom
andrinoff:fix/textarea-performance

Conversation

@andrinoff

@andrinoff andrinoff commented Jul 30, 2026

Copy link
Copy Markdown
Member
  • I have read CONTRIBUTING.md.
  • I have created a discussion that was approved by a maintainer (for new features).

What?

Replaces the textarea's hash-keyed wrap memoization with a per-row wrap cache, and makes rendering window-bound instead of buffer-bound.

The new wrapCache holds one entry per logical row with that row's soft-wrapped lines, plus an offsets table mapping each row to the visual line it starts at. Entries are validated by comparing the cached source slice against the live one (length plus backing pointer), so any edit that replaces a row's contents is detected for free; edits that mutate a row in place (transpose, case changes, insert) invalidate explicitly. Structural edits (splitLine, mergeLineAbove/Below, multi-line paste) shift the cache with insert/remove, so untouched rows below the cursor are never re-wrapped.

viewLines() replaces view(). It renders only the visual lines inside the viewport window and hands the viewport a []string through SetContentLines instead of a joined blob. Off-screen lines stay empty and the viewport still scrolls over the full content. visualLineOffsets, rowForVisualLine, totalVisualLines, and cursorLineNumber now read the offset table by binary search instead of walking and re-wrapping every row. wrap() tracks the current row width as it goes rather than re-measuring the accumulated line per word.

Also deletes internal/memoization

Why?

The old cache was keyed by sha256(string(runes) + width), so every lookup hashed the full line content, and cursorLineNumber, totalVisualLines, and view each walked all rows on every keystroke. Rendering built the entire buffer as one string regardless of what was visible. Cost was O(content) rather than O(viewport), so a textarea holding a large document became unusable.

Measured on a 60-column, 20-line-tall textarea, old to new: at 100 lines, view goes 2.41 ms to 0.61 ms and keystroke-plus-view 5.13 ms to 1.15 ms; at 1,000 lines, 41.2 ms to 1.00 ms and 85.6 ms to 1.17 ms; at 10,000 lines, 241.8 ms to 0.56 ms and 671.0 ms to 1.14 ms. Render cost is now flat in content size. SetValue is unchanged at 2.6 to 2.9 ms, since the initial wrap is still cold there.

Fixes CHARM-1891
Fixes #1023

Signed-off-by: drew <me@andrinoff.com>
@andrinoff

Copy link
Copy Markdown
Member Author
2026-07-31.14-49-07.mp4

The first part showed matcha integrated with these changes. The second one is pre-compiled with the upstream bubbles.

The performance is WAY better and it is usable now (the previous one took 10 seconds of scrolling to do nothing), but it is still quite slow, i'll try to look into it further, possibly missed something

@andrinoff
andrinoff marked this pull request as ready for review August 1, 2026 18:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

textarea horrible performance with a lot of text

1 participant