From 8f3c5585a216f208a73ee06b0eb95df932b9e245 Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Thu, 24 Sep 2026 11:23:23 +0100 Subject: [PATCH 1/4] Add code blocks with more than 3 backticks --- .../syntax_highlighting/comments.dart.golden | 52 +++++++++++++++++++ .../syntax_highlighting/comments.dart | 24 +++++++++ 2 files changed, 76 insertions(+) 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 d010c2f573b..f43bbd146ef 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,55 @@ >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 +>/// three backticks is nested code +#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ comment.block.documentation.dart +>/// ``` +#^^^^^^^ comment.block.documentation.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 +>/// still code +#^^^^^^^^^^^^^^ comment.block.documentation.dart +>/// `````````````````` +#^^^^^^^^^^^^^^^^^^^^^^ comment.block.documentation.dart +>/// +#^^^ comment.block.documentation.dart +>/// Text +#^^^^ comment.block.documentation.dart +# ^^^^ comment.block.documentation.dart variable.other.source.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 44e03b17114..a052b8d5f05 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 @@ var g; /// 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; From 8b15701bd1b72f4005c7a1a69fbb4ef0c962adaa Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Thu, 24 Sep 2026 11:24:17 +0100 Subject: [PATCH 2/4] Handle backticks correctly --- packages/devtools_app/assets/dart_syntax.json | 16 +++---- .../lib/src/screens/debugger/span_parser.dart | 43 ++++++++++++++++--- .../screens/debugger/span_parser_test.dart | 2 +- .../syntax_highlighting/comments.dart.golden | 18 +++++--- 4 files changed, 56 insertions(+), 23 deletions(-) diff --git a/packages/devtools_app/assets/dart_syntax.json b/packages/devtools_app/assets/dart_syntax.json index f05d62d8ed6..f746ff4e338 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 062abf05227..dc1f1d12ac2 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 @@ class _MultilineMatcher extends GrammarMatcher { 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 ?? []) { if (pattern.scan(grammar, scanner, scopeStack)) { @@ -421,14 +420,41 @@ class _MultilineMatcher extends GrammarMatcher { } } - 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 = beginMatch.group(groupIndex); + 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 +482,13 @@ class _MultilineMatcher extends GrammarMatcher { 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/test/screens/debugger/span_parser_test.dart b/packages/devtools_app/test/screens/debugger/span_parser_test.dart index 42f64641373..f542740a55a 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 @@ void main() { // 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().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 f43bbd146ef..de79f28de32 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 @@ -111,11 +111,14 @@ #^^^^ comment.block.documentation.dart # ^^^^^^^^^^^^^^^^^^^^^ comment.block.documentation.dart variable.other.source.dart >/// ``` -#^^^^^^^ comment.block.documentation.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 +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ comment.block.documentation.dart variable.other.source.dart >/// ``` -#^^^^^^^ comment.block.documentation.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 @@ -139,16 +142,17 @@ #^^^^ comment.block.documentation.dart # ^^^^^^^^^^^^^^^^^^^^^ comment.block.documentation.dart variable.other.source.dart >/// ``` -#^^^^^^^ comment.block.documentation.dart +#^^^^ comment.block.documentation.dart +# ^^^ comment.block.documentation.dart variable.other.source.dart >/// still code -#^^^^^^^^^^^^^^ comment.block.documentation.dart +#^^^^ 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 -# ^^^^ comment.block.documentation.dart variable.other.source.dart +#^^^^^^^^ comment.block.documentation.dart >var moreClosingBackticks; #^^^ storage.type.primitive.dart # ^ punctuation.terminator.dart From 128f54e94ef5c80f7f896c0ed8807236921b7103 Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Thu, 24 Sep 2026 11:31:29 +0100 Subject: [PATCH 3/4] Update NEXT_RELEASE_NOTES.md --- packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md index 10b1377f826..259f9e9ee21 100644 --- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md +++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md @@ -59,6 +59,9 @@ To learn more about DevTools, check out the 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 From ae86547d2568b991cbdf68b6d89ad4d76ca09442 Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Thu, 24 Sep 2026 11:48:20 +0100 Subject: [PATCH 4/4] Address Gemini's comment It won't happen here, but if the grammar somehow referenced a match that didn't exist --- .../devtools_app/lib/src/screens/debugger/span_parser.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 dc1f1d12ac2..872aef3a219 100644 --- a/packages/devtools_app/lib/src/screens/debugger/span_parser.dart +++ b/packages/devtools_app/lib/src/screens/debugger/span_parser.dart @@ -443,7 +443,9 @@ class _MultilineMatcher extends GrammarMatcher { RegExp(r'\\([0-9]+)'), (match) { final groupIndex = int.parse(match.group(1)!); - final capturedText = beginMatch.group(groupIndex); + final capturedText = groupIndex <= beginMatch.groupCount + ? beginMatch.group(groupIndex) + : null; return capturedText == null // No capture, keep the original text. ? match.group(0)!