blob: afbe23a0f2e36f22865579560cedd79cca3fc2ab [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 '../ast.dart';
import '../block_parser.dart';
import '../charcode.dart';
import '../line.dart';
import '../patterns.dart';
import '../util.dart';
import 'block_syntax.dart';
import 'code_block_syntax.dart';
import 'paragraph_syntax.dart';
/// Parses email-style blockquotes: `> quote`.
class BlockquoteSyntax extends BlockSyntax {
@override
RegExp get pattern => blockquotePattern;
const BlockquoteSyntax();
/// Whether this blockquote ends with a lazy continuation line.
// The definition of lazy continuation lines:
// https://spec.commonmark.org/0.30/#lazy-continuation-line
static var _lazyContinuation = false;
@override
List<Line> parseChildLines(BlockParser parser) {
// Grab all of the lines that form the blockquote, stripping off the ">".
final childLines = <Line>[];
_lazyContinuation = false;
while (!parser.isDone) {
final currentLine = parser.current;
final match = pattern.firstMatch(parser.current.content);
if (match != null) {
// A block quote marker consists of a `>` together with an optional
// following space of indentation, see
// https://spec.commonmark.org/0.30/#block-quote-marker.
final markerStart = match.match.indexOf('>');
int markerEnd;
if (currentLine.content.length > 1) {
var hasSpace = false;
// Check if there is a following space if the marker is not at the end
// of this line.
if (markerStart < currentLine.content.length - 1) {
final nextChar = currentLine.content.codeUnitAt(markerStart + 1);
hasSpace = nextChar == $tab || nextChar == $space;
}
markerEnd = markerStart + (hasSpace ? 2 : 1);
} else {
markerEnd = markerStart + 1;
}
childLines.add(Line(currentLine.content.substring(markerEnd)));
parser.advance();
_lazyContinuation = false;
continue;
}
final lastLine = childLines.last;
// A paragraph continuation is OK. This is content that cannot be parsed
// as any other syntax except Paragraph, and it doesn't match the bar in
// a Setext header.
// Because indented code blocks cannot interrupt paragraphs, a line
// matched CodeBlockSyntax is also paragraph continuation text.
final otherMatched =
parser.blockSyntaxes.firstWhere((s) => s.canParse(parser));
if ((otherMatched is ParagraphSyntax &&
!lastLine.isBlankLine &&
!codeFencePattern.hasMatch(lastLine.content)) ||
(otherMatched is CodeBlockSyntax &&
!indentPattern.hasMatch(lastLine.content))) {
childLines.add(parser.current);
_lazyContinuation = true;
parser.advance();
} else {
break;
}
}
return childLines;
}
@override
Node parse(BlockParser parser) {
final childLines = parseChildLines(parser);
// Recursively parse the contents of the blockquote.
final children = BlockParser(childLines, parser.document).parseLines(
// The setext heading underline cannot be a lazy continuation line in a
// block quote.
// https://spec.commonmark.org/0.30/#example-93
disabledSetextHeading: _lazyContinuation,
parentSyntax: this,
);
return Element('blockquote', children);
}
}