Refactor CodeBlockSyntax (dart-lang/markdown#488)

* Refactor CodeBlockSyntax

* Update stats

* Fix some requests
diff --git a/pkgs/markdown/lib/src/block_syntaxes/code_block_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/code_block_syntax.dart
index f37baed..326e0b2 100644
--- a/pkgs/markdown/lib/src/block_syntaxes/code_block_syntax.dart
+++ b/pkgs/markdown/lib/src/block_syntaxes/code_block_syntax.dart
@@ -23,25 +23,21 @@
     final childLines = <String?>[];
 
     while (!parser.isDone) {
-      final match = pattern.firstMatch(parser.current);
-      if (match != null) {
-        childLines.add(match[1]);
-        parser.advance();
-      } else {
-        // If there's a codeblock, then a newline, then a codeblock, keep the
-        // code blocks together.
-        final nextMatch =
-            parser.next != null ? pattern.firstMatch(parser.next!) : null;
-        if (parser.current.trim() == '' && nextMatch != null) {
-          childLines.add('');
-          childLines.add(nextMatch[1]);
-          parser.advance();
-          parser.advance();
-        } else {
-          break;
-        }
+      final isBlankLine = parser.current.isBlank;
+      if (isBlankLine && _shouldEnd(parser)) {
+        break;
       }
+
+      if (!isBlankLine &&
+          childLines.isNotEmpty &&
+          pattern.hasMatch(parser.current) != true) {
+        break;
+      }
+      childLines.add(parser.current.dedent().text);
+
+      parser.advance();
     }
+
     return childLines;
   }
 
@@ -59,4 +55,24 @@
 
     return Element('pre', [Element.text('code', content)]);
   }
+
+  bool _shouldEnd(BlockParser parser) {
+    var i = 1;
+    while (true) {
+      final nextLine = parser.peek(i);
+      // EOF
+      if (nextLine == null) {
+        return true;
+      }
+
+      // It does not matter how many blank lines between chunks:
+      // https://spec.commonmark.org/0.30/#example-111
+      if (nextLine.isBlank) {
+        i++;
+        continue;
+      }
+
+      return pattern.hasMatch(nextLine) == false;
+    }
+  }
 }
diff --git a/pkgs/markdown/lib/src/util.dart b/pkgs/markdown/lib/src/util.dart
index 02e26ab..b482c1c 100644
--- a/pkgs/markdown/lib/src/util.dart
+++ b/pkgs/markdown/lib/src/util.dart
@@ -131,3 +131,57 @@
 
   return buffer.toString();
 }
+
+extension StringExtensions on String {
+  /// Removes up to [length] characters of leading whitespace.
+  // The way of handling tabs: https://spec.commonmark.org/0.30/#tabs
+  DedentedText dedent([int length = 4]) {
+    final whitespaceMatch = RegExp('^[ \t]{0,$length}').firstMatch(this);
+    const tabSize = 4;
+
+    int? tabRemaining;
+    var start = 0;
+    final whitespaces = whitespaceMatch?[0];
+    if (whitespaces != null) {
+      var indentLength = 0;
+      for (start; start < whitespaces.length; start++) {
+        final isTab = whitespaces[start] == '\t';
+        if (isTab) {
+          indentLength += tabSize;
+          tabRemaining = 4;
+        } else {
+          indentLength += 1;
+        }
+        if (indentLength >= length) {
+          if (tabRemaining != null) {
+            tabRemaining = indentLength - length;
+          }
+          if (indentLength == length || isTab) {
+            start += 1;
+          }
+          break;
+        }
+        if (tabRemaining != null) {
+          tabRemaining = 0;
+        }
+      }
+    }
+    return DedentedText(substring(start), tabRemaining);
+  }
+
+  /// Whether this string contains only whitespaces.
+  bool get isBlank => trim().isEmpty;
+}
+
+/// A class that describes a dedented text.
+class DedentedText {
+  /// The dedented text.
+  final String text;
+
+  /// How many spaces of a tab that remains after part of it has been consumed.
+  ///
+  /// `null` means we did not read a `tab`.
+  final int? tabRemaining;
+
+  DedentedText(this.text, this.tabRemaining);
+}
diff --git a/pkgs/markdown/test/common_mark/indented_code_blocks.unit b/pkgs/markdown/test/common_mark/indented_code_blocks.unit
index 66404a8..ce142fe 100644
--- a/pkgs/markdown/test/common_mark/indented_code_blocks.unit
+++ b/pkgs/markdown/test/common_mark/indented_code_blocks.unit
@@ -52,8 +52,10 @@
 <pre><code>chunk1
 
 chunk2
-</code></pre>
-<pre><code>chunk3
+
+
+
+chunk3
 </code></pre>
 >>> Indented code blocks - 112
     chunk1
@@ -108,7 +110,6 @@
 
 <<<
 <pre><code>foo
-
 </code></pre>
 >>> Indented code blocks - 118
     foo  
diff --git a/pkgs/markdown/test/gfm/indented_code_blocks.unit b/pkgs/markdown/test/gfm/indented_code_blocks.unit
index 0dffa1d..68fc6d1 100644
--- a/pkgs/markdown/test/gfm/indented_code_blocks.unit
+++ b/pkgs/markdown/test/gfm/indented_code_blocks.unit
@@ -52,8 +52,10 @@
 <pre><code>chunk1
 
 chunk2
-</code></pre>
-<pre><code>chunk3
+
+
+
+chunk3
 </code></pre>
 >>> Indented code blocks - 82
     chunk1
@@ -108,7 +110,6 @@
 
 <<<
 <pre><code>foo
-
 </code></pre>
 >>> Indented code blocks - 88
     foo  
diff --git a/pkgs/markdown/test/original/code_blocks.unit b/pkgs/markdown/test/original/code_blocks.unit
index b219fe7..02e6746 100644
--- a/pkgs/markdown/test/original/code_blocks.unit
+++ b/pkgs/markdown/test/original/code_blocks.unit
@@ -45,10 +45,12 @@
 <<<
 <pre><code>zero
 one
-</code></pre>
-<pre><code>two
-</code></pre>
-<pre><code>three
+
+
+two
+
+
+three
 </code></pre>
 >>> escape HTML characters
     <&>
diff --git a/pkgs/markdown/tool/common_mark_stats.json b/pkgs/markdown/tool/common_mark_stats.json
index daaa718..8368b9a 100644
--- a/pkgs/markdown/tool/common_mark_stats.json
+++ b/pkgs/markdown/tool/common_mark_stats.json
@@ -384,13 +384,13 @@
   "108": "strict",
   "109": "strict",
   "110": "strict",
-  "111": "fail",
+  "111": "strict",
   "112": "strict",
   "113": "strict",
   "114": "strict",
   "115": "strict",
   "116": "strict",
-  "117": "loose",
+  "117": "strict",
   "118": "strict"
  },
  "Inlines": {
diff --git a/pkgs/markdown/tool/common_mark_stats.txt b/pkgs/markdown/tool/common_mark_stats.txt
index 337d16f..910b446 100644
--- a/pkgs/markdown/tool/common_mark_stats.txt
+++ b/pkgs/markdown/tool/common_mark_stats.txt
@@ -10,7 +10,7 @@
   15 of   15 – 100.0%  Hard line breaks
   44 of   44 – 100.0%  HTML blocks
   21 of   22 –  95.5%  Images
-  11 of   12 –  91.7%  Indented code blocks
+  12 of   12 – 100.0%  Indented code blocks
    1 of    1 – 100.0%  Inlines
   21 of   27 –  77.8%  Link reference definitions
   89 of   90 –  98.9%  Links
@@ -24,5 +24,5 @@
   11 of   11 – 100.0%  Tabs
    3 of    3 – 100.0%  Textual content
   19 of   19 – 100.0%  Thematic breaks
- 634 of  652 –  97.2%  TOTAL
- 614 of  634 –  96.8%  TOTAL Strict
+ 635 of  652 –  97.4%  TOTAL
+ 616 of  635 –  97.0%  TOTAL Strict
diff --git a/pkgs/markdown/tool/gfm_stats.json b/pkgs/markdown/tool/gfm_stats.json
index 91de967..a431a10 100644
--- a/pkgs/markdown/tool/gfm_stats.json
+++ b/pkgs/markdown/tool/gfm_stats.json
@@ -399,13 +399,13 @@
   "78": "strict",
   "79": "strict",
   "80": "strict",
-  "81": "fail",
+  "81": "strict",
   "82": "strict",
   "83": "strict",
   "84": "strict",
   "85": "strict",
   "86": "strict",
-  "87": "loose",
+  "87": "strict",
   "88": "strict"
  },
  "Inlines": {
diff --git a/pkgs/markdown/tool/gfm_stats.txt b/pkgs/markdown/tool/gfm_stats.txt
index b7598f9..0a103e9 100644
--- a/pkgs/markdown/tool/gfm_stats.txt
+++ b/pkgs/markdown/tool/gfm_stats.txt
@@ -12,7 +12,7 @@
   15 of   15 – 100.0%  Hard line breaks
   43 of   43 – 100.0%  HTML blocks
   21 of   22 –  95.5%  Images
-  11 of   12 –  91.7%  Indented code blocks
+  12 of   12 – 100.0%  Indented code blocks
    1 of    1 – 100.0%  Inlines
   22 of   28 –  78.6%  Link reference definitions
   86 of   87 –  98.9%  Links
@@ -28,5 +28,5 @@
   11 of   11 – 100.0%  Tabs
    3 of    3 – 100.0%  Textual content
   19 of   19 – 100.0%  Thematic breaks
- 652 of  671 –  97.2%  TOTAL
- 631 of  652 –  96.8%  TOTAL Strict
+ 653 of  671 –  97.3%  TOTAL
+ 633 of  653 –  96.9%  TOTAL Strict