From 2ba145f15e2eb85e3b6c4b12eb3b9a042c97d947 Mon Sep 17 00:00:00 2001 From: RobinDev Date: Mon, 14 Sep 2026 23:16:30 +0200 Subject: [PATCH] fix: render hard line breaks --- src/Tokens/TextToken.php | 18 ++++++++++- tests/Tokens/TextTokenTest.php | 56 ++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/Tokens/TextToken.php b/src/Tokens/TextToken.php index 98cfef4..56e953b 100644 --- a/src/Tokens/TextToken.php +++ b/src/Tokens/TextToken.php @@ -13,7 +13,23 @@ public function __construct( public function parse(Parser $parser): string { - return $this->content; + // A line ending preceded by two or more spaces, or by a backslash, + // is a hard line break — unless it ends the block, where it is + // dropped rather than rendered. + if ( + ! str_contains($this->content, "\n") + && ! str_contains($this->content, "\r") + ) { + return $this->content; + } + + return ( + preg_replace( + '/(?: {2,}|\\\\)(\r\n|\n|\r)(?!\z)/', + '
$1', + $this->content, + ) ?? $this->content + ); } public function append(string $content): void diff --git a/tests/Tokens/TextTokenTest.php b/tests/Tokens/TextTokenTest.php index 708fcf7..826b769 100644 --- a/tests/Tokens/TextTokenTest.php +++ b/tests/Tokens/TextTokenTest.php @@ -4,6 +4,8 @@ use PHPUnit\Framework\Attributes\Test; use Tempest\Markdown\Parser; +use Tempest\Markdown\Rules\BoldRule; +use Tempest\Markdown\Rules\TextRule; use Tempest\Markdown\Tests\ParserTestCase; use Tempest\Markdown\Tokens\TextToken; @@ -16,4 +18,58 @@ public function test_parse(): void $this->assertEquals('Hello, world!', $token->parse(new Parser())); } + + #[Test] + public function parse_renders_a_hard_break_after_two_spaces(): void + { + $parser = new Parser(highlighter: null, rules: [new TextRule()]); + + $this->assertSame( + "a
\nb", + new TextToken("a \nb")->parse($parser), + ); + $this->assertSame( + "a
\nb", + new TextToken("a \nb")->parse($parser), + ); + } + + #[Test] + public function parse_renders_a_hard_break_after_a_backslash(): void + { + $parser = new Parser(highlighter: null, rules: [new TextRule()]); + + $this->assertSame( + "a
\nb", + new TextToken("a\\\nb")->parse($parser), + ); + } + + #[Test] + public function parse_keeps_a_soft_break_as_is(): void + { + $parser = new Parser(highlighter: null, rules: [new TextRule()]); + + $this->assertSame("a\nb", new TextToken("a\nb")->parse($parser)); + $this->assertSame("a \nb", new TextToken("a \nb")->parse($parser)); + } + + #[Test] + public function parse_drops_a_hard_break_at_the_end_of_the_content(): void + { + $parser = new Parser(highlighter: null, rules: [new TextRule()]); + + $this->assertSame("a \n", new TextToken("a \n")->parse($parser)); + } + + #[Test] + public function lex_renders_a_hard_break_around_other_inline_tokens(): void + { + $html = (string) new Parser(highlighter: null, rules: [ + new BoldRule(), + new TextRule(), + ])->parse("a **b** \nc"); + + $this->assertSame("a b
\nc", $html); + } }