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;