From 680e5bc0be5ff67fd5cd69554efd23cf56bbf200 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 10 May 2026 22:34:48 +0000 Subject: [PATCH] perf(RichTextViewer): share isMarkdown decision via CachedRichTextParser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The markdown check is purely a function of `content`, but it was a top-level function in RichTextViewer.kt — every distinct instance of the same content (e.g. a quoted note rendered inside its quoter, or the same viral note shown multiple places in a feed) re-scanned the string. Move the decision into `CachedRichTextParser` with its own small LRU keyed on content.hashCode(). Composables still wrap the call in `remember(content)` so recompositions skip even the LRU lookup. --- .../amethyst/service/CachedRichTextParser.kt | 20 +++++++++++++++++++ .../amethyst/ui/components/RichTextViewer.kt | 11 +--------- 2 files changed, 21 insertions(+), 10 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 ef442e3c5..6ca953b4e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt @@ -28,6 +28,7 @@ import com.vitorpamplona.amethyst.commons.richtext.UrlParser object CachedRichTextParser { private val richTextCache = LruCache(50) + private val isMarkdownCache = LruCache(200) private fun hashCodeCache( content: String, @@ -69,6 +70,25 @@ 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 ed5559c0f..f54f53494 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 @@ -122,15 +122,6 @@ import com.vitorpamplona.quartz.nipB7Blossom.BlossomUri import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -fun isMarkdown(content: String): Boolean = - content.startsWith("> ") || - content.startsWith("# ") || - content.contains("##") || - content.contains("__") || - content.contains("**") || - content.contains("```") || - content.contains("](") - @Composable fun RichTextViewer( content: String, @@ -145,7 +136,7 @@ fun RichTextViewer( nav: INav, ) { Column(modifier = modifier) { - if (remember(content) { isMarkdown(content) }) { + if (remember(content) { CachedRichTextParser.isMarkdown(content) }) { RenderContentAsMarkdown(content, tags, canPreview, quotesLeft, backgroundColor, callbackUri, accountViewModel, nav) } else { RenderRegular(content, tags, canPreview, quotesLeft, backgroundColor, callbackUri, authorPubKey, accountViewModel, nav)