Overview
When working with large or streaming markdown content, reparsing the entire document on every update can become expensive. MarkdownView’s incremental parser intelligently:- Identifies the stable prefix of blocks that haven’t changed
- Parses only the tail portion that contains new or modified content
- Merges the cached prefix with the newly parsed tail
- Maintains accurate block ranges for future incremental updates
The incremental parser is automatically used when calling
setMarkdown(string:) on MarkdownTextView. For manual control, use the parseIncremental method directly.The parseIncremental Method
The core incremental parsing API is available in MarkdownParser:
Sources/MarkdownParser/MarkdownParser/MarkdownParser.swift:110
Parameters
previousMarkdown: The complete markdown string from the last parsenewMarkdown: The updated markdown string (must be longer than previous)previousBlocks: The parsed block nodes from the previous parse resultpreviousRanges: Optional cached block ranges from a previousparseBlockRange()call
Return Value
ReturnsIncrementalParseResult on success, or nil if incremental parsing is not possible (e.g., content was deleted or modified in the middle).
stablePrefixBlockCount: Number of blocks from the previous parse that remain unchangedtailResult: Parse result containing only the new/modified blocksblockRanges: Updated block ranges for the complete document
RootBlockRange
TheRootBlockRange structure maps markdown source positions to output blocks:
Sources/MarkdownParser/MarkdownParser/MarkdownParser.swift:56
Properties
type: The markdown node type (e.g., paragraph, code block, list)startIndex: Character position where this block starts in the sourceendIndex: Character position where this block ends in the sourceoutputBlockCount: Number of rendered blocks produced after transformation
Some markdown blocks expand into multiple output blocks after processing. For example, a list might be transformed into multiple specialized block nodes. The
outputBlockCount tracks this expansion.Stable Prefix Optimization
The incremental parser uses a sophisticated algorithm to determine the stable prefix:- Prefix Validation: Verifies that
newMarkdownstarts with the exactpreviousMarkdowncontent - Tail Block Calculation: Determines how many blocks from the end might have changed
- Boundary Detection: Uses block ranges to find the exact character position where stable content ends
- Text Comparison: Ensures the stable portion of text hasn’t been modified
Sources/MarkdownParser/MarkdownParser/MarkdownParser.swift:110-160
Tail Block Heuristics
The parser uses intelligent heuristics to determine the minimum tail size:Sources/MarkdownParser/MarkdownParser/MarkdownParser.swift:205
- Base: Reparse at least 1 block by default
- Block Type: Reparse 2 blocks if the last block is a list, blockquote, code block, or table
- Continuation Markers: Reparse 4 blocks if the suffix contains list markers, blockquotes, or open tables
- Open Code Fence: Reparse 5 blocks if there’s an unclosed code fence (``` or ~~~)
Usage Example
Direct API Usage
Automatic Incremental Parsing
When usingMarkdownTextView, incremental parsing is handled automatically:
Sources/MarkdownView/MarkdownTextView/MarkdownTextView+Private.swift:66-165
Plain Text Append Fast Path
For simple text appends to the last paragraph, MarkdownView uses an even faster optimization that skips parsing entirely:Sources/MarkdownView/MarkdownTextView/MarkdownTextView+Private.swift:178
This fast path activates when:
- The new content is an append (has the previous markdown as prefix)
- The appended text contains only plain text (no markdown syntax)
- The last block is a paragraph containing only text nodes
- No special characters are present:
\n\r`*_[]()!$<>|~\\
Math Identifier Shifting
When merging incremental results, math expressions need special handling to avoid identifier conflicts:Sources/MarkdownParser/MarkdownParser/MarkdownParser.swift:325
This function:
- Finds the maximum math identifier in the stable prefix blocks
- Shifts all math identifiers in the tail result by
offset + 1 - Updates the math context dictionary with shifted keys
Performance Characteristics
| Operation | Time |
|---|---|
| Plain-text stream append (steady-state) | <0.1 ms |
| Parse 500 blocks (full parse) | ~5 ms |
| Parse 50 blocks (incremental tail) | ~1 ms |
| Merge stable prefix with tail | <0.1 ms |
Performance measurements from
README.md benchmarks. Actual performance depends on document complexity and hardware.Best Practices
1. Cache Block Ranges
2. Handle Nil Gracefully
3. Use MarkdownTextView for Automatic Management
TheMarkdownTextView handles all incremental parsing logic automatically, including caching ranges and merging results.
See Also
- Performance Optimization - General performance best practices
- MarkdownParser API Reference - Complete parser documentation