From a97529d7afdbb8eb04c98700a31d8cf79fc6e630 Mon Sep 17 00:00:00 2001 From: davotoula Date: Tue, 23 Jun 2026 18:13:19 +0200 Subject: [PATCH] fix: detect markdown when a heading/list follows a blank line isMarkdown() treated the second newline of a blank line as a non-space character, flipping the line-start tracker off. ATX headings, blockquotes, and list markers that follow the standard blank-line spacing went undetected, so NIP-23 long-form articles made of prose plus section headings rendered as raw text. Exclude newline/carriage-return from the line-start guard so a blank line stays at line start. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../amethyst/service/CachedRichTextParser.kt | 9 ++++- .../CachedRichTextParserMarkdownTest.kt | 40 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt index 7a401f57b8..ea4dfc2402 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt @@ -207,7 +207,14 @@ object CachedRichTextParser { // checks that need to know "how much non-space text has // appeared on the current line". if (isNewLine) { - if (c != ' ' && c != '\t') { + // A blank line is still "at line start": skipping '\n'/'\r' + // here keeps isNewLine true so a heading/blockquote/list + // marker on the next line (the standard `\n\n#` spacing) is + // still recognized as line-leading. Without the newline + // exclusion, the second '\n' of a blank line flipped + // isNewLine to false and ATX headings after a blank line + // went undetected. + if (c != ' ' && c != '\t' && c != '\n' && c != '\r') { isNewLine = false nonSpaceCharCountOnLine = 1 lastNonSpaceChar = c diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/CachedRichTextParserMarkdownTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/CachedRichTextParserMarkdownTest.kt index 7f5c78ba4d..366e7e322e 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/CachedRichTextParserMarkdownTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/CachedRichTextParserMarkdownTest.kt @@ -68,6 +68,46 @@ class CachedRichTextParserMarkdownTest { @Test fun atxAfterNewlineIsMarkdown() = assertTrue(CachedRichTextParser.isMarkdown("intro\n# Heading")) + // ---- Line-leading markers after a BLANK line ------------------------ + // Regression: the standard `\n\n#` spacing (a blank line before a + // heading) was undetected because the blank line's second '\n' flipped + // the line-start tracker off. NIP-23 long-form articles that are pure + // prose plus `## Section` headings rendered as raw text as a result. + + @Test fun atxAfterBlankLineIsMarkdown() = assertTrue(CachedRichTextParser.isMarkdown("intro\n\n# Heading")) + + @Test fun atxH3AfterBlankLineIsMarkdown() = assertTrue(CachedRichTextParser.isMarkdown("intro\n\n### Heading")) + + @Test fun atxAfterMultipleBlankLinesIsMarkdown() = assertTrue(CachedRichTextParser.isMarkdown("intro\n\n\n\n## Heading")) + + @Test fun atxAfterCrlfBlankLineIsMarkdown() = assertTrue(CachedRichTextParser.isMarkdown("intro\r\n\r\n## Heading")) + + @Test fun blockquoteAfterBlankLineIsMarkdown() = assertTrue(CachedRichTextParser.isMarkdown("intro\n\n> quoted")) + + @Test fun bulletAfterBlankLineIsMarkdown() = assertTrue(CachedRichTextParser.isMarkdown("intro\n\n- item")) + + @Test fun orderedListAfterBlankLineIsMarkdown() = assertTrue(CachedRichTextParser.isMarkdown("intro\n\n1. first")) + + @Test + fun proseArticleWithHeadingAfterBlankLineIsMarkdown() = + assertTrue( + CachedRichTextParser.isMarkdown( + "The cryptographic part of commerce has been solved.\n\n" + + "What follows is a history.\n\n" + + "### The merchant posts of the Hansa\n\n" + + "Long before any king claimed a monopoly on letters.", + ), + ) + + // Blank lines alone must NOT promote ordinary prose to markdown. + @Test + fun plainProseWithBlankLinesIsNotMarkdown() = + assertFalse( + CachedRichTextParser.isMarkdown( + "Just a normal sentence.\n\nAnother paragraph with no markdown at all.", + ), + ) + // ---- Blockquote ----------------------------------------------------- @Test fun blockquoteIsMarkdown() = assertTrue(CachedRichTextParser.isMarkdown("> a quote"))