blob: cc80cdc7929d6e86081a72301b10dd48ab7fb413 [file] [log] [blame]
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:charcode/charcode.dart';
import '../ast.dart';
import '../block_parser.dart';
import '../patterns.dart';
import '../util.dart';
import 'block_syntax.dart';
/// Parses preformatted code blocks between two ~~~ or ``` sequences.
///
/// See the CommonMark spec: https://spec.commonmark.org/0.29/#fenced-code-blocks
class FencedCodeBlockSyntax extends BlockSyntax {
@override
RegExp get pattern => codeFencePattern;
const FencedCodeBlockSyntax();
@override
bool canParse(BlockParser parser) {
final match = pattern.firstMatch(parser.current);
if (match == null) return false;
final codeFence = match.group(1)!;
final infoString = match.group(2);
// From the CommonMark spec:
//
// > If the info string comes after a backtick fence, it may not contain
// > any backtick characters.
return (codeFence.codeUnitAt(0) != $backquote ||
!infoString!.codeUnits.contains($backquote));
}
@override
List<String> parseChildLines(BlockParser parser, [String? endBlock]) {
endBlock ??= '';
final childLines = <String>[];
parser.advance();
while (!parser.isDone) {
final match = pattern.firstMatch(parser.current);
if (match == null || !match[1]!.startsWith(endBlock)) {
childLines.add(parser.current);
parser.advance();
} else {
parser.advance();
break;
}
}
return childLines;
}
@override
Node parse(BlockParser parser) {
// Get the syntax identifier, if there is one.
final match = pattern.firstMatch(parser.current)!;
final endBlock = match.group(1);
var infoString = match.group(2)!;
final childLines = parseChildLines(parser, endBlock);
// The Markdown tests expect a trailing newline.
childLines.add('');
var text = childLines.join('\n');
if (parser.document.encodeHtml) {
text = escapeHtml(text);
}
final code = Element.text('code', text);
// the info-string should be trimmed
// http://spec.commonmark.org/0.22/#example-100
infoString = infoString.trim();
if (infoString.isNotEmpty) {
// only use the first word in the syntax
// http://spec.commonmark.org/0.22/#example-100
final firstSpace = infoString.indexOf(' ');
if (firstSpace >= 0) {
infoString = infoString.substring(0, firstSpace);
}
if (parser.document.encodeHtml) {
infoString = escapeHtmlAttribute(infoString);
}
code.attributes['class'] = 'language-$infoString';
}
final element = Element('pre', [code]);
return element;
}
}