Skip to main content

Custom Pipeline

The document pipeline is an additive API introduced in 0.4.0 in the repository. Existing blocks, fromMarkdown, asSliver, builders, and selection controllers keep their compatibility path. The published Dart API reference may not contain these additions until a package release.

Choose how much to replace

LayerExtend a defaultReplace the engine
ParserDefaultMarkdownParser(rules: ...)MarkdownParser and MarkdownParserSession
RendererRendererRegistry(definitions: ...)MarkdownRenderer
AnimatorConfigure DefaultMarkdownAnimatorMarkdownAnimator and MarkdownAnimatorSession

The choices are independent. A custom parser can use handwritten rules, another library, or an external AST; it does not have to initialize tree-sitter. Its output is authoritative. A renderer handles node kinds and produces static fragments; an animator schedules those fragments without interpreting Markdown. Unknown node kinds use a literal-source renderer fallback.

import 'package:animated_streaming_markdown/animated_streaming_markdown.dart';

// Supply your implementations of the three public contracts.
final parser = MarkdownStreamParser(parser: myParser);
await parser.start();
final result = await parser.append(chunk);

final view = AnimatedStreamingMarkdown.fromDocument(
document: result.document!,
renderer: myRenderer,
animator: myAnimator,
enableSelection: true,
);

Keep the parser in application state and dispose it when the document is no longer needed. The widget owns its animator session, not the caller's parser. Use finish() for input EOF; animation completion is a separate event. Custom async parser operations are queued without dropping chunks. Errors are reported instead of switching to another parser; custom sessions should commit snapshots only after successful parsing.

Parser output and lazy inner parsers

MarkdownDocumentSnapshot holds source, revision, hierarchy, opaque node IDs, completion, and change information. Preserve identity when content continues. MarkdownSourceRange uses UTF-16 source offsets; legacy startByte and endByte use UTF-8. SourceOffsetIndex converts between them.

The Pipeline Lab source shows a custom parser with demand-driven inner factories for fenced code and Mermaid. The first matching fence creates a handler; unchanged block results are cached. Ordinary prose does not create those handlers.

This registry is example code, not a package-wide parser requirement. It is synchronous lazy construction, not asynchronous module loading or viewport-based parsing. C/C++ provides lexical highlighting and limited checks; Mermaid supports a flowchart subset. Replace either handler for a broader grammar, or implement an async MarkdownParserSession for loading and parsing with futures.

Renderer fragments and animation

A MarkdownRendererDefinition describes one node kind. Its MarkdownRenderComponent contains semantic fragments and a widget factory that runs only when materialized. Full MarkdownRenderer implementations can replace registry lookup and composition entirely.

Fragments declare selectable text, source mappings, and copy metadata before widgets mount. Register geometry with component.selectable. Transformed text needs boundary mappings or atomic selection; decorative widgets need no logical text. Text and Text.rich preserve span styling when split for animation. Other widgets can provide sliceBuilder or remain atomic.

A custom MarkdownAnimatorSession controls segmentation, scheduling, progress, pause/resume, and disposal. It returns slices in the original fragment's UTF-16 coordinates; it must not change the logical text or mapping. Atomic fragments remain whole. The default animator supports word, grapheme, fragment, and input-arrival segmentation; arrival chunks are not assumed to be LLM tokens.

The lab's AnswerAnimator(tokenize: ...) demonstrates an application tokenizer callback for characters, words, or lines. This callback belongs to the example; implement the public animator contract when segmentation needs document context.

Selection, copy, and Sliver

Copy strategies consume captured document/static metadata, including offscreen fragments. Animation progress does not change copy output. A custom copy provider should respect its selected slice; exact-source copy requires an accurate source snapshot. Binary image clipboard transport is an optional adapter, separate from HTML image markup.

SliverAnimatedStreamingMarkdown.fromDocument shares the document pipeline. Place AnimatedStreamingMarkdownSelectionArea outside CustomScrollView for selectable Slivers. Virtualization is by outer component, so a large table or code block can remain one large child.

The document path's selection controller uses snapshot source text. The legacy blocks path retains its reconstructed projection. This migration does not promise identical visual defaults between the two paths or exact scrolling to unmeasured content.

Try the example

Open Pipeline Lab from the chat example's science button, or run:

cd example
flutter run -t lib/pipeline_lab.dart

Choose Parser, Renderer, Animator, and Tokenization independently. Replay local source without credentials, or use the original chat API settings for a streamed response. The inner-parser counters show when handlers load and parse. JetBrains Mono and syntax palettes are bundled/configured by the example; ASM does not impose them on applications.

See the step-by-step lab guide and document contract guide for extension examples and current limitations.