parseBlocks function

List<MdNode> parseBlocks(
  1. List<String> lines,
  2. bool useDollar, [
  3. MarkdownBlockRegistry? registry
])

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;
}