Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions Sources/Textual/Internal/TextFragment/TextBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,12 @@ extension Text {
return text
}

self = textValues.reduce(Text(verbatim: "")) { partialResult, text in
Text("\(partialResult)\(text)")
}
// Concatenate via `Text + Text` rather than `Text("\(partialResult)\(text)")`.
// The interpolation form produces a `LocalizedStringKey`-backed `Text` at every
// reduce step; resolving the resulting N-deep chain recurses ~8 stack frames per
// level during SwiftUI's render pass and overflows the main-thread stack for
// content that produces a few hundred attributed runs (e.g. emoji-dense notes).
self = textValues.reduce(Text(verbatim: ""), +)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve the balanced TextBuilder on merge

When this PR is merged into current main, this line conflicts with the existing TextBuilder rewrite there; resolving the conflict by keeping this reduce(..., +) path would drop the plain-run coalescing and balanced concatenation that main already uses. For inputs with thousands of attributed runs, such as large syntax-highlighted code blocks or emoji-dense text, this still builds one left-deep Text concatenation node per run, so SwiftUI can recurse through thousands of ConcatenatedTextStorage nodes and hit the same stack-overflow class this PR is meant to fix. Resolve the conflict by keeping the current main implementation rather than replacing it with this left fold.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve the balanced TextBuilder on merge

When this PR is merged into current main, this line conflicts with the existing TextBuilder rewrite there; resolving the conflict by keeping this reduce(..., +) path would drop the plain-run coalescing and balanced concatenation that main already uses. For inputs with thousands of attributed runs, such as large syntax-highlighted code blocks or emoji-dense text, this still builds one left-deep Text concatenation node per run, so SwiftUI can recurse through thousands of ConcatenatedTextStorage nodes and hit the same stack-overflow class this PR is meant to fix. Resolve the conflict by keeping the current main implementation rather than replacing it with this left fold.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Balance concatenation to avoid another deep Text tree

For inputs that still produce many runs, such as a large syntax-highlighted code block built in HighlightedTextFragment.Model.highlight, this left fold constructs an N-deep Text + Text tree. That avoids the LocalizedStringKey recursion from the old interpolation path, but it still leaves SwiftUI with a deeply nested concatenation to resolve during layout, so the same stack-overflow class can recur when there are thousands of token/link/attachment runs. Build the concatenation as a balanced tree (or coalesce plain attributed runs before concatenating) so tree depth is bounded instead of proportional to the number of runs.

Useful? React with 👍 / 👎.

}

private init(placeholderSize size: CGSize) {
Expand Down