Skip to content

text: parse prefix extensions incrementally in TextViewState::set_text (#3291) - #3294

Merged
madcodelife merged 2 commits into
longbridge:mainfrom
Yi-111-a:fix/3291-incremental-set-text-extension
Sep 28, 2026
Merged

madcodelife merged 2 commits into
longbridge:mainfrom
Yi-111-a:fix/3291-incremental-set-text-extension

Conversation

@Yi-111-a

Copy link
Copy Markdown
Contributor

Problem

TextView::markdown(id, text) hands its text to the keyed state every frame, through set_element_text → set_text. When the new text extends the current one, set_text noticed text.starts_with(self.text), but only used that to note stream fade before calling increment_update(text, false, cx), which performed a full replacement reparse:

  • Up to MAX_SYNC_FULL_REPLACE_BYTES (4KB), it parsed synchronously on the UI thread and sent a baseline ack to the background parser;
  • Above 4KB, it parsed in the background and invalidated measured heights for all blocks upon landing;
  • It bumped selection_revision, resetting active selections on every streaming token growth;
  • Over a multi-token stream (e.g. streaming LLM outputs in ai-chat), every update cost $O(n)$, totaling $O(n^2)$ time over the entire stream.

Solution

In TextViewState::set_text:

  1. When !self.text.is_empty() && text.starts_with(self.text.as_str()), take the incremental append path matching push_str:
    • Compute delta = &text[self.text.len()..];
    • Note stream fade extension at self.text.len();
    • Append delta to self.text;
    • Dispatch self.increment_update(delta, true, cx) so only the trailing block is reparsed without resetting selection or invalidating earlier measured block heights.
  2. Keep full replacement for initial updates (self.text.is_empty()), text truncation, or non-prefix replacements.

Validation

  • Added set_text_prefix_extension_updates_incrementally_like_push_str testing that prefix extensions via set_text retain selection_revision and full_update_revision (unlike replacements).
  • Added set_text_streaming_matches_push_str_parsed_content verifying parity between streaming chunks through set_text vs push_str across multi-paragraph, inline code, and list structures.
  • Ran cargo test -p gpui-base text::state (81 tests passed).
  • Ran cargo clippy -p gpui-base (0 errors).

Closes #3291

Fixes #3291

@madcodelife

Copy link
Copy Markdown
Member

Thanks for the fix!

I pushed a follow-up commit: set_text now appends incrementally only for Markdown. HTML, and any update after a failed parse, still reparses in full, because appending gives wrong results in those cases. I also added tests that check streaming against a full parse.

@madcodelife
madcodelife enabled auto-merge (squash) September 28, 2026 14:11
@madcodelife
madcodelife merged commit 2960200 into longbridge:main Sep 28, 2026
12 checks passed
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.

TextViewState::set_text: parse prefix extensions incrementally like push_str

2 participants