diff --git a/lib/src/org/grammar.dart b/lib/src/org/grammar.dart index 3a3dbb5..ad40bc9 100644 --- a/lib/src/org/grammar.dart +++ b/lib/src/org/grammar.dart @@ -108,12 +108,15 @@ class OrgContentGrammarDefinition extends GrammarDefinition { Parser paragraph() => ref0(indent).flatten('Paragraph indent expected') & - ref1(textRun, ref0(nonParagraphElements)).plusLazy(ref0(paragraphEnd)); + ref1(textRun, ref0(nonParagraphElements) | newline().repeat(2)) + .plusLazy(ref0(paragraphEnd)) & + ref0(blankLines).optional(); Parser nonParagraphElements() => element()..replace(ref0(paragraph), noOpFail()); - Parser paragraphEnd() => endOfInput() | ref0(nonParagraphElements); + Parser paragraphEnd() => + endOfInput() | newline().repeat(2) | ref0(nonParagraphElements); Parser textRun([Parser? limit]) => ref0(object) | ref1(plainText, limit); diff --git a/lib/src/org/model.dart b/lib/src/org/model.dart index 7719607..6546387 100644 --- a/lib/src/org/model.dart +++ b/lib/src/org/model.dart @@ -1426,10 +1426,11 @@ class OrgListOrderedItem extends OrgListItem { } class OrgParagraph extends OrgParentNode { - OrgParagraph(this.indent, this.body, [super.id]); + OrgParagraph(this.indent, this.body, this.trailing, [super.id]); final String indent; final OrgContent body; + final String trailing; @override List get children => [body]; @@ -1440,7 +1441,9 @@ class OrgParagraph extends OrgParentNode { @override bool contains(Pattern pattern) => - indent.contains(pattern) || body.contains(pattern); + indent.contains(pattern) || + body.contains(pattern) || + trailing.contains(pattern); @override String toString() => 'OrgParagraph'; @@ -1449,17 +1452,20 @@ class OrgParagraph extends OrgParentNode { void _toMarkupImpl(OrgSerializer buf) { buf ..write(indent) - ..visit(body); + ..visit(body) + ..write(trailing); } OrgParagraph copyWith({ String? indent, OrgContent? body, + String? trailing, String? id, }) => OrgParagraph( indent ?? this.indent, body ?? this.body, + trailing ?? this.trailing, id ?? this.id, ); } diff --git a/lib/src/org/parser.dart b/lib/src/org/parser.dart index 3982d71..42f08f8 100644 --- a/lib/src/org/parser.dart +++ b/lib/src/org/parser.dart @@ -124,8 +124,9 @@ class OrgContentParserDefinition extends OrgContentGrammarDefinition { Parser paragraph() => super.paragraph().map((items) { final indent = items[0] as String; final bodyElements = items[1] as List; + final trailing = items[2] as String; final body = OrgContent(bodyElements.cast()); - return OrgParagraph(indent, body); + return OrgParagraph(indent, body, trailing); }); @override diff --git a/test/org/grammar/content_test.dart b/test/org/grammar/content_test.dart index 622bbd9..822e636 100644 --- a/test/org/grammar/content_test.dart +++ b/test/org/grammar/content_test.dart @@ -20,8 +20,8 @@ bazinga'''); [ 'foo bar ', ['*', 'biz', '*'], - '\n\n' - ] + ], + '\n\n', ], [ ' ', @@ -34,7 +34,8 @@ bazinga'''); ], [ '', - ['bazinga'] + ['bazinga'], + '' ] ]); }); @@ -44,7 +45,8 @@ bazinga'''); expect(result.value, [ [ '', - ['a ', 'http://example.com', ' b'] + ['a ', 'http://example.com', ' b'], + '', ] ]); }); @@ -53,7 +55,8 @@ bazinga'''); expect(result.value, [ [ '', - ['a ', 'https://example.com', ' b'] + ['a ', 'https://example.com', ' b'], + '', ] ]); }); @@ -71,7 +74,8 @@ bazinga'''); ']' ], ' b' - ] + ], + '' ] ]); }); @@ -89,7 +93,8 @@ bazinga'''); ']' ], ' b' - ] + ], + '' ] ]); }); @@ -102,7 +107,8 @@ foo'''); ['', '#+blah', '\n'], [ '', - ['foo'] + ['foo'], + '' ] ]); }); @@ -113,7 +119,8 @@ foo'''); [' ', '#+blah', '\n'], [ '', - ['foo'] + ['foo'], + '' ] ]); }); @@ -125,12 +132,14 @@ foo'''); expect(result.value, [ [ '', - ['a\n'] + ['a\n'], + '' ], ['', '#+blah', '\n'], [ '', - ['foo'] + ['foo'], + '' ] ]); }); @@ -157,7 +166,8 @@ foo'''); ], [ ' ', - ['bar'] + ['bar'], + '' ] ]); }); @@ -339,7 +349,8 @@ Text 'foo ', [r'$', 'bar', r'$'], ' baz' - ] + ], + '' ] ]); }); @@ -353,7 +364,8 @@ Text [r'$', 'i', r'$'], ' to ', [r'$', 'j', r'$'] - ] + ], + '' ] ]); }); @@ -366,7 +378,8 @@ Text 'foo ', [r'$$', ' a^2 + b^2 + c^2 ', r'$$'], ' baz' - ] + ], + '' ] ]); }); @@ -379,7 +392,8 @@ Text 'foo ', [r'\(', '1/0', r'\)'], ' baz' - ] + ], + '' ] ]); }); @@ -392,7 +406,8 @@ Text 'foo ', [r'\[', r'\infty', r'\]'], ' baz' - ] + ], + '' ] ]); }); @@ -407,7 +422,8 @@ Text 'I think ', [r'\', 'there4', ''], ' I am' - ] + ], + '' ] ]); }); @@ -421,7 +437,8 @@ I am'''); 'I think ', [r'\', 'there4', ''], '\nI am' - ] + ], + '' ] ]); }); @@ -433,7 +450,8 @@ I am'''); [ 'I think ', [r'\', 'there4', ''], - ] + ], + '' ] ]); }); @@ -445,7 +463,8 @@ I am'''); [ 'I think ', [r'\', 'there4', '{}'], - ] + ], + '' ] ]); }); @@ -458,7 +477,8 @@ I am'''); 'I think ', [r'\', 'there4', '{}'], 'I am' - ] + ], + '' ] ]); }); @@ -473,7 +493,8 @@ I am'''); 'I think there', ['^', '4'], ' I am' - ] + ], + '' ] ]); }); @@ -486,7 +507,8 @@ I am'''); 'I drink H', ['_', '2O'], ', OK?' - ] + ], + '' ] ]); // This fails because the "reverseId" formulation fails when it sees the @@ -505,7 +527,8 @@ I am'''); 'I think there', ['^', '4'], '\nI am' - ] + ], + '' ] ]); }); @@ -517,7 +540,8 @@ I am'''); [ 'I think there', ['^', '4'], - ] + ], + '' ] ]); }); @@ -529,7 +553,8 @@ I am'''); [ 'I think there', ['^', '{4}'], - ] + ], + '' ] ]); }); @@ -542,7 +567,8 @@ I am'''); 'I think there', ['^', '{4}'], 'I am' - ] + ], + '' ] ]); }); @@ -555,7 +581,8 @@ I am'''); 'I think there', ['^', '(4 + 4)'], 'I am' - ] + ], + '' ] ]); }); @@ -568,7 +595,8 @@ I am'''); 'I think there', ['^', 'four'], ' I am' - ] + ], + '' ] ]); }); @@ -581,7 +609,8 @@ I am'''); 'I think there', ['^', '*'], 'I am' - ] + ], + '' ] ]); }); @@ -595,7 +624,8 @@ I am'''); ['^', '4'], ['_', 'I'], ' am' - ] + ], + '' ] ]); }); @@ -609,7 +639,8 @@ I am'''); ['_', '4'], ['^', 'I'], ' am' - ] + ], + '' ] ]); }); @@ -627,7 +658,8 @@ bar [ [ '', - ['foo\n'] + ['foo\n'], + '' ], [ ' # Local Variables:\n', @@ -638,7 +670,8 @@ bar ], [ '', - ['\nbar\n'] + ['\nbar\n'], + '' ] ], ); diff --git a/test/org/grammar/paragraph_test.dart b/test/org/grammar/paragraph_test.dart index d486bab..3dd7275 100644 --- a/test/org/grammar/paragraph_test.dart +++ b/test/org/grammar/paragraph_test.dart @@ -11,9 +11,16 @@ void main() { biz baz'''); expect(result.value, [ '', - ['foo bar\nbiz baz'] + ['foo bar\nbiz baz'], + '' ]); }); + test('too many line breaks', () { + final result = parser.parse('''foo bar + +biz baz'''); + expect(result, isA()); + }); test('with inline objects', () { final result = parser.parse('''go to [[http://example.com][example]] for *fun*, @@ -31,7 +38,8 @@ maybe'''); ' for ', ['*', 'fun', '*'], ',\nmaybe' - ] + ], + '' ]); }); }); diff --git a/test/org/parser/parser_test.dart b/test/org/parser/parser_test.dart index e3c182b..d1cf6c2 100644 --- a/test/org/parser/parser_test.dart +++ b/test/org/parser/parser_test.dart @@ -24,7 +24,7 @@ void main() { final document = parsed.value as OrgDocument; final paragraph = document.content!.children[0] as OrgParagraph; final text = paragraph.body.children[0] as OrgPlainText; - expect(text.content, 'An introduction.\n\n'); + expect(text.content, 'An introduction.'); final topSection = document.sections[0]; final topContent0 = topSection.headline.title!.children[0] as OrgPlainText;