Skip to content

text: skip escape code parsing when only measuring or trimming - #448

Open
TheNetherWatcher wants to merge 1 commit into
jedib0t:mainfrom
TheNetherWatcher:perf/text-width-skip-parse
Open

TheNetherWatcher wants to merge 1 commit into
jedib0t:mainfrom
TheNetherWatcher:perf/text-width-skip-parse

Conversation

@TheNetherWatcher

Copy link
Copy Markdown
Contributor

Proposed Changes

  • LongestLineLen, StringWidthWithoutEscSequences and Trim only need to know where escape sequences start and end, but each completed sequence also went through ParseSeq (map, strings.Split, strings.Replace, string copy). An unexported skipParse flag on EscSeqParser lets these callers track boundaries only. table calls LongestLineLen once per cell and the width/trim helpers per line, and progress calls them on every render.
  • RuneWidth returns the width of ASCII runes directly (1 for printable, 0 for control) instead of going through the runewidth lookup. A test checks all 128 ASCII runes against runewidth with EastAsianWidth on and off.
  • Added LongestLineLen and Trim benchmarks next to the existing width one, and a test that a skipParse parser reports the same InSequence() state as a full one, byte by byte, for CSI, 256-colour, OSC 8 (BEL and ST), unterminated and oversized sequences.

Benchmarks (Apple M4 Pro, benchstat, n=10):

Benchmark Before After
StringWidthWithoutEscSequences/Escaped 1092 ns, 776 B, 34 allocs 359 ns, 8 B, 1 alloc
LongestLineLen/Escaped 1094 ns, 776 B, 34 allocs 371 ns, 8 B, 1 alloc
Trim/Escaped 1157 ns, 920 B, 36 allocs 432 ns, 152 B, 3 allocs
StringWidthWithoutEscSequences/Plain 389 ns 354 ns (-9%)
Trim/Plain 279 ns 251 ns (-10%)
table Render / RenderLarge 10.1 µs / 1.99 ms 9.3 µs / 1.80 ms (-8% / -10%)

I also compared the old and new versions of the three functions on 300k random strings (colour codes, OSC 8 links, malformed and oversized sequences, CJK, random trim widths): identical output.

LongestLineLen, StringWidthWithoutEscSequences and Trim only need to know
where escape sequences start and end, but every completed sequence went
through ParseSeq (map, split, replace, string copy). Add an unexported
skipParse flag so these callers only track sequence boundaries.

Also return the width of ASCII runes directly in RuneWidth instead of
going through the runewidth table lookup.
@sonarqubecloud

sonarqubecloud Bot commented Nov 6, 2026

Copy link
Copy Markdown

@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 42262909460

Coverage remained the same at 100.0%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: 15 of 15 lines across 2 files are fully covered (100%).
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 5174
Covered Lines: 5174
Line Coverage: 100.0%
Coverage Strength: 1.21 hits per line

💛 - Coveralls

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.

2 participants