From 7e2e3304e1ffb546aab21b2e68ee931c3108d2e1 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 11 May 2026 00:25:36 +0000 Subject: [PATCH] refactor(RichTextViewer): move isMarkdown onto RichTextViewerState MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cleaner conceptual model: the parsed state object is the place where all derived facts about the content live, so `isMarkdown` becomes a field on `RichTextViewerState` (populated by `RichTextParser.parseText` using the same cheap heuristic). `RichTextViewer` now calls `CachedRichTextParser.parseText` once at the top and dispatches on `state.isMarkdown` instead of running a separate scan. `CachedRichTextParser.isMarkdown(content)` and its dedicated `isMarkdownCache` go away — the single `richTextCache` carries the decision alongside the parsed segments. Inner callers (`DisplaySecretEmoji`, `MultiSetCompose` reaction preview, `DisplayUncitedHashtags`) are unaffected: they continue to receive a fully-parsed state with all segments populated. --- .../amethyst/service/CachedRichTextParser.kt | 20 ------------------- .../amethyst/ui/components/RichTextViewer.kt | 6 +++++- .../commons/richtext/RichTextParser.kt | 13 ++++++++++++ .../richtext/RichTextParserSegments.kt | 1 + 4 files changed, 19 insertions(+), 21 deletions(-) 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 6ca953b4e4..ef442e3c5f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt @@ -28,7 +28,6 @@ import com.vitorpamplona.amethyst.commons.richtext.UrlParser object CachedRichTextParser { private val richTextCache = LruCache(50) - private val isMarkdownCache = LruCache(200) private fun hashCodeCache( content: String, @@ -70,25 +69,6 @@ object CachedRichTextParser { newUrls } } - - // Shared across every RichTextViewer instance so that the same content quoted in multiple - // notes only pays for the scan once. The decision is purely a function of `content`. - fun isMarkdown(content: String): Boolean { - val key = content.hashCode() - isMarkdownCache[key]?.let { return it } - val result = computeIsMarkdown(content) - isMarkdownCache.put(key, result) - return result - } - - private fun computeIsMarkdown(content: String): Boolean = - content.startsWith("> ") || - content.startsWith("# ") || - content.contains("##") || - content.contains("__") || - content.contains("**") || - content.contains("```") || - content.contains("](") } object CachedUrlParser { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt index f54f53494c..62471f9509 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt @@ -136,7 +136,11 @@ fun RichTextViewer( nav: INav, ) { Column(modifier = modifier) { - if (remember(content) { CachedRichTextParser.isMarkdown(content) }) { + val state = + remember(content, tags) { + CachedRichTextParser.parseText(content, tags, callbackUri, authorPubKey) + } + if (state.isMarkdown) { RenderContentAsMarkdown(content, tags, canPreview, quotesLeft, backgroundColor, callbackUri, accountViewModel, nav) } else { RenderRegular(content, tags, canPreview, quotesLeft, backgroundColor, callbackUri, authorPubKey, accountViewModel, nav) diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/richtext/RichTextParser.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/richtext/RichTextParser.kt index e3cb263137..f3ed691345 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/richtext/RichTextParser.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/richtext/RichTextParser.kt @@ -224,6 +224,7 @@ class RichTextParser { customEmoji = emojiMap.toImmutableMap(), paragraphs = segments, tags = tags, + isMarkdown = isMarkdown(content), ) } @@ -411,6 +412,18 @@ class RichTextParser { } companion object { + // Cheap heuristic: stored on the parsed state so callers (e.g. RichTextViewer's + // markdown vs regular dispatch) can read the decision off the cached result instead + // of running a separate scan + maintaining a separate cache. + fun isMarkdown(content: String): Boolean = + content.startsWith("> ") || + content.startsWith("# ") || + content.contains("##") || + content.contains("__") || + content.contains("**") || + content.contains("```") || + content.contains("](") + val longDatePattern: Regex = Regex("^\\d{4}-\\d{2}-\\d{2}$") val shortDatePattern: Regex = Regex("^\\d{2}-\\d{2}-\\d{2}$") diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/richtext/RichTextParserSegments.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/richtext/RichTextParserSegments.kt index c0aa426c06..39a79378d1 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/richtext/RichTextParserSegments.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/richtext/RichTextParserSegments.kt @@ -33,6 +33,7 @@ class RichTextViewerState( val customEmoji: ImmutableMap, val paragraphs: ImmutableList, val tags: ImmutableListOfLists, + val isMarkdown: Boolean = false, ) @Immutable