Rewrite SetextHeaderSyntax (dart-lang/markdown#500)

* Rewrite SetextHeaderSyntax

* Update CHANGELOG.md

* Update stats

* Fix some comments
diff --git a/pkgs/markdown/CHANGELOG.md b/pkgs/markdown/CHANGELOG.md
index 91da8b7..47cb1a5 100644
--- a/pkgs/markdown/CHANGELOG.md
+++ b/pkgs/markdown/CHANGELOG.md
@@ -21,6 +21,8 @@
 * Add a new type: `Line`.
 * Add a new optional parameter `parentSyntax` for `parseLines()` of
   `BlockParser`, which can be used when parsing nested blocks.
+* Add a new optional parameter `disabledSetextHeading` for `parseLines()` of
+  `BlockParser`, which is used to disable the `SetextHeaderSyntax`.
 
 ## 6.0.1
 
diff --git a/pkgs/markdown/lib/src/block_parser.dart b/pkgs/markdown/lib/src/block_parser.dart
index 023e9cb..2564759 100644
--- a/pkgs/markdown/lib/src/block_parser.dart
+++ b/pkgs/markdown/lib/src/block_parser.dart
@@ -35,6 +35,18 @@
   /// Index of the current line.
   int _pos = 0;
 
+  /// Starting line of the last unconsumed content.
+  int _start = 0;
+
+  /// The lines from [_start] to [_pos] (inclusive), it works as a buffer for
+  /// some blocks, for example:
+  /// When the [ParagraphSyntax] parsing process is interrupted by the
+  /// [SetextHeaderSyntax], so this structure is not a paragraph but a setext
+  /// heading, then the [ParagraphSyntax.parse] does not have to retreat the
+  /// reading position, it only needs to return `null`, the [SetextHeaderSyntax]
+  /// will pick up the lines in [linesToConsume].
+  List<Line> get linesToConsume => lines.getRange(_start, _pos + 1).toList();
+
   /// Whether the parser has encountered a blank line between two block-level
   /// elements.
   bool encounteredBlankLine = false;
@@ -114,10 +126,22 @@
   BlockSyntax? get parentSyntax => _parentSyntax;
   BlockSyntax? _parentSyntax;
 
+  /// Whether the [SetextHeadingSyntax] is disabled temporarily.
+  bool get setextHeadingDisabled => _setextHeadingDisabled;
+  bool _setextHeadingDisabled = false;
+
+  /// The [BlockSyntax] which is running now.
+  /// The value is `null` until we found the first matched [BlockSyntax].
+  BlockSyntax? get currentSyntax => _currentSyntax;
+  BlockSyntax? _currentSyntax;
+
   List<Node> parseLines({
     BlockSyntax? parentSyntax,
+    bool disabledSetextHeading = false,
   }) {
     _parentSyntax = parentSyntax;
+    _setextHeadingDisabled = disabledSetextHeading;
+
     final blocks = <Node>[];
 
     // If the `_pos` does not change before and after `parse()`, never try to
@@ -134,6 +158,7 @@
         }
 
         if (syntax.canParse(this)) {
+          _currentSyntax = syntax;
           final positionBefore = _pos;
           final block = syntax.parse(this);
           if (block != null) {
@@ -141,6 +166,10 @@
           }
           neverMatch = _pos != positionBefore ? null : syntax;
 
+          if (block != null || syntax is EmptyBlockSyntax) {
+            _start = _pos;
+          }
+
           break;
         }
       }
diff --git a/pkgs/markdown/lib/src/block_syntaxes/block_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/block_syntax.dart
index 298dc05..947824a 100644
--- a/pkgs/markdown/lib/src/block_syntaxes/block_syntax.dart
+++ b/pkgs/markdown/lib/src/block_syntaxes/block_syntax.dart
@@ -34,6 +34,19 @@
     return childLines;
   }
 
+  /// Returns the block which interrupts current syntax parsing if there is one,
+  /// otherwise returns `null`.
+  ///
+  /// Make sure to check if [parser] `isDone` is `false` first.
+  BlockSyntax? interruptedBy(BlockParser parser) {
+    for (final syntax in parser.blockSyntaxes) {
+      if (syntax.canParse(parser) && syntax.canEndBlock(parser)) {
+        return syntax;
+      }
+    }
+    return null;
+  }
+
   /// Gets whether or not [parser]'s current line should end the previous block.
   static bool isAtBlockEnd(BlockParser parser) {
     if (parser.isDone) return true;
diff --git a/pkgs/markdown/lib/src/block_syntaxes/blockquote_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/blockquote_syntax.dart
index f57346f..439af43 100644
--- a/pkgs/markdown/lib/src/block_syntaxes/blockquote_syntax.dart
+++ b/pkgs/markdown/lib/src/block_syntaxes/blockquote_syntax.dart
@@ -19,10 +19,15 @@
 
   const BlockquoteSyntax();
 
+  /// Whether this blockquote ends with a lazy continuation line.
+  // The definition of lazy continuation lines:
+  // https://spec.commonmark.org/0.30/#lazy-continuation-line
+  static var _lazyContinuation = false;
   @override
   List<Line> parseChildLines(BlockParser parser) {
     // Grab all of the lines that form the blockquote, stripping off the ">".
     final childLines = <Line>[];
+    _lazyContinuation = false;
 
     while (!parser.isDone) {
       final currentLine = parser.current;
@@ -47,6 +52,7 @@
         }
         childLines.add(Line(currentLine.content.substring(markerEnd)));
         parser.advance();
+        _lazyContinuation = false;
         continue;
       }
 
@@ -65,6 +71,7 @@
           (otherMatched is CodeBlockSyntax &&
               !indentPattern.hasMatch(lastLine.content))) {
         childLines.add(parser.current);
+        _lazyContinuation = true;
         parser.advance();
       } else {
         break;
@@ -79,7 +86,12 @@
     final childLines = parseChildLines(parser);
 
     // Recursively parse the contents of the blockquote.
-    final children = BlockParser(childLines, parser.document).parseLines();
+    final children = BlockParser(childLines, parser.document).parseLines(
+      // The setext heading underline cannot be a lazy continuation line in a
+      // block quote.
+      // https://spec.commonmark.org/0.30/#example-93
+      disabledSetextHeading: _lazyContinuation,
+    );
 
     return Element('blockquote', children);
   }
diff --git a/pkgs/markdown/lib/src/block_syntaxes/paragraph_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/paragraph_syntax.dart
index 83ae08c..affdefd 100644
--- a/pkgs/markdown/lib/src/block_syntaxes/paragraph_syntax.dart
+++ b/pkgs/markdown/lib/src/block_syntaxes/paragraph_syntax.dart
@@ -8,6 +8,7 @@
 import '../patterns.dart';
 import '../util.dart';
 import 'block_syntax.dart';
+import 'setext_header_syntax.dart';
 
 /// Parses paragraphs of regular text.
 class ParagraphSyntax extends BlockSyntax {
@@ -27,15 +28,27 @@
   bool canParse(BlockParser parser) => true;
 
   @override
-  Node parse(BlockParser parser) {
-    final childLines = <String>[];
+  Node? parse(BlockParser parser) {
+    final childLines = <String>[parser.current.content];
 
+    parser.advance();
+    var interruptedBySetextHeading = false;
     // Eat until we hit something that ends a paragraph.
-    while (!BlockSyntax.isAtBlockEnd(parser)) {
+    while (!parser.isDone) {
+      final syntax = interruptedBy(parser);
+      if (syntax != null) {
+        interruptedBySetextHeading = syntax is SetextHeaderSyntax;
+        break;
+      }
       childLines.add(parser.current.content);
       parser.advance();
     }
 
+    // It is not a paragraph, but a setext heading.
+    if (interruptedBySetextHeading) {
+      return null;
+    }
+
     final paragraphLines = _extractReflinkDefinitions(parser, childLines);
     if (paragraphLines == null) {
       // Paragraph consisted solely of reference link definitions.
diff --git a/pkgs/markdown/lib/src/block_syntaxes/setext_header_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/setext_header_syntax.dart
index d4777a5..49f4eda 100644
--- a/pkgs/markdown/lib/src/block_syntaxes/setext_header_syntax.dart
+++ b/pkgs/markdown/lib/src/block_syntaxes/setext_header_syntax.dart
@@ -6,65 +6,39 @@
 import '../block_parser.dart';
 import '../patterns.dart';
 import 'block_syntax.dart';
+import 'paragraph_syntax.dart';
 
 /// Parses setext-style headers.
 class SetextHeaderSyntax extends BlockSyntax {
   @override
-  RegExp get pattern => dummyPattern;
+  RegExp get pattern => setextPattern;
 
   const SetextHeaderSyntax();
 
   @override
   bool canParse(BlockParser parser) {
-    if (!_interperableAsParagraph(parser.current.content)) return false;
-    var i = 1;
-    while (true) {
-      final nextLine = parser.peek(i);
-      if (nextLine == null) {
-        // We never reached an underline.
-        return false;
-      }
-      if (setextPattern.hasMatch(nextLine.content)) {
-        return true;
-      }
-      // Ensure that we're still in something like paragraph text.
-      if (!_interperableAsParagraph(nextLine.content)) {
-        return false;
-      }
-      i++;
+    final lastSyntax = parser.currentSyntax;
+    if (parser.setextHeadingDisabled || lastSyntax is! ParagraphSyntax) {
+      return false;
     }
+    return pattern.hasMatch(parser.current.content);
   }
 
   @override
-  Node parse(BlockParser parser) {
-    final lines = <String>[];
-    String? tag;
-    while (!parser.isDone) {
-      final match = setextPattern.firstMatch(parser.current.content);
-      if (match == null) {
-        // More text.
-        lines.add(parser.current.content);
-        parser.advance();
-        continue;
-      } else {
-        // The underline.
-        tag = (match[1]![0] == '=') ? 'h1' : 'h2';
-        parser.advance();
-        break;
-      }
+  Node? parse(BlockParser parser) {
+    final lines = parser.linesToConsume;
+    if (lines.length < 2) {
+      return null;
     }
 
-    final contents = UnparsedContent(lines.join('\n').trimRight());
+    // Remove the last line which is a marker.
+    lines.removeLast();
 
-    return Element(tag!, [contents]);
+    final marker = parser.current.content.trim();
+    final level = (marker[0] == '=') ? '1' : '2';
+    final content = lines.map((e) => e.content).join('\n').trimRight();
+
+    parser.advance();
+    return Element('h$level', [UnparsedContent(content)]);
   }
-
-  bool _interperableAsParagraph(String line) =>
-      !(indentPattern.hasMatch(line) ||
-          codeFencePattern.hasMatch(line) ||
-          headerPattern.hasMatch(line) ||
-          blockquotePattern.hasMatch(line) ||
-          hrPattern.hasMatch(line) ||
-          listPattern.hasMatch(line) ||
-          emptyPattern.hasMatch(line));
 }
diff --git a/pkgs/markdown/test/common_mark/setext_headings.unit b/pkgs/markdown/test/common_mark/setext_headings.unit
index dca5f93..702c72c 100644
--- a/pkgs/markdown/test/common_mark/setext_headings.unit
+++ b/pkgs/markdown/test/common_mark/setext_headings.unit
@@ -115,9 +115,10 @@
 ===
 <<<
 <blockquote>
-<p>foo</p>
+<p>foo
+bar
+===</p>
 </blockquote>
-<h1>bar</h1>
 >>> Setext headings - 94
 - Foo
 ---
diff --git a/pkgs/markdown/test/gfm/setext_headings.unit b/pkgs/markdown/test/gfm/setext_headings.unit
index 4306bf4..725a175 100644
--- a/pkgs/markdown/test/gfm/setext_headings.unit
+++ b/pkgs/markdown/test/gfm/setext_headings.unit
@@ -115,9 +115,10 @@
 ===
 <<<
 <blockquote>
-<p>foo</p>
+<p>foo
+bar
+===</p>
 </blockquote>
-<h1>bar</h1>
 >>> Setext headings - 64
 - Foo
 ---
diff --git a/pkgs/markdown/tool/common_mark_stats.json b/pkgs/markdown/tool/common_mark_stats.json
index 12a63e8..d009416 100644
--- a/pkgs/markdown/tool/common_mark_stats.json
+++ b/pkgs/markdown/tool/common_mark_stats.json
@@ -645,7 +645,7 @@
   "90": "strict",
   "91": "strict",
   "92": "strict",
-  "93": "fail",
+  "93": "strict",
   "94": "strict",
   "95": "strict",
   "96": "strict",
diff --git a/pkgs/markdown/tool/common_mark_stats.txt b/pkgs/markdown/tool/common_mark_stats.txt
index eb0ac59..4bd7bda 100644
--- a/pkgs/markdown/tool/common_mark_stats.txt
+++ b/pkgs/markdown/tool/common_mark_stats.txt
@@ -19,10 +19,10 @@
    8 of    8 – 100.0%  Paragraphs
    1 of    1 – 100.0%  Precedence
   21 of   21 – 100.0%  Raw HTML
-  26 of   27 –  96.3%  Setext headings
+  27 of   27 – 100.0%  Setext headings
    2 of    2 – 100.0%  Soft line breaks
   11 of   11 – 100.0%  Tabs
    3 of    3 – 100.0%  Textual content
   19 of   19 – 100.0%  Thematic breaks
- 644 of  652 –  98.8%  TOTAL
- 626 of  644 –  97.2%  TOTAL Strict
+ 645 of  652 –  98.9%  TOTAL
+ 627 of  645 –  97.2%  TOTAL Strict
diff --git a/pkgs/markdown/tool/gfm_stats.json b/pkgs/markdown/tool/gfm_stats.json
index 43cd122..879abf6 100644
--- a/pkgs/markdown/tool/gfm_stats.json
+++ b/pkgs/markdown/tool/gfm_stats.json
@@ -658,7 +658,7 @@
   "60": "strict",
   "61": "strict",
   "62": "strict",
-  "63": "fail",
+  "63": "strict",
   "64": "strict",
   "65": "strict",
   "66": "strict",
diff --git a/pkgs/markdown/tool/gfm_stats.txt b/pkgs/markdown/tool/gfm_stats.txt
index c14547e..4e82a21 100644
--- a/pkgs/markdown/tool/gfm_stats.txt
+++ b/pkgs/markdown/tool/gfm_stats.txt
@@ -21,12 +21,12 @@
    8 of    8 – 100.0%  Paragraphs
    1 of    1 – 100.0%  Precedence
   21 of   21 – 100.0%  Raw HTML
-  26 of   27 –  96.3%  Setext headings
+  27 of   27 – 100.0%  Setext headings
    2 of    2 – 100.0%  Soft line breaks
    2 of    2 – 100.0%  Strikethrough (extension)
    8 of    8 – 100.0%  Tables (extension)
   11 of   11 – 100.0%  Tabs
    3 of    3 – 100.0%  Textual content
   19 of   19 – 100.0%  Thematic breaks
- 662 of  671 –  98.7%  TOTAL
- 643 of  662 –  97.1%  TOTAL Strict
+ 663 of  671 –  98.8%  TOTAL
+ 644 of  663 –  97.1%  TOTAL Strict