Skip to content

Add attributed string postprocessor - #78

Open
tseylerd wants to merge 1 commit into
gonzalezreal:mainfrom
tseylerd:main
Open

Add attributed string postprocessor#78
tseylerd wants to merge 1 commit into
gonzalezreal:mainfrom
tseylerd:main

Conversation

@tseylerd

@tseylerd tseylerd commented Jul 24, 2026

Copy link
Copy Markdown

Problem

MarkupParser only runs when the input string changes, so there is no way to add attributes whose source is external to the content (for example, highlight search occurences). Highlighting (or any content-independent customization) currently requires re-parsing and re-rendering which is very inefficient.

Solution

Introduces an AttributedStringPostprocessor protocol and .textual.attributedStringPostprocessor(_:) modifier that lets callers transform the parsed AttributedString independently of markup content. Applies to both InlineText and StructuredText.

The postprocessor runs after parsing and attachment resolution, and is re-invoked whenever either the parsed attributed string or the postprocessor itself changes. Equality (from Hashable) drives onChange-based invalidation, so conforming types reflect state affecting output in their ==.

struct SearchHighlighter: AttributedStringPostprocessor {
  let query: String

  func postprocess(input: AttributedString) -> AttributedString {
    var output = input
    var start = output.startIndex

    while let range = output[start...].range(of: query, options: .caseInsensitive) {
      output[range].backgroundColor = .yellow.opacity(0.4)
      start = range.upperBound
    }

    return output
  }
}

InlineText(markdown: "Hello, world")
  .textual.attributedStringPostprocessor(SearchHighlighter(query: "world"))

Alternatives Considered

  • Implementing custom MarkupParser: inefficient, requires re-parsing every time without string changes.

Testing

  • New tests pass: swift test --filter AttributedStringPostprocessorTests (3 tests).
  • Demo app "Postprocessor" page.

The MarkupParser only runs when the input string changes, so there
is no way to add attributes whose source is external to the content. This adds a
postprocessing stage that runs after parsing and is re-invoked
whenever either the content or the postprocessor itself changes,
enabling content-independent customization like search highlighting
in both InlineText and StructuredText.
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.

1 participant