From ce3aa2f5d27653a513053966d0fb1ef1b7c152e2 Mon Sep 17 00:00:00 2001 From: Sebastiano Poggi Date: Wed, 10 Jul 2024 18:38:41 +0200 Subject: [PATCH] Allow customizing the CommonMark Parser (#444) This is required by one of our customers, and it allows them to customize the handling of standard tags (e.g., remove images). It is an opt-in behaviour and it does not impact existing Markdown handling code or behaviour. --- markdown/core/api/core.api | 14 ++++-- .../processing/MarkdownParserFactory.kt | 50 +++++++++++++++++++ .../markdown/processing/MarkdownProcessor.kt | 28 ++++------- 3 files changed, 72 insertions(+), 20 deletions(-) create mode 100644 markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/MarkdownParserFactory.kt diff --git a/markdown/core/api/core.api b/markdown/core/api/core.api index 64849a927..d3a68a137 100644 --- a/markdown/core/api/core.api +++ b/markdown/core/api/core.api @@ -442,12 +442,20 @@ public abstract interface class org/jetbrains/jewel/markdown/extensions/Markdown public abstract fun getBlockRenderer ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownBlockRendererExtension; } +public final class org/jetbrains/jewel/markdown/processing/MarkdownParserFactory { + public static final field $stable I + public static final field INSTANCE Lorg/jetbrains/jewel/markdown/processing/MarkdownParserFactory; + public final fun create (ZLjava/util/List;Lkotlin/jvm/functions/Function1;)Lorg/commonmark/parser/Parser; + public static synthetic fun create$default (Lorg/jetbrains/jewel/markdown/processing/MarkdownParserFactory;ZLjava/util/List;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/commonmark/parser/Parser; +} + public final class org/jetbrains/jewel/markdown/processing/MarkdownProcessor { public static final field $stable I public fun ()V - public fun (Ljava/util/List;Z)V - public synthetic fun (Ljava/util/List;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun ([Lorg/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension;)V + public fun (Ljava/util/List;ZLorg/commonmark/parser/Parser;)V + public synthetic fun (Ljava/util/List;ZLorg/commonmark/parser/Parser;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Z[Lorg/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension;)V + public synthetic fun (Z[Lorg/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun processChildren (Lorg/commonmark/node/Node;)Ljava/util/List; public final fun processMarkdownDocument (Ljava/lang/String;)Ljava/util/List; } diff --git a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/MarkdownParserFactory.kt b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/MarkdownParserFactory.kt new file mode 100644 index 000000000..e4abc9c84 --- /dev/null +++ b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/MarkdownParserFactory.kt @@ -0,0 +1,50 @@ +package org.jetbrains.jewel.markdown.processing + +import org.commonmark.parser.IncludeSourceSpans +import org.commonmark.parser.Parser +import org.jetbrains.jewel.markdown.extensions.MarkdownProcessorExtension + +/** + * Simplifies creating a [CommonMark `Parser`][Parser] while also + * supporting Jewel's [MarkdownProcessorExtension]s and the `optimizeEdits` + * flag. + */ +public object MarkdownParserFactory { + /** + * Create a [CommonMark `Parser`][Parser] with the provided [extensions]. + * The parser's [Builder][Parser.Builder] can be customized by providing a + * non-null [customizeBuilder] lambda. + * + * Make sure to provide the right value for [optimizeEdits], matching the + * one provided to the [MarkdownProcessor], or this parser will not be set + * up correctly. + * + * @param optimizeEdits If true, sets up the [Parser] to allow for edits + * optimization in the [MarkdownProcessor]. + * @param extensions A list of [MarkdownProcessorExtension] to attach. + * @param customizeBuilder Allows customizing the [Parser.Builder] before + * its [build()][Parser.Builder.build] method is called. + */ + public fun create( + optimizeEdits: Boolean, + extensions: List = emptyList(), + customizeBuilder: (Parser.Builder.() -> Parser.Builder)? = null, + ): Parser = + Parser.builder() + .extensions(extensions.map(MarkdownProcessorExtension::parserExtension)) + .run { + val builder = + if (optimizeEdits) { + includeSourceSpans(IncludeSourceSpans.BLOCKS) + } else { + this + } + + if (customizeBuilder != null) { + builder.customizeBuilder() + } else { + builder + } + } + .build() +} diff --git a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/MarkdownProcessor.kt b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/MarkdownProcessor.kt index 52d813d4a..ff051e37c 100644 --- a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/MarkdownProcessor.kt +++ b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/MarkdownProcessor.kt @@ -14,7 +14,6 @@ import org.commonmark.node.Node import org.commonmark.node.OrderedList import org.commonmark.node.Paragraph import org.commonmark.node.ThematicBreak -import org.commonmark.parser.IncludeSourceSpans import org.commonmark.parser.Parser import org.intellij.lang.annotations.Language import org.jetbrains.annotations.TestOnly @@ -30,25 +29,20 @@ import org.jetbrains.jewel.markdown.rendering.DefaultInlineMarkdownRenderer import org.commonmark.node.ListBlock as CMListBlock /** - * @param optimizeEdits Optional. Indicates whether the processing should only update the changed blocks - * by keeping a previous state in memory. Default is `true`, use `false` for immutable data. + * @param optimizeEdits Optional. Indicates whether the processing should + * only update the changed blocks by keeping a previous state in memory. + * Default is `true`, use `false` for immutable data. */ @ExperimentalJewelApi public class MarkdownProcessor( private val extensions: List = emptyList(), private val optimizeEdits: Boolean = true, + private val commonMarkParser: Parser = MarkdownParserFactory.create(optimizeEdits, extensions), ) { - public constructor(vararg extensions: MarkdownProcessorExtension) : this(extensions.toList()) - - private val commonMarkParser = - Parser.builder() - .let { builder -> - builder.extensions(extensions.map(MarkdownProcessorExtension::parserExtension)) - if (optimizeEdits) { - builder.includeSourceSpans(IncludeSourceSpans.BLOCKS) - } - builder.build() - } + public constructor( + optimizeEdits: Boolean = true, + vararg extensions: MarkdownProcessorExtension, + ) : this(extensions.toList(), optimizeEdits) private data class State(val lines: List, val blocks: List, val indexes: List>) @@ -58,9 +52,9 @@ public class MarkdownProcessor( internal fun getCurrentIndexesInTest() = currentState.indexes /** - * Parses a Markdown document, translating from CommonMark 0.31.2 - * to a list of [MarkdownBlock]. Inline Markdown in leaf nodes - * is contained in [InlineMarkdown], which can be rendered + * Parses a Markdown document, translating from CommonMark + * 0.31.2 to a list of [MarkdownBlock]. Inline Markdown in leaf + * nodes is contained in [InlineMarkdown], which can be rendered * to an [androidx.compose.ui.text.AnnotatedString] by using * [DefaultInlineMarkdownRenderer.renderAsAnnotatedString]. *