From 071d42d406ed94bee3a79307146dbf2e5d7205b3 Mon Sep 17 00:00:00 2001 From: Jacob Logsdon Date: Thu, 28 Mar 2024 17:17:20 -0700 Subject: [PATCH] Add an autolink extension for Markdown Exposes the autolink extension offered by the commonmark java library, to markdown rendering in Compose. Since links are already supported for processing and rendering, we only need to hookup the parserExtension to get this to work. --- gradle/libs.versions.toml | 1 + .../extensions/MarkdownProcessorExtension.kt | 17 +++++++++------- .../markdown/processing/MarkdownProcessor.kt | 2 +- markdown/extension/autolink/api/autolink.api | 8 ++++++++ markdown/extension/autolink/build.gradle.kts | 17 ++++++++++++++++ .../autolink/AutolinkProcessorExtension.kt | 20 +++++++++++++++++++ .../AutolinkProcessorExtensionTest.kt | 20 +++++++++++++++++++ samples/standalone/build.gradle.kts | 2 +- .../view/markdown/MarkdownPreview.kt | 8 ++++++-- settings.gradle.kts | 1 + 10 files changed, 85 insertions(+), 11 deletions(-) create mode 100644 markdown/extension/autolink/api/autolink.api create mode 100644 markdown/extension/autolink/build.gradle.kts create mode 100644 markdown/extension/autolink/src/main/kotlin/org/jetbrains/jewel/markdown/extension/autolink/AutolinkProcessorExtension.kt create mode 100644 markdown/extension/autolink/src/test/kotlin/org/jetbrains/jewel/markdown/extension/autolink/AutolinkProcessorExtensionTest.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 56b05fe54..5b013282b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,6 +16,7 @@ poko = "0.13.1" [libraries] commonmark-core = { module = "org.commonmark:commonmark", version.ref = "commonmark" } +commonmark-ext-autolink = { module = "org.commonmark:commonmark-ext-autolink", version.ref = "commonmark" } filePicker = { module = "com.darkrockstudios:mpfilepicker", version = "3.1.0" } diff --git a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension.kt b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension.kt index f484bf883..039f5c895 100644 --- a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension.kt +++ b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension.kt @@ -9,25 +9,28 @@ import org.jetbrains.jewel.markdown.MarkdownBlock /** An extension for the Jewel Markdown processing engine. */ @ExperimentalJewelApi public interface MarkdownProcessorExtension { - /** * A CommonMark [ParserExtension] that will be used to parse the extended - * syntax represented by this extension instance. + * syntax represented by this extension instance. Null in the case where + * parsing is already handled by an existing [org.commonmark.parser.Parser]. */ - public val parserExtension: ParserExtension + public val parserExtension: ParserExtension? /** * A CommonMark [TextContentRendererExtension] that will be used to render * the text content of the CommonMark [CustomBlock] produced by the - * [parserExtension]. + * [parserExtension]. Null in the case where rendering is already + * handled by an existing [org.commonmark.renderer.Renderer]. */ - public val textRendererExtension: TextContentRendererExtension + public val textRendererExtension: TextContentRendererExtension? /** * An extension for * [`MarkdownParser`][org.jetbrains.jewel.markdown.parsing.MarkdownParser] * that will transform a supported [CustomBlock] into the corresponding - * [MarkdownBlock.CustomBlock]. + * [MarkdownBlock.CustomBlock]. Null in the case where processing + * is already be handled by [org.jetbrains.jewel.markdown.processing.MarkdownProcessor] + * or another [org.jetbrains.jewel.markdown.extensions.MarkdownProcessorExtension]. */ - public val processorExtension: MarkdownBlockProcessorExtension + public val processorExtension: MarkdownBlockProcessorExtension? } diff --git a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/MarkdownProcessor.kt b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/MarkdownProcessor.kt index 626920173..a30750bc4 100644 --- a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/MarkdownProcessor.kt +++ b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/MarkdownProcessor.kt @@ -189,7 +189,7 @@ public class MarkdownProcessor( is ThematicBreak -> MarkdownBlock.ThematicBreak is HtmlBlock -> toMarkdownHtmlBlockOrNull() is CustomBlock -> { - extensions.find { it.processorExtension.canProcess(this) } + extensions.find { it.processorExtension?.canProcess(this) == true } ?.processorExtension?.processMarkdownBlock(this, this@MarkdownProcessor) } else -> null diff --git a/markdown/extension/autolink/api/autolink.api b/markdown/extension/autolink/api/autolink.api new file mode 100644 index 000000000..70461da1d --- /dev/null +++ b/markdown/extension/autolink/api/autolink.api @@ -0,0 +1,8 @@ +public final class org/jetbrains/jewel/markdown/extension/autolink/AutolinkProcessorExtension : org/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension { + public static final field $stable I + public static final field INSTANCE Lorg/jetbrains/jewel/markdown/extension/autolink/AutolinkProcessorExtension; + public fun getParserExtension ()Lorg/commonmark/parser/Parser$ParserExtension; + public fun getProcessorExtension ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownBlockProcessorExtension; + public fun getTextRendererExtension ()Lorg/commonmark/renderer/text/TextContentRenderer$TextContentRendererExtension; +} + diff --git a/markdown/extension/autolink/build.gradle.kts b/markdown/extension/autolink/build.gradle.kts new file mode 100644 index 000000000..052dbcea8 --- /dev/null +++ b/markdown/extension/autolink/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + jewel + `jewel-publish` + `jewel-check-public-api` + alias(libs.plugins.composeDesktop) +} + +dependencies { + implementation(projects.markdown.core) + implementation(libs.commonmark.ext.autolink) + testImplementation(compose.desktop.uiTestJUnit4) +} + +publishing.publications.named("main") { + val ijpTarget = project.property("ijp.target") as String + artifactId = "jewel-markdown-extension-${project.name}-$ijpTarget" +} diff --git a/markdown/extension/autolink/src/main/kotlin/org/jetbrains/jewel/markdown/extension/autolink/AutolinkProcessorExtension.kt b/markdown/extension/autolink/src/main/kotlin/org/jetbrains/jewel/markdown/extension/autolink/AutolinkProcessorExtension.kt new file mode 100644 index 000000000..ad8161dea --- /dev/null +++ b/markdown/extension/autolink/src/main/kotlin/org/jetbrains/jewel/markdown/extension/autolink/AutolinkProcessorExtension.kt @@ -0,0 +1,20 @@ +package org.jetbrains.jewel.markdown.extension.autolink + +import org.commonmark.ext.autolink.AutolinkExtension +import org.commonmark.parser.Parser.ParserExtension +import org.commonmark.renderer.text.TextContentRenderer +import org.jetbrains.jewel.markdown.extensions.MarkdownBlockProcessorExtension +import org.jetbrains.jewel.markdown.extensions.MarkdownProcessorExtension + +public object AutolinkProcessorExtension : MarkdownProcessorExtension { + override val parserExtension: ParserExtension + get() = AutolinkExtension.create() as ParserExtension + + /** + * Rendering and processing is already handled by [org.jetbrains.jewel.markdown.rendering.DefaultInlineMarkdownRenderer] + */ + override val textRendererExtension: TextContentRenderer.TextContentRendererExtension? + get() = null + override val processorExtension: MarkdownBlockProcessorExtension? + get() = null +} diff --git a/markdown/extension/autolink/src/test/kotlin/org/jetbrains/jewel/markdown/extension/autolink/AutolinkProcessorExtensionTest.kt b/markdown/extension/autolink/src/test/kotlin/org/jetbrains/jewel/markdown/extension/autolink/AutolinkProcessorExtensionTest.kt new file mode 100644 index 000000000..8e14e7aa5 --- /dev/null +++ b/markdown/extension/autolink/src/test/kotlin/org/jetbrains/jewel/markdown/extension/autolink/AutolinkProcessorExtensionTest.kt @@ -0,0 +1,20 @@ +package org.jetbrains.jewel.markdown.extension.autolink + +import org.jetbrains.jewel.markdown.InlineMarkdown +import org.jetbrains.jewel.markdown.MarkdownBlock +import org.jetbrains.jewel.markdown.processing.MarkdownProcessor +import org.junit.Assert.assertTrue +import org.junit.Test + +class AutolinkProcessorExtensionTest { + // testing a simple case to assure wiring up our AutolinkProcessorExtension works correctly + @Test + fun `https text gets processed into a link`() { + val processor = MarkdownProcessor(listOf(AutolinkProcessorExtension)) + val rawMarkDown = "https://commonmark.org" + val processed = processor.processMarkdownDocument(rawMarkDown) + val paragraph = processed[0] as MarkdownBlock.Paragraph + + assertTrue(paragraph.inlineContent[0] is InlineMarkdown.Link) + } +} diff --git a/samples/standalone/build.gradle.kts b/samples/standalone/build.gradle.kts index 6cff7ddff..2d8714780 100644 --- a/samples/standalone/build.gradle.kts +++ b/samples/standalone/build.gradle.kts @@ -14,7 +14,7 @@ dependencies { implementation(projects.intUi.intUiDecoratedWindow) implementation(projects.markdown.intUiStandaloneStyling) implementation(projects.markdown.extension.gfmAlerts) - + implementation(projects.markdown.extension.autolink) implementation(compose.desktop.currentOs) { exclude(group = "org.jetbrains.compose.material") } diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownPreview.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownPreview.kt index 8baa7ed87..f3f6aba6e 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownPreview.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownPreview.kt @@ -31,6 +31,7 @@ import org.jetbrains.jewel.intui.markdown.styling.extension.github.alerts.dark import org.jetbrains.jewel.intui.markdown.styling.extension.github.alerts.light import org.jetbrains.jewel.intui.markdown.styling.light import org.jetbrains.jewel.markdown.MarkdownBlock +import org.jetbrains.jewel.markdown.extension.autolink.AutolinkProcessorExtension import org.jetbrains.jewel.markdown.extensions.github.alerts.AlertStyling import org.jetbrains.jewel.markdown.extensions.github.alerts.GitHubAlertProcessorExtension import org.jetbrains.jewel.markdown.extensions.github.alerts.GitHubAlertRendererExtension @@ -43,14 +44,17 @@ import java.awt.Desktop import java.net.URI @Composable -internal fun MarkdownPreview(rawMarkdown: String, modifier: Modifier = Modifier) { +internal fun MarkdownPreview( + rawMarkdown: String, + modifier: Modifier = Modifier, +) { val isDark = JewelTheme.isDark val markdownStyling = remember(isDark) { if (isDark) MarkdownStyling.dark() else MarkdownStyling.light() } var markdownBlocks by remember { mutableStateOf(emptyList()) } - val extensions = listOf(GitHubAlertProcessorExtension) + val extensions = listOf(GitHubAlertProcessorExtension, AutolinkProcessorExtension) val processor = remember { MarkdownProcessor(extensions) } LaunchedEffect(rawMarkdown) { diff --git a/settings.gradle.kts b/settings.gradle.kts index 7d7e33b08..69a2b6a3e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -36,6 +36,7 @@ include( ":int-ui:int-ui-decorated-window", ":int-ui:int-ui-standalone", ":markdown:core", + ":markdown:extension:autolink", ":markdown:extension:gfm-alerts", ":markdown:int-ui-standalone-styling", ":markdown:ide-laf-bridge-styling",