parseBlocks function
Implementation
List<MdNode> parseBlocks(
List<String> lines,
bool useDollar, [
MarkdownBlockRegistry? registry,
]) {
final out = <MdNode>[];
final n = lines.length;
var i = 0;
while (i < n) {
final raw = lines[i];
if (isBlank(raw)) {
i += 1;
continue;
}
final trimmed = raw.trimLeft();
final custom = registry?.match(lines, i);
if (custom != null) {
out.add(custom.node);
i = custom.endLine;
continue;
}
// Fenced code block ```lang ... ```
if (trimmed.startsWith('```')) {
final language = trimmed.substring(3).trim();
i += 1;
final code = <String>[];
var closed = false;
while (i < n) {
if (lines[i].trimLeft().startsWith('```')) {
closed = true;
i += 1;
break;
}
code.add(lines[i]);
i += 1;
}
out.add(
MdCodeBlock(language: language, code: code.join('\n'), closed: closed),
);
continue;
}
// Block LaTeX \[ ... \]
if (trimmed.startsWith('\\[')) {
var first = trimmed;
while (first.startsWith('\\[')) {
first = first.substring(2);
}
final end = first.indexOf('\\]');
if (end != -1) {
out.add(MdBlockLatex(tex: first.substring(0, end).trim()));
i += 1;
continue;
}
final content = <String>[];
if (first.trim().isNotEmpty) {
content.add(first);
}
i += 1;
while (i < n) {
final lineEnd = lines[i].indexOf('\\]');
if (lineEnd != -1) {
final pre = lines[i].substring(0, lineEnd);
if (pre.trim().isNotEmpty) {
content.add(pre);
}
i += 1;
break;
}
content.add(lines[i]);
i += 1;
}
out.add(MdBlockLatex(tex: content.join('\n').trim()));
continue;
}
// Heading
final heading = isHeading(trimmed);
if (heading != null) {
out.add(
MdHeading(
level: heading.level,
children: parseInline(heading.content, useDollar),
),
);
i += 1;
continue;
}
// Horizontal rule
if (isHr(trimmed)) {
out.add(const MdHorizontalRule());
i += 1;
continue;
}
// Blockquote
if (trimmed.startsWith('>')) {
final inner = <String>[];
while (i < n) {
if (isBlank(lines[i])) {
break;
}
final t = lines[i].trimLeft();
if (t.startsWith('>')) {
var stripped = t.substring(1);
if (stripped.startsWith(' ')) {
stripped = stripped.substring(1);
}
inner.add(stripped);
} else {
// lazy continuation line
inner.add(t);
}
i += 1;
}
out.add(MdBlockQuote(children: parseBlocks(inner, useDollar, registry)));
continue;
}
// Table
final tableEnd = _tryTable(lines, i, useDollar, out);
if (tableEnd != null) {
i = tableEnd;
continue;
}
// Checkbox / radio
final checkbox = checkboxMarker(trimmed);
if (checkbox != null) {
out.add(
MdCheckbox(
checked: checkbox.checked,
children: parseInline(checkbox.content, useDollar),
),
);
i += 1;
continue;
}
final radio = radioMarker(trimmed);
if (radio != null) {
out.add(
MdRadio(
selected: radio.selected,
children: parseInline(radio.content, useDollar),
),
);
i += 1;
continue;
}
// Lists
if (unorderedMarker(trimmed) != null) {
final r = _parseListInner(lines, i, false, useDollar, registry);
out.add(MdUnorderedList(items: r.items));
i = r.next;
continue;
}
if (orderedMarker(trimmed) != null) {
final r = _parseListInner(lines, i, true, useDollar, registry);
out.add(MdOrderedList(start: r.start, items: r.items));
i = r.next;
continue;
}
// Paragraph: gather consecutive non-blank, non-block lines.
final para = <String>[];
while (i < n) {
if (isBlank(lines[i])) {
break;
}
final t = lines[i].trimLeft();
if (_startsBlock(t) ||
(para.isNotEmpty && registry?.match(lines, i) != null)) {
break;
}
para.add(t);
i += 1;
}
// Joined on the newline, not on a space. CommonMark folds a single
// newline inside a paragraph into a space, and this package has never
// done that: a model writes a list of bullet glyphs, or a run of short
// lines, and folding them produced one long wrapped line. Folding also
// swallowed the two breaks CommonMark does define — two trailing spaces
// and a trailing backslash — because the newline they mark was gone
// before the inline parser ran.
out.add(MdParagraph(children: parseInline(para.join('\n'), useDollar)));
}
return out;
}