From c38198c536d61d76a360d383543efc34094be800 Mon Sep 17 00:00:00 2001 From: Sebastiano Poggi Date: Mon, 10 Feb 2025 17:32:32 +0000 Subject: [PATCH] [JEWEL-748] Define delimited inlines extension API, implement strikethrough This includes a bunch of changes to the Markdown inline APIs to better align with CommonMark and our actual needs; a clean-up of the previous half-hearted attempt at supporting custom inline nodes (it did not and could never have worked in the way it was implemented), and some misc tweaks and cleanup. --- .idea/modules.xml | 1 + .../foundation/layout/BasicTableLayout.kt | 7 +- platform/jewel/gradle/libs.versions.toml | 3 +- platform/jewel/markdown/README.md | 4 +- platform/jewel/markdown/core/api/core.api | 41 +- .../jewel/markdown/InlineMarkdown.kt | 27 +- .../jewel/markdown/WithInlineMarkdown.kt | 2 + .../jewel/markdown/WithTextContent.kt | 2 + .../MarkdownBlockProcessorExtension.kt | 6 + ...rkdownDelimitedInlineProcessorExtension.kt | 23 ++ ...arkdownDelimitedInlineRendererExtension.kt | 41 ++ .../MarkdownInlineProcessorExtension.kt | 21 - .../MarkdownInlineRendererExtension.kt | 17 - .../extensions/MarkdownProcessorExtension.kt | 13 +- .../extensions/MarkdownRendererExtension.kt | 6 +- .../markdown/processing/MarkdownProcessor.kt | 32 +- .../markdown/processing/ProcessingUtil.kt | 50 +-- .../DefaultInlineMarkdownRenderer.kt | 42 +- .../markdown/rendering/MarkdownStyling.kt | 5 + .../ScrollSyncMarkdownBlockRenderer.kt | 2 +- .../org/jetbrains/jewel/markdown/TestUtils.kt | 48 ++- .../scrolling/ScrollingSynchronizerTest.kt | 224 +++++------ .../extension/autolink/api/autolink.api | 2 +- .../extension/gfm-alerts/api/gfm-alerts.api | 4 +- .../extension/gfm-strikethrough/README.md | 41 ++ .../api/gfm-strikethrough.api | 48 +++ .../gfm-strikethrough/build.gradle.kts | 22 + ...el.markdown.extension.gfmStrikethrough.iml | 375 ++++++++++++++++++ ...ubStrikethroughInlineProcessorExtension.kt | 33 ++ ...HubStrikethroughInlineRendererExtension.kt | 43 ++ .../strikethrough/GitHubStrikethroughNode.kt | 19 + .../GitHubStrikethroughProcessorExtension.kt | 29 ++ .../GitHubStrikethroughRendererExtension.kt | 13 + .../extension/gfm-tables/api/gfm-tables.api | 4 +- .../tables/GitHubTableProcessorExtension.kt | 12 +- .../github/tables/GitHubTableStyling.kt | 10 +- .../api/ide-laf-bridge-styling.api | 2 +- .../bridge/BridgeProvideMarkdownStyling.kt | 8 +- .../github/alerts/BridgeGitHubAlertStyling.kt | 2 + .../api/int-ui-standalone-styling.api | 2 +- .../standalone/IntUiProvideMarkdownStyling.kt | 12 +- .../github/tables/IntUiGitHubTableStyling.kt | 2 + .../jewel/samples/standalone/build.gradle.kts | 5 +- ...llij.platform.jewel.samples.standalone.iml | 17 +- .../standalone/markdown/MarkdownPreview.kt | 23 +- platform/jewel/settings.gradle.kts | 42 +- .../jewel/ui/util/ModifierExtensions.kt | 2 +- .../org/jetbrains/jewel/BasicJewelUiTest.kt | 6 +- .../org/jetbrains/jewel/PainterHintTest.kt | 3 +- 49 files changed, 1061 insertions(+), 337 deletions(-) create mode 100644 platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineProcessorExtension.kt create mode 100644 platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineRendererExtension.kt delete mode 100644 platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownInlineProcessorExtension.kt delete mode 100644 platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownInlineRendererExtension.kt create mode 100644 platform/jewel/markdown/extension/gfm-strikethrough/README.md create mode 100644 platform/jewel/markdown/extension/gfm-strikethrough/api/gfm-strikethrough.api create mode 100644 platform/jewel/markdown/extension/gfm-strikethrough/build.gradle.kts create mode 100644 platform/jewel/markdown/extension/gfm-strikethrough/intellij.platform.jewel.markdown.extension.gfmStrikethrough.iml create mode 100644 platform/jewel/markdown/extension/gfm-strikethrough/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughInlineProcessorExtension.kt create mode 100644 platform/jewel/markdown/extension/gfm-strikethrough/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughInlineRendererExtension.kt create mode 100644 platform/jewel/markdown/extension/gfm-strikethrough/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughNode.kt create mode 100644 platform/jewel/markdown/extension/gfm-strikethrough/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughProcessorExtension.kt create mode 100644 platform/jewel/markdown/extension/gfm-strikethrough/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughRendererExtension.kt diff --git a/.idea/modules.xml b/.idea/modules.xml index 4503f206f1fe9..579ace8bf6789 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -764,6 +764,7 @@ + diff --git a/platform/jewel/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/layout/BasicTableLayout.kt b/platform/jewel/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/layout/BasicTableLayout.kt index 6b59870ac5bb0..85de416733301 100644 --- a/platform/jewel/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/layout/BasicTableLayout.kt +++ b/platform/jewel/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/layout/BasicTableLayout.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import kotlin.math.max +import org.jetbrains.jewel.foundation.modifier.thenIf /** * A simple table that sizes columns to take as much room as they need. If the horizontal space available is less than @@ -34,7 +35,7 @@ import kotlin.math.max * @param rowCount The number of rows this table has. * @param columnCount The number of columns this table has. * @param cellBorderColor The color of the cell borders. Set to [Color.Unspecified] to avoid drawing the borders — in - * which case, the [cellBorderWidth] acts as padding. + * which case, the [cellBorderWidth] acts as a padding. * @param modifier Modifier to apply to the table. * @param cellBorderWidth The width of the table's borders. * @param rows The rows that make up the table. Each row is a list of composables, one per row cell. @@ -214,7 +215,3 @@ private fun Modifier.drawTableBorders( ) } } - -// TODO remove this once thenIf is moved to foundation -private inline fun Modifier.thenIf(precondition: Boolean, action: Modifier.() -> Modifier): Modifier = - if (precondition) action() else this diff --git a/platform/jewel/gradle/libs.versions.toml b/platform/jewel/gradle/libs.versions.toml index d2bec47dac7b7..8f97110ba9453 100644 --- a/platform/jewel/gradle/libs.versions.toml +++ b/platform/jewel/gradle/libs.versions.toml @@ -18,6 +18,7 @@ ktfmtGradlePlugin = "0.20.1" [libraries] commonmark-core = { module = "org.commonmark:commonmark", version.ref = "commonmark" } commonmark-ext-autolink = { module = "org.commonmark:commonmark-ext-autolink", version.ref = "commonmark" } +commonmark-ext-gfm-strikethrough = { module = "org.commonmark:commonmark-ext-gfm-strikethrough", version.ref = "commonmark" } commonmark-ext-gfm-tables = { module = "org.commonmark:commonmark-ext-gfm-tables", version.ref = "commonmark" } filePicker = { module = "com.darkrockstudios:mpfilepicker", version = "3.1.0" } @@ -52,4 +53,4 @@ kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlinx-binaryCompatValidator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "kotlinxBinaryCompat" } kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } kotlinter = { id = "org.jmailen.kotlinter", version.ref = "kotlinterGradlePlugin" } -ktfmt = { id = "com.ncorti.ktfmt.gradle", version.ref = "ktfmtGradlePlugin" } +ktfmt = { id = "com.ncorti.ktfmt.gradle", version.ref = "ktfmtGradlePlugin" } \ No newline at end of file diff --git a/platform/jewel/markdown/README.md b/platform/jewel/markdown/README.md index 954cee544b720..8a84a126e5b52 100644 --- a/platform/jewel/markdown/README.md +++ b/platform/jewel/markdown/README.md @@ -14,12 +14,12 @@ Additional supported Markdown, via extensions: * Autolink (standard CommonMark, but provided as extension) — see [`extension-autolink`](extension/autolink) * Tables ([GitHub Flavored Markdown](https://github.github.com/gfm/#tables-extension-)) — see [ `extension-gfm-tables`](extension/gfm-tables) +* Strikethrough ([GitHub Flavored Markdown](https://github.github.com/gfm/#strikethrough-extension-)) [alerts-specs]: https://github.com/orgs/community/discussions/16925 On the roadmap, but not currently supported — in no particular order: -* Strikethrough ([GitHub Flavored Markdown](https://github.github.com/gfm/#strikethrough-extension-)) * Image loading (via [Coil 3](https://coil-kt.github.io/coil/upgrading_to_coil3/)) * Task list items ([GitHub Flavored Markdown](https://github.github.com/gfm/#task-list-items-extension-)) * Keyboard shortcuts highlighting (specialized HTML handling) @@ -159,4 +159,4 @@ You can see this in action running the Standalone sample, and selecting Markdown The following image shows the Jewel Markdown renderer displaying the Jewel readme. -![Image showing the Markdown renderer page from the Jewel standalone sample](../art/docs/markdown-renderer.png) +![Image showing the Markdown renderer page from the Jewel standalone sample](../art/docs/markdown-renderer.png) \ No newline at end of file diff --git a/platform/jewel/markdown/core/api/core.api b/platform/jewel/markdown/core/api/core.api index 97ebebe628259..51b4529cabb54 100644 --- a/platform/jewel/markdown/core/api/core.api +++ b/platform/jewel/markdown/core/api/core.api @@ -10,12 +10,13 @@ public final class org/jetbrains/jewel/markdown/InlineMarkdown$Code : org/jetbra public fun toString ()Ljava/lang/String; } -public abstract interface class org/jetbrains/jewel/markdown/InlineMarkdown$CustomNode : org/jetbrains/jewel/markdown/InlineMarkdown { - public abstract fun contentOrNull ()Ljava/lang/String; +public abstract interface class org/jetbrains/jewel/markdown/InlineMarkdown$CustomDelimitedNode : org/jetbrains/jewel/markdown/InlineMarkdown, org/jetbrains/jewel/markdown/WithInlineMarkdown { + public abstract fun getClosingDelimiter ()Ljava/lang/String; + public abstract fun getOpeningDelimiter ()Ljava/lang/String; } -public final class org/jetbrains/jewel/markdown/InlineMarkdown$CustomNode$DefaultImpls { - public static fun contentOrNull (Lorg/jetbrains/jewel/markdown/InlineMarkdown$CustomNode;)Ljava/lang/String; +public final class org/jetbrains/jewel/markdown/InlineMarkdown$CustomDelimitedNode$DefaultImpls { + public static fun getClosingDelimiter (Lorg/jetbrains/jewel/markdown/InlineMarkdown$CustomDelimitedNode;)Ljava/lang/String; } public final class org/jetbrains/jewel/markdown/InlineMarkdown$Emphasis : org/jetbrains/jewel/markdown/InlineMarkdown, org/jetbrains/jewel/markdown/WithInlineMarkdown { @@ -264,14 +265,14 @@ public abstract interface class org/jetbrains/jewel/markdown/extensions/Markdown public abstract fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$CustomBlock;Lorg/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer;Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V } -public abstract interface class org/jetbrains/jewel/markdown/extensions/MarkdownInlineProcessorExtension { - public abstract fun canProcess (Lorg/commonmark/node/CustomNode;)Z - public abstract fun processInlineMarkdown (Lorg/commonmark/node/CustomNode;Lorg/jetbrains/jewel/markdown/processing/MarkdownProcessor;)Lorg/jetbrains/jewel/markdown/InlineMarkdown$CustomNode; +public abstract interface class org/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineProcessorExtension { + public abstract fun canProcess (Lorg/commonmark/node/Delimited;)Z + public abstract fun processDelimitedInline (Lorg/commonmark/node/Delimited;Lorg/jetbrains/jewel/markdown/processing/MarkdownProcessor;)Lorg/jetbrains/jewel/markdown/InlineMarkdown$CustomDelimitedNode; } -public abstract interface class org/jetbrains/jewel/markdown/extensions/MarkdownInlineRendererExtension { - public abstract fun canRender (Lorg/jetbrains/jewel/markdown/InlineMarkdown$CustomNode;)Z - public abstract fun render (Lorg/jetbrains/jewel/markdown/InlineMarkdown$CustomNode;Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer;Z)V +public abstract interface class org/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineRendererExtension { + public abstract fun canRender (Lorg/jetbrains/jewel/markdown/InlineMarkdown$CustomDelimitedNode;)Z + public abstract fun render (Lorg/jetbrains/jewel/markdown/InlineMarkdown$CustomDelimitedNode;Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;ZLkotlin/jvm/functions/Function1;)Landroidx/compose/ui/text/AnnotatedString; } public final class org/jetbrains/jewel/markdown/extensions/MarkdownKt { @@ -287,26 +288,26 @@ public final class org/jetbrains/jewel/markdown/extensions/MarkdownKt { public abstract interface class org/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension { public abstract fun getBlockProcessorExtension ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownBlockProcessorExtension; - public abstract fun getInlineProcessorExtension ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownInlineProcessorExtension; + public abstract fun getDelimitedInlineProcessorExtension ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineProcessorExtension; public abstract fun getParserExtension ()Lorg/commonmark/parser/Parser$ParserExtension; public abstract fun getTextRendererExtension ()Lorg/commonmark/renderer/text/TextContentRenderer$TextContentRendererExtension; } public final class org/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension$DefaultImpls { public static fun getBlockProcessorExtension (Lorg/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension;)Lorg/jetbrains/jewel/markdown/extensions/MarkdownBlockProcessorExtension; - public static fun getInlineProcessorExtension (Lorg/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension;)Lorg/jetbrains/jewel/markdown/extensions/MarkdownInlineProcessorExtension; + public static fun getDelimitedInlineProcessorExtension (Lorg/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension;)Lorg/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineProcessorExtension; public static fun getParserExtension (Lorg/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension;)Lorg/commonmark/parser/Parser$ParserExtension; public static fun getTextRendererExtension (Lorg/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension;)Lorg/commonmark/renderer/text/TextContentRenderer$TextContentRendererExtension; } public abstract interface class org/jetbrains/jewel/markdown/extensions/MarkdownRendererExtension { public abstract fun getBlockRenderer ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownBlockRendererExtension; - public abstract fun getInlineRenderer ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownInlineRendererExtension; + public abstract fun getDelimitedInlineRenderer ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineRendererExtension; } public final class org/jetbrains/jewel/markdown/extensions/MarkdownRendererExtension$DefaultImpls { public static fun getBlockRenderer (Lorg/jetbrains/jewel/markdown/extensions/MarkdownRendererExtension;)Lorg/jetbrains/jewel/markdown/extensions/MarkdownBlockRendererExtension; - public static fun getInlineRenderer (Lorg/jetbrains/jewel/markdown/extensions/MarkdownRendererExtension;)Lorg/jetbrains/jewel/markdown/extensions/MarkdownInlineRendererExtension; + public static fun getDelimitedInlineRenderer (Lorg/jetbrains/jewel/markdown/extensions/MarkdownRendererExtension;)Lorg/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineRendererExtension; } public final class org/jetbrains/jewel/markdown/processing/MarkdownParserFactory { @@ -321,26 +322,27 @@ public final class org/jetbrains/jewel/markdown/processing/MarkdownProcessor { public fun ()V public fun (Ljava/util/List;Lorg/jetbrains/jewel/markdown/MarkdownMode;Lorg/commonmark/parser/Parser;)V public synthetic fun (Ljava/util/List;Lorg/jetbrains/jewel/markdown/MarkdownMode;Lorg/commonmark/parser/Parser;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getBlockExtensions ()Ljava/util/List; + public final fun getDelimitedInlineExtensions ()Ljava/util/List; public final fun getExtensions ()Ljava/util/List; + public final fun plus (Lorg/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension;)Lorg/jetbrains/jewel/markdown/processing/MarkdownProcessor; public final fun processChildren (Lorg/commonmark/node/Node;)Ljava/util/List; public final fun processMarkdownDocument (Ljava/lang/String;)Ljava/util/List; + public final fun withExtension (Lorg/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension;)Lorg/jetbrains/jewel/markdown/processing/MarkdownProcessor; } public final class org/jetbrains/jewel/markdown/processing/ProcessingUtilKt { - public static final fun readInlineContent (Lorg/commonmark/node/Node;Lorg/jetbrains/jewel/markdown/processing/MarkdownProcessor;)Ljava/util/List; + public static final fun readInlineMarkdown (Lorg/commonmark/node/Node;Lorg/jetbrains/jewel/markdown/processing/MarkdownProcessor;)Ljava/util/List; public static final fun toInlineMarkdownOrNull (Lorg/commonmark/node/Node;Lorg/jetbrains/jewel/markdown/processing/MarkdownProcessor;)Lorg/jetbrains/jewel/markdown/InlineMarkdown; } public class org/jetbrains/jewel/markdown/rendering/DefaultInlineMarkdownRenderer : org/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer { public static final field $stable I - public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/DefaultInlineMarkdownRenderer$Companion; public fun (Ljava/util/List;)V + protected final fun getDelimitedNodeRendererExtensions ()Ljava/util/List; public fun renderAsAnnotatedString (Ljava/lang/Iterable;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;ZLkotlin/jvm/functions/Function1;)Landroidx/compose/ui/text/AnnotatedString; } -public final class org/jetbrains/jewel/markdown/rendering/DefaultInlineMarkdownRenderer$Companion { -} - public class org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRenderer : org/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer { public static final field $stable I public fun (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling;Ljava/util/List;Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer;)V @@ -438,6 +440,7 @@ public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling { public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Companion; public synthetic fun (FLorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Paragraph;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$BlockQuote;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Image;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$ThematicBreak;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$HtmlBlock;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public fun equals (Ljava/lang/Object;)Z + public final fun getBaseInlinesStyling ()Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling; public final fun getBlockQuote ()Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$BlockQuote; public final fun getBlockVerticalSpacing-D9Ej5fM ()F public final fun getCode ()Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code; diff --git a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/InlineMarkdown.kt b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/InlineMarkdown.kt index 43ad7fdf492d5..74770490f2f68 100644 --- a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/InlineMarkdown.kt +++ b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/InlineMarkdown.kt @@ -2,7 +2,10 @@ package org.jetbrains.jewel.markdown import org.jetbrains.jewel.foundation.GenerateDataFunctions -/** A run of inline Markdown used as content for [block-level elements][MarkdownBlock]. */ +/** + * An inline Markdown node, usually found as content for [block-level elements][MarkdownBlock] or other inline nodes + * annotated with the [WithInlineMarkdown] interface. + */ public sealed interface InlineMarkdown { @GenerateDataFunctions public class Code(override val content: String) : InlineMarkdown, WithTextContent { @@ -20,12 +23,26 @@ public sealed interface InlineMarkdown { override fun toString(): String = "Code(content='$content')" } - public interface CustomNode : InlineMarkdown { + /** + * An inline node that is delimited by a fixed delimiter, and can be rendered directly into an + * [androidx.compose.ui.text.AnnotatedString]. + * + * This type of node can be parsed by a + * [org.jetbrains.jewel.markdown.extensions.MarkdownDelimitedInlineProcessorExtension] and rendered by a + * [org.jetbrains.jewel.markdown.extensions.MarkdownDelimitedInlineRendererExtension]. + */ + public interface CustomDelimitedNode : InlineMarkdown, WithInlineMarkdown { /** - * If this custom node has a text-based representation, this function should return it. Otherwise, it should - * return null. + * The string used to indicate the beginning of this type of inline node. Can be identical to the + * [closingDelimiter]. */ - public fun contentOrNull(): String? = null + public val openingDelimiter: String + + /** + * The string used to indicate the end of this type of inline node. Can be identical to the [openingDelimiter]. + */ + public val closingDelimiter: String + get() = openingDelimiter } @GenerateDataFunctions diff --git a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/WithInlineMarkdown.kt b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/WithInlineMarkdown.kt index 6259e4b58143e..d919e9eae1f60 100644 --- a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/WithInlineMarkdown.kt +++ b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/WithInlineMarkdown.kt @@ -1,5 +1,7 @@ package org.jetbrains.jewel.markdown +/** An inline Markdown node that contains other [InlineMarkdown] nodes. */ public interface WithInlineMarkdown { + /** Child inline Markdown nodes. */ public val inlineContent: List } diff --git a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/WithTextContent.kt b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/WithTextContent.kt index d06a8c6ae0ee6..bd85b300fdcf6 100644 --- a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/WithTextContent.kt +++ b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/WithTextContent.kt @@ -1,5 +1,7 @@ package org.jetbrains.jewel.markdown +/** An inline Markdown node that has a plain text content, or can be rendered as plain text. */ public interface WithTextContent { + /** The plain text content or representation of this node. */ public val content: String } diff --git a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownBlockProcessorExtension.kt b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownBlockProcessorExtension.kt index 9ffdbf9249076..3bfcef7334332 100644 --- a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownBlockProcessorExtension.kt +++ b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownBlockProcessorExtension.kt @@ -4,11 +4,13 @@ import org.commonmark.node.CustomBlock import org.jetbrains.jewel.markdown.MarkdownBlock import org.jetbrains.jewel.markdown.processing.MarkdownProcessor +/** Extension that can process a custom block-level [CustomBlock]. */ public interface MarkdownBlockProcessorExtension { /** * Returns true if the [block] can be processed by this extension instance. * * @param block The [CustomBlock] to parse + * @return True if this extension can process the provided [CustomBlock], false otherwise. */ public fun canProcess(block: CustomBlock): Boolean @@ -16,6 +18,10 @@ public interface MarkdownBlockProcessorExtension { * Processes the [block] as a [MarkdownBlock.CustomBlock], if possible. Note that you should always check that * [canProcess] returns true for the same [block], as implementations might throw an exception for unsupported block * types. + * + * @param block The [CustomBlock] to process, for which this extension's [canProcess] returned true. + * @param processor The [MarkdownProcessor] to use for processing. + * @return null if the processing fails, otherwise the processed [MarkdownBlock.CustomBlock]. */ public fun processMarkdownBlock(block: CustomBlock, processor: MarkdownProcessor): MarkdownBlock.CustomBlock? } diff --git a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineProcessorExtension.kt b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineProcessorExtension.kt new file mode 100644 index 0000000000000..6d8f307485380 --- /dev/null +++ b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineProcessorExtension.kt @@ -0,0 +1,23 @@ +package org.jetbrains.jewel.markdown.extensions + +import org.commonmark.node.Delimited +import org.jetbrains.jewel.markdown.InlineMarkdown +import org.jetbrains.jewel.markdown.processing.MarkdownProcessor + +/** An extension for parsing [org.commonmark.node.Delimited] inline nodes. */ +public interface MarkdownDelimitedInlineProcessorExtension { + /** Checks whether the [delimited] can be processed by this instance. */ + public fun canProcess(delimited: Delimited): Boolean + + /** + * Processes a [delimited] for which [canProcess] returned `true` into a [InlineMarkdown.CustomDelimitedNode]. + * + * @param delimited A [Delimited] produced by a [MarkdownProcessor]. + * @param markdownProcessor The [MarkdownProcessor] to use to parse this [Delimited]. + * @return The [InlineMarkdown.CustomDelimitedNode] obtained from [delimited]. + */ + public fun processDelimitedInline( + delimited: Delimited, + markdownProcessor: MarkdownProcessor, + ): InlineMarkdown.CustomDelimitedNode +} diff --git a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineRendererExtension.kt b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineRendererExtension.kt new file mode 100644 index 0000000000000..181b21f85b46d --- /dev/null +++ b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineRendererExtension.kt @@ -0,0 +1,41 @@ +package org.jetbrains.jewel.markdown.extensions + +import androidx.compose.ui.text.AnnotatedString +import org.jetbrains.jewel.markdown.InlineMarkdown +import org.jetbrains.jewel.markdown.InlineMarkdown.CustomDelimitedNode +import org.jetbrains.jewel.markdown.rendering.InlineMarkdownRenderer +import org.jetbrains.jewel.markdown.rendering.InlinesStyling + +/** + * An extension for [InlineMarkdownRenderer] that can render [InlineMarkdown.CustomDelimitedNode]s backed by a + * [org.commonmark.node.Delimited] node. + * + * Only `Delimited` nodes that can be rendered as an [AnnotatedString] are supported; other kinds of inline node aren't. + */ +public interface MarkdownDelimitedInlineRendererExtension { + /** + * Check whether the provided [node] node can be rendered by this extension. + * + * @param node The [CustomDelimitedNode] to check. + * @return True if this instance can render [node], false otherwise. + */ + public fun canRender(node: CustomDelimitedNode): Boolean + + /** + * Render a [CustomDelimitedNode] into an [AnnotatedString.Builder]. Note that if [canRender] returns `false` for + * [node], the implementation might throw. + * + * @param node The [CustomDelimitedNode] to render. + * @param inlineRenderer The [InlineMarkdownRenderer] to use to render the node and its content. + * @param inlinesStyling The styling to use to render the node and its content. + * @param enabled When false, the node will be rendered with a disabled appearance (e.g., grayed out). + * @param onUrlClicked Lambda that will be invoked when URLs inside this node are clicked. + */ + public fun render( + node: CustomDelimitedNode, + inlineRenderer: InlineMarkdownRenderer, + inlinesStyling: InlinesStyling, + enabled: Boolean, + onUrlClicked: ((String) -> Unit)?, + ): AnnotatedString +} diff --git a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownInlineProcessorExtension.kt b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownInlineProcessorExtension.kt deleted file mode 100644 index 6586710441550..0000000000000 --- a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownInlineProcessorExtension.kt +++ /dev/null @@ -1,21 +0,0 @@ -package org.jetbrains.jewel.markdown.extensions - -import org.commonmark.node.CustomNode -import org.jetbrains.jewel.markdown.InlineMarkdown -import org.jetbrains.jewel.markdown.processing.MarkdownProcessor - -public interface MarkdownInlineProcessorExtension { - /** - * Returns true if the [node] can be processed by this extension instance. - * - * @param node The [CustomNode] to parse - */ - public fun canProcess(node: CustomNode): Boolean - - /** - * Processes the [node] as a [InlineMarkdown.CustomNode], if possible. Note that you should always check that - * [canProcess] returns true for the same [node], as implementations might throw an exception for unsupported node - * types. - */ - public fun processInlineMarkdown(node: CustomNode, processor: MarkdownProcessor): InlineMarkdown.CustomNode? -} diff --git a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownInlineRendererExtension.kt b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownInlineRendererExtension.kt deleted file mode 100644 index 723d0a2c007cd..0000000000000 --- a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownInlineRendererExtension.kt +++ /dev/null @@ -1,17 +0,0 @@ -package org.jetbrains.jewel.markdown.extensions - -import org.jetbrains.jewel.markdown.InlineMarkdown -import org.jetbrains.jewel.markdown.InlineMarkdown.CustomNode -import org.jetbrains.jewel.markdown.rendering.InlineMarkdownRenderer - -/** An extension for [InlineMarkdownRenderer] that can render one or more [InlineMarkdown.CustomNode]s. */ -public interface MarkdownInlineRendererExtension { - /** Check whether the provided [inline] can be rendered by this extension. */ - public fun canRender(inline: CustomNode): Boolean - - /** - * Render a [CustomNode] as an annotated string. Note that if [canRender] returns `false` for [inline], the - * implementation might throw. - */ - public fun render(inline: CustomNode, inlineRenderer: InlineMarkdownRenderer, enabled: Boolean) -} diff --git a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension.kt b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension.kt index 231db7e6be2fb..e1e5a57536cb0 100644 --- a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension.kt +++ b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension.kt @@ -11,7 +11,7 @@ public interface MarkdownProcessorExtension { * A CommonMark [ParserExtension] that will be used to parse the extended syntax represented by this extension * instance. * - * Can be null if all required processing is already handled by an existing [org.commonmark.parser.Parser]. + * Can be null if all required parsing is already handled by an existing [org.commonmark.parser.Parser]. */ public val parserExtension: ParserExtension? get() = null @@ -38,14 +38,11 @@ public interface MarkdownProcessorExtension { get() = null /** - * An extension for [`MarkdownProcessor`][org.jetbrains.jewel.markdown.processing.MarkdownProcessor] that will - * transform a supported [org.commonmark.node.CustomNode] into the corresponding - * [org.jetbrains.jewel.markdown.InlineMarkdown.CustomNode]. + * A [MarkdownDelimitedInlineProcessorExtension] that will transform a supported [org.commonmark.node.Delimited] + * inline node into the corresponding [org.jetbrains.jewel.markdown.InlineMarkdown.CustomDelimitedNode]. * - * Can be null if all required processing is already handled by - * [org.jetbrains.jewel.markdown.processing.MarkdownProcessor] or another - * [org.jetbrains.jewel.markdown.extensions.MarkdownProcessorExtension]. + * Can be null if this extension does not handle custom delimited inline nodes. */ - public val inlineProcessorExtension: MarkdownInlineProcessorExtension? + public val delimitedInlineProcessorExtension: MarkdownDelimitedInlineProcessorExtension? get() = null } diff --git a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownRendererExtension.kt b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownRendererExtension.kt index 82f1199bd6ab7..e57cd2abd9560 100644 --- a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownRendererExtension.kt +++ b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownRendererExtension.kt @@ -17,11 +17,11 @@ public interface MarkdownRendererExtension { /** * An extension for [`InlineMarkdownRenderer`][org.jetbrains.jewel.markdown.rendering.InlineMarkdownRenderer] that - * will render a supported [`CustomNode`][org.jetbrains.jewel.markdown.InlineMarkdown.CustomNode] into an annotated + * will render supported [org.jetbrains.jewel.markdown.InlineMarkdown.CustomDelimitedNode]s into an annotated * string. * - * Can be null if this extension doesn't support rendering inline nodes. + * Can be null if this extension doesn't support rendering delimited inline nodes. */ - public val inlineRenderer: MarkdownInlineRendererExtension? + public val delimitedInlineRenderer: MarkdownDelimitedInlineRendererExtension? get() = null } diff --git a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/MarkdownProcessor.kt b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/MarkdownProcessor.kt index d0f19f7df8686..f8e799a5f3dc7 100644 --- a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/MarkdownProcessor.kt +++ b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/MarkdownProcessor.kt @@ -28,6 +28,8 @@ import org.jetbrains.jewel.markdown.MarkdownBlock import org.jetbrains.jewel.markdown.MarkdownBlock.CodeBlock import org.jetbrains.jewel.markdown.MarkdownBlock.ListBlock import org.jetbrains.jewel.markdown.MarkdownMode +import org.jetbrains.jewel.markdown.extensions.MarkdownBlockProcessorExtension +import org.jetbrains.jewel.markdown.extensions.MarkdownDelimitedInlineProcessorExtension import org.jetbrains.jewel.markdown.extensions.MarkdownProcessorExtension import org.jetbrains.jewel.markdown.rendering.DefaultInlineMarkdownRenderer import org.jetbrains.jewel.markdown.scrolling.ScrollingSynchronizer @@ -59,8 +61,19 @@ public class MarkdownProcessor( public val extensions: List = emptyList(), private val markdownMode: MarkdownMode = MarkdownMode.Standalone, private val commonMarkParser: Parser = - MarkdownParserFactory.create(markdownMode is MarkdownMode.EditorPreview, extensions), + MarkdownParserFactory.create(optimizeEdits = markdownMode is MarkdownMode.EditorPreview, extensions), ) { + /** The [block-level processor extensions][MarkdownBlockProcessorExtension]s used by this processor. */ + public val blockExtensions: List = + extensions.mapNotNull { it.blockProcessorExtension } + + /** + * The [delimited inline node processor extensions][MarkdownDelimitedInlineProcessorExtension]s used by this + * processor. + */ + public val delimitedInlineExtensions: List = + extensions.mapNotNull { it.delimitedInlineProcessorExtension } + private var currentState = State(emptyList(), emptyList(), emptyList()) @TestOnly internal fun getCurrentIndexesInTest() = currentState.indexes @@ -248,10 +261,7 @@ public class MarkdownProcessor( is ThematicBreak -> MarkdownBlock.ThematicBreak is HtmlBlock -> toMarkdownHtmlBlockOrNull() is CustomBlock -> { - extensions - .find { it.blockProcessorExtension?.canProcess(this) == true } - ?.blockProcessorExtension - ?.processMarkdownBlock(this, this@MarkdownProcessor) + blockExtensions.find { it.canProcess(this) }?.processMarkdownBlock(this, this@MarkdownProcessor) } else -> null @@ -267,14 +277,14 @@ public class MarkdownProcessor( } private fun Paragraph.toMarkdownParagraph(): MarkdownBlock.Paragraph = - MarkdownBlock.Paragraph(readInlineContent(this@MarkdownProcessor).toList()) + MarkdownBlock.Paragraph(readInlineMarkdown(this@MarkdownProcessor)) private fun BlockQuote.toMarkdownBlockQuote(): MarkdownBlock.BlockQuote = MarkdownBlock.BlockQuote(processChildren(this)) private fun Heading.toMarkdownHeadingOrNull(): MarkdownBlock.Heading? { if (level < 1 || level > 6) return null - return MarkdownBlock.Heading(inlineContent = readInlineContent(this@MarkdownProcessor).toList(), level = level) + return MarkdownBlock.Heading(inlineContent = readInlineMarkdown(this@MarkdownProcessor), level = level) } private fun FencedCodeBlock.toMarkdownCodeBlockOrNull(): CodeBlock.FencedCodeBlock = @@ -345,5 +355,13 @@ public class MarkdownProcessor( return MarkdownBlock.HtmlBlock(literal.trimEnd('\n')) } + /** Creates a copy of this [MarkdownProcessor] with the same properties, plus the provided [extension]. */ + @ExperimentalJewelApi + public operator fun plus(extension: MarkdownProcessorExtension): MarkdownProcessor = withExtension(extension) + + /** Creates a copy of this [MarkdownProcessor] with the same properties, plus the provided [extension]. */ + public fun withExtension(extension: MarkdownProcessorExtension): MarkdownProcessor = + MarkdownProcessor(extensions + extension, markdownMode, commonMarkParser) + private data class State(val lines: List, val blocks: List, val indexes: List>) } diff --git a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/ProcessingUtil.kt b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/ProcessingUtil.kt index 39c05112dad9f..887fb9ea82e3a 100644 --- a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/ProcessingUtil.kt +++ b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/ProcessingUtil.kt @@ -1,7 +1,7 @@ package org.jetbrains.jewel.markdown.processing import org.commonmark.node.Code as CMCode -import org.commonmark.node.CustomNode as CMCustomNode +import org.commonmark.node.Delimited import org.commonmark.node.Emphasis as CMEmphasis import org.commonmark.node.HardLineBreak as CMHardLineBreak import org.commonmark.node.HtmlInline as CMHtmlInline @@ -11,21 +11,24 @@ import org.commonmark.node.Node import org.commonmark.node.SoftLineBreak as CMSoftLineBreak import org.commonmark.node.StrongEmphasis as CMStrongEmphasis import org.commonmark.node.Text as CMText +import org.commonmark.parser.beta.ParsedInline import org.jetbrains.jewel.foundation.ExperimentalJewelApi +import org.jetbrains.jewel.foundation.util.JewelLogger import org.jetbrains.jewel.markdown.InlineMarkdown import org.jetbrains.jewel.markdown.WithInlineMarkdown import org.jetbrains.jewel.markdown.WithTextContent /** - * Reads the contents of this [Node] as a list of [InlineMarkdown] nodes. + * Reads all supported child inline nodes into a list of [InlineMarkdown] nodes, using the provided [markdownProcessor] + * (and its registered extensions). * * @param markdownProcessor Used to parse the inline contents as needed. * @return A list of the contents as parsed [InlineMarkdown]. * @see toInlineMarkdownOrNull */ @ExperimentalJewelApi -public fun Node.readInlineContent(markdownProcessor: MarkdownProcessor): List = buildList { - var current = this@readInlineContent.firstChild +public fun Node.readInlineMarkdown(markdownProcessor: MarkdownProcessor): List = buildList { + var current = this@readInlineMarkdown.firstChild while (current != null) { val inline = current.toInlineMarkdownOrNull(markdownProcessor) if (inline != null) add(inline) @@ -35,14 +38,13 @@ public fun Node.readInlineContent(markdownProcessor: MarkdownProcessor): List - InlineMarkdown.Emphasis(delimiter = openingDelimiter, inlineContent = readInlineContent(markdownProcessor)) + InlineMarkdown.Emphasis(delimiter = openingDelimiter, inlineContent = readInlineMarkdown(markdownProcessor)) - is CMStrongEmphasis -> InlineMarkdown.StrongEmphasis(openingDelimiter, readInlineContent(markdownProcessor)) + is CMStrongEmphasis -> InlineMarkdown.StrongEmphasis(openingDelimiter, readInlineMarkdown(markdownProcessor)) is CMCode -> InlineMarkdown.Code(literal) is CMHtmlInline -> InlineMarkdown.HtmlInline(literal) is CMImage -> { - val inlineContent = readInlineContent(markdownProcessor) + val inlineContent = readInlineMarkdown(markdownProcessor) InlineMarkdown.Image( source = destination, alt = inlineContent.renderAsSimpleText().trim(), @@ -73,11 +75,11 @@ public fun Node.toInlineMarkdownOrNull(markdownProcessor: MarkdownProcessor): In is CMHardLineBreak -> InlineMarkdown.HardLineBreak is CMSoftLineBreak -> InlineMarkdown.SoftLineBreak - is CMCustomNode -> - markdownProcessor.extensions - .find { it.inlineProcessorExtension?.canProcess(this) == true } - ?.inlineProcessorExtension - ?.processInlineMarkdown(this, markdownProcessor) + is Delimited -> + markdownProcessor.delimitedInlineExtensions + .find { it.canProcess(this) } + ?.processDelimitedInline(this, markdownProcessor) + is ParsedInline -> null // Unsupported — see JEWEL-747 else -> error("Unexpected block $this") } @@ -88,19 +90,11 @@ internal fun List.renderAsSimpleText(): String = buildString { when (node) { is WithInlineMarkdown -> append(node.inlineContent.renderAsSimpleText()) is WithTextContent -> append(node.content) - - is InlineMarkdown.CustomNode -> { - val textContent = node.contentOrNull() - if (textContent != null) { - append(' ') - append(textContent) - } - } - is InlineMarkdown.HardLineBreak -> append('\n') is InlineMarkdown.SoftLineBreak -> append(' ') else -> { - // Ignore other nodes + JewelLogger.getInstance("MarkdownProcessingUtil") + .debug("Ignoring node ${node.javaClass.simpleName} for text rendering") } } } diff --git a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultInlineMarkdownRenderer.kt b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultInlineMarkdownRenderer.kt index 60fc29271516b..2c21ca98e1de9 100644 --- a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultInlineMarkdownRenderer.kt +++ b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultInlineMarkdownRenderer.kt @@ -1,6 +1,5 @@ package org.jetbrains.jewel.markdown.rendering -import androidx.compose.foundation.text.appendInlineContent import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString.Builder @@ -9,11 +8,15 @@ import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import org.jetbrains.jewel.foundation.ExperimentalJewelApi import org.jetbrains.jewel.markdown.InlineMarkdown +import org.jetbrains.jewel.markdown.extensions.MarkdownDelimitedInlineRendererExtension import org.jetbrains.jewel.markdown.extensions.MarkdownRendererExtension @ExperimentalJewelApi -public open class DefaultInlineMarkdownRenderer(private val rendererExtensions: List) : +public open class DefaultInlineMarkdownRenderer(rendererExtensions: List) : InlineMarkdownRenderer { + protected val delimitedNodeRendererExtensions: List = + rendererExtensions.mapNotNull { it.delimitedInlineRenderer } + public override fun renderAsAnnotatedString( inlineMarkdown: Iterable, styling: InlinesStyling, @@ -77,24 +80,23 @@ public open class DefaultInlineMarkdownRenderer(private val rendererExtensions: } is InlineMarkdown.Image -> { - appendInlineContent( - INLINE_IMAGE, - buildString { - appendLine(child.source) - append(child.alt) - if (!child.title.isNullOrBlank()) { - appendLine() - append(child.title) - } - }, - ) + // TODO not supported yet — see JEWEL-746 } - is InlineMarkdown.CustomNode -> - rendererExtensions - .find { it.inlineRenderer?.canRender(child) == true } - ?.inlineRenderer - ?.render(child, inlineRenderer = this@DefaultInlineMarkdownRenderer, enabled) + is InlineMarkdown.CustomDelimitedNode -> { + val delimitedNodeRendererExtension = + delimitedNodeRendererExtensions.find { it.canRender(child) } ?: continue + + append( + delimitedNodeRendererExtension.render( + node = child, + inlineRenderer = this@DefaultInlineMarkdownRenderer, + inlinesStyling = styling, + enabled = enabled, + onUrlClicked = onUrlClicked, + ) + ) + } } } } @@ -119,8 +121,4 @@ public open class DefaultInlineMarkdownRenderer(private val rendererExtensions: pop(popTo) } - - public companion object { - internal const val INLINE_IMAGE = "inline_image" - } } diff --git a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/MarkdownStyling.kt b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/MarkdownStyling.kt index feab1ba0dd798..3e14bd1cb233e 100644 --- a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/MarkdownStyling.kt +++ b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/MarkdownStyling.kt @@ -1,3 +1,5 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the +// Apache 2.0 license. package org.jetbrains.jewel.markdown.rendering import androidx.compose.foundation.layout.PaddingValues @@ -12,6 +14,7 @@ import androidx.compose.ui.text.TextLinkStyles import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp +import org.jetbrains.jewel.foundation.ExperimentalJewelApi import org.jetbrains.jewel.foundation.GenerateDataFunctions import org.jetbrains.jewel.markdown.rendering.MarkdownStyling.Code.Fenced.InfoPosition import org.jetbrains.jewel.markdown.rendering.MarkdownStyling.Code.Fenced.InfoPosition.BottomCenter @@ -34,6 +37,8 @@ public class MarkdownStyling( public val thematicBreak: ThematicBreak, public val htmlBlock: HtmlBlock, ) { + @ExperimentalJewelApi public val baseInlinesStyling: InlinesStyling = paragraph.inlinesStyling + @GenerateDataFunctions public class Paragraph(override val inlinesStyling: InlinesStyling) : WithInlinesStyling { override fun equals(other: Any?): Boolean { diff --git a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/scrolling/ScrollSyncMarkdownBlockRenderer.kt b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/scrolling/ScrollSyncMarkdownBlockRenderer.kt index 18717c8151431..9bc0c4ab74d47 100644 --- a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/scrolling/ScrollSyncMarkdownBlockRenderer.kt +++ b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/scrolling/ScrollSyncMarkdownBlockRenderer.kt @@ -84,7 +84,7 @@ public open class ScrollSyncMarkdownBlockRenderer( val content = block.content val highlightedCode by - LocalCodeHighlighter.current.highlight(content, mimeType).collectAsState(AnnotatedString(content)) + LocalCodeHighlighter.current.highlight(content, block.mimeType).collectAsState(AnnotatedString(content)) val actualBlock by rememberUpdatedState(block) AutoScrollableBlock(actualBlock, synchronizer) { diff --git a/platform/jewel/markdown/core/src/test/kotlin/org/jetbrains/jewel/markdown/TestUtils.kt b/platform/jewel/markdown/core/src/test/kotlin/org/jetbrains/jewel/markdown/TestUtils.kt index 5f1f5a9ecee9e..bfc18429d436b 100644 --- a/platform/jewel/markdown/core/src/test/kotlin/org/jetbrains/jewel/markdown/TestUtils.kt +++ b/platform/jewel/markdown/core/src/test/kotlin/org/jetbrains/jewel/markdown/TestUtils.kt @@ -23,24 +23,28 @@ public fun List.assertEquals(vararg expected: MarkdownBlock) { ) } -public fun List.findDifferences(expected: List, indentSize: Int): List = buildList { - val indent = " ".repeat(indentSize) - val thisSize = this@findDifferences.size - if (expected.size != thisSize) { - add("$indent * Content size mismatch. Was $thisSize, but we expected ${expected.size}") - add("$indent Actual: ${this@findDifferences}") - add("$indent Expected: $expected\n") - add("$indent ℹ️ Note: skipping cells comparison as it's meaningless") - return@buildList - } +public fun List.findDifferences(expected: List, indentSize: Int): List = + buildList { + val indent = " ".repeat(indentSize) + val thisSize = this@findDifferences.size + if (expected.size != thisSize) { + add("$indent * Content size mismatch. Was $thisSize, but we expected ${expected.size}") + add("$indent Actual: ${this@findDifferences}") + add("$indent Expected: $expected\n") + add("$indent ℹ️ Note: skipping cells comparison as it's meaningless") + return@buildList + } - for ((i, item) in this@findDifferences.withIndex()) { - val difference = item.findDifferenceWith(expected[i], indentSize + 2) - if (difference.isNotEmpty()) { - add("$indent * Item #$i is not the same as the expected value.\n\n" + "${difference.joinToString("\n")}\n") + for ((i, item) in this@findDifferences.withIndex()) { + val difference = item.findDifferenceWith(expected[i], indentSize + 2) + if (difference.isNotEmpty()) { + add( + "$indent * Item #$i is not the same as the expected value.\n\n" + + "${difference.joinToString("\n")}\n" + ) + } } } -} private fun MarkdownBlock.findDifferenceWith(expected: MarkdownBlock, indentSize: Int): List { val indent = " ".repeat(indentSize) @@ -168,19 +172,25 @@ private fun diffList(actual: ListBlock, expected: MarkdownBlock, indentSize: Int public fun paragraph(content: String): Paragraph = Paragraph(InlineMarkdown.Text(content)) -public fun heading(level: Int, vararg inlineContent: InlineMarkdown): Heading = Heading(inlineContent = inlineContent, level = level) +public fun heading(level: Int, vararg inlineContent: InlineMarkdown): Heading = + Heading(inlineContent = inlineContent, level = level) public fun indentedCodeBlock(content: String): IndentedCodeBlock = IndentedCodeBlock(content) -public fun fencedCodeBlock(content: String, mimeType: MimeType? = null): FencedCodeBlock = FencedCodeBlock(content, mimeType) +public fun fencedCodeBlock(content: String, mimeType: MimeType? = null): FencedCodeBlock = + FencedCodeBlock(content, mimeType) public fun blockQuote(vararg contents: MarkdownBlock): BlockQuote = BlockQuote(contents.toList()) public fun unorderedList(vararg items: ListItem, isTight: Boolean = true, marker: String = "-"): UnorderedList = UnorderedList(items.toList(), isTight, marker) -public fun orderedList(vararg items: ListItem, isTight: Boolean = true, startFrom: Int = 1, delimiter: String = "."): OrderedList = - OrderedList(items.toList(), isTight, startFrom, delimiter) +public fun orderedList( + vararg items: ListItem, + isTight: Boolean = true, + startFrom: Int = 1, + delimiter: String = ".", +): OrderedList = OrderedList(items.toList(), isTight, startFrom, delimiter) public fun listItem(vararg items: MarkdownBlock): ListItem = ListItem(*items) diff --git a/platform/jewel/markdown/core/src/test/kotlin/org/jetbrains/jewel/markdown/scrolling/ScrollingSynchronizerTest.kt b/platform/jewel/markdown/core/src/test/kotlin/org/jetbrains/jewel/markdown/scrolling/ScrollingSynchronizerTest.kt index b309c67fe4072..e965ba4a4897d 100644 --- a/platform/jewel/markdown/core/src/test/kotlin/org/jetbrains/jewel/markdown/scrolling/ScrollingSynchronizerTest.kt +++ b/platform/jewel/markdown/core/src/test/kotlin/org/jetbrains/jewel/markdown/scrolling/ScrollingSynchronizerTest.kt @@ -1,5 +1,7 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the // Apache 2.0 license. +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the +// Apache 2.0 license. package org.jetbrains.jewel.markdown.scrolling import androidx.compose.foundation.ScrollState @@ -68,10 +70,10 @@ public class ScrollingSynchronizerTest { public fun headings() { val markdown = """ - # Heading 1 - ## Heading 2 - ### Heading 3 - """ + # Heading 1 + ## Heading 2 + ### Heading 3 + """ .trimIndent() doTest(markdown) { scrollState, synchronizer -> synchronizer.scrollToLine(0) @@ -99,12 +101,12 @@ public class ScrollingSynchronizerTest { public fun paragraphs() { val markdown = """ - p1 + p1 - p2 + p2 - p3 - """ + p3 + """ .trimIndent() doTest(markdown) { scrollState, synchronizer -> synchronizer.scrollToLine(1) @@ -131,17 +133,17 @@ public class ScrollingSynchronizerTest { public fun `empty spaces`() { val markdown = """ - # Heading 1 - - - # Heading 2 - - - ## Heading 3 - - - """ - .trimIndent() + |# Heading 1 + | + | + |# Heading 2 + | + | + |## Heading 3 + | + | + """ + .trimMargin() doTest(markdown) { scrollState, synchronizer -> synchronizer.scrollToLine(1) val h2Top = scrollState.value @@ -179,12 +181,12 @@ public class ScrollingSynchronizerTest { public fun `unordered list`() { val markdown = """ - Items: - - item 1 - - subitem A - - item 2 - - item 3 - """ + Items: + - item 1 + - subitem A + - item 2 + - item 3 + """ .trimIndent() doTest(markdown) { scrollState, synchronizer -> synchronizer.scrollToLine(1) @@ -213,12 +215,12 @@ public class ScrollingSynchronizerTest { public fun `ordered list`() { val markdown = """ - Items: - 1. item 1 - 1. subitem A - 2. item 2 - 3. item 3 - """ + Items: + 1. item 1 + 1. subitem A + 2. item 2 + 3. item 3 + """ .trimIndent() doTest(markdown) { scrollState, synchronizer -> synchronizer.scrollToLine(1) @@ -247,14 +249,14 @@ public class ScrollingSynchronizerTest { public fun `fenced code block`() { val markdown = """ - ```kotlin - package my.awesome.pkg + ```kotlin + package my.awesome.pkg - fun main() { - println("Hello world") - } - ``` - """ + fun main() { + println("Hello world") + } + ``` + """ .trimIndent() doTest(markdown) { scrollState, synchronizer -> synchronizer.scrollToLine(1) @@ -296,14 +298,14 @@ public class ScrollingSynchronizerTest { public fun `indented code block`() { val markdown = """ - Here starts the indented code block. + Here starts the indented code block. - package my.awesome.pkg + package my.awesome.pkg - fun main() { - println("Hello world") - } - """ + fun main() { + println("Hello world") + } + """ .trimIndent() doTest(markdown) { scrollState, synchronizer -> synchronizer.scrollToLine(2) @@ -345,28 +347,28 @@ public class ScrollingSynchronizerTest { public fun `add a block`() { val firstRun = """ - ```kotlin - package my.awesome.pkg + ```kotlin + package my.awesome.pkg - fun main() { - println("Hello world") - } - ``` - """ + fun main() { + println("Hello world") + } + ``` + """ .trimIndent() val secondRun = """ - **CHANGE** + **CHANGE** - ```kotlin - package my.awesome.pkg + ```kotlin + package my.awesome.pkg - fun main() { - println("Hello world") - } - ``` - """ + fun main() { + println("Hello world") + } + ``` + """ .trimIndent() doTest(firstRun, secondRun) { scrollState, synchronizer -> @@ -409,28 +411,28 @@ public class ScrollingSynchronizerTest { public fun `remove a block`() { val firstRun = """ - **CHANGE** + **CHANGE** - ```kotlin - package my.awesome.pkg + ```kotlin + package my.awesome.pkg - fun main() { - println("Hello world") - } - ``` - """ + fun main() { + println("Hello world") + } + ``` + """ .trimIndent() val secondRun = """ - ```kotlin - package my.awesome.pkg + ```kotlin + package my.awesome.pkg - fun main() { - println("Hello world") - } - ``` - """ + fun main() { + println("Hello world") + } + ``` + """ .trimIndent() doTest(firstRun, secondRun) { scrollState, synchronizer -> @@ -473,27 +475,27 @@ public class ScrollingSynchronizerTest { public fun `change a block`() { val firstRun = """ - ```kotlin - package my.awesome.pkg + ```kotlin + package my.awesome.pkg - fun main() { - println("Hello world") - } - ``` - """ + fun main() { + println("Hello world") + } + ``` + """ .trimIndent() val secondRun = """ - ```kotlin - package my.awesome.pkg + ```kotlin + package my.awesome.pkg - fun main() { - val name = "Steve" - println("Hello " + name) - } - ``` - """ + fun main() { + val name = "Steve" + println("Hello " + name) + } + ``` + """ .trimIndent() doTest(firstRun, secondRun) { scrollState, synchronizer -> @@ -541,36 +543,36 @@ public class ScrollingSynchronizerTest { public fun `merge code blocks`() { val firstRun = """ - ```kotlin - package my.awesome.pkg + ```kotlin + package my.awesome.pkg - fun main() { - println("Hello world") - } - ``` + fun main() { + println("Hello world") + } + ``` - ```kotlin - fun foo() { - println("Foo") - } - ``` - """ + ```kotlin + fun foo() { + println("Foo") + } + ``` + """ .trimIndent() val secondRun = """ - ```kotlin - package my.awesome.pkg + ```kotlin + package my.awesome.pkg - fun main() { - println("Hello world") - } + fun main() { + println("Hello world") + } - fun foo() { - println("Foo") - } - ``` - """ + fun foo() { + println("Foo") + } + ``` + """ .trimIndent() doTest(firstRun, secondRun) { scrollState, synchronizer -> diff --git a/platform/jewel/markdown/extension/autolink/api/autolink.api b/platform/jewel/markdown/extension/autolink/api/autolink.api index 56b2b87c6875d..84a16300f97a0 100644 --- a/platform/jewel/markdown/extension/autolink/api/autolink.api +++ b/platform/jewel/markdown/extension/autolink/api/autolink.api @@ -2,7 +2,7 @@ public final class org/jetbrains/jewel/markdown/extension/autolink/AutolinkProce public static final field $stable I public static final field INSTANCE Lorg/jetbrains/jewel/markdown/extension/autolink/AutolinkProcessorExtension; public fun getBlockProcessorExtension ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownBlockProcessorExtension; - public fun getInlineProcessorExtension ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownInlineProcessorExtension; + public fun getDelimitedInlineProcessorExtension ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineProcessorExtension; public fun getParserExtension ()Lorg/commonmark/parser/Parser$ParserExtension; public fun getTextRendererExtension ()Lorg/commonmark/renderer/text/TextContentRenderer$TextContentRendererExtension; } diff --git a/platform/jewel/markdown/extension/gfm-alerts/api/gfm-alerts.api b/platform/jewel/markdown/extension/gfm-alerts/api/gfm-alerts.api index f30e69f7369aa..2b1053d8e4ea0 100644 --- a/platform/jewel/markdown/extension/gfm-alerts/api/gfm-alerts.api +++ b/platform/jewel/markdown/extension/gfm-alerts/api/gfm-alerts.api @@ -133,7 +133,7 @@ public final class org/jetbrains/jewel/markdown/extensions/github/alerts/GitHubA public static final field $stable I public static final field INSTANCE Lorg/jetbrains/jewel/markdown/extensions/github/alerts/GitHubAlertProcessorExtension; public fun getBlockProcessorExtension ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownBlockProcessorExtension; - public fun getInlineProcessorExtension ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownInlineProcessorExtension; + public fun getDelimitedInlineProcessorExtension ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineProcessorExtension; public fun getParserExtension ()Lorg/commonmark/parser/Parser$ParserExtension; public fun getTextRendererExtension ()Lorg/commonmark/renderer/text/TextContentRenderer$TextContentRendererExtension; } @@ -142,7 +142,7 @@ public final class org/jetbrains/jewel/markdown/extensions/github/alerts/GitHubA public static final field $stable I public fun (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/AlertStyling;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling;)V public fun getBlockRenderer ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownBlockRendererExtension; - public fun getInlineRenderer ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownInlineRendererExtension; + public fun getDelimitedInlineRenderer ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineRendererExtension; } public final class org/jetbrains/jewel/markdown/extensions/github/alerts/ImportantAlertStyling : org/jetbrains/jewel/markdown/extensions/github/alerts/BaseAlertStyling { diff --git a/platform/jewel/markdown/extension/gfm-strikethrough/README.md b/platform/jewel/markdown/extension/gfm-strikethrough/README.md new file mode 100644 index 0000000000000..dd4ad740420f0 --- /dev/null +++ b/platform/jewel/markdown/extension/gfm-strikethrough/README.md @@ -0,0 +1,41 @@ +# GitHub Flavored Markdown — strikethrough extension + +This extension adds support for strikethrough, +a [GFM extension](https://github.github.com/gfm/#strikethrough-extension-) over +CommonMark. Strikethrough nodes are parsed by the `commonmark-ext-gfm-strikethrough` library, and rendered simply by +wrapping their content in a `SpanStyle` that applies the `LineThrough` decoration. + +![Screenshot of a strikethrough](../../../art/docs/gfm-strikethrough.png) + +## Usage + +To use the strikethrough extension, you need to add the `GitHubStrikethroughProcessorExtension` to your +`MarkdownProcessor`, and the +`GitHubStrikethroughRendererExtension` to the `MarkdownBlockRenderer`. For example, in standalone mode: + +```kotlin +val isDark = JewelTheme.isDark + +val markdownStyling = remember(isDark) { if (isDark) MarkdownStyling.dark() else MarkdownStyling.light() } + +val processor = remember { MarkdownProcessor(listOf(GitHubStrikethroughProcessorExtension)) } + +val blockRenderer = + remember(markdownStyling) { + if (isDark) { + MarkdownBlockRenderer.dark( + styling = markdownStyling, + rendererExtensions = listOf(GitHubStrikethroughRendererExtension), + ) + } else { + MarkdownBlockRenderer.light( + styling = markdownStyling, + rendererExtensions = listOf(GitHubStrikethroughRendererExtension), + ) + } + } + +ProvideMarkdownStyling(markdownStyling, blockRenderer, NoOpCodeHighlighter) { + // Your UI that renders Markdown goes here +} +``` \ No newline at end of file diff --git a/platform/jewel/markdown/extension/gfm-strikethrough/api/gfm-strikethrough.api b/platform/jewel/markdown/extension/gfm-strikethrough/api/gfm-strikethrough.api new file mode 100644 index 0000000000000..fae9e7f34cc59 --- /dev/null +++ b/platform/jewel/markdown/extension/gfm-strikethrough/api/gfm-strikethrough.api @@ -0,0 +1,48 @@ +public final class org/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughInlineProcessorExtension : org/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineProcessorExtension { + public static final field $stable I + public static final field INSTANCE Lorg/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughInlineProcessorExtension; + public fun canProcess (Lorg/commonmark/node/Delimited;)Z + public fun processDelimitedInline (Lorg/commonmark/node/Delimited;Lorg/jetbrains/jewel/markdown/processing/MarkdownProcessor;)Lorg/jetbrains/jewel/markdown/InlineMarkdown$CustomDelimitedNode; +} + +public final class org/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughInlineRendererExtension : org/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineRendererExtension { + public static final field $stable I + public static final field INSTANCE Lorg/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughInlineRendererExtension; + public fun canRender (Lorg/jetbrains/jewel/markdown/InlineMarkdown$CustomDelimitedNode;)Z + public fun render (Lorg/jetbrains/jewel/markdown/InlineMarkdown$CustomDelimitedNode;Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;ZLkotlin/jvm/functions/Function1;)Landroidx/compose/ui/text/AnnotatedString; +} + +public final class org/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughNode : org/jetbrains/jewel/markdown/InlineMarkdown$CustomDelimitedNode, org/jetbrains/jewel/markdown/WithInlineMarkdown { + public static final field $stable I + public fun (Ljava/lang/String;Ljava/util/List;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/List; + public final fun copy (Ljava/lang/String;Ljava/util/List;)Lorg/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughNode; + public static synthetic fun copy$default (Lorg/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughNode;Ljava/lang/String;Ljava/util/List;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughNode; + public fun equals (Ljava/lang/Object;)Z + public fun getClosingDelimiter ()Ljava/lang/String; + public final fun getDelimiter ()Ljava/lang/String; + public fun getInlineContent ()Ljava/util/List; + public fun getOpeningDelimiter ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughProcessorExtension : org/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension { + public static final field $stable I + public fun ()V + public fun (Z)V + public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun getBlockProcessorExtension ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownBlockProcessorExtension; + public fun getDelimitedInlineProcessorExtension ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineProcessorExtension; + public fun getParserExtension ()Lorg/commonmark/parser/Parser$ParserExtension; + public fun getTextRendererExtension ()Lorg/commonmark/renderer/text/TextContentRenderer$TextContentRendererExtension; +} + +public final class org/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughRendererExtension : org/jetbrains/jewel/markdown/extensions/MarkdownRendererExtension { + public static final field $stable I + public static final field INSTANCE Lorg/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughRendererExtension; + public fun getBlockRenderer ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownBlockRendererExtension; + public fun getDelimitedInlineRenderer ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineRendererExtension; +} + diff --git a/platform/jewel/markdown/extension/gfm-strikethrough/build.gradle.kts b/platform/jewel/markdown/extension/gfm-strikethrough/build.gradle.kts new file mode 100644 index 0000000000000..3a585738f053f --- /dev/null +++ b/platform/jewel/markdown/extension/gfm-strikethrough/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + jewel + `jewel-publish` + `jewel-check-public-api` + alias(libs.plugins.composeDesktop) + alias(libs.plugins.compose.compiler) +} + +dependencies { + implementation(projects.markdown.core) + implementation(libs.commonmark.ext.gfm.strikethrough) + testImplementation(compose.desktop.uiTestJUnit4) +} + +publishing.publications.named("main") { + val ijpTarget = project.property("ijp.target") as String + artifactId = "jewel-markdown-extension-${project.name}-$ijpTarget" +} + +publicApiValidation { + excludedClassRegexes = setOf("org.jetbrains.jewel.markdown.extensions.github.strikethrough.*") +} \ No newline at end of file diff --git a/platform/jewel/markdown/extension/gfm-strikethrough/intellij.platform.jewel.markdown.extension.gfmStrikethrough.iml b/platform/jewel/markdown/extension/gfm-strikethrough/intellij.platform.jewel.markdown.extension.gfmStrikethrough.iml new file mode 100644 index 0000000000000..805a799646876 --- /dev/null +++ b/platform/jewel/markdown/extension/gfm-strikethrough/intellij.platform.jewel.markdown.extension.gfmStrikethrough.iml @@ -0,0 +1,375 @@ + + + + + + + + + + + + + + + + + $MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-compose-compiler-plugin/2.1.0/kotlin-compose-compiler-plugin-2.1.0.jar + $MAVEN_REPOSITORY$/dev/drewhamilton/poko/poko-compiler-plugin/0.18.2/poko-compiler-plugin-0.18.2.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + c53f675b2af4696e6022598145a44a9e9feb35216a188163ae5bf99a18e76bdb + + + + + + + + + + + + + + + + + + f1e334b73d49ceab00afe776a4c393de77b4fc805e9ff488dac596a2663b1dc1 + + + + + + + + + + + + + + + + + + 66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9 + + + + + + + + + + + + + + + + + + a7dfdfd2b9d6668c646275948eba357aab6407f635f2dc09fe90258a2f202337 + + + + + + + + + + + + + + + + + + 34dfab5b4fa8b2913f00facdddc049e2fef4b176f8e7cde0ad306485674c3b5c + + + + + + + + + + + + + + + + + + 5c84c3bb1c1b636ae36030a0f98d4629afa29e192a9c78f57f22ce2c8ff6bc96 + + + + + + + + + + + + + + + + + + c106c0f705717889663555fe4cb7742612bd47b56a128bf7a9f589f24c64cb08 + + + + + + + + + + + + + + + + + + 8ff9a2b8c93b0287d8fa837d5b0917923168cb97dea1ada1a8d4aa7bb17c9f15 + + + + + + + + + + + + + + + + + + 41da5d61c56877b5b5d01e2a2bcb967cbfaf4d45c1a1aa124c0601c7a3de74e7 + + + + + + + + + + + + + + + + + + 7b0f19724082cbfcbc66e5abea2b9bc92cf08a1ea11e191933ed43801eb3cd05 + + + + + + + + + + + + + + + + + + bff3d1e895fd5abd54ee725dab59214acabf900e1a0784544d20cf20521bf9f3 + + + + + + + + + + + + + + + + + + 62c816073195cc0119dc1d66b178544bdb772ef8bee4879954528c6798b17ebe + + + + + + + + + + + + + + + + + + 5c84c3bb1c1b636ae36030a0f98d4629afa29e192a9c78f57f22ce2c8ff6bc96 + + + + + + + + + + + + + + + + + + c106c0f705717889663555fe4cb7742612bd47b56a128bf7a9f589f24c64cb08 + + + + + + + + + + + + + + + + + + 1b409141a9ee6ed38e76a8fb8f9cf280030bf45375ba7e07e60b2742039b573e + + + + + + + + + + + + + + + + + + c5cfad0b125e978d915f8b4c38630bc5d548e59fe8a0e838c0cc84283c605943 + + + + + + + + + + + + + + + + + + 5c84c3bb1c1b636ae36030a0f98d4629afa29e192a9c78f57f22ce2c8ff6bc96 + + + + + + + + + + + + + + + + + + + + + 7385cb637f04dc4cbda4ddca9c2fcd2af7ac536a50e4c8d2c77f4748bb14bf41 + + + 679338e0b7fc15c02d275d598654b01a149893bc28a87992e90123c8d06af25b + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/platform/jewel/markdown/extension/gfm-strikethrough/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughInlineProcessorExtension.kt b/platform/jewel/markdown/extension/gfm-strikethrough/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughInlineProcessorExtension.kt new file mode 100644 index 0000000000000..bf2dbe47e67e0 --- /dev/null +++ b/platform/jewel/markdown/extension/gfm-strikethrough/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughInlineProcessorExtension.kt @@ -0,0 +1,33 @@ +package org.jetbrains.jewel.markdown.extensions.github.strikethrough + +import org.commonmark.ext.gfm.strikethrough.Strikethrough +import org.commonmark.node.Delimited +import org.commonmark.node.Node +import org.jetbrains.jewel.markdown.InlineMarkdown +import org.jetbrains.jewel.markdown.extensions.MarkdownDelimitedInlineProcessorExtension +import org.jetbrains.jewel.markdown.processing.MarkdownProcessor +import org.jetbrains.jewel.markdown.processing.readInlineMarkdown + +/** + * A [MarkdownDelimitedInlineProcessorExtension] that turns [Strikethrough] nodes into [GitHubStrikethroughNode]s. + * + * Strikethrough is a GitHub Flavored Markdown extension, defined + * [in the GFM specs](https://github.github.com/gfm/#strikethrough-extension-). + * + * @see GitHubStrikethroughProcessorExtension + * @see GitHubStrikethroughNode + * @see GitHubStrikethroughRendererExtension + */ +public object GitHubStrikethroughInlineProcessorExtension : MarkdownDelimitedInlineProcessorExtension { + override fun canProcess(delimited: Delimited): Boolean = delimited is Strikethrough + + override fun processDelimitedInline( + delimited: Delimited, + markdownProcessor: MarkdownProcessor, + ): InlineMarkdown.CustomDelimitedNode = + GitHubStrikethroughNode( + delimited.openingDelimiter, + // Alas, CommonMark APIs kinda suck, so we need to hard-cast to Node... + (delimited as Node).readInlineMarkdown(markdownProcessor), + ) +} diff --git a/platform/jewel/markdown/extension/gfm-strikethrough/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughInlineRendererExtension.kt b/platform/jewel/markdown/extension/gfm-strikethrough/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughInlineRendererExtension.kt new file mode 100644 index 0000000000000..96a430ab88d40 --- /dev/null +++ b/platform/jewel/markdown/extension/gfm-strikethrough/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughInlineRendererExtension.kt @@ -0,0 +1,43 @@ +package org.jetbrains.jewel.markdown.extensions.github.strikethrough + +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.withStyle +import org.jetbrains.jewel.markdown.InlineMarkdown +import org.jetbrains.jewel.markdown.extensions.MarkdownDelimitedInlineRendererExtension +import org.jetbrains.jewel.markdown.rendering.InlineMarkdownRenderer +import org.jetbrains.jewel.markdown.rendering.InlinesStyling + +/** + * An extension for [`MarkdownInlineRenderer`][org.jetbrains.jewel.markdown.rendering.InlineMarkdownRenderer] that + * renders [GitHubStrikethroughNode]s into annotated strings. + */ +public object GitHubStrikethroughInlineRendererExtension : MarkdownDelimitedInlineRendererExtension { + private val strikethroughSpanStyle = SpanStyle(textDecoration = TextDecoration.Companion.LineThrough) + + override fun canRender(node: InlineMarkdown.CustomDelimitedNode): Boolean = node is GitHubStrikethroughNode + + override fun render( + node: InlineMarkdown.CustomDelimitedNode, + inlineRenderer: InlineMarkdownRenderer, + inlinesStyling: InlinesStyling, + enabled: Boolean, + onUrlClicked: ((String) -> Unit)?, + ): AnnotatedString { + val strikethroughNode = node as GitHubStrikethroughNode + return buildAnnotatedString { + withStyle(strikethroughSpanStyle) { + append( + inlineRenderer.renderAsAnnotatedString( + inlineMarkdown = strikethroughNode.inlineContent, + styling = inlinesStyling, + enabled = enabled, + onUrlClicked = onUrlClicked, + ) + ) + } + } + } +} diff --git a/platform/jewel/markdown/extension/gfm-strikethrough/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughNode.kt b/platform/jewel/markdown/extension/gfm-strikethrough/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughNode.kt new file mode 100644 index 0000000000000..0d1e6c6a320cb --- /dev/null +++ b/platform/jewel/markdown/extension/gfm-strikethrough/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughNode.kt @@ -0,0 +1,19 @@ +package org.jetbrains.jewel.markdown.extensions.github.strikethrough + +import org.jetbrains.jewel.markdown.InlineMarkdown +import org.jetbrains.jewel.markdown.WithInlineMarkdown + +/** + * A Markdown inline node representing a strike-through. It contains other inline nodes. + * + * Strikethrough is a GitHub Flavored Markdown extension, defined + * [in the GFM specs](https://github.github.com/gfm/#strikethrough-extension-). + * + * @see org.commonmark.ext.gfm.strikethrough.Strikethrough + * @see GitHubStrikethroughProcessorExtension + * @see GitHubStrikethroughRendererExtension + */ +public data class GitHubStrikethroughNode(val delimiter: String, override val inlineContent: List) : + InlineMarkdown.CustomDelimitedNode, WithInlineMarkdown { + override val openingDelimiter: String = delimiter +} diff --git a/platform/jewel/markdown/extension/gfm-strikethrough/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughProcessorExtension.kt b/platform/jewel/markdown/extension/gfm-strikethrough/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughProcessorExtension.kt new file mode 100644 index 0000000000000..51f6a364c8f0f --- /dev/null +++ b/platform/jewel/markdown/extension/gfm-strikethrough/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughProcessorExtension.kt @@ -0,0 +1,29 @@ +package org.jetbrains.jewel.markdown.extensions.github.strikethrough + +import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension +import org.commonmark.parser.Parser.ParserExtension +import org.commonmark.renderer.text.TextContentRenderer +import org.jetbrains.jewel.markdown.extensions.MarkdownDelimitedInlineProcessorExtension +import org.jetbrains.jewel.markdown.extensions.MarkdownProcessorExtension + +/** + * Adds support for inline strikethrough to a [org.jetbrains.jewel.markdown.processing.MarkdownProcessor]. + * + * Strikethrough is a GitHub Flavored Markdown extension, defined + * [in the GFM specs](https://github.github.com/gfm/#strikethrough-extension-). + * + * @see StrikethroughExtension + * @see GitHubStrikethroughNode + * @see GitHubStrikethroughRendererExtension + */ +public class GitHubStrikethroughProcessorExtension(requireTwoTildes: Boolean = false) : MarkdownProcessorExtension { + private val commonMarkExtension = StrikethroughExtension.builder().requireTwoTildes(requireTwoTildes).build() + + override val parserExtension: ParserExtension = commonMarkExtension as ParserExtension + + override val textRendererExtension: TextContentRenderer.TextContentRendererExtension = + commonMarkExtension as TextContentRenderer.TextContentRendererExtension + + override val delimitedInlineProcessorExtension: MarkdownDelimitedInlineProcessorExtension = + GitHubStrikethroughInlineProcessorExtension +} diff --git a/platform/jewel/markdown/extension/gfm-strikethrough/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughRendererExtension.kt b/platform/jewel/markdown/extension/gfm-strikethrough/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughRendererExtension.kt new file mode 100644 index 0000000000000..b5a404457a025 --- /dev/null +++ b/platform/jewel/markdown/extension/gfm-strikethrough/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughRendererExtension.kt @@ -0,0 +1,13 @@ +package org.jetbrains.jewel.markdown.extensions.github.strikethrough + +import org.jetbrains.jewel.markdown.extensions.MarkdownDelimitedInlineRendererExtension +import org.jetbrains.jewel.markdown.extensions.MarkdownRendererExtension + +/** + * A [MarkdownRendererExtension] that supports rendering + * [org.jetbrains.jewel.markdown.InlineMarkdown.CustomDelimitedNode]s into [androidx.compose.ui.text.AnnotatedString]s. + */ +public object GitHubStrikethroughRendererExtension : MarkdownRendererExtension { + public override val delimitedInlineRenderer: MarkdownDelimitedInlineRendererExtension = + GitHubStrikethroughInlineRendererExtension +} diff --git a/platform/jewel/markdown/extension/gfm-tables/api/gfm-tables.api b/platform/jewel/markdown/extension/gfm-tables/api/gfm-tables.api index 80434a86cf946..4d52b611cfeb4 100644 --- a/platform/jewel/markdown/extension/gfm-tables/api/gfm-tables.api +++ b/platform/jewel/markdown/extension/gfm-tables/api/gfm-tables.api @@ -57,7 +57,7 @@ public final class org/jetbrains/jewel/markdown/extensions/github/tables/GitHubT public static final field $stable I public static final field INSTANCE Lorg/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableProcessorExtension; public fun getBlockProcessorExtension ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownBlockProcessorExtension; - public fun getInlineProcessorExtension ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownInlineProcessorExtension; + public fun getDelimitedInlineProcessorExtension ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineProcessorExtension; public fun getParserExtension ()Lorg/commonmark/parser/Parser$ParserExtension; public fun getTextRendererExtension ()Lorg/commonmark/renderer/text/TextContentRenderer$TextContentRendererExtension; } @@ -66,7 +66,7 @@ public final class org/jetbrains/jewel/markdown/extensions/github/tables/GitHubT public static final field $stable I public fun (Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableStyling;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling;)V public fun getBlockRenderer ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownBlockRendererExtension; - public fun getInlineRenderer ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownInlineRendererExtension; + public fun getDelimitedInlineRenderer ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineRendererExtension; } public final class org/jetbrains/jewel/markdown/extensions/github/tables/RowBackgroundStyle : java/lang/Enum { diff --git a/platform/jewel/markdown/extension/gfm-tables/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableProcessorExtension.kt b/platform/jewel/markdown/extension/gfm-tables/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableProcessorExtension.kt index 21dc1543b80e6..f7ef827187de9 100644 --- a/platform/jewel/markdown/extension/gfm-tables/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableProcessorExtension.kt +++ b/platform/jewel/markdown/extension/gfm-tables/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableProcessorExtension.kt @@ -24,9 +24,15 @@ import org.jetbrains.jewel.markdown.extensions.MarkdownBlockRendererExtension import org.jetbrains.jewel.markdown.extensions.MarkdownProcessorExtension import org.jetbrains.jewel.markdown.extensions.MarkdownRendererExtension import org.jetbrains.jewel.markdown.processing.MarkdownProcessor -import org.jetbrains.jewel.markdown.processing.readInlineContent +import org.jetbrains.jewel.markdown.processing.readInlineMarkdown import org.jetbrains.jewel.markdown.rendering.MarkdownStyling +/** + * Adds support for table parsing. Tables are a GitHub Flavored Markdown extension, defined + * [in the GFM specs](https://github.github.com/gfm/#tables-extension-). + * + * @see TablesExtension + */ @OptIn(ExperimentalJewelApi::class) public object GitHubTableProcessorExtension : MarkdownProcessorExtension { override val parserExtension: ParserExtension = GitHubTablesCommonMarkExtension @@ -54,7 +60,7 @@ public object GitHubTableProcessorExtension : MarkdownProcessorExtension { TableCell( rowIndex = 0, columnIndex = columnIndex, - content = cell.readInlineContent(processor), + content = cell.readInlineMarkdown(processor), alignment = getAlignment(cell), ) } @@ -67,7 +73,7 @@ public object GitHubTableProcessorExtension : MarkdownProcessorExtension { TableCell( rowIndex = rowIndex + 1, // The header is row zero columnIndex = columnIndex, - content = cell.readInlineContent(processor), + content = cell.readInlineMarkdown(processor), alignment = getAlignment(cell), ) }, diff --git a/platform/jewel/markdown/extension/gfm-tables/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableStyling.kt b/platform/jewel/markdown/extension/gfm-tables/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableStyling.kt index 1263fc3677f03..1d9d7aa1d217b 100644 --- a/platform/jewel/markdown/extension/gfm-tables/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableStyling.kt +++ b/platform/jewel/markdown/extension/gfm-tables/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableStyling.kt @@ -54,13 +54,12 @@ public class GfmTableStyling( return result } - override fun toString(): String { - return "GfmTableStyling(" + + override fun toString(): String = + "GfmTableStyling(" + "colors=$colors, " + "metrics=$metrics, " + "headerBaseFontWeight=$headerBaseFontWeight" + ")" - } public companion object } @@ -94,14 +93,13 @@ public class GfmTableColors( return result } - override fun toString(): String { - return "GfmTableColors(" + + override fun toString(): String = + "GfmTableColors(" + "borderColor=$borderColor, " + "rowBackgroundColor=$rowBackgroundColor, " + "alternateRowBackgroundColor=$alternateRowBackgroundColor, " + "rowBackgroundStyle=$rowBackgroundStyle" + ")" - } public companion object } diff --git a/platform/jewel/markdown/ide-laf-bridge-styling/api/ide-laf-bridge-styling.api b/platform/jewel/markdown/ide-laf-bridge-styling/api/ide-laf-bridge-styling.api index c8fd643b876d4..cc0866a376380 100644 --- a/platform/jewel/markdown/ide-laf-bridge-styling/api/ide-laf-bridge-styling.api +++ b/platform/jewel/markdown/ide-laf-bridge-styling/api/ide-laf-bridge-styling.api @@ -4,7 +4,7 @@ public final class org/jetbrains/jewel/intui/markdown/bridge/BridgeMarkdownBlock } public final class org/jetbrains/jewel/intui/markdown/bridge/BridgeProvideMarkdownStylingKt { - public static final fun ProvideMarkdownStyling (Lcom/intellij/openapi/project/Project;Ljava/lang/String;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling;Lorg/jetbrains/jewel/markdown/MarkdownMode;Lorg/jetbrains/jewel/markdown/processing/MarkdownProcessor;Lorg/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V + public static final fun ProvideMarkdownStyling (Lcom/intellij/openapi/project/Project;Ljava/lang/String;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling;Lorg/jetbrains/jewel/markdown/processing/MarkdownProcessor;Lorg/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V public static final fun ProvideMarkdownStyling (Ljava/lang/String;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling;Lorg/jetbrains/jewel/markdown/MarkdownMode;Lorg/jetbrains/jewel/markdown/processing/MarkdownProcessor;Lorg/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer;Lorg/jetbrains/jewel/foundation/code/highlighting/CodeHighlighter;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V } diff --git a/platform/jewel/markdown/ide-laf-bridge-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/bridge/BridgeProvideMarkdownStyling.kt b/platform/jewel/markdown/ide-laf-bridge-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/bridge/BridgeProvideMarkdownStyling.kt index 727f308d64b2f..2733d8d039528 100644 --- a/platform/jewel/markdown/ide-laf-bridge-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/bridge/BridgeProvideMarkdownStyling.kt +++ b/platform/jewel/markdown/ide-laf-bridge-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/bridge/BridgeProvideMarkdownStyling.kt @@ -1,3 +1,5 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the +// Apache 2.0 license. package org.jetbrains.jewel.intui.markdown.bridge import androidx.compose.runtime.Composable @@ -14,7 +16,6 @@ import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.intui.markdown.bridge.styling.create import org.jetbrains.jewel.markdown.MarkdownMode import org.jetbrains.jewel.markdown.extensions.LocalMarkdownBlockRenderer -import org.jetbrains.jewel.markdown.extensions.LocalMarkdownMode import org.jetbrains.jewel.markdown.extensions.LocalMarkdownProcessor import org.jetbrains.jewel.markdown.extensions.LocalMarkdownStyling import org.jetbrains.jewel.markdown.processing.MarkdownProcessor @@ -35,7 +36,6 @@ public fun ProvideMarkdownStyling( ) { CompositionLocalProvider( LocalMarkdownStyling provides markdownStyling, - LocalMarkdownMode provides markdownMode, LocalMarkdownProcessor provides markdownProcessor, LocalMarkdownBlockRenderer provides markdownBlockRenderer, LocalCodeHighlighter provides codeHighlighter, @@ -50,8 +50,7 @@ public fun ProvideMarkdownStyling( project: Project, themeName: String = JewelTheme.name, markdownStyling: MarkdownStyling = remember(themeName) { MarkdownStyling.create() }, - markdownMode: MarkdownMode = remember { MarkdownMode.Standalone }, - markdownProcessor: MarkdownProcessor = remember { MarkdownProcessor(markdownMode = markdownMode) }, + markdownProcessor: MarkdownProcessor = remember { MarkdownProcessor() }, markdownBlockRenderer: MarkdownBlockRenderer = remember(markdownStyling) { MarkdownBlockRenderer.create(markdownStyling) }, content: @Composable () -> Unit, @@ -61,7 +60,6 @@ public fun ProvideMarkdownStyling( ProvideMarkdownStyling( themeName = themeName, markdownStyling = markdownStyling, - markdownMode = markdownMode, markdownProcessor = markdownProcessor, markdownBlockRenderer = markdownBlockRenderer, codeHighlighter = codeHighlighter, diff --git a/platform/jewel/markdown/ide-laf-bridge-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/bridge/styling/extensions/github/alerts/BridgeGitHubAlertStyling.kt b/platform/jewel/markdown/ide-laf-bridge-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/bridge/styling/extensions/github/alerts/BridgeGitHubAlertStyling.kt index 89f626451107d..ad0fb78f38ac5 100644 --- a/platform/jewel/markdown/ide-laf-bridge-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/bridge/styling/extensions/github/alerts/BridgeGitHubAlertStyling.kt +++ b/platform/jewel/markdown/ide-laf-bridge-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/bridge/styling/extensions/github/alerts/BridgeGitHubAlertStyling.kt @@ -1,3 +1,5 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the +// Apache 2.0 license. package org.jetbrains.jewel.intui.markdown.bridge.styling.extensions.github.alerts import androidx.compose.foundation.layout.PaddingValues diff --git a/platform/jewel/markdown/int-ui-standalone-styling/api/int-ui-standalone-styling.api b/platform/jewel/markdown/int-ui-standalone-styling/api/int-ui-standalone-styling.api index 4eacec1d82d24..43c87ae44a13b 100644 --- a/platform/jewel/markdown/int-ui-standalone-styling/api/int-ui-standalone-styling.api +++ b/platform/jewel/markdown/int-ui-standalone-styling/api/int-ui-standalone-styling.api @@ -6,7 +6,7 @@ public final class org/jetbrains/jewel/intui/markdown/standalone/IntUiMarkdownBl } public final class org/jetbrains/jewel/intui/markdown/standalone/IntUiProvideMarkdownStylingKt { - public static final fun ProvideMarkdownStyling (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling;Lorg/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer;Lorg/jetbrains/jewel/foundation/code/highlighting/CodeHighlighter;Lorg/jetbrains/jewel/markdown/MarkdownMode;Lorg/jetbrains/jewel/markdown/processing/MarkdownProcessor;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V + public static final fun ProvideMarkdownStyling (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling;Lorg/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer;Lorg/jetbrains/jewel/foundation/code/highlighting/CodeHighlighter;Lorg/jetbrains/jewel/markdown/processing/MarkdownProcessor;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V public static final fun ProvideMarkdownStyling (ZLorg/jetbrains/jewel/markdown/rendering/MarkdownStyling;Lorg/jetbrains/jewel/markdown/MarkdownMode;Lorg/jetbrains/jewel/markdown/processing/MarkdownProcessor;Lorg/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer;Lorg/jetbrains/jewel/foundation/code/highlighting/CodeHighlighter;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V } diff --git a/platform/jewel/markdown/int-ui-standalone-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/standalone/IntUiProvideMarkdownStyling.kt b/platform/jewel/markdown/int-ui-standalone-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/standalone/IntUiProvideMarkdownStyling.kt index 36234459c73a5..6c52b5480402f 100644 --- a/platform/jewel/markdown/int-ui-standalone-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/standalone/IntUiProvideMarkdownStyling.kt +++ b/platform/jewel/markdown/int-ui-standalone-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/standalone/IntUiProvideMarkdownStyling.kt @@ -1,3 +1,5 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the +// Apache 2.0 license. package org.jetbrains.jewel.intui.markdown.standalone import androidx.compose.runtime.Composable @@ -12,7 +14,6 @@ import org.jetbrains.jewel.intui.markdown.standalone.styling.dark import org.jetbrains.jewel.intui.markdown.standalone.styling.light import org.jetbrains.jewel.markdown.MarkdownMode import org.jetbrains.jewel.markdown.extensions.LocalMarkdownBlockRenderer -import org.jetbrains.jewel.markdown.extensions.LocalMarkdownMode import org.jetbrains.jewel.markdown.extensions.LocalMarkdownProcessor import org.jetbrains.jewel.markdown.extensions.LocalMarkdownStyling import org.jetbrains.jewel.markdown.processing.MarkdownProcessor @@ -31,8 +32,8 @@ public fun ProvideMarkdownStyling( MarkdownStyling.light() } }, - markdownMode: MarkdownMode = remember { MarkdownMode.Standalone }, - markdownProcessor: MarkdownProcessor = remember { MarkdownProcessor() }, + markdownMode: MarkdownMode = MarkdownMode.Standalone, + markdownProcessor: MarkdownProcessor = remember { MarkdownProcessor(markdownMode = markdownMode) }, markdownBlockRenderer: MarkdownBlockRenderer = remember(markdownStyling) { if (isDark) { @@ -46,7 +47,6 @@ public fun ProvideMarkdownStyling( ) { CompositionLocalProvider( LocalMarkdownStyling provides markdownStyling, - LocalMarkdownMode provides markdownMode, LocalMarkdownProcessor provides markdownProcessor, LocalMarkdownBlockRenderer provides markdownBlockRenderer, LocalCodeHighlighter provides codeHighlighter, @@ -61,13 +61,11 @@ public fun ProvideMarkdownStyling( markdownStyling: MarkdownStyling, markdownBlockRenderer: MarkdownBlockRenderer, codeHighlighter: CodeHighlighter, - markdownMode: MarkdownMode = MarkdownMode.Standalone, - markdownProcessor: MarkdownProcessor = remember { MarkdownProcessor(markdownMode = markdownMode) }, + markdownProcessor: MarkdownProcessor = remember { MarkdownProcessor() }, content: @Composable () -> Unit, ) { CompositionLocalProvider( LocalMarkdownStyling provides markdownStyling, - LocalMarkdownMode provides markdownMode, LocalMarkdownProcessor provides markdownProcessor, LocalMarkdownBlockRenderer provides markdownBlockRenderer, LocalCodeHighlighter provides codeHighlighter, diff --git a/platform/jewel/markdown/int-ui-standalone-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/standalone/styling/extensions/github/tables/IntUiGitHubTableStyling.kt b/platform/jewel/markdown/int-ui-standalone-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/standalone/styling/extensions/github/tables/IntUiGitHubTableStyling.kt index 7c6667c2e6d56..eb03d178fb318 100644 --- a/platform/jewel/markdown/int-ui-standalone-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/standalone/styling/extensions/github/tables/IntUiGitHubTableStyling.kt +++ b/platform/jewel/markdown/int-ui-standalone-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/standalone/styling/extensions/github/tables/IntUiGitHubTableStyling.kt @@ -6,6 +6,8 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import org.jetbrains.jewel.intui.standalone.theme.dark +import org.jetbrains.jewel.intui.standalone.theme.light import org.jetbrains.jewel.markdown.extensions.github.tables.GfmTableColors import org.jetbrains.jewel.markdown.extensions.github.tables.GfmTableMetrics import org.jetbrains.jewel.markdown.extensions.github.tables.GfmTableStyling diff --git a/platform/jewel/samples/standalone/build.gradle.kts b/platform/jewel/samples/standalone/build.gradle.kts index 2328d7b48fb82..8f22448df9cfa 100644 --- a/platform/jewel/samples/standalone/build.gradle.kts +++ b/platform/jewel/samples/standalone/build.gradle.kts @@ -14,8 +14,9 @@ dependencies { implementation(projects.intUi.intUiDecoratedWindow) implementation(projects.markdown.intUiStandaloneStyling) implementation(projects.markdown.extension.gfmAlerts) - implementation(projects.markdown.extension.autolink) + implementation(projects.markdown.extension.gfmStrikethrough) implementation(projects.markdown.extension.gfmTables) + implementation(projects.markdown.extension.autolink) implementation(compose.desktop.currentOs) { exclude(group = "org.jetbrains.compose.material") } implementation(compose.components.resources) implementation(project(":samples:showcase")) @@ -65,4 +66,4 @@ tasks { setExecutable(javaLauncher.map { it.executablePath.asFile.absolutePath }.get()) } } -} +} \ No newline at end of file diff --git a/platform/jewel/samples/standalone/intellij.platform.jewel.samples.standalone.iml b/platform/jewel/samples/standalone/intellij.platform.jewel.samples.standalone.iml index 846773a7043b8..6b248997615a3 100644 --- a/platform/jewel/samples/standalone/intellij.platform.jewel.samples.standalone.iml +++ b/platform/jewel/samples/standalone/intellij.platform.jewel.samples.standalone.iml @@ -70,15 +70,20 @@ - + + - - - + + + + + + + @@ -134,9 +139,5 @@ - - - - \ No newline at end of file diff --git a/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/markdown/MarkdownPreview.kt b/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/markdown/MarkdownPreview.kt index 52a5c70fef82a..c35dd654f0a91 100644 --- a/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/markdown/MarkdownPreview.kt +++ b/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/markdown/MarkdownPreview.kt @@ -33,6 +33,8 @@ import org.jetbrains.jewel.markdown.extension.autolink.AutolinkProcessorExtensio 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 +import org.jetbrains.jewel.markdown.extensions.github.strikethrough.GitHubStrikethroughProcessorExtension +import org.jetbrains.jewel.markdown.extensions.github.strikethrough.GitHubStrikethroughRendererExtension import org.jetbrains.jewel.markdown.extensions.github.tables.GfmTableStyling import org.jetbrains.jewel.markdown.extensions.github.tables.GitHubTableProcessorExtension import org.jetbrains.jewel.markdown.extensions.github.tables.GitHubTableRendererExtension @@ -55,7 +57,12 @@ public fun MarkdownPreview(modifier: Modifier = Modifier, rawMarkdown: CharSeque // potentially involving ViewModels, dependency injection, etc. val processor = remember { MarkdownProcessor( - listOf(GitHubAlertProcessorExtension, AutolinkProcessorExtension, GitHubTableProcessorExtension) + listOf( + AutolinkProcessorExtension, + GitHubAlertProcessorExtension, + GitHubStrikethroughProcessorExtension(), + GitHubTableProcessorExtension, + ) ) } @@ -71,19 +78,21 @@ public fun MarkdownPreview(modifier: Modifier = Modifier, rawMarkdown: CharSeque remember(markdownStyling) { if (isDark) { MarkdownBlockRenderer.dark( - styling = MarkdownStyling.dark(), + styling = markdownStyling, rendererExtensions = listOf( - GitHubAlertRendererExtension(AlertStyling.dark(), MarkdownStyling.dark()), + GitHubAlertRendererExtension(AlertStyling.dark(), markdownStyling), + GitHubStrikethroughRendererExtension, GitHubTableRendererExtension(GfmTableStyling.dark(), markdownStyling), ), ) } else { MarkdownBlockRenderer.light( - styling = MarkdownStyling.light(), + styling = markdownStyling, rendererExtensions = listOf( - GitHubAlertRendererExtension(AlertStyling.light(), MarkdownStyling.light()), + GitHubAlertRendererExtension(AlertStyling.light(), markdownStyling), + GitHubStrikethroughRendererExtension, GitHubTableRendererExtension(GfmTableStyling.light(), markdownStyling), ), ) @@ -103,8 +112,10 @@ public fun MarkdownPreview(modifier: Modifier = Modifier, rawMarkdown: CharSeque PaddingValues(start = 8.dp, top = 8.dp, end = 8.dp + scrollbarContentSafePadding(), bottom = 8.dp), state = lazyListState, selectable = true, - onUrlClick = { url -> Desktop.getDesktop().browse(URI.create(url)) }, + onUrlClick = onUrlClick(), ) } } } + +private fun onUrlClick(): (String) -> Unit = { url -> Desktop.getDesktop().browse(URI.create(url)) } diff --git a/platform/jewel/settings.gradle.kts b/platform/jewel/settings.gradle.kts index c9650bdd155c9..f9a55b9ec9475 100644 --- a/platform/jewel/settings.gradle.kts +++ b/platform/jewel/settings.gradle.kts @@ -12,9 +12,7 @@ pluginManagement { gradlePluginPortal() mavenCentral() } - plugins { - kotlin("jvm") version "2.1.0" - } + plugins { kotlin("jvm") version "2.1.0" } } dependencyResolutionManagement { @@ -45,6 +43,7 @@ include( ":markdown:core", ":markdown:extension:autolink", ":markdown:extension:gfm-alerts", + ":markdown:extension:gfm-strikethrough", ":markdown:extension:gfm-tables", ":markdown:int-ui-standalone-styling", ":markdown:ide-laf-bridge-styling", @@ -52,7 +51,7 @@ include( ":samples:showcase", ":samples:standalone", ":ui", - ":ui-test", + ":ui-tests", ) gradleEnterprise { @@ -67,22 +66,22 @@ val isWindows get() = System.getProperty("os.name").contains("win", true) val gradleCommand: String by -lazy(LazyThreadSafetyMode.NONE) { - val gradlewFilename = - if (isWindows) { - "gradlew.bat" + lazy(LazyThreadSafetyMode.NONE) { + val gradlewFilename = + if (isWindows) { + "gradlew.bat" + } else { + "gradlew" + } + + val gradlew = File(rootProject.projectDir, gradlewFilename) + if (gradlew.exists() && gradlew.isFile && gradlew.canExecute()) { + logger.info("Using gradlew wrapper at ${gradlew.invariantSeparatorsPath}") + gradlew.invariantSeparatorsPath } else { - "gradlew" + "gradle" } - - val gradlew = File(rootProject.projectDir, gradlewFilename) - if (gradlew.exists() && gradlew.isFile && gradlew.canExecute()) { - logger.info("Using gradlew wrapper at ${gradlew.invariantSeparatorsPath}") - gradlew.invariantSeparatorsPath - } else { - "gradle" } -} val shebang = if (isWindows) "" else "#!/bin/sh" @@ -92,17 +91,22 @@ gitHooks { // language=Shell Script """ |#### Note: this hook was autogenerated. You can edit it in settings.gradle.kts - |GRADLEW=$gradleCommand + |OLD_DIR=$(pwd) + |cd ${rootDir.absolutePath} + |GRADLEW=./gradlew |if ! ${'$'}GRADLEW ktfmtCheck ; then | ${'$'}GRADLEW ktfmtFormat | echo 1>&2 "\nktfmt found problems; commit the result and re-push" + | cd ${'$'}OLD_DIR | exit 1 |fi | + |cd ${'$'}OLD_DIR + | """ .trimMargin() } } createHooks(overwriteExisting = true) -} +} \ No newline at end of file diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/util/ModifierExtensions.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/util/ModifierExtensions.kt index 6eda2db382a0a..ebd232ca3edd8 100644 --- a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/util/ModifierExtensions.kt +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/util/ModifierExtensions.kt @@ -8,7 +8,7 @@ import org.jetbrains.annotations.ApiStatus * otherwise. */ @Deprecated( - message = "This modifier has been moved to org.jetbrains.jewel.foundation.util. Please update your imports.", + message = "This modifier has been moved to org.jetbrains.jewel.foundation.modifier. Please update your imports.", ReplaceWith("thenIf(precondition, action)", "org.jetbrains.jewel.foundation.modifier.thenIf"), ) @ApiStatus.ScheduledForRemoval(inVersion = "2025.2") diff --git a/platform/jewel/ui/src/test/kotlin/org/jetbrains/jewel/BasicJewelUiTest.kt b/platform/jewel/ui/src/test/kotlin/org/jetbrains/jewel/BasicJewelUiTest.kt index b15d9dbb50086..bc487028375ca 100644 --- a/platform/jewel/ui/src/test/kotlin/org/jetbrains/jewel/BasicJewelUiTest.kt +++ b/platform/jewel/ui/src/test/kotlin/org/jetbrains/jewel/BasicJewelUiTest.kt @@ -7,13 +7,13 @@ import kotlinx.coroutines.runBlocking import org.junit.Rule public open class BasicJewelUiTest { - @get:Rule - public val composeRule: ComposeContentTestRule = createComposeRule() + @get:Rule public val composeRule: ComposeContentTestRule = createComposeRule() @Suppress("ImplicitUnitReturnType") - protected fun runComposeTest(composable: @Composable () -> Unit, block: suspend ComposeContentTestRule.() -> Unit): Unit = + protected fun runComposeTest(composable: @Composable () -> Unit, block: suspend ComposeContentTestRule.() -> Unit) { runBlocking { composeRule.setContent(composable) composeRule.block() } + } } diff --git a/platform/jewel/ui/src/test/kotlin/org/jetbrains/jewel/PainterHintTest.kt b/platform/jewel/ui/src/test/kotlin/org/jetbrains/jewel/PainterHintTest.kt index 79163f216910d..590ce240ab0c3 100644 --- a/platform/jewel/ui/src/test/kotlin/org/jetbrains/jewel/PainterHintTest.kt +++ b/platform/jewel/ui/src/test/kotlin/org/jetbrains/jewel/PainterHintTest.kt @@ -29,7 +29,7 @@ import org.junit.Test @Suppress("ImplicitUnitReturnType") public class PainterHintTest : BasicJewelUiTest() { @Test - public fun `empty hint should be ignored`(): Unit = + public fun `empty hint should be ignored`() { runComposeTest({ OverrideDarkMode(isDark = false) { val provider = rememberResourcePainterProvider("icons/github.svg", PainterHintTest::class.java) @@ -46,6 +46,7 @@ public class PainterHintTest : BasicJewelUiTest() { }) { awaitIdle() } + } private class TestPainterProviderScope( density: Density,