Handle code blocks that use > 3 backticks in syntax highlighting (#10021)

* Add code blocks with more than 3 backticks

* Handle backticks correctly

* Update NEXT_RELEASE_NOTES.md

* Address Gemini's comment

It won't happen here, but if the grammar somehow referenced a match that didn't exist
diff --git a/packages/devtools_app/assets/dart_syntax.json b/packages/devtools_app/assets/dart_syntax.json
index f05d62d..f746ff4 100644
--- a/packages/devtools_app/assets/dart_syntax.json
+++ b/packages/devtools_app/assets/dart_syntax.json
@@ -1,6 +1,6 @@
 {
 	"name": "Dart",
-	"version": "1.6.0",
+	"version": "1.8.0",
 	"fileTypes": [
 		"dart"
 	],
@@ -76,12 +76,12 @@
 
 	"repository": {
 		"dartdoc-codeblock-triple": {
-			"begin": "^\\s*///\\s*(?!\\s*```)",
+			"begin": "^\\s*///\\s*",
 			"end": "$",
 			"contentName": "variable.other.source.dart"
 		},
 		"dartdoc-codeblock-block": {
-			"begin": "^\\s*\\*\\s*(?!(\\s*```|\/))",
+			"begin": "^\\s*\\*\\s*",
 			"end": "$",
 			"contentName": "variable.other.source.dart"
 		},
@@ -96,8 +96,8 @@
 					}
 				},
 				{
-					"begin": "^\\s*///\\s*(```)",
-					"end": "^\\s*///\\s*(```)|^(?!\\s*///)",
+					"begin": "^\\s*///\\s*(`{3,})",
+					"end": "^\\s*///\\s*(?=\\1)`*|^(?!\\s*///)",
 					"patterns": [
 						{
 							"include": "#dartdoc-codeblock-triple"
@@ -105,8 +105,8 @@
 					]
 				},
 				{
-					"begin": "^\\s*\\*\\s*(```)",
-					"end": "^\\s*\\*\\s*(```)|^(?=\\s*\\*\/)",
+					"begin": "^\\s*\\*\\s*(`{3,})",
+					"end": "^\\s*\\*\\s*(?=\\1)`*|^(?=\\s*\\*\/)",
 					"patterns": [
 						{
 							"include": "#dartdoc-codeblock-block"
@@ -118,7 +118,7 @@
 					"name": "variable.other.source.dart"
 				},
 				{
-					"match": "(?:\\*|\\/\\/)\\s{4,}(.*?)(?=($|\\*\\/))",
+					"match": "(?:\\*|\\/\\/)\\s{5,}(.*?)(?=($|\\*\\/))",
 					"captures": {
 						"1": {
 							"name": "variable.other.source.dart"
diff --git a/packages/devtools_app/lib/src/screens/debugger/span_parser.dart b/packages/devtools_app/lib/src/screens/debugger/span_parser.dart
index 062abf0..872aef3 100644
--- a/packages/devtools_app/lib/src/screens/debugger/span_parser.dart
+++ b/packages/devtools_app/lib/src/screens/debugger/span_parser.dart
@@ -403,10 +403,9 @@
     Grammar grammar,
     LineScanner scanner,
     ScopeStack scopeStack,
+    RegExp resolvedEnd,
   ) {
-    while (!scanner.isDone &&
-        end != null &&
-        !scanner.matchesOnCurrentLine(end!)) {
+    while (!scanner.isDone && !scanner.matchesOnCurrentLine(resolvedEnd)) {
       bool foundMatch = false;
       for (final pattern in patterns ?? <GrammarMatcher>[]) {
         if (pattern.scan(grammar, scanner, scopeStack)) {
@@ -421,14 +420,43 @@
     }
   }
 
-  void _scanEnd(Grammar grammar, LineScanner scanner, ScopeStack scopeStack) {
+  void _scanEnd(
+    Grammar grammar,
+    LineScanner scanner,
+    ScopeStack scopeStack,
+    RegExp resolvedEnd,
+  ) {
     final location = scanner.location;
-    if (end != null && !scanner.scanOnCurrentLine(end!)) {
+    if (!scanner.scanOnCurrentLine(resolvedEnd)) {
       return;
     }
     _processCaptureHelper(grammar, scanner, scopeStack, endCaptures, location);
   }
 
+  /// Returns an updated `end` regex that handles references in the form `\\1`
+  /// to reference matches from the `begin` regex.
+  ///
+  /// For example, this allows matching the correct number of backticks to close
+  /// a code block as were used to open it.
+  RegExp _resolveEnd(Match beginMatch) {
+    final resolvedPattern = end!.pattern.replaceAllMapped(
+      RegExp(r'\\([0-9]+)'),
+      (match) {
+        final groupIndex = int.parse(match.group(1)!);
+        final capturedText = groupIndex <= beginMatch.groupCount
+            ? beginMatch.group(groupIndex)
+            : null;
+        return capturedText == null
+            // No capture, keep the original text.
+            ? match.group(0)!
+            // Otherwise, escape the referenced text in case it contains regex
+            // chars.
+            : RegExp.escape(capturedText);
+      },
+    );
+    return RegExp(resolvedPattern, multiLine: true);
+  }
+
   void _processCaptureHelper(
     Grammar grammar,
     LineScanner scanner,
@@ -456,10 +484,13 @@
     scopeStack.push(name, scanner.location);
     _scanBegin(grammar, scanner, scopeStack);
     if (end != null) {
+      // Resolve any references in the end regex that reference captures from
+      // the begin match.
+      final resolvedEnd = _resolveEnd(scanner.lastMatch!);
       scopeStack.push(contentName, scanner.location);
-      _scanUpToEndMatch(grammar, scanner, scopeStack);
+      _scanUpToEndMatch(grammar, scanner, scopeStack, resolvedEnd);
       scopeStack.pop(contentName, scanner.location);
-      _scanEnd(grammar, scanner, scopeStack);
+      _scanEnd(grammar, scanner, scopeStack, resolvedEnd);
     } else if (whileCond != null) {
       // Find the range of the string that is matched by the while condition.
       final start = scanner.position;
diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
index 993227c..b309745 100644
--- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
+++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
@@ -62,6 +62,9 @@
   narrow, such as when DevTools is embedded in an IDE side panel. The controls
   now scroll horizontally instead of overflowing.
   [#9949](https://github.com/flutter/devtools/pull/9949)
+* Improved syntax highlighting of fenced code blocks that use more than three
+  backticks.
+  [#10021](https://github.com/flutter/devtools/pull/10021)
 
 ## Network profiler updates
 
diff --git a/packages/devtools_app/test/screens/debugger/span_parser_test.dart b/packages/devtools_app/test/screens/debugger/span_parser_test.dart
index 42f6464..f542740 100644
--- a/packages/devtools_app/test/screens/debugger/span_parser_test.dart
+++ b/packages/devtools_app/test/screens/debugger/span_parser_test.dart
@@ -108,7 +108,7 @@
       // These goldens are updated using the usual Flutter --update-goldens
       // flag:
       //
-      //     flutter test test/shared/span_parser_test.dart --update-goldens
+      //     flutter test test/screens/debugger/span_parser_test.dart --update-goldens
       final testFiles = testDataDirectory.listSync().whereType<File>().where(
         (file) => path.extension(file.path) == '.dart',
       );
diff --git a/packages/devtools_app/test/test_infra/goldens/syntax_highlighting/comments.dart.golden b/packages/devtools_app/test/test_infra/goldens/syntax_highlighting/comments.dart.golden
index d010c2f..de79f28 100644
--- a/packages/devtools_app/test/test_infra/goldens/syntax_highlighting/comments.dart.golden
+++ b/packages/devtools_app/test/test_infra/goldens/syntax_highlighting/comments.dart.golden
@@ -100,3 +100,59 @@
 >var h;
 #^^^ storage.type.primitive.dart
 #     ^ punctuation.terminator.dart
+>
+>/// Multiline dartdoc comment with more than three backticks.
+#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ comment.block.documentation.dart
+>///
+#^^^ comment.block.documentation.dart
+>/// ``````
+#^^^^^^^^^^ comment.block.documentation.dart
+>/// code in six backticks
+#^^^^ comment.block.documentation.dart
+#    ^^^^^^^^^^^^^^^^^^^^^ comment.block.documentation.dart variable.other.source.dart
+>/// ```
+#^^^^ comment.block.documentation.dart
+#    ^^^ comment.block.documentation.dart variable.other.source.dart
+>/// three backticks is nested code
+#^^^^ comment.block.documentation.dart
+#    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ comment.block.documentation.dart variable.other.source.dart
+>/// ```
+#^^^^ comment.block.documentation.dart
+#    ^^^ comment.block.documentation.dart variable.other.source.dart
+>/// more code
+#^^^^ comment.block.documentation.dart
+#    ^^^^^^^^^ comment.block.documentation.dart variable.other.source.dart
+>/// ``````
+#^^^^^^^^^^ comment.block.documentation.dart
+>///
+#^^^ comment.block.documentation.dart
+>/// Text
+#^^^^^^^^ comment.block.documentation.dart
+>var moreBackticks;
+#^^^ storage.type.primitive.dart
+#                 ^ punctuation.terminator.dart
+>
+>/// Multiline dartdoc comment with more closing backticks.
+#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ comment.block.documentation.dart
+>///
+#^^^ comment.block.documentation.dart
+>/// ``````
+#^^^^^^^^^^ comment.block.documentation.dart
+>/// code in six backticks
+#^^^^ comment.block.documentation.dart
+#    ^^^^^^^^^^^^^^^^^^^^^ comment.block.documentation.dart variable.other.source.dart
+>/// ```
+#^^^^ comment.block.documentation.dart
+#    ^^^ comment.block.documentation.dart variable.other.source.dart
+>/// still code
+#^^^^ comment.block.documentation.dart
+#    ^^^^^^^^^^ comment.block.documentation.dart variable.other.source.dart
+>/// ``````````````````
+#^^^^^^^^^^^^^^^^^^^^^^ comment.block.documentation.dart
+>///
+#^^^ comment.block.documentation.dart
+>/// Text
+#^^^^^^^^ comment.block.documentation.dart
+>var moreClosingBackticks;
+#^^^ storage.type.primitive.dart
+#                        ^ punctuation.terminator.dart
diff --git a/packages/devtools_app/test/test_infra/test_data/syntax_highlighting/comments.dart b/packages/devtools_app/test/test_infra/test_data/syntax_highlighting/comments.dart
index 44e03b1..a052b8d 100644
--- a/packages/devtools_app/test/test_infra/test_data/syntax_highlighting/comments.dart
+++ b/packages/devtools_app/test/test_infra/test_data/syntax_highlighting/comments.dart
@@ -46,3 +46,27 @@
 /// Dartdoc with reference to [a].
 /// And a link to [example.org](http://example.org/).
 var h;
+
+/// Multiline dartdoc comment with more than three backticks.
+///
+/// ``````
+/// code in six backticks
+/// ```
+/// three backticks is nested code
+/// ```
+/// more code
+/// ``````
+///
+/// Text
+var moreBackticks;
+
+/// Multiline dartdoc comment with more closing backticks.
+///
+/// ``````
+/// code in six backticks
+/// ```
+/// still code
+/// ``````````````````
+///
+/// Text
+var moreClosingBackticks;