Skip to content

perf(diffs/edit): Cut wrap-mode re-measurement on typing and scroll - #1011

Merged
ije merged 2 commits into
beta-1.3from
ije/improve-text-wrap-mode-perfs
Jul 21, 2026
Merged

perf(diffs/edit): Cut wrap-mode re-measurement on typing and scroll#1011
ije merged 2 commits into
beta-1.3from
ije/improve-text-wrap-mode-perfs

Conversation

@ije

@ije ije commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Type or scroll in a wrapped file with long lines: every keystroke and scroll re-measured wrap points character by character for each line, one Range.getBoundingClientRect() per character, making minified or long-line files sluggish.

Three changes to wrap measurement and its caching:

  • Find each wrap point by binary-searching for the first grapheme whose rect top drops to the next visual line (tops are non-decreasing with text offset), replacing the per-character scan: O(wraps * log chars) measurements instead of O(chars).
  • On edits that renumber no lines, invalidate only the lines the edit rewrote instead of every line at or after it. Per-edit line deltas guard the multi-edit batch whose net delta cancels to zero while still shifting lines between the edits.
  • Keep wrap offsets across render-range syncs: they depend on line text, font metrics, and content width, not on rendered rows. The sites where those inputs change (document swap, font load, width resize, cleanup) now clear the cache themselves, with a 10k-line cap since entries persist across scrolls.

Two regression tests in editorWrapCaretPosition.test.ts, backed by a
measurement counter added to the mock harness:

  • A 10-row / 1000-char line resolves with < 500 rect measurements while the
    caret still lands on the right visual row/column (old code: exactly 1000).
  • After a same-line-count edit to line 0, moving the caret within line 1 adds
    zero measurements (old code re-measured line 1 from scratch).

Type or scroll in a wrapped file with long lines: every keystroke and
scroll re-measured wrap points character by character for each line,
one Range.getBoundingClientRect() per character, making minified or
long-line files sluggish.

Three changes to wrap measurement and its caching:

- Find each wrap point by binary-searching for the first grapheme
  whose rect top drops to the next visual line (tops are
  non-decreasing with text offset), replacing the per-character scan:
  O(wraps * log chars) measurements instead of O(chars).
- On edits that renumber no lines, invalidate only the lines the edit
  rewrote instead of every line at or after it. Per-edit line deltas
  guard the multi-edit batch whose net delta cancels to zero while
  still shifting lines between the edits.
- Keep wrap offsets across render-range syncs: they depend on line
  text, font metrics, and content width, not on rendered rows. The
  sites where those inputs change (document swap, font load, width
  resize, cleanup) now clear the cache themselves, with a 10k-line
  cap since entries persist across scrolls.
@vercel

vercel Bot commented Jul 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pierre-docs-diffs Ready Ready Preview Jul 20, 2026 4:53pm
pierre-docs-diffshub Ready Ready Preview Jul 20, 2026 4:53pm
pierre-docs-trees Ready Ready Preview Jul 20, 2026 4:53pm
pierrejs-diff-demo Ready Ready Preview Jul 20, 2026 4:53pm

Request Review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fd2ebf605a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines 1316 to 1319
#resetCache(): void {
this.#lineYCache.clear();
this.#wrapLineOffsetsCache.clear();
this.#lineElementsCache.clear();
this.#lastAccessedCharX = undefined;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Invalidate wrap offsets across scroll-mode edits

When the same editor is toggled from wrap to scroll, this cache now survives the render sync, but #applyChange only deletes wrap offsets inside if (this.#isWrap). Editing a previously measured line while overflow is scroll therefore leaves stale offsets behind; toggling back to wrap reuses measurements for the old text, which misplaces wrapped carets/selections until some unrelated width/font/document invalidation happens.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in 5a2102c

@amadeus amadeus left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems good, but it's probably a bit above my head on how it works 😅

@ije
ije merged commit 3f040b9 into beta-1.3 Jul 21, 2026
8 checks passed
@ije
ije deleted the ije/improve-text-wrap-mode-perfs branch July 21, 2026 09:38
amadeus pushed a commit that referenced this pull request Jul 21, 2026
…1011)

Type or scroll in a wrapped file with long lines: every keystroke and
scroll re-measured wrap points character by character for each line,
one Range.getBoundingClientRect() per character, making minified or
long-line files sluggish.

Three changes to wrap measurement and its caching:

- Find each wrap point by binary-searching for the first grapheme
  whose rect top drops to the next visual line (tops are
  non-decreasing with text offset), replacing the per-character scan:
  O(wraps * log chars) measurements instead of O(chars).
- On edits that renumber no lines, invalidate only the lines the edit
  rewrote instead of every line at or after it. Per-edit line deltas
  guard the multi-edit batch whose net delta cancels to zero while
  still shifting lines between the edits.
- Keep wrap offsets across render-range syncs: they depend on line
  text, font metrics, and content width, not on rendered rows. The
  sites where those inputs change (document swap, font load, width
  resize, cleanup) now clear the cache themselves, with a 10k-line
  cap since entries persist across scrolls.
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