diff --git a/README.md b/README.md index da6bc9cf4..5de89917d 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,10 @@ The project is split in modules: * `int-ui-decorated-window` has a standalone version of the Int UI styling values for the custom window decoration that can be used in any Compose for Desktop app 6. `ide-laf-bridge` contains the Swing LaF bridge to use in IntelliJ Platform plugins (see more below) -7. `samples` contains the example apps, which showcase the available components: +7. `markdown` contains a few modules: + * `core` the core logic for parsing and rendering Markdown documents with Jewel, using GitHub-like styling + * `extensions` contains several extensions to the base CommonMark specs that can be used to add more features +8. `samples` contains the example apps, which showcase the available components: * `standalone` is a regular CfD app, using the standalone theme definitions and custom window decoration * `ide-plugin` is an IntelliJ plugin that showcases the use of the Swing Bridge diff --git a/build.gradle.kts b/build.gradle.kts index d698907af..f12d5bc14 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,6 +9,8 @@ dependencies { sarif(projects.ideLafBridge) sarif(projects.intUi.intUiDecoratedWindow) sarif(projects.intUi.intUiStandalone) + sarif(projects.markdown.core) + sarif(projects.markdown.extensionGfmAlerts) sarif(projects.samples.idePlugin) sarif(projects.samples.standalone) sarif(projects.ui) diff --git a/buildSrc/src/main/kotlin/ValidatePublicApiTask.kt b/buildSrc/src/main/kotlin/ValidatePublicApiTask.kt index c97e3c00b..1a6ecdb03 100644 --- a/buildSrc/src/main/kotlin/ValidatePublicApiTask.kt +++ b/buildSrc/src/main/kotlin/ValidatePublicApiTask.kt @@ -1,13 +1,18 @@ import org.gradle.api.GradleException import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.Input import org.gradle.api.tasks.SourceTask import org.gradle.api.tasks.TaskAction import java.io.File import java.util.Stack +import java.util.regex.PatternSyntaxException @CacheableTask open class ValidatePublicApiTask : SourceTask() { + @Input + var excludedClassRegexes: Set = emptySet() + init { group = "verification" @@ -26,11 +31,20 @@ open class ValidatePublicApiTask : SourceTask() { logger.info("Validating ${source.files.size} API file(s)...") val violations = mutableMapOf>() + val excludedRegexes = excludedClassRegexes.map { + try { + it.toRegex() + } catch (ignored: PatternSyntaxException) { + throw GradleException("Invalid data exclusion regex: '$it'") + } + }.toSet() + inputs.files.forEach { apiFile -> logger.lifecycle("Validating public API from file ${apiFile.path}") apiFile.useLines { lines -> val actualDataClasses = findDataClasses(lines) + .filterExclusions(excludedRegexes) if (actualDataClasses.isNotEmpty()) { violations[apiFile] = actualDataClasses @@ -92,6 +106,19 @@ open class ValidatePublicApiTask : SourceTask() { .keys return actualDataClasses } + + private fun Set.filterExclusions(excludedRegexes: Set): Set { + if (excludedRegexes.isEmpty()) return this + + return filterNot { dataClassFqn -> + val isExcluded = excludedRegexes.any { it.matchEntire(dataClassFqn) != null } + + if (isExcluded) { + logger.info(" Ignoring excluded data class $dataClassFqn") + } + isExcluded + }.toSet() + } } @Suppress("DataClassShouldBeImmutable") // Only used in a loop, saves memory and is faster diff --git a/buildSrc/src/main/kotlin/jewel-check-public-api.gradle.kts b/buildSrc/src/main/kotlin/jewel-check-public-api.gradle.kts index a87d62661..95653b1f1 100644 --- a/buildSrc/src/main/kotlin/jewel-check-public-api.gradle.kts +++ b/buildSrc/src/main/kotlin/jewel-check-public-api.gradle.kts @@ -1,5 +1,7 @@ @file:Suppress("UnstableApiUsage") +import org.jetbrains.jewel.buildlogic.apivalidation.ApiValidationExtension + plugins { id("org.jetbrains.kotlinx.binary-compatibility-validator") id("dev.drewhamilton.poko") @@ -23,12 +25,19 @@ kotlin { explicitApi() } +val extension = project.extensions.create("publicApiValidation", ApiValidationExtension::class.java) + +with(extension) { + excludedClassRegexes.convention(emptySet()) +} + tasks { val validatePublicApi = register("validatePublicApi") { include { it.file.extension == "api" } source(project.fileTree("api")) dependsOn(named("apiCheck")) + excludedClassRegexes = project.the().excludedClassRegexes.get() } named("check") { dependsOn(validatePublicApi) } diff --git a/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/apivalidation/ApiValidationExtension.kt b/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/apivalidation/ApiValidationExtension.kt new file mode 100644 index 000000000..fbb3af8fa --- /dev/null +++ b/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/apivalidation/ApiValidationExtension.kt @@ -0,0 +1,8 @@ +package org.jetbrains.jewel.buildlogic.apivalidation + +import org.gradle.api.provider.SetProperty + +interface ApiValidationExtension { + + val excludedClassRegexes: SetProperty +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 79bdec976..bf7000ece 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,4 +1,5 @@ [versions] +commonmark = "0.21.0" composeDesktop = "1.6.0-dev1397" detekt = "1.23.4" dokka = "1.8.20" @@ -14,6 +15,10 @@ kotlinxBinaryCompat = "0.14.0" poko = "0.13.1" [libraries] +commonmark-core = { module = "org.commonmark:commonmark", version.ref = "commonmark" } + +filePicker = { module = "com.darkrockstudios:mpfilepicker", version = "3.1.0" } + kotlinSarif = { module = "io.github.detekt.sarif4k:sarif4k", version.ref = "kotlinSarif" } kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" } diff --git a/markdown/README.md b/markdown/README.md new file mode 100644 index 000000000..a6add7cf1 --- /dev/null +++ b/markdown/README.md @@ -0,0 +1,121 @@ +## Jewel Markdown Renderer + +> [!IMPORTANT] +> The Jewel Markdown renderer is currently considered **experimental**. Its API and implementations may change at any +> time, and no guarantees are made for binary and source compatibility. It might also have bugs and missing features. + +Adds the ability to render Markdown as native Compose UI. + +Currently supports the [CommonMark 0.30](https://spec.commonmark.org/0.30/) specs. When `commonmark-java` will be +updated to support the new 0.31.2 specs, we'll inherit that. + +Additional supported Markdown, via extensions: + +* Alerts ([GitHub Flavored Markdown][alerts-specs]) — see [`extension-gfm-alerts`](extension-gfm-alerts) + +[alerts-specs]: https://github.com/orgs/community/discussions/16925 + +On the roadmap, but not currently supported — in no particular order: + +* Tables ([GitHub Flavored Markdown](https://github.github.com/gfm/#tables-extension-)) +* 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/)) +* Auto-linking ([GitHub Flavored Markdown](https://github.github.com/gfm/#autolinks-extension-)) +* Task list items ([GitHub Flavored Markdown](https://github.github.com/gfm/#task-list-items-extension-)) +* Keyboard shortcuts highlighting (specialized HTML handling) +* Collapsing sections ([GitHub Flavored Markdown][details-specs]) +* Theme-sensitive image loading ([GitHub Flavored Markdown][dark-mode-pics-specs]) +* Emojis ([GitHub Flavored Markdown][emoji-specs]) +* Footnotes ([GitHub Flavored Markdown][footnotes-specs]) + +[details-specs]: https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-collapsed-sections + +[dark-mode-pics-specs]: https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#specifying-the-theme-an-image-is-shown-to + +[emoji-specs]: https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#using-emojis + +[footnotes-specs]: https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#footnotes + +Not supported, and not on the roadmap: + +* Inline HTML rendering +* Mermaid diagrams (GitHub Flavored Markdown) +* LaTeX rendering, both inline and not (GitHub Flavored Markdown) +* topoJSON/geoJSON rendering (GitHub Flavored Markdown) +* 3D STL models (GitHub Flavored Markdown) +* Rich rendering of embeds such as videos, YouTube, GitHub Gists/... + +## How to use Jewel's Markdown renderer + +The process that leads to rendering Markdown in a native UI is two-pass. + +The first pass is an upfront rendering that pre-processes blocks into `MarkdownBlock`s but doesn't touch the inline +Markdown. It's recommended to run this outside of the composition, since it has no dependencies on it. + +```kotlin +// Somewhere outside of composition... +val processor = MarkdownProcessor() +val rawMarkdown = "..." +val processedBlocks = processor.processMarkdownDocument(rawMarkdown) +``` + +The second pass is done in the composition, and essentially renders a series of `MarkdownBlock`s into native Jewel UI: + +```kotlin +@Composable +fun Markdown(blocks: List) { + val isDark = JewelTheme.isDark + val blockRenderer = remember(markdownStyling, isDark) { + if (isDark) MarkdownBlockRenderer.dark() else MarkdownBlockRenderer.light() + } + + val scrollState = rememberScrollState() + SelectionContainer(Modifier.fillMaxSize()) { + Column( + state = scrollState, + verticalArrangement = Arrangement.spacedBy(markdownStyling.blockVerticalSpacing), + ) { + items(markdownBlocks) { blockRenderer.render(it) } + } + } +} +``` + +If you expect long Markdown documents, you can also use a `LazyColumn` to get better performances. + +### Using extensions + +By default, the processor will ignore any kind of Markdown it doesn't support. To support additional features, such as +ones found in GitHub Flavored Markdown, you can use extensions. If you don't specify any extension, the processor will +be restricted to the [CommonMark specs](https://specs.commonmark.org) as supported by +[`commonmark-java`](https://github.com/commonmark/commonmark-java). + +Extensions are composed of two parts: a parsing and a rendering part. The two parts need to be passed to the +`MarkdownProcessor` and `MarkdownBlockRenderer`, respectively: + +```kotlin +// Where the parsing happens... +val parsingExtensions = listOf(/*...*/) +val processor = MarkdownProcessor(extensions) + +// Where the rendering happens... +val blockRenderer = remember(markdownStyling, isDark) { + if (isDark) { + MarkdownBlockRenderer.dark( + rendererExtensions = listOf(/*...*/), + inlineRenderer = InlineMarkdownRenderer.default(parsingExtensions), + ) + } else { + MarkdownBlockRenderer.light( + rendererExtensions = listOf(/*...*/), + inlineRenderer = InlineMarkdownRenderer.default(parsingExtensions), + ) + } +} +``` + +It is strongly recommended to use the corresponding set of rendering extensions as the ones used for parsing, otherwise +the custom blocks will be parsed but not rendered. + +Note that you should create an `InlineMarkdownRenderer` with the same list of extensions that was used to build the +processor, as even though inline rendering extensions are not supported yet, they will be in the future. diff --git a/markdown/core/api/core.api b/markdown/core/api/core.api new file mode 100644 index 000000000..0f44f669e --- /dev/null +++ b/markdown/core/api/core.api @@ -0,0 +1,868 @@ +public abstract interface class org/jetbrains/jewel/markdown/BlockWithInlineMarkdown { + public abstract fun getInlineContent-Ns87O_s ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/InlineMarkdown { + public static final synthetic fun box-impl (Ljava/lang/String;)Lorg/jetbrains/jewel/markdown/InlineMarkdown; + public static fun constructor-impl (Ljava/lang/String;)Ljava/lang/String; + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (Ljava/lang/String;Ljava/lang/Object;)Z + public static final fun equals-impl0 (Ljava/lang/String;Ljava/lang/String;)Z + public final fun getContent ()Ljava/lang/String; + public fun hashCode ()I + public static fun hashCode-impl (Ljava/lang/String;)I + public fun toString ()Ljava/lang/String; + public static fun toString-impl (Ljava/lang/String;)Ljava/lang/String; + public final synthetic fun unbox-impl ()Ljava/lang/String; +} + +public abstract interface class org/jetbrains/jewel/markdown/MarkdownBlock { +} + +public final class org/jetbrains/jewel/markdown/MarkdownBlock$BlockQuote : org/jetbrains/jewel/markdown/MarkdownBlock { + public static final field $stable I + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$BlockQuote; + public static synthetic fun copy$default (Lorg/jetbrains/jewel/markdown/MarkdownBlock$BlockQuote;Ljava/util/List;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$BlockQuote; + public fun equals (Ljava/lang/Object;)Z + public final fun getContent ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract interface class org/jetbrains/jewel/markdown/MarkdownBlock$CodeBlock : org/jetbrains/jewel/markdown/MarkdownBlock { + public abstract fun getContent ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/MarkdownBlock$CodeBlock$FencedCodeBlock : org/jetbrains/jewel/markdown/MarkdownBlock$CodeBlock { + public static final field $stable I + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2-EIRQHX8 ()Ljava/lang/String; + public final fun copy-k5OzbWQ (Ljava/lang/String;Ljava/lang/String;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$CodeBlock$FencedCodeBlock; + public static synthetic fun copy-k5OzbWQ$default (Lorg/jetbrains/jewel/markdown/MarkdownBlock$CodeBlock$FencedCodeBlock;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$CodeBlock$FencedCodeBlock; + public fun equals (Ljava/lang/Object;)Z + public fun getContent ()Ljava/lang/String; + public final fun getMimeType-EIRQHX8 ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/MarkdownBlock$CodeBlock$IndentedCodeBlock : org/jetbrains/jewel/markdown/MarkdownBlock$CodeBlock { + public static final field $stable I + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$CodeBlock$IndentedCodeBlock; + public static synthetic fun copy$default (Lorg/jetbrains/jewel/markdown/MarkdownBlock$CodeBlock$IndentedCodeBlock;Ljava/lang/String;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$CodeBlock$IndentedCodeBlock; + public fun equals (Ljava/lang/Object;)Z + public fun getContent ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract interface class org/jetbrains/jewel/markdown/MarkdownBlock$Extension : org/jetbrains/jewel/markdown/MarkdownBlock { +} + +public abstract interface class org/jetbrains/jewel/markdown/MarkdownBlock$Heading : org/jetbrains/jewel/markdown/BlockWithInlineMarkdown, org/jetbrains/jewel/markdown/MarkdownBlock { +} + +public final class org/jetbrains/jewel/markdown/MarkdownBlock$Heading$H1 : org/jetbrains/jewel/markdown/MarkdownBlock$Heading { + public static final field $stable I + public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1-Ns87O_s ()Ljava/lang/String; + public final fun copy-0TBB8uk (Ljava/lang/String;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H1; + public static synthetic fun copy-0TBB8uk$default (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H1;Ljava/lang/String;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H1; + public fun equals (Ljava/lang/Object;)Z + public fun getInlineContent-Ns87O_s ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/MarkdownBlock$Heading$H2 : org/jetbrains/jewel/markdown/MarkdownBlock$Heading { + public static final field $stable I + public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1-Ns87O_s ()Ljava/lang/String; + public final fun copy-0TBB8uk (Ljava/lang/String;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H2; + public static synthetic fun copy-0TBB8uk$default (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H2;Ljava/lang/String;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H2; + public fun equals (Ljava/lang/Object;)Z + public fun getInlineContent-Ns87O_s ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/MarkdownBlock$Heading$H3 : org/jetbrains/jewel/markdown/MarkdownBlock$Heading { + public static final field $stable I + public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1-Ns87O_s ()Ljava/lang/String; + public final fun copy-0TBB8uk (Ljava/lang/String;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H3; + public static synthetic fun copy-0TBB8uk$default (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H3;Ljava/lang/String;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H3; + public fun equals (Ljava/lang/Object;)Z + public fun getInlineContent-Ns87O_s ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/MarkdownBlock$Heading$H4 : org/jetbrains/jewel/markdown/MarkdownBlock$Heading { + public static final field $stable I + public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1-Ns87O_s ()Ljava/lang/String; + public final fun copy-0TBB8uk (Ljava/lang/String;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H4; + public static synthetic fun copy-0TBB8uk$default (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H4;Ljava/lang/String;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H4; + public fun equals (Ljava/lang/Object;)Z + public fun getInlineContent-Ns87O_s ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/MarkdownBlock$Heading$H5 : org/jetbrains/jewel/markdown/MarkdownBlock$Heading { + public static final field $stable I + public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1-Ns87O_s ()Ljava/lang/String; + public final fun copy-0TBB8uk (Ljava/lang/String;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H5; + public static synthetic fun copy-0TBB8uk$default (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H5;Ljava/lang/String;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H5; + public fun equals (Ljava/lang/Object;)Z + public fun getInlineContent-Ns87O_s ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/MarkdownBlock$Heading$H6 : org/jetbrains/jewel/markdown/MarkdownBlock$Heading { + public static final field $stable I + public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1-Ns87O_s ()Ljava/lang/String; + public final fun copy-0TBB8uk (Ljava/lang/String;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H6; + public static synthetic fun copy-0TBB8uk$default (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H6;Ljava/lang/String;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H6; + public fun equals (Ljava/lang/Object;)Z + public fun getInlineContent-Ns87O_s ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/MarkdownBlock$HtmlBlock : org/jetbrains/jewel/markdown/MarkdownBlock { + public static final field $stable I + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$HtmlBlock; + public static synthetic fun copy$default (Lorg/jetbrains/jewel/markdown/MarkdownBlock$HtmlBlock;Ljava/lang/String;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$HtmlBlock; + public fun equals (Ljava/lang/Object;)Z + public final fun getContent ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/MarkdownBlock$Image : org/jetbrains/jewel/markdown/MarkdownBlock { + public static final field $stable I + public fun (Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$Image; + public static synthetic fun copy$default (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Image;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$Image; + public fun equals (Ljava/lang/Object;)Z + public final fun getAltString ()Ljava/lang/String; + public final fun getUrl ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract interface class org/jetbrains/jewel/markdown/MarkdownBlock$ListBlock : org/jetbrains/jewel/markdown/MarkdownBlock { + public abstract fun getItems ()Ljava/util/List; + public abstract fun isTight ()Z +} + +public final class org/jetbrains/jewel/markdown/MarkdownBlock$ListBlock$OrderedList : org/jetbrains/jewel/markdown/MarkdownBlock$ListBlock { + public static final field $stable I + public fun (Ljava/util/List;ZIC)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Z + public final fun component3 ()I + public final fun component4 ()C + public final fun copy (Ljava/util/List;ZIC)Lorg/jetbrains/jewel/markdown/MarkdownBlock$ListBlock$OrderedList; + public static synthetic fun copy$default (Lorg/jetbrains/jewel/markdown/MarkdownBlock$ListBlock$OrderedList;Ljava/util/List;ZICILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$ListBlock$OrderedList; + public fun equals (Ljava/lang/Object;)Z + public final fun getDelimiter ()C + public fun getItems ()Ljava/util/List; + public final fun getStartFrom ()I + public fun hashCode ()I + public fun isTight ()Z + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/MarkdownBlock$ListBlock$UnorderedList : org/jetbrains/jewel/markdown/MarkdownBlock$ListBlock { + public static final field $stable I + public fun (Ljava/util/List;ZC)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Z + public final fun component3 ()C + public final fun copy (Ljava/util/List;ZC)Lorg/jetbrains/jewel/markdown/MarkdownBlock$ListBlock$UnorderedList; + public static synthetic fun copy$default (Lorg/jetbrains/jewel/markdown/MarkdownBlock$ListBlock$UnorderedList;Ljava/util/List;ZCILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$ListBlock$UnorderedList; + public fun equals (Ljava/lang/Object;)Z + public final fun getBulletMarker ()C + public fun getItems ()Ljava/util/List; + public fun hashCode ()I + public fun isTight ()Z + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/MarkdownBlock$ListItem : org/jetbrains/jewel/markdown/MarkdownBlock { + public static final field $stable I + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$ListItem; + public static synthetic fun copy$default (Lorg/jetbrains/jewel/markdown/MarkdownBlock$ListItem;Ljava/util/List;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$ListItem; + public fun equals (Ljava/lang/Object;)Z + public final fun getContent ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/MarkdownBlock$Paragraph : org/jetbrains/jewel/markdown/BlockWithInlineMarkdown, org/jetbrains/jewel/markdown/MarkdownBlock { + public static final field $stable I + public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1-Ns87O_s ()Ljava/lang/String; + public final fun copy-0TBB8uk (Ljava/lang/String;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$Paragraph; + public static synthetic fun copy-0TBB8uk$default (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Paragraph;Ljava/lang/String;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$Paragraph; + public fun equals (Ljava/lang/Object;)Z + public fun getInlineContent-Ns87O_s ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/MarkdownBlock$ThematicBreak : org/jetbrains/jewel/markdown/MarkdownBlock { + public static final field $stable I + public static final field INSTANCE Lorg/jetbrains/jewel/markdown/MarkdownBlock$ThematicBreak; +} + +public final class org/jetbrains/jewel/markdown/MimeType { + public static final field ATTR_FOLDER_TYPE Ljava/lang/String; + public static final field ATTR_ROLE Ljava/lang/String; + public static final field ATTR_ROOT_TAG Ljava/lang/String; + public static final field VALUE_MANIFEST Ljava/lang/String; + public static final field VALUE_RESOURCE Ljava/lang/String; + public static final fun base-ip6yS68 (Ljava/lang/String;)Ljava/lang/String; + public static final synthetic fun box-impl (Ljava/lang/String;)Lorg/jetbrains/jewel/markdown/MimeType; + public static fun constructor-impl (Ljava/lang/String;)Ljava/lang/String; + public static final fun displayName-impl (Ljava/lang/String;)Ljava/lang/String; + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (Ljava/lang/String;Ljava/lang/Object;)Z + public static final fun equals-impl0 (Ljava/lang/String;Ljava/lang/String;)Z + public fun hashCode ()I + public static fun hashCode-impl (Ljava/lang/String;)I + public fun toString ()Ljava/lang/String; + public static fun toString-impl (Ljava/lang/String;)Ljava/lang/String; + public final synthetic fun unbox-impl ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/MimeType$Known { + public static final field $stable I + public static final field INSTANCE Lorg/jetbrains/jewel/markdown/MimeType$Known; + public final fun fromMarkdownLanguageName-HxXrbs8 (Ljava/lang/String;)Ljava/lang/String; + public final fun getAGSL-ip6yS68 ()Ljava/lang/String; + public final fun getAIDL-ip6yS68 ()Ljava/lang/String; + public final fun getC-ip6yS68 ()Ljava/lang/String; + public final fun getCPP-ip6yS68 ()Ljava/lang/String; + public final fun getDART-ip6yS68 ()Ljava/lang/String; + public final fun getGO-ip6yS68 ()Ljava/lang/String; + public final fun getGRADLE-ip6yS68 ()Ljava/lang/String; + public final fun getGRADLE_KTS-ip6yS68 ()Ljava/lang/String; + public final fun getGROOVY-ip6yS68 ()Ljava/lang/String; + public final fun getJAVA-ip6yS68 ()Ljava/lang/String; + public final fun getJAVASCRIPT-ip6yS68 ()Ljava/lang/String; + public final fun getJSON-ip6yS68 ()Ljava/lang/String; + public final fun getKOTLIN-ip6yS68 ()Ljava/lang/String; + public final fun getMANIFEST-ip6yS68 ()Ljava/lang/String; + public final fun getPROGUARD-ip6yS68 ()Ljava/lang/String; + public final fun getPROPERTIES-ip6yS68 ()Ljava/lang/String; + public final fun getPROTO-ip6yS68 ()Ljava/lang/String; + public final fun getPYTHON-ip6yS68 ()Ljava/lang/String; + public final fun getREGEX-ip6yS68 ()Ljava/lang/String; + public final fun getRESOURCE-ip6yS68 ()Ljava/lang/String; + public final fun getRUST-ip6yS68 ()Ljava/lang/String; + public final fun getSHELL-ip6yS68 ()Ljava/lang/String; + public final fun getSQL-ip6yS68 ()Ljava/lang/String; + public final fun getSVG-ip6yS68 ()Ljava/lang/String; + public final fun getTEXT-ip6yS68 ()Ljava/lang/String; + public final fun getTOML-ip6yS68 ()Ljava/lang/String; + public final fun getTYPESCRIPT-ip6yS68 ()Ljava/lang/String; + public final fun getUNKNOWN-ip6yS68 ()Ljava/lang/String; + public final fun getVERSION_CATALOG-ip6yS68 ()Ljava/lang/String; + public final fun getXML-ip6yS68 ()Ljava/lang/String; + public final fun getYAML-ip6yS68 ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/MimeTypeKt { + public static final fun isGradle-K9GpHcc (Ljava/lang/String;)Z + public static final fun isJava-K9GpHcc (Ljava/lang/String;)Z + public static final fun isKotlin-K9GpHcc (Ljava/lang/String;)Z + public static final fun isManifest-K9GpHcc (Ljava/lang/String;)Z + public static final fun isProto-K9GpHcc (Ljava/lang/String;)Z + public static final fun isRegex-K9GpHcc (Ljava/lang/String;)Z + public static final fun isSql-K9GpHcc (Ljava/lang/String;)Z + public static final fun isVersionCatalog-K9GpHcc (Ljava/lang/String;)Z + public static final fun isXml-K9GpHcc (Ljava/lang/String;)Z +} + +public abstract interface class org/jetbrains/jewel/markdown/extensions/MarkdownBlockProcessorExtension { + public abstract fun canProcess (Lorg/commonmark/node/CustomBlock;)Z + public abstract fun processMarkdownBlock (Lorg/commonmark/node/CustomBlock;Lorg/jetbrains/jewel/markdown/processing/MarkdownProcessor;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$Extension; +} + +public abstract interface class org/jetbrains/jewel/markdown/extensions/MarkdownBlockRendererExtension { + public abstract fun canRender (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Extension;)Z + public abstract fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Extension;Lorg/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer;Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer;Landroidx/compose/runtime/Composer;I)V +} + +public abstract interface class org/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension { + public abstract fun getParserExtension ()Lorg/commonmark/parser/Parser$ParserExtension; + public abstract fun getProcessorExtension ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownBlockProcessorExtension; + public abstract fun getTextRendererExtension ()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 final class org/jetbrains/jewel/markdown/processing/MarkdownProcessor { + public static final field $stable I + public fun ()V + public fun (Ljava/util/List;)V + public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun ([Lorg/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension;)V + public final fun processChildren (Lorg/commonmark/node/Node;)Ljava/util/List; + public final fun processMarkdownDocument (Ljava/lang/String;)Ljava/util/List; +} + +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 + public fun ([Lorg/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension;)V + public fun renderAsAnnotatedString-44Ri3s0 (Ljava/lang/String;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;)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;Lkotlin/jvm/functions/Function1;)V + public fun render (Ljava/util/List;Landroidx/compose/runtime/Composer;I)V + public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$BlockQuote;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$BlockQuote;Landroidx/compose/runtime/Composer;I)V + public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$CodeBlock$FencedCodeBlock;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced;Landroidx/compose/runtime/Composer;I)V + public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$CodeBlock$IndentedCodeBlock;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Indented;Landroidx/compose/runtime/Composer;I)V + public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$CodeBlock;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code;Landroidx/compose/runtime/Composer;I)V + public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H1;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H1;Landroidx/compose/runtime/Composer;I)V + public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H2;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H2;Landroidx/compose/runtime/Composer;I)V + public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H3;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H3;Landroidx/compose/runtime/Composer;I)V + public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H4;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H4;Landroidx/compose/runtime/Composer;I)V + public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H5;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H5;Landroidx/compose/runtime/Composer;I)V + public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H6;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H6;Landroidx/compose/runtime/Composer;I)V + public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading;Landroidx/compose/runtime/Composer;I)V + public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$HtmlBlock;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$HtmlBlock;Landroidx/compose/runtime/Composer;I)V + public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Image;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Image;Landroidx/compose/runtime/Composer;I)V + public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$ListBlock$OrderedList;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Ordered;Landroidx/compose/runtime/Composer;I)V + public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$ListBlock$UnorderedList;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Unordered;Landroidx/compose/runtime/Composer;I)V + public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$ListBlock;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List;Landroidx/compose/runtime/Composer;I)V + public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$ListItem;Landroidx/compose/runtime/Composer;I)V + public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Paragraph;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Paragraph;Landroidx/compose/runtime/Composer;I)V + public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock;Landroidx/compose/runtime/Composer;I)V + public fun renderThematicBreak (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$ThematicBreak;Landroidx/compose/runtime/Composer;I)V +} + +public final class org/jetbrains/jewel/markdown/rendering/DefaultStylingKt { + public static final fun dark (Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling$Companion;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/SpanStyle;Landroidx/compose/ui/text/SpanStyle;Landroidx/compose/ui/text/SpanStyle;Landroidx/compose/ui/text/SpanStyle;Landroidx/compose/ui/text/SpanStyle;Z)Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling; + public static final fun dark (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Companion;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Indented;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code; + public static final fun dark (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$Companion;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H1;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H2;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H3;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H4;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H5;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H6;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading; + public static final fun dark (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Companion;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Ordered;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Unordered;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List; + public static final fun dark (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Paragraph$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Paragraph; + public static synthetic fun dark$default (Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling$Companion;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/SpanStyle;Landroidx/compose/ui/text/SpanStyle;Landroidx/compose/ui/text/SpanStyle;Landroidx/compose/ui/text/SpanStyle;Landroidx/compose/ui/text/SpanStyle;ZILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling; + public static synthetic fun dark$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Companion;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Indented;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code; + public static synthetic fun dark$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$Companion;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H1;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H2;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H3;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H4;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H5;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H6;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading; + public static synthetic fun dark$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Companion;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Ordered;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Unordered;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List; + public static synthetic fun dark$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Paragraph$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Paragraph; + public static final fun dark-9ek060M (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Unordered$Companion;Ljava/lang/Character;Landroidx/compose/ui/text/TextStyle;FFFLandroidx/compose/foundation/layout/PaddingValues;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Unordered; + public static synthetic fun dark-9ek060M$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Unordered$Companion;Ljava/lang/Character;Landroidx/compose/ui/text/TextStyle;FFFLandroidx/compose/foundation/layout/PaddingValues;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Unordered; + public static final fun dark-EnRY0Kc (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$ThematicBreak$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJ)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$ThematicBreak; + public static synthetic fun dark-EnRY0Kc$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$ThematicBreak$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$ThematicBreak; + public static final fun dark-OgMsbsM (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$HtmlBlock$Companion;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/ui/graphics/Shape;JFJZ)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$HtmlBlock; + public static synthetic fun dark-OgMsbsM$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$HtmlBlock$Companion;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/ui/graphics/Shape;JFJZILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$HtmlBlock; + public static final fun dark-RKTsvxU (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Indented$Companion;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/ui/graphics/Shape;JFJZZ)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Indented; + public static synthetic fun dark-RKTsvxU$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Indented$Companion;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/ui/graphics/Shape;JFJZZILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Indented; + public static final fun dark-Zc45R8w (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced$Companion;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/ui/graphics/Shape;JFJZZLandroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/layout/PaddingValues;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced$InfoPosition;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced; + public static synthetic fun dark-Zc45R8w$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced$Companion;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/ui/graphics/Shape;JFJZZLandroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/layout/PaddingValues;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced$InfoPosition;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced; + public static final fun dark-aX9k8as (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H1$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H1; + public static final fun dark-aX9k8as (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H2$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H2; + public static final fun dark-aX9k8as (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H3$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H3; + public static final fun dark-aX9k8as (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H4$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H4; + public static final fun dark-aX9k8as (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H5$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H5; + public static final fun dark-aX9k8as (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H6$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H6; + public static synthetic fun dark-aX9k8as$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H1$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H1; + public static synthetic fun dark-aX9k8as$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H2$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H2; + public static synthetic fun dark-aX9k8as$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H3$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H3; + public static synthetic fun dark-aX9k8as$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H4$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H4; + public static synthetic fun dark-aX9k8as$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H5$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H5; + public static synthetic fun dark-aX9k8as$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H6$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H6; + public static final fun dark-jfnsLPA (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Companion;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;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling; + public static synthetic fun dark-jfnsLPA$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Companion;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;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling; + public static final fun dark-kgLgf-Y (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Ordered$Companion;Landroidx/compose/ui/text/TextStyle;FFIFFLandroidx/compose/foundation/layout/PaddingValues;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Ordered; + public static synthetic fun dark-kgLgf-Y$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Ordered$Companion;Landroidx/compose/ui/text/TextStyle;FFIFFLandroidx/compose/foundation/layout/PaddingValues;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Ordered; + public static final fun dark-pI2OzKA (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$BlockQuote$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;IJ)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$BlockQuote; + public static synthetic fun dark-pI2OzKA$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$BlockQuote$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;IJILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$BlockQuote; + public static final fun default-1Fc8zlc (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Image$Companion;Landroidx/compose/ui/Alignment;Landroidx/compose/ui/layout/ContentScale;Landroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/ui/graphics/Shape;JFJ)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Image; + public static synthetic fun default-1Fc8zlc$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Image$Companion;Landroidx/compose/ui/Alignment;Landroidx/compose/ui/layout/ContentScale;Landroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/ui/graphics/Shape;JFJILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Image; + public static final fun light (Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling$Companion;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/SpanStyle;Landroidx/compose/ui/text/SpanStyle;Landroidx/compose/ui/text/SpanStyle;Landroidx/compose/ui/text/SpanStyle;Landroidx/compose/ui/text/SpanStyle;Z)Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling; + public static final fun light (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Companion;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Indented;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code; + public static final fun light (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$Companion;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H1;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H2;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H3;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H4;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H5;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H6;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading; + public static final fun light (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Companion;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Ordered;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Unordered;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List; + public static final fun light (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Paragraph$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Paragraph; + public static synthetic fun light$default (Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling$Companion;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/SpanStyle;Landroidx/compose/ui/text/SpanStyle;Landroidx/compose/ui/text/SpanStyle;Landroidx/compose/ui/text/SpanStyle;Landroidx/compose/ui/text/SpanStyle;ZILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling; + public static synthetic fun light$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Companion;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Indented;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code; + public static synthetic fun light$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$Companion;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H1;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H2;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H3;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H4;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H5;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H6;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading; + public static synthetic fun light$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Companion;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Ordered;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Unordered;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List; + public static synthetic fun light$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Paragraph$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Paragraph; + public static final fun light-9ek060M (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Unordered$Companion;Ljava/lang/Character;Landroidx/compose/ui/text/TextStyle;FFFLandroidx/compose/foundation/layout/PaddingValues;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Unordered; + public static synthetic fun light-9ek060M$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Unordered$Companion;Ljava/lang/Character;Landroidx/compose/ui/text/TextStyle;FFFLandroidx/compose/foundation/layout/PaddingValues;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Unordered; + public static final fun light-EnRY0Kc (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$ThematicBreak$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJ)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$ThematicBreak; + public static synthetic fun light-EnRY0Kc$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$ThematicBreak$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$ThematicBreak; + public static final fun light-OgMsbsM (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$HtmlBlock$Companion;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/ui/graphics/Shape;JFJZ)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$HtmlBlock; + public static synthetic fun light-OgMsbsM$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$HtmlBlock$Companion;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/ui/graphics/Shape;JFJZILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$HtmlBlock; + public static final fun light-RKTsvxU (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Indented$Companion;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/ui/graphics/Shape;JFJZZ)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Indented; + public static synthetic fun light-RKTsvxU$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Indented$Companion;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/ui/graphics/Shape;JFJZZILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Indented; + public static final fun light-Zc45R8w (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced$Companion;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/ui/graphics/Shape;JFJZZLandroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/layout/PaddingValues;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced$InfoPosition;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced; + public static synthetic fun light-Zc45R8w$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced$Companion;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/ui/graphics/Shape;JFJZZLandroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/layout/PaddingValues;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced$InfoPosition;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced; + public static final fun light-aX9k8as (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H1$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H1; + public static final fun light-aX9k8as (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H2$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H2; + public static final fun light-aX9k8as (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H3$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H3; + public static final fun light-aX9k8as (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H4$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H4; + public static final fun light-aX9k8as (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H5$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H5; + public static final fun light-aX9k8as (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H6$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H6; + public static synthetic fun light-aX9k8as$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H1$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H1; + public static synthetic fun light-aX9k8as$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H2$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H2; + public static synthetic fun light-aX9k8as$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H3$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H3; + public static synthetic fun light-aX9k8as$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H4$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H4; + public static synthetic fun light-aX9k8as$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H5$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H5; + public static synthetic fun light-aX9k8as$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H6$Companion;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H6; + public static final fun light-jfnsLPA (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Companion;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;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling; + public static synthetic fun light-jfnsLPA$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Companion;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;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling; + public static final fun light-kgLgf-Y (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Ordered$Companion;Landroidx/compose/ui/text/TextStyle;FFIFFLandroidx/compose/foundation/layout/PaddingValues;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Ordered; + public static synthetic fun light-kgLgf-Y$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Ordered$Companion;Landroidx/compose/ui/text/TextStyle;FFIFFLandroidx/compose/foundation/layout/PaddingValues;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Ordered; + public static final fun light-pI2OzKA (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$BlockQuote$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;IJ)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$BlockQuote; + public static synthetic fun light-pI2OzKA$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$BlockQuote$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;IJILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$BlockQuote; +} + +public abstract interface class org/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer { + public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer$Companion; + public abstract fun renderAsAnnotatedString-44Ri3s0 (Ljava/lang/String;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;)Landroidx/compose/ui/text/AnnotatedString; +} + +public final class org/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer$Companion { + public final fun default (Ljava/util/List;)Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer; + public static synthetic fun default$default (Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer$Companion;Ljava/util/List;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer; +} + +public final class org/jetbrains/jewel/markdown/rendering/InlinesStyling { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling$Companion; + public fun (Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/SpanStyle;Landroidx/compose/ui/text/SpanStyle;Landroidx/compose/ui/text/SpanStyle;Landroidx/compose/ui/text/SpanStyle;Landroidx/compose/ui/text/SpanStyle;Z)V + public fun equals (Ljava/lang/Object;)Z + public final fun getEmphasis ()Landroidx/compose/ui/text/SpanStyle; + public final fun getInlineCode ()Landroidx/compose/ui/text/SpanStyle; + public final fun getInlineHtml ()Landroidx/compose/ui/text/SpanStyle; + public final fun getLink ()Landroidx/compose/ui/text/SpanStyle; + public final fun getRenderInlineHtml ()Z + public final fun getStrongEmphasis ()Landroidx/compose/ui/text/SpanStyle; + public final fun getTextStyle ()Landroidx/compose/ui/text/TextStyle; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/rendering/InlinesStyling$Companion { +} + +public abstract interface class org/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer { + public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer$Companion; + public abstract fun render (Ljava/util/List;Landroidx/compose/runtime/Composer;I)V + public abstract fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$BlockQuote;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$BlockQuote;Landroidx/compose/runtime/Composer;I)V + public abstract fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$CodeBlock$FencedCodeBlock;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced;Landroidx/compose/runtime/Composer;I)V + public abstract fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$CodeBlock$IndentedCodeBlock;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Indented;Landroidx/compose/runtime/Composer;I)V + public abstract fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$CodeBlock;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code;Landroidx/compose/runtime/Composer;I)V + public abstract fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H1;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H1;Landroidx/compose/runtime/Composer;I)V + public abstract fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H2;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H2;Landroidx/compose/runtime/Composer;I)V + public abstract fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H3;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H3;Landroidx/compose/runtime/Composer;I)V + public abstract fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H4;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H4;Landroidx/compose/runtime/Composer;I)V + public abstract fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H5;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H5;Landroidx/compose/runtime/Composer;I)V + public abstract fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading$H6;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H6;Landroidx/compose/runtime/Composer;I)V + public abstract fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading;Landroidx/compose/runtime/Composer;I)V + public abstract fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$HtmlBlock;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$HtmlBlock;Landroidx/compose/runtime/Composer;I)V + public abstract fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Image;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Image;Landroidx/compose/runtime/Composer;I)V + public abstract fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$ListBlock$OrderedList;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Ordered;Landroidx/compose/runtime/Composer;I)V + public abstract fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$ListBlock$UnorderedList;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Unordered;Landroidx/compose/runtime/Composer;I)V + public abstract fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$ListBlock;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List;Landroidx/compose/runtime/Composer;I)V + public abstract fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$ListItem;Landroidx/compose/runtime/Composer;I)V + public abstract fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Paragraph;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Paragraph;Landroidx/compose/runtime/Composer;I)V + public abstract fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock;Landroidx/compose/runtime/Composer;I)V + public abstract fun renderThematicBreak (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$ThematicBreak;Landroidx/compose/runtime/Composer;I)V +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer$Companion { + public final fun dark (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling;Ljava/util/List;Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer; + public static synthetic fun dark$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer$Companion;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling;Ljava/util/List;Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer; + public final fun light (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling;Ljava/util/List;Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer; + public static synthetic fun light$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer$Companion;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling;Ljava/util/List;Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer; +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling { + public static final field $stable I + 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 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; + public final fun getHeading ()Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading; + public final fun getHtmlBlock ()Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$HtmlBlock; + public final fun getImage ()Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Image; + public final fun getList ()Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List; + public final fun getParagraph ()Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Paragraph; + public final fun getThematicBreak ()Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$ThematicBreak; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$BlockQuote { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$BlockQuote$Companion; + public synthetic fun (Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;IJLkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z + public final fun getLineColor-0d7_KjU ()J + public final fun getLineWidth-D9Ej5fM ()F + public final fun getPadding ()Landroidx/compose/foundation/layout/PaddingValues; + public final fun getPathEffect ()Landroidx/compose/ui/graphics/PathEffect; + public final fun getStrokeCap-KaPHkGw ()I + public final fun getTextColor-0d7_KjU ()J + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$BlockQuote$Companion { +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Companion; + public fun (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Indented;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced;)V + public fun equals (Ljava/lang/Object;)Z + public final fun getFenced ()Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced; + public final fun getIndented ()Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Indented; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Companion { +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced$Companion; + public synthetic fun (Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/ui/graphics/Shape;JFJZZLandroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/layout/PaddingValues;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced$InfoPosition;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z + public final fun getBackground-0d7_KjU ()J + public final fun getBorderColor-0d7_KjU ()J + public final fun getBorderWidth-D9Ej5fM ()F + public final fun getFillWidth ()Z + public final fun getInfoPadding ()Landroidx/compose/foundation/layout/PaddingValues; + public final fun getInfoPosition ()Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced$InfoPosition; + public final fun getInfoTextStyle ()Landroidx/compose/ui/text/TextStyle; + public final fun getPadding ()Landroidx/compose/foundation/layout/PaddingValues; + public final fun getScrollsHorizontally ()Z + public final fun getShape ()Landroidx/compose/ui/graphics/Shape; + public final fun getTextStyle ()Landroidx/compose/ui/text/TextStyle; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced$Companion { +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced$InfoPosition : java/lang/Enum { + public static final field BottomCenter Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced$InfoPosition; + public static final field BottomEnd Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced$InfoPosition; + public static final field BottomStart Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced$InfoPosition; + public static final field Hide Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced$InfoPosition; + public static final field TopCenter Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced$InfoPosition; + public static final field TopEnd Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced$InfoPosition; + public static final field TopStart Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced$InfoPosition; + public static fun valueOf (Ljava/lang/String;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced$InfoPosition; + public static fun values ()[Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced$InfoPosition; +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Indented { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Indented$Companion; + public synthetic fun (Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/ui/graphics/Shape;JFJZZLkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z + public final fun getBackground-0d7_KjU ()J + public final fun getBorderColor-0d7_KjU ()J + public final fun getBorderWidth-D9Ej5fM ()F + public final fun getFillWidth ()Z + public final fun getPadding ()Landroidx/compose/foundation/layout/PaddingValues; + public final fun getScrollsHorizontally ()Z + public final fun getShape ()Landroidx/compose/ui/graphics/Shape; + public final fun getTextStyle ()Landroidx/compose/ui/text/TextStyle; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Indented$Companion { +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$Companion { +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$Companion; + public fun (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H1;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H2;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H3;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H4;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H5;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H6;)V + public fun equals (Ljava/lang/Object;)Z + public final fun getH1 ()Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H1; + public final fun getH2 ()Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H2; + public final fun getH3 ()Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H3; + public final fun getH4 ()Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H4; + public final fun getH5 ()Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H5; + public final fun getH6 ()Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H6; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$Companion { +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H1 : org/jetbrains/jewel/markdown/rendering/WithInlinesStyling, org/jetbrains/jewel/markdown/rendering/WithUnderline { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H1$Companion; + public synthetic fun (Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z + public fun getInlinesStyling ()Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling; + public final fun getPadding ()Landroidx/compose/foundation/layout/PaddingValues; + public fun getUnderlineColor-0d7_KjU ()J + public fun getUnderlineGap-D9Ej5fM ()F + public fun getUnderlineWidth-D9Ej5fM ()F + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H1$Companion { +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H2 : org/jetbrains/jewel/markdown/rendering/WithInlinesStyling, org/jetbrains/jewel/markdown/rendering/WithUnderline { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H2$Companion; + public synthetic fun (Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z + public fun getInlinesStyling ()Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling; + public final fun getPadding ()Landroidx/compose/foundation/layout/PaddingValues; + public fun getUnderlineColor-0d7_KjU ()J + public fun getUnderlineGap-D9Ej5fM ()F + public fun getUnderlineWidth-D9Ej5fM ()F + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H2$Companion { +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H3 : org/jetbrains/jewel/markdown/rendering/WithInlinesStyling, org/jetbrains/jewel/markdown/rendering/WithUnderline { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H3$Companion; + public synthetic fun (Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z + public fun getInlinesStyling ()Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling; + public final fun getPadding ()Landroidx/compose/foundation/layout/PaddingValues; + public fun getUnderlineColor-0d7_KjU ()J + public fun getUnderlineGap-D9Ej5fM ()F + public fun getUnderlineWidth-D9Ej5fM ()F + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H3$Companion { +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H4 : org/jetbrains/jewel/markdown/rendering/WithInlinesStyling, org/jetbrains/jewel/markdown/rendering/WithUnderline { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H4$Companion; + public synthetic fun (Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z + public fun getInlinesStyling ()Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling; + public final fun getPadding ()Landroidx/compose/foundation/layout/PaddingValues; + public fun getUnderlineColor-0d7_KjU ()J + public fun getUnderlineGap-D9Ej5fM ()F + public fun getUnderlineWidth-D9Ej5fM ()F + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H4$Companion { +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H5 : org/jetbrains/jewel/markdown/rendering/WithInlinesStyling, org/jetbrains/jewel/markdown/rendering/WithUnderline { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H5$Companion; + public synthetic fun (Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z + public fun getInlinesStyling ()Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling; + public final fun getPadding ()Landroidx/compose/foundation/layout/PaddingValues; + public fun getUnderlineColor-0d7_KjU ()J + public fun getUnderlineGap-D9Ej5fM ()F + public fun getUnderlineWidth-D9Ej5fM ()F + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H5$Companion { +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H6 : org/jetbrains/jewel/markdown/rendering/WithInlinesStyling, org/jetbrains/jewel/markdown/rendering/WithUnderline { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H6$Companion; + public synthetic fun (Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;FJFLandroidx/compose/foundation/layout/PaddingValues;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z + public fun getInlinesStyling ()Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling; + public final fun getPadding ()Landroidx/compose/foundation/layout/PaddingValues; + public fun getUnderlineColor-0d7_KjU ()J + public fun getUnderlineGap-D9Ej5fM ()F + public fun getUnderlineWidth-D9Ej5fM ()F + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$H6$Companion { +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$HtmlBlock { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$HtmlBlock$Companion; + public synthetic fun (Landroidx/compose/ui/text/TextStyle;Landroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/ui/graphics/Shape;JFJZLkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z + public final fun getBackground-0d7_KjU ()J + public final fun getBorderColor-0d7_KjU ()J + public final fun getBorderWidth-D9Ej5fM ()F + public final fun getFillWidth ()Z + public final fun getPadding ()Landroidx/compose/foundation/layout/PaddingValues; + public final fun getShape ()Landroidx/compose/ui/graphics/Shape; + public final fun getTextStyle ()Landroidx/compose/ui/text/TextStyle; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$HtmlBlock$Companion { +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$Image { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Image$Companion; + public synthetic fun (Landroidx/compose/ui/Alignment;Landroidx/compose/ui/layout/ContentScale;Landroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/ui/graphics/Shape;JFJLkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z + public final fun getAlignment ()Landroidx/compose/ui/Alignment; + public final fun getBackground-0d7_KjU ()J + public final fun getBorderColor-0d7_KjU ()J + public final fun getBorderWidth-D9Ej5fM ()F + public final fun getContentScale ()Landroidx/compose/ui/layout/ContentScale; + public final fun getPadding ()Landroidx/compose/foundation/layout/PaddingValues; + public final fun getShape ()Landroidx/compose/ui/graphics/Shape; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$Image$Companion { +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$List { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Companion; + public fun (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Ordered;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Unordered;)V + public fun equals (Ljava/lang/Object;)Z + public final fun getOrdered ()Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Ordered; + public final fun getUnordered ()Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Unordered; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Companion { +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Ordered { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Ordered$Companion; + public synthetic fun (Landroidx/compose/ui/text/TextStyle;FFIFFLandroidx/compose/foundation/layout/PaddingValues;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z + public final fun getItemVerticalSpacing-D9Ej5fM ()F + public final fun getItemVerticalSpacingTight-D9Ej5fM ()F + public final fun getNumberContentGap-D9Ej5fM ()F + public final fun getNumberMinWidth-D9Ej5fM ()F + public final fun getNumberStyle ()Landroidx/compose/ui/text/TextStyle; + public final fun getNumberTextAlign-e0LSkKk ()I + public final fun getPadding ()Landroidx/compose/foundation/layout/PaddingValues; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Ordered$Companion { +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Unordered { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Unordered$Companion; + public synthetic fun (Ljava/lang/Character;Landroidx/compose/ui/text/TextStyle;FFFLandroidx/compose/foundation/layout/PaddingValues;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z + public final fun getBullet ()Ljava/lang/Character; + public final fun getBulletContentGap-D9Ej5fM ()F + public final fun getBulletStyle ()Landroidx/compose/ui/text/TextStyle; + public final fun getItemVerticalSpacing-D9Ej5fM ()F + public final fun getItemVerticalSpacingTight-D9Ej5fM ()F + public final fun getPadding ()Landroidx/compose/foundation/layout/PaddingValues; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$List$Unordered$Companion { +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$Paragraph : org/jetbrains/jewel/markdown/rendering/WithInlinesStyling { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Paragraph$Companion; + public fun (Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;)V + public fun equals (Ljava/lang/Object;)Z + public fun getInlinesStyling ()Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$Paragraph$Companion { +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$ThematicBreak { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$ThematicBreak$Companion; + public synthetic fun (Landroidx/compose/foundation/layout/PaddingValues;FJLkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z + public final fun getLineColor-0d7_KjU ()J + public final fun getLineWidth-D9Ej5fM ()F + public final fun getPadding ()Landroidx/compose/foundation/layout/PaddingValues; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling$ThematicBreak$Companion { +} + +public abstract interface class org/jetbrains/jewel/markdown/rendering/WithInlinesStyling { + public abstract fun getInlinesStyling ()Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling; +} + +public abstract interface class org/jetbrains/jewel/markdown/rendering/WithUnderline { + public abstract fun getUnderlineColor-0d7_KjU ()J + public abstract fun getUnderlineGap-D9Ej5fM ()F + public abstract fun getUnderlineWidth-D9Ej5fM ()F +} + diff --git a/markdown/core/build.gradle.kts b/markdown/core/build.gradle.kts new file mode 100644 index 000000000..17fe051e3 --- /dev/null +++ b/markdown/core/build.gradle.kts @@ -0,0 +1,19 @@ +plugins { + jewel + `jewel-publish` + `jewel-check-public-api` + alias(libs.plugins.composeDesktop) +} + +dependencies { + api(projects.ui) + + implementation(libs.commonmark.core) + + testImplementation(compose.desktop.uiTestJUnit4) +} + +publicApiValidation { + // We don't foresee changes to the data models for now + excludedClassRegexes = setOf("org.jetbrains.jewel.markdown.MarkdownBlock.*") +} diff --git a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/MarkdownBlock.kt b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/MarkdownBlock.kt new file mode 100644 index 000000000..5c6a9026d --- /dev/null +++ b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/MarkdownBlock.kt @@ -0,0 +1,83 @@ +package org.jetbrains.jewel.markdown + +import org.intellij.lang.annotations.Language + +public sealed interface MarkdownBlock { + + public data class Paragraph(override val inlineContent: InlineMarkdown) : + MarkdownBlock, BlockWithInlineMarkdown + + public sealed interface Heading : MarkdownBlock, BlockWithInlineMarkdown { + + public data class H1(override val inlineContent: InlineMarkdown) : Heading + + public data class H2(override val inlineContent: InlineMarkdown) : Heading + + public data class H3(override val inlineContent: InlineMarkdown) : Heading + + public data class H4(override val inlineContent: InlineMarkdown) : Heading + + public data class H5(override val inlineContent: InlineMarkdown) : Heading + + public data class H6(override val inlineContent: InlineMarkdown) : Heading + } + + public data class BlockQuote(val content: List) : MarkdownBlock + + public sealed interface ListBlock : MarkdownBlock { + + public val items: List + public val isTight: Boolean + + public data class OrderedList( + override val items: List, + override val isTight: Boolean, + val startFrom: Int, + val delimiter: Char, + ) : ListBlock + + public data class UnorderedList( + override val items: List, + override val isTight: Boolean, + val bulletMarker: Char, + ) : ListBlock + } + + public data class ListItem( + val content: List, + ) : MarkdownBlock + + public sealed interface CodeBlock : MarkdownBlock { + + public val content: String + + public data class IndentedCodeBlock( + override val content: String, + ) : CodeBlock + + public data class FencedCodeBlock( + override val content: String, + val mimeType: MimeType?, + ) : CodeBlock + } + + public data class Image(val url: String, val altString: String?) : MarkdownBlock + + public object ThematicBreak : MarkdownBlock + + public data class HtmlBlock(val content: String) : MarkdownBlock + + public interface Extension : MarkdownBlock +} + +public interface BlockWithInlineMarkdown { + + public val inlineContent: InlineMarkdown +} + +/** + * A run of inline Markdown used as content for + * [block-level elements][MarkdownBlock]. + */ +@JvmInline +public value class InlineMarkdown(@Language("Markdown") public val content: String) diff --git a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/MimeType.kt b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/MimeType.kt new file mode 100644 index 000000000..56a9bd0d1 --- /dev/null +++ b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/MimeType.kt @@ -0,0 +1,431 @@ +package org.jetbrains.jewel.markdown + +import org.jetbrains.jewel.markdown.MimeType.Known.AGSL +import org.jetbrains.jewel.markdown.MimeType.Known.DART +import org.jetbrains.jewel.markdown.MimeType.Known.JSON +import org.jetbrains.jewel.markdown.MimeType.Known.KOTLIN +import org.jetbrains.jewel.markdown.MimeType.Known.PYTHON +import org.jetbrains.jewel.markdown.MimeType.Known.RUST +import org.jetbrains.jewel.markdown.MimeType.Known.TYPESCRIPT +import org.jetbrains.jewel.markdown.MimeType.Known.YAML + +/** + * Represents the language and dialect of a source snippet, as an RFC 2046 + * mime type. + * + * For example, a Kotlin source file may have the mime type `text/kotlin`. + * However, if it corresponds to a `build.gradle.kts` file, we'll also + * attach the mime parameter `role=gradle`, resulting in mime type + * `text/kotlin; role=gradle`. + * + * For XML resource files, we'll attach other attributes; for example + * `role=manifest` for Android manifest files and `role=resource` for + * XML resource files. For the latter we may also attach for example + * `folderType=values`, and for XML files in general, the root tag, such + * as `text/xml; role=resource; folderType=layout; rootTag=LinearLayout`. + * + * This class does not implement *all* aspects of the RFC; in particular, + * we don't treat attributes as case-insensitive, and we only support + * value tokens, not value strings -- neither of these are needed for our + * purposes. + * + * This is implemented using a value class, such that behind the scenes + * we're really just passing a String around. This also means we can't + * initialize related values such as the corresponding Markdown fenced + * block names, or IntelliJ language id's. Instead, these are looked up via + * `when`-tables. When adding a new language, update all lookup methods: + * * [displayName] + */ +@JvmInline +public value class MimeType(private val mimeType: String) { + + public fun displayName(): String = + when (normalizeString()) { + Known.KOTLIN.mimeType -> if (isGradle()) "Gradle DSL" else "Kotlin" + Known.JAVA.mimeType -> "Java" + Known.XML.mimeType -> { + when (getRole()) { + null -> "XML" + VALUE_MANIFEST -> "Manifest" + VALUE_RESOURCE -> { + val folderType = getAttribute(ATTR_FOLDER_TYPE) + folderType?.capitalizeAsciiOnly() ?: "XML" + } + + else -> "XML" + } + } + + Known.JSON.mimeType -> "JSON" + Known.TEXT.mimeType -> "Text" + Known.REGEX.mimeType -> "Regular Expression" + Known.GROOVY.mimeType -> if (isGradle()) "Gradle" else "Groovy" + Known.TOML.mimeType -> if (isVersionCatalog()) "Version Catalog" else "TOML" + Known.C.mimeType -> "C" + Known.CPP.mimeType -> "C++" + Known.SVG.mimeType -> "SVG" + Known.AIDL.mimeType -> "AIDL" + Known.SQL.mimeType -> "SQL" + Known.PROGUARD.mimeType -> "Shrinker Config" + Known.PROPERTIES.mimeType -> "Properties" + Known.PROTO.mimeType -> "Protobuf" + Known.PYTHON.mimeType -> "Python" + Known.DART.mimeType -> "Dart" + Known.RUST.mimeType -> "Rust" + Known.JAVASCRIPT.mimeType -> "JavaScript" + Known.AGSL.mimeType -> "Android Graphics Shading Language" + Known.SHELL.mimeType -> "Shell Script" + Known.YAML.mimeType -> "YAML" + Known.GO.mimeType -> "Go" + else -> mimeType + } + + private fun normalizeString(): String { + when (this) { + // Built-ins are already normalized, don't do string and sorting work + Known.KOTLIN, + Known.JAVA, + Known.TEXT, + Known.XML, + Known.PROPERTIES, + Known.TOML, + Known.JSON, + Known.REGEX, + Known.GROOVY, + Known.C, + Known.CPP, + Known.SVG, + Known.AIDL, + Known.PROTO, + Known.SQL, + Known.PROGUARD, + Known.MANIFEST, + Known.RESOURCE, + Known.GRADLE, + Known.GRADLE_KTS, + Known.VERSION_CATALOG, + Known.PYTHON, + Known.DART, + Known.RUST, + Known.JAVASCRIPT, + Known.TYPESCRIPT, + Known.AGSL, + Known.SHELL, + Known.YAML, + Known.GO, + Known.UNKNOWN, + -> return this.mimeType + } + + val baseEnd = mimeType.indexOf(';') + val normalizedBase = + when (val base = if (baseEnd == -1) mimeType else mimeType.substring(0, baseEnd)) { + "text/x-java-source", + "application/x-java", + "text/x-java", + -> Known.JAVA.mimeType + + "application/kotlin-source", + "text/x-kotlin", + "text/x-kotlin-source", + -> KOTLIN.mimeType + + "application/xml" -> Known.XML.mimeType + "application/json", + "application/vnd.api+json", + "application/hal+json", + "application/ld+json", + -> JSON.mimeType + + "image/svg+xml" -> Known.XML.mimeType + "text/x-python", + "application/x-python-script", + -> PYTHON.mimeType + + "text/dart", + "text/x-dart", + "application/dart", + "application/x-dart", + -> DART.mimeType + + "application/javascript", + "application/x-javascript", + "text/ecmascript", + "application/ecmascript", + "application/x-ecmascript", + -> Known.JAVASCRIPT.mimeType + + "application/typescript" + "application/x-typescript" -> TYPESCRIPT.mimeType + "text/x-rust", + "application/x-rust", + -> RUST.mimeType + + "text/x-sksl" -> AGSL.mimeType + "application/yaml", + "text/x-yaml", + "application/x-yaml", + -> YAML.mimeType + + else -> base + } + + if (baseEnd == -1) { + return normalizedBase + } + + val attributes = + mimeType + .split(';') + .asSequence() + .drop(1) + .sorted() + .mapNotNull { + val index = it.indexOf('=') + if (index != -1) { + it.substring(0, index).trim() to it.substring(index + 1).trim() + } else { + null + } + } + .filter { isRelevantAttribute(it.first) } + .map { "${it.first}=${it.second}" } + .joinToString("; ") + + return if (attributes.isNotBlank()) { + "$normalizedBase; $attributes" + } else { + normalizedBase + } + } + + /** + * Returns whether the given attribute should be included in a normalized + * string + */ + private fun isRelevantAttribute(attribute: String): Boolean = when (attribute) { + ATTR_ROLE, + ATTR_ROOT_TAG, + ATTR_FOLDER_TYPE, + -> true + + else -> false + } + + /** + * Returns just the language portion of the mime type. + * + * For example, for `text/kotlin; role=gradle` this will return + * `text/kotlin`. For `text/plain; charset=us-ascii` this returns + * `text/plain` + */ + public fun base(): MimeType = MimeType(mimeType.substringBefore(';').trim()) + + internal fun getRole(): String? = getAttribute(ATTR_ROLE) + + private fun getFolderType(): String? = getAttribute(ATTR_FOLDER_TYPE) + + private fun getAttribute(name: String): String? { + val marker = "$name=" + var start = mimeType.indexOf(marker) + if (start == -1) { + return null + } + start += marker.length + var end = start + while (end < mimeType.length && !mimeType[end].isWhitespace()) { + end++ + } + return mimeType.substring(start, end).removeSurrounding("\"") + } + + override fun toString(): String = mimeType + + private companion object { + + /** + * Attribute used to indicate the role this source file plays; for example, + * an XML file may be a "manifest" or a "resource". + */ + const val ATTR_ROLE: String = "role" + + /** + * For XML resource files, the folder type if any (such as "values" or + * "layout") + */ + const val ATTR_FOLDER_TYPE: String = "folderType" + + /** For XML files, the root tag in the content */ + const val ATTR_ROOT_TAG: String = "rootTag" + + const val VALUE_RESOURCE = "resource" + const val VALUE_MANIFEST = "manifest" + } + + public object Known { + // Well known mime types for major languages. + + /** + * Well known name for Kotlin source snippets. This is the base mime type; + * consider using [isKotlin] instead to check if a mime type represents + * Kotlin code such that it also picks up `build.gradle.kts` files + * (which carry extra attributes in the mime type; see [GRADLE_KTS].) + */ + public val KOTLIN: MimeType = MimeType("text/kotlin") + + /** Well known name for Java source snippets. */ + public val JAVA: MimeType = MimeType("text/java") + + /** Well known mime type for text files. */ + public val TEXT: MimeType = MimeType("text/plain") + + /** + * Special marker mimetype for unknown or unspecified mime types. These + * will generally be treated as [TEXT] for editor purposes. (The standard + * "unknown" mime type is application/octet-stream (from RFC 2046) but we + * know this isn't binary data; it's text.) + * + * Note that [MimeType] is generally nullable in places where it's optional + * instead of being set to this value, but this mime type is there for + * places where we need a specific value to point to. + */ + public val UNKNOWN: MimeType = MimeType("text/unknown") + + /** + * Well known name for XML source snippets. This is the base mime type; + * consider using [isXml] instead to check if a mime type represents any + * XML such that it also picks up manifest files, resource files etc., + * which all carry extra attributes in the mime type; see for example + * [MANIFEST] and [RESOURCE]. + */ + public val XML: MimeType = MimeType("text/xml") + public val PROPERTIES: MimeType = MimeType("text/properties") + public val TOML: MimeType = MimeType("text/toml") + public val JSON: MimeType = MimeType("text/json") + public val REGEX: MimeType = MimeType("text/x-regex-source") + public val GROOVY: MimeType = MimeType("text/groovy") + public val C: MimeType = MimeType("text/c") + public val CPP: MimeType = MimeType("text/c++") + public val SVG: MimeType = MimeType("image/svg+xml") + public val AIDL: MimeType = MimeType("text/x-aidl-source") + public val PROTO: MimeType = MimeType("text/x-protobuf") + public val SQL: MimeType = MimeType("text/x-sql") + public val PROGUARD: MimeType = MimeType("text/x-proguard") + public val PYTHON: MimeType = MimeType("text/python") + public val JAVASCRIPT: MimeType = MimeType("text/javascript") + public val TYPESCRIPT: MimeType = MimeType("text/typescript") + public val DART: MimeType = MimeType("application/dart") + public val RUST: MimeType = MimeType("text/rust") + public val AGSL: MimeType = MimeType("text/x-agsl") + public val SHELL: MimeType = MimeType("application/x-sh") + public val YAML: MimeType = MimeType("text/yaml") + public val GO: MimeType = MimeType("text/go") + + /** + * Note that most resource files will also have a folder type, so don't use + * equality on this mime type + */ + public val RESOURCE: MimeType = MimeType("$XML; $ATTR_ROLE=resource") + public val MANIFEST: MimeType = MimeType("$XML;$ATTR_ROLE=manifest $ATTR_ROOT_TAG=manifest") + public val GRADLE: MimeType = MimeType("$GROOVY; $ATTR_ROLE=gradle") + public val GRADLE_KTS: MimeType = MimeType("$KOTLIN; $ATTR_ROLE=gradle") + public val VERSION_CATALOG: MimeType = MimeType("$TOML; $ATTR_ROLE=version-catalog") + + /** Maps from a markdown language [name] back to a mime type. */ + public fun fromMarkdownLanguageName(name: String): MimeType? = + when (name) { + "kotlin", + "kt", + "kts", + -> KOTLIN + + "java" -> JAVA + "xml" -> XML + "json", + "json5", + -> JSON + + "regex", + "regexp", + -> REGEX + + "groovy" -> GROOVY + "toml" -> TOML + "c" -> C + "c++" -> CPP + "svg" -> SVG + "aidl" -> AIDL + "sql" -> SQL + "properties" -> PROPERTIES + "protobuf" -> PROTO + "python2", + "python3", + "py", + "python", + -> PYTHON + + "dart" -> DART + "rust" -> RUST + "js", + "javascript", + -> JAVASCRIPT + + "typescript" -> TYPESCRIPT + "sksl" -> AGSL + "sh", + "bash", + "zsh", + "shell", + -> SHELL + + "yaml", + "yml", + -> YAML + + "go", + "golang", + -> YAML + + else -> null + } + } +} + +/** Is the base language for this mime type Kotlin? */ +public fun MimeType?.isKotlin(): Boolean = this?.base() == MimeType.Known.KOTLIN + +/** Is the base language for this mime type Java? */ +public fun MimeType?.isJava(): Boolean = this?.base() == MimeType.Known.JAVA + +/** Is the base language for this mime type XML? */ +public fun MimeType?.isXml(): Boolean = this?.base() == MimeType.Known.XML + +/** Is this a Gradle file (which could be in Groovy, *or*, Kotlin) */ +public fun MimeType?.isGradle(): Boolean = this?.getRole() == "gradle" + +/** Is this a version catalog file (which could be in TOML, or in Groovy) */ +public fun MimeType?.isVersionCatalog(): Boolean = this?.getRole() == "version-catalog" + +/** Is this an Android manifest file? */ +public fun MimeType?.isManifest(): Boolean = this?.getRole() == "manifest" + +/** Is the base language for this mime type SQL? */ +public fun MimeType?.isSql(): Boolean = this?.base() == MimeType.Known.SQL + +/** Is the base language for this mime type a regular expression? */ +public fun MimeType?.isRegex(): Boolean = this?.base() == MimeType.Known.REGEX + +/** Is the base language for this mime type a protobuf? */ +public fun MimeType?.isProto(): Boolean = this?.base() == MimeType.Known.PROTO + +private fun String.capitalizeAsciiOnly(): String { + if (isEmpty()) return this + val c = this[0] + return if (c in 'a'..'z') { + buildString(length) { + append(c.uppercaseChar()) + append(this@capitalizeAsciiOnly, 1, this@capitalizeAsciiOnly.length) + } + } else { + this + } +} diff --git a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownBlockProcessorExtension.kt b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownBlockProcessorExtension.kt new file mode 100644 index 000000000..996e67a8d --- /dev/null +++ b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownBlockProcessorExtension.kt @@ -0,0 +1,23 @@ +package org.jetbrains.jewel.markdown.extensions + +import org.commonmark.node.CustomBlock +import org.jetbrains.jewel.markdown.MarkdownBlock +import org.jetbrains.jewel.markdown.processing.MarkdownProcessor + +public interface MarkdownBlockProcessorExtension { + + /** + * Returns true if the [block] can be processed by this extension instance. + * + * @param block The [CustomBlock] to parse + */ + public fun canProcess(block: CustomBlock): Boolean + + /** + * Processes the [block] as a [MarkdownBlock.Extension], 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. + */ + public fun processMarkdownBlock(block: CustomBlock, processor: MarkdownProcessor): MarkdownBlock.Extension? +} diff --git a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownBlockRendererExtension.kt b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownBlockRendererExtension.kt new file mode 100644 index 000000000..7b3d728c3 --- /dev/null +++ b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownBlockRendererExtension.kt @@ -0,0 +1,28 @@ +package org.jetbrains.jewel.markdown.extensions + +import androidx.compose.runtime.Composable +import org.jetbrains.jewel.markdown.MarkdownBlock +import org.jetbrains.jewel.markdown.MarkdownBlock.Extension +import org.jetbrains.jewel.markdown.rendering.InlineMarkdownRenderer +import org.jetbrains.jewel.markdown.rendering.MarkdownBlockRenderer + +/** + * An extension for [MarkdownBlockRenderer] that can render one or more + * [MarkdownBlock.Extension]s. + */ +public interface MarkdownBlockRendererExtension { + + /** Check whether the provided [block] can be rendered by this extension. */ + public fun canRender(block: Extension): Boolean + + /** + * Render a [MarkdownBlock.Extension] as a native Composable. Note that if + * [canRender] returns `false` for [block], the implementation might throw. + */ + @Composable + public fun render( + block: Extension, + blockRenderer: MarkdownBlockRenderer, + inlineRenderer: InlineMarkdownRenderer, + ) +} diff --git a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension.kt b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension.kt new file mode 100644 index 000000000..8f5d706b4 --- /dev/null +++ b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension.kt @@ -0,0 +1,33 @@ +package org.jetbrains.jewel.markdown.extensions + +import org.commonmark.node.CustomBlock +import org.commonmark.parser.Parser.ParserExtension +import org.commonmark.renderer.text.TextContentRenderer.TextContentRendererExtension +import org.jetbrains.jewel.foundation.ExperimentalJewelApi +import org.jetbrains.jewel.markdown.MarkdownBlock + +/** An extension for the Jewel Markdown processing engine. */ +@ExperimentalJewelApi +public interface MarkdownProcessorExtension { + + /** + * A CommonMark [ParserExtension] that will be used to parse the extended + * syntax represented by this extension instance. + */ + public val parserExtension: ParserExtension + + /** + * A CommonMark [TextContentRendererExtension] that will be used to render + * the text content of the CommonMark [CustomBlock] produced by the + * [parserExtension]. + */ + public val textRendererExtension: TextContentRendererExtension + + /** + * An extension for + * [`MarkdownParser`][org.jetbrains.jewel.markdown.parsing.MarkdownParser] + * that will transform a supported [CustomBlock] into the corresponding + * [MarkdownBlock.Extension]. + */ + public val processorExtension: MarkdownBlockProcessorExtension +} diff --git a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownRendererExtension.kt b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownRendererExtension.kt new file mode 100644 index 000000000..6ec625008 --- /dev/null +++ b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/MarkdownRendererExtension.kt @@ -0,0 +1,17 @@ +package org.jetbrains.jewel.markdown.extensions + +import org.jetbrains.jewel.foundation.ExperimentalJewelApi + +/** An extension for the Jewel Markdown rendering engine. */ +@ExperimentalJewelApi +public interface MarkdownRendererExtension { + + /** + * An extension for + * [`MarkdownBlockRenderer`][org.jetbrains.jewel.markdown.rendering.MarkdownBlockRenderer] + * that will render a supported + * [`Extension`][org.jetbrains.jewel.markdown.MarkdownBlock.Extension] into + * a native Jewel UI. + */ + public val blockRenderer: MarkdownBlockRendererExtension +} 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 new file mode 100644 index 000000000..561aafded --- /dev/null +++ b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/MarkdownProcessor.kt @@ -0,0 +1,347 @@ +package org.jetbrains.jewel.markdown.processing + +import org.commonmark.node.BlockQuote +import org.commonmark.node.BulletList +import org.commonmark.node.Code +import org.commonmark.node.CustomBlock +import org.commonmark.node.Document +import org.commonmark.node.Emphasis +import org.commonmark.node.FencedCodeBlock +import org.commonmark.node.HardLineBreak +import org.commonmark.node.Heading +import org.commonmark.node.HtmlBlock +import org.commonmark.node.HtmlInline +import org.commonmark.node.Image +import org.commonmark.node.IndentedCodeBlock +import org.commonmark.node.Link +import org.commonmark.node.ListBlock +import org.commonmark.node.ListItem +import org.commonmark.node.Node +import org.commonmark.node.OrderedList +import org.commonmark.node.Paragraph +import org.commonmark.node.SoftLineBreak +import org.commonmark.node.StrongEmphasis +import org.commonmark.node.Text +import org.commonmark.node.ThematicBreak +import org.commonmark.parser.Parser +import org.commonmark.renderer.text.TextContentRenderer +import org.intellij.lang.annotations.Language +import org.jetbrains.jewel.foundation.ExperimentalJewelApi +import org.jetbrains.jewel.markdown.InlineMarkdown +import org.jetbrains.jewel.markdown.MarkdownBlock +import org.jetbrains.jewel.markdown.MarkdownBlock.CodeBlock +import org.jetbrains.jewel.markdown.MarkdownBlock.Heading.H1 +import org.jetbrains.jewel.markdown.MarkdownBlock.Heading.H2 +import org.jetbrains.jewel.markdown.MarkdownBlock.Heading.H3 +import org.jetbrains.jewel.markdown.MarkdownBlock.Heading.H4 +import org.jetbrains.jewel.markdown.MarkdownBlock.Heading.H5 +import org.jetbrains.jewel.markdown.MarkdownBlock.Heading.H6 +import org.jetbrains.jewel.markdown.MarkdownBlock.ListBlock.UnorderedList +import org.jetbrains.jewel.markdown.MimeType +import org.jetbrains.jewel.markdown.extensions.MarkdownProcessorExtension +import org.jetbrains.jewel.markdown.rendering.DefaultInlineMarkdownRenderer + +@ExperimentalJewelApi +public class MarkdownProcessor(private val extensions: List = emptyList()) { + + public constructor(vararg extensions: MarkdownProcessorExtension) : this(extensions.toList()) + + private val commonMarkParser = + Parser.builder().extensions(extensions.map { it.parserExtension }).build() + + private val textContentRenderer = + TextContentRenderer.builder() + .extensions(extensions.map { it.textRendererExtension }) + .build() + + /** + * Parses a Markdown document, translating from CommonMark 0.30 + * 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]. + * + * The contents of [InlineMarkdown] is equivalent to the original, but + * normalized and simplified, and cleaned up as follows: + * * Replace HTML entities with the corresponding character (escaped, if it + * is necessary) + * * Inline link and image references and omit the reference blocks + * * Use the destination as text for links when no text is set (escaped, if + * it is necessary) + * * Normalize link titles to always use double quotes as enclosing + * character + * * Normalize backticks in inline code runs + * * Convert links in image descriptions to plain text + * * Drop empty nodes with no visual representation (e.g., links with no + * text and destination) + * * Remove unnecessary escapes + * * Escape non-formatting instances of ``*_`~<>[]()!`` for clarity + * + * The contents of code blocks aren't transformed in any way. HTML blocks + * get their outer whitespace trimmed, and so does inline HTML. + * + * @see DefaultInlineMarkdownRenderer + */ + public fun processMarkdownDocument(@Language("Markdown") rawMarkdown: String): List { + val document = + commonMarkParser.parse(rawMarkdown) as? Document + ?: error("This doesn't look like a Markdown document") + + return processChildren(document) + } + + private fun Node.tryProcessMarkdownBlock(): MarkdownBlock? = + // Non-Block children are ignored + when (this) { + is BlockQuote -> toMarkdownBlockQuote() + is Heading -> toMarkdownHeadingOrNull() + is Paragraph -> toMarkdownParagraphOrNull() + is FencedCodeBlock -> toMarkdownCodeBlockOrNull() + is IndentedCodeBlock -> toMarkdownCodeBlockOrNull() + is Image -> toMarkdownImageOrNull() + is BulletList -> toMarkdownListOrNull() + is OrderedList -> toMarkdownListOrNull() + is ThematicBreak -> MarkdownBlock.ThematicBreak + is HtmlBlock -> toMarkdownHtmlBlockOrNull() + is CustomBlock -> { + extensions.find { it.processorExtension.canProcess(this) } + ?.processorExtension?.processMarkdownBlock(this, this@MarkdownProcessor) + } + + else -> null + } + + private fun BlockQuote.toMarkdownBlockQuote(): MarkdownBlock.BlockQuote = + MarkdownBlock.BlockQuote(processChildren(this)) + + private fun Heading.toMarkdownHeadingOrNull(): MarkdownBlock.Heading? = + when (level) { + 1 -> H1(contentsAsInlineMarkdown()) + 2 -> H2(contentsAsInlineMarkdown()) + 3 -> H3(contentsAsInlineMarkdown()) + 4 -> H4(contentsAsInlineMarkdown()) + 5 -> H5(contentsAsInlineMarkdown()) + 6 -> H6(contentsAsInlineMarkdown()) + else -> null + } + + private fun Paragraph.toMarkdownParagraphOrNull(): MarkdownBlock.Paragraph? { + val inlineMarkdown = contentsAsInlineMarkdown() + + if (inlineMarkdown.isBlank()) return null + return MarkdownBlock.Paragraph(inlineMarkdown) + } + + private fun FencedCodeBlock.toMarkdownCodeBlockOrNull(): CodeBlock.FencedCodeBlock = + CodeBlock.FencedCodeBlock( + literal.trimEnd('\n'), + MimeType.Known.fromMarkdownLanguageName(info), + ) + + private fun IndentedCodeBlock.toMarkdownCodeBlockOrNull(): CodeBlock.IndentedCodeBlock = + CodeBlock.IndentedCodeBlock(literal.trimEnd('\n')) + + private fun Image.toMarkdownImageOrNull(): MarkdownBlock.Image? { + if (destination.isBlank()) return null + + return MarkdownBlock.Image(destination.trim(), title.trim()) + } + + private fun BulletList.toMarkdownListOrNull(): UnorderedList? { + val children = processListItems() + if (children.isEmpty()) return null + + return UnorderedList(children, isTight, bulletMarker) + } + + private fun OrderedList.toMarkdownListOrNull(): MarkdownBlock.ListBlock.OrderedList? { + val children = processListItems() + if (children.isEmpty()) return null + + return MarkdownBlock.ListBlock.OrderedList(children, isTight, startNumber, delimiter) + } + + private fun ListBlock.processListItems() = buildList { + forEachChild { child -> + if (child !is ListItem) return@forEachChild + add(MarkdownBlock.ListItem(processChildren(child))) + } + } + + public fun processChildren(node: Node): List = buildList { + node.forEachChild { child -> + val parsedBlock = child.tryProcessMarkdownBlock() + if (parsedBlock != null) this.add(parsedBlock) + } + } + + private fun Node.forEachChild(action: (Node) -> Unit) { + var child = firstChild + + while (child != null) { + action(child) + child = child.next + } + } + + private fun HtmlBlock.toMarkdownHtmlBlockOrNull(): MarkdownBlock.HtmlBlock? { + if (literal.isBlank()) return null + return MarkdownBlock.HtmlBlock(content = literal.trimEnd('\n')) + } + + private fun Node.contentsAsInlineMarkdown() = + InlineMarkdown(buildString { appendInlineMarkdownFrom(this@contentsAsInlineMarkdown) }) + + private fun StringBuilder.appendInlineMarkdownFrom( + node: Node, + allowLinks: Boolean = true, + ignoreNestedEmphasis: Boolean = false, + ) { + var child = node.firstChild + + while (child != null) { + when (child) { + is Text -> append(child.literal.escapeInlineMarkdownChars()) + is Image -> appendImage(child) + + is Emphasis -> { + append(child.openingDelimiter) + appendInlineMarkdownFrom(child, !ignoreNestedEmphasis) + append(child.closingDelimiter) + } + + is StrongEmphasis -> { + append(child.openingDelimiter) + appendInlineMarkdownFrom(child) + append(child.closingDelimiter) + } + + is Code -> append(child.literal.asInlineCodeString()) + is Link -> appendLink(child, allowLinks) + + is HardLineBreak -> appendLine() + is SoftLineBreak -> append(' ') + is HtmlInline -> append(child.literal.trim()) + } + child = child.next + } + } + + private fun StringBuilder.appendImage(child: Image) { + append("![") + appendInlineMarkdownFrom(child, allowLinks = false) + + // Escape dangling backslashes at the end of the text + val backSlashCount = takeLastWhile { it == '\\' }.length + if (backSlashCount % 2 != 0) append('\\') + + append("](") + append(child.destination.escapeLinkDestination()) + + if (!child.title.isNullOrBlank()) { + append(" \"") + append(child.title.replace("\"", "\\\"").trim()) + append('"') + } + append(')') + } + + private fun StringBuilder.appendLink(child: Link, allowLinks: Boolean) { + val hasText = child.firstChild != null + if (child.destination.isNullOrBlank() && !hasText) { + // Ignore links with no destination and no text + return + } + + if (allowLinks) { + append('[') + + if (hasText) { + // Link text cannot contain links — just in case... + appendInlineMarkdownFrom(child, allowLinks = false) + + // Escape dangling backslashes at the end of the text + val backSlashCount = takeLastWhile { it == '\\' }.length + if (backSlashCount % 2 != 0) append('\\') + } else { + // No text: use the destination + append(child.destination.escapeLinkDestinationForUseInText()) + } + + append("](") + append(child.destination.escapeLinkDestination()) + + if (!child.title.isNullOrBlank()) { + append(" \"") + append(child.title.replace("\"", "\\\"").trim()) + append('"') + } + append(')') + } else { + append(plainTextContents(child)) + } + } + + private fun String.escapeLinkDestinationForUseInText() = + replace("[", "[").replace("]", "]") + + private fun String.escapeLinkDestination(): String { + val escaped = replace(">", "//>").replace("(", "\\(").replace(")", "\\)") + return if (any { it.isWhitespace() && it != '\n' }) "<$escaped>" else escaped + } + + private fun String.escapeInlineMarkdownChars() = + buildString(length) { + var precedingBackslashesCount = 0 + var isNewLine = true + + for (char in this@escapeInlineMarkdownChars) { + when (char) { + '\\' -> precedingBackslashesCount++ + '\n' -> isNewLine = true + else -> { + val isUnescaped = (precedingBackslashesCount % 2) == 0 + if (char in "*_~`<>[]()!" && (isNewLine || isUnescaped)) { + append('\\') + } + + isNewLine = false + precedingBackslashesCount = 0 + } + } + append(char) + } + } + + private fun String.asInlineCodeString(): String { + // Base case: doesn't contain backticks + if (!contains("`")) { + return "`$this`" + } + + var currentCount = 0 + var longestCount = 0 + + // First, count the longest run of backticks in the string + for (char in this) { + if (char == '`') { + currentCount++ + } else { + if (currentCount > longestCount) { + longestCount = currentCount + } + currentCount = 0 + } + } + + if (currentCount > longestCount) longestCount = currentCount + + // Then wrap it in n + 1 backticks to avoid early termination + val backticks = "`".repeat(longestCount + 1) + return "$backticks$this$backticks" + } + + private fun InlineMarkdown.isBlank(): Boolean = content.isBlank() + + private fun plainTextContents(node: Node): String = textContentRenderer.render(node) +} diff --git a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultInlineMarkdownRenderer.kt b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultInlineMarkdownRenderer.kt new file mode 100644 index 000000000..9a77ae465 --- /dev/null +++ b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultInlineMarkdownRenderer.kt @@ -0,0 +1,120 @@ +package org.jetbrains.jewel.markdown.rendering + +import androidx.compose.foundation.text.appendInlineContent +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.AnnotatedString.Builder +import androidx.compose.ui.text.ExperimentalTextApi +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.UrlAnnotation +import androidx.compose.ui.text.buildAnnotatedString +import org.commonmark.node.Block +import org.commonmark.node.Code +import org.commonmark.node.Emphasis +import org.commonmark.node.HardLineBreak +import org.commonmark.node.HtmlInline +import org.commonmark.node.Image +import org.commonmark.node.Link +import org.commonmark.node.Node +import org.commonmark.node.Paragraph +import org.commonmark.node.SoftLineBreak +import org.commonmark.node.StrongEmphasis +import org.commonmark.node.Text +import org.commonmark.parser.Parser +import org.commonmark.renderer.text.TextContentRenderer +import org.jetbrains.jewel.foundation.ExperimentalJewelApi +import org.jetbrains.jewel.markdown.InlineMarkdown +import org.jetbrains.jewel.markdown.extensions.MarkdownProcessorExtension + +@ExperimentalJewelApi +public open class DefaultInlineMarkdownRenderer(rendererExtensions: List) : InlineMarkdownRenderer { + + public constructor(vararg extensions: MarkdownProcessorExtension) : this(extensions.toList()) + + private val commonMarkParser = + Parser.builder().extensions(rendererExtensions.map { it.parserExtension }).build() + + private val plainTextRenderer = + TextContentRenderer.builder() + .extensions(rendererExtensions.map { it.textRendererExtension }) + .build() + + public override fun renderAsAnnotatedString( + inlineMarkdown: InlineMarkdown, + styling: InlinesStyling, + ): AnnotatedString = + buildAnnotatedString { + val node = commonMarkParser.parse(inlineMarkdown.content) + appendInlineMarkdownFrom(node, styling) + } + + @OptIn(ExperimentalTextApi::class) + private fun Builder.appendInlineMarkdownFrom(node: Node, styling: InlinesStyling) { + var child = node.firstChild + + while (child != null) { + when (child) { + is Paragraph -> appendInlineMarkdownFrom(child, styling) + is Image -> { + appendInlineContent( + INLINE_IMAGE, + child.destination + "\n" + plainTextRenderer.render(child), + ) + } + + is Text -> append(child.literal) + is Emphasis -> { + withStyles(styling.emphasis, child) { appendInlineMarkdownFrom(it, styling) } + } + + is StrongEmphasis -> { + withStyles(styling.strongEmphasis, child) { appendInlineMarkdownFrom(it, styling) } + } + + is Code -> { + withStyles(styling.inlineCode, child) { append(it.literal) } + } + + is Link -> { + withStyles(styling.link, child) { + pushUrlAnnotation(UrlAnnotation(it.destination)) + appendInlineMarkdownFrom(it, styling) + } + } + + is HardLineBreak, + is SoftLineBreak, + -> appendLine() + + is HtmlInline -> { + if (styling.renderInlineHtml) { + withStyles(styling.inlineHtml, child) { append(it.literal.trim()) } + } + } + + is Block -> { + error("Only inline Markdown can be rendered to an AnnotatedString. Found: $child") + } + } + child = child.next + } + } + + // The T type parameter is needed to avoid issues with capturing lambdas + // making smart cast of the child local variable impossible. + private inline fun Builder.withStyles( + spanStyle: SpanStyle, + node: T, + action: Builder.(T) -> Unit, + ) { + val popTo = pushStyle(spanStyle) + + action(node) + + pop(popTo) + } + + public companion object { + + internal const val INLINE_IMAGE = "inline_image" + } +} diff --git a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRenderer.kt b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRenderer.kt new file mode 100644 index 000000000..486e333fc --- /dev/null +++ b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRenderer.kt @@ -0,0 +1,513 @@ +package org.jetbrains.jewel.markdown.rendering + +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.HorizontalScrollbar +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.rememberScrollbarAdapter +import androidx.compose.foundation.text.ClickableText +import androidx.compose.foundation.text.selection.DisableSelection +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.isSpecified +import androidx.compose.ui.input.pointer.PointerIcon +import androidx.compose.ui.input.pointer.pointerHoverIcon +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.ExperimentalTextApi +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.LayoutDirection.Ltr +import androidx.compose.ui.unit.dp +import org.jetbrains.jewel.foundation.ExperimentalJewelApi +import org.jetbrains.jewel.foundation.modifier.onHover +import org.jetbrains.jewel.foundation.theme.LocalContentColor +import org.jetbrains.jewel.markdown.BlockWithInlineMarkdown +import org.jetbrains.jewel.markdown.MarkdownBlock +import org.jetbrains.jewel.markdown.MarkdownBlock.BlockQuote +import org.jetbrains.jewel.markdown.MarkdownBlock.CodeBlock +import org.jetbrains.jewel.markdown.MarkdownBlock.CodeBlock.FencedCodeBlock +import org.jetbrains.jewel.markdown.MarkdownBlock.CodeBlock.IndentedCodeBlock +import org.jetbrains.jewel.markdown.MarkdownBlock.Extension +import org.jetbrains.jewel.markdown.MarkdownBlock.Heading +import org.jetbrains.jewel.markdown.MarkdownBlock.Heading.H1 +import org.jetbrains.jewel.markdown.MarkdownBlock.Heading.H2 +import org.jetbrains.jewel.markdown.MarkdownBlock.Heading.H3 +import org.jetbrains.jewel.markdown.MarkdownBlock.Heading.H4 +import org.jetbrains.jewel.markdown.MarkdownBlock.Heading.H5 +import org.jetbrains.jewel.markdown.MarkdownBlock.Heading.H6 +import org.jetbrains.jewel.markdown.MarkdownBlock.HtmlBlock +import org.jetbrains.jewel.markdown.MarkdownBlock.Image +import org.jetbrains.jewel.markdown.MarkdownBlock.ListBlock +import org.jetbrains.jewel.markdown.MarkdownBlock.ListBlock.OrderedList +import org.jetbrains.jewel.markdown.MarkdownBlock.ListBlock.UnorderedList +import org.jetbrains.jewel.markdown.MarkdownBlock.ListItem +import org.jetbrains.jewel.markdown.MarkdownBlock.Paragraph +import org.jetbrains.jewel.markdown.MarkdownBlock.ThematicBreak +import org.jetbrains.jewel.markdown.extensions.MarkdownRendererExtension +import org.jetbrains.jewel.ui.Orientation.Horizontal +import org.jetbrains.jewel.ui.component.Divider +import org.jetbrains.jewel.ui.component.Text + +@ExperimentalJewelApi +public open class DefaultMarkdownBlockRenderer( + private val rootStyling: MarkdownStyling, + private val rendererExtensions: List, + private val inlineRenderer: InlineMarkdownRenderer, + private val onUrlClick: (String) -> Unit, +) : MarkdownBlockRenderer { + + @Composable + override fun render(blocks: List) { + Column(verticalArrangement = Arrangement.spacedBy(rootStyling.blockVerticalSpacing)) { + for (block in blocks) { + render(block) + } + } + } + + @Composable + override fun render(block: MarkdownBlock) { + when (block) { + is BlockQuote -> render(block, rootStyling.blockQuote) + is FencedCodeBlock -> render(block, rootStyling.code.fenced) + is IndentedCodeBlock -> render(block, rootStyling.code.indented) + is H1 -> render(block, rootStyling.heading.h1) + is H2 -> render(block, rootStyling.heading.h2) + is H3 -> render(block, rootStyling.heading.h3) + is H4 -> render(block, rootStyling.heading.h4) + is H5 -> render(block, rootStyling.heading.h5) + is H6 -> render(block, rootStyling.heading.h6) + is HtmlBlock -> render(block, rootStyling.htmlBlock) + is Image -> render(block, rootStyling.image) + is OrderedList -> render(block, rootStyling.list.ordered) + is UnorderedList -> render(block, rootStyling.list.unordered) + is ListItem -> render(block) + is Paragraph -> render(block, rootStyling.paragraph) + ThematicBreak -> renderThematicBreak(rootStyling.thematicBreak) + is Extension -> { + rendererExtensions.find { it.blockRenderer.canRender(block) } + ?.blockRenderer?.render(block, this, inlineRenderer) + } + } + } + + @Composable + override fun render(block: Paragraph, styling: MarkdownStyling.Paragraph) { + val renderedContent = rememberRenderedContent(block, styling.inlinesStyling) + SimpleClickableText(text = renderedContent, textStyle = styling.inlinesStyling.textStyle) + } + + @Composable + override fun render(block: Heading, styling: MarkdownStyling.Heading) { + when (block) { + is H1 -> render(block, styling) + is H2 -> render(block, styling) + is H3 -> render(block, styling) + is H4 -> render(block, styling) + is H5 -> render(block, styling) + is H6 -> render(block, styling) + } + } + + @Composable + override fun render(block: H1, styling: MarkdownStyling.Heading.H1) { + val renderedContent = rememberRenderedContent(block, styling.inlinesStyling) + Heading( + renderedContent, + styling.inlinesStyling.textStyle, + styling.padding, + styling.underlineWidth, + styling.underlineColor, + styling.underlineGap, + ) + } + + @Composable + override fun render(block: H2, styling: MarkdownStyling.Heading.H2) { + val renderedContent = rememberRenderedContent(block, styling.inlinesStyling) + Heading( + renderedContent, + styling.inlinesStyling.textStyle, + styling.padding, + styling.underlineWidth, + styling.underlineColor, + styling.underlineGap, + ) + } + + @Composable + override fun render(block: H3, styling: MarkdownStyling.Heading.H3) { + val renderedContent = rememberRenderedContent(block, styling.inlinesStyling) + Heading( + renderedContent, + styling.inlinesStyling.textStyle, + styling.padding, + styling.underlineWidth, + styling.underlineColor, + styling.underlineGap, + ) + } + + @Composable + override fun render(block: H4, styling: MarkdownStyling.Heading.H4) { + val renderedContent = rememberRenderedContent(block, styling.inlinesStyling) + Heading( + renderedContent, + styling.inlinesStyling.textStyle, + styling.padding, + styling.underlineWidth, + styling.underlineColor, + styling.underlineGap, + ) + } + + @Composable + override fun render(block: H5, styling: MarkdownStyling.Heading.H5) { + val renderedContent = rememberRenderedContent(block, styling.inlinesStyling) + Heading( + renderedContent, + styling.inlinesStyling.textStyle, + styling.padding, + styling.underlineWidth, + styling.underlineColor, + styling.underlineGap, + ) + } + + @Composable + override fun render(block: H6, styling: MarkdownStyling.Heading.H6) { + val renderedContent = rememberRenderedContent(block, styling.inlinesStyling) + Heading( + renderedContent, + styling.inlinesStyling.textStyle, + styling.padding, + styling.underlineWidth, + styling.underlineColor, + styling.underlineGap, + ) + } + + @Composable + private fun Heading( + renderedContent: AnnotatedString, + textStyle: TextStyle, + paddingValues: PaddingValues, + underlineWidth: Dp, + underlineColor: Color, + underlineGap: Dp, + ) { + Column(modifier = Modifier.padding(paddingValues)) { + SimpleClickableText(text = renderedContent, textStyle = textStyle) + + if (underlineWidth > 0.dp && underlineColor.isSpecified) { + Spacer(Modifier.height(underlineGap)) + Divider(Horizontal, color = underlineColor, thickness = underlineWidth) + } + } + } + + @Composable + override fun render( + block: BlockQuote, + styling: MarkdownStyling.BlockQuote, + ) { + Column( + Modifier.drawBehind { + val isLtr = layoutDirection == Ltr + val lineWidthPx = styling.lineWidth.toPx() + val x = if (isLtr) lineWidthPx / 2 else size.width - lineWidthPx / 2 + + drawLine( + styling.lineColor, + Offset(x, 0f), + Offset(x, size.height), + lineWidthPx, + styling.strokeCap, + styling.pathEffect, + ) + } + .padding(styling.padding), + verticalArrangement = Arrangement.spacedBy(rootStyling.blockVerticalSpacing), + ) { + CompositionLocalProvider(LocalContentColor provides styling.textColor) { + render(block.content) + } + } + } + + @Composable + override fun render(block: ListBlock, styling: MarkdownStyling.List) { + when (block) { + is OrderedList -> render(block, styling.ordered) + is UnorderedList -> render(block, styling.unordered) + } + } + + @Composable + override fun render(block: OrderedList, styling: MarkdownStyling.List.Ordered) { + val itemSpacing = + if (block.isTight) { + styling.itemVerticalSpacingTight + } else { + styling.itemVerticalSpacing + } + + Column( + modifier = Modifier.padding(styling.padding), + verticalArrangement = Arrangement.spacedBy(itemSpacing), + ) { + for ((index, item) in block.items.withIndex()) { + Row { + val number = block.startFrom + index + Text( + text = "$number${block.delimiter}", + style = styling.numberStyle, + modifier = Modifier.widthIn(min = styling.numberMinWidth), + textAlign = styling.numberTextAlign, + ) + + Spacer(Modifier.width(styling.numberContentGap)) + + render(item) + } + } + } + } + + @Composable + override fun render(block: UnorderedList, styling: MarkdownStyling.List.Unordered) { + val itemSpacing = + if (block.isTight) { + styling.itemVerticalSpacingTight + } else { + styling.itemVerticalSpacing + } + + Column( + modifier = Modifier.padding(styling.padding), + verticalArrangement = Arrangement.spacedBy(itemSpacing), + ) { + for (item in block.items) { + Row { + Text(text = styling.bullet.toString(), style = styling.bulletStyle) + + Spacer(Modifier.width(styling.bulletContentGap)) + + render(item) + } + } + } + } + + @Composable + override fun render(block: ListItem) { + Column(verticalArrangement = Arrangement.spacedBy(rootStyling.blockVerticalSpacing)) { + render(block.content) + } + } + + @Composable + override fun render(block: CodeBlock, styling: MarkdownStyling.Code) { + when (block) { + is FencedCodeBlock -> render(block, styling.fenced) + is IndentedCodeBlock -> render(block, styling.indented) + } + } + + @Composable + override fun render(block: IndentedCodeBlock, styling: MarkdownStyling.Code.Indented) { + HorizontallyScrollingContainer( + isScrollable = styling.scrollsHorizontally, + Modifier.background(styling.background, styling.shape) + .border(styling.borderWidth, styling.borderColor, styling.shape) + .pointerHoverIcon(PointerIcon.Default, true) + .then(if (styling.fillWidth) Modifier.fillMaxWidth() else Modifier), + ) { + Text(block.content, style = styling.textStyle, modifier = Modifier.padding(styling.padding)) + } + } + + @Composable + override fun render(block: FencedCodeBlock, styling: MarkdownStyling.Code.Fenced) { + HorizontallyScrollingContainer( + isScrollable = styling.scrollsHorizontally, + Modifier.background(styling.background, styling.shape) + .border(styling.borderWidth, styling.borderColor, styling.shape) + .pointerHoverIcon(PointerIcon.Default, true) + .then(if (styling.fillWidth) Modifier.fillMaxWidth() else Modifier), + ) { + Column(Modifier.padding(styling.padding)) { + if (block.mimeType != null && styling.infoPosition.verticalAlignment == Alignment.Top) { + FencedBlockInfo( + block.mimeType.displayName(), + styling.infoPosition.horizontalAlignment + ?: error("No horizontal alignment for position ${styling.infoPosition.name}"), + styling.infoTextStyle, + Modifier.fillMaxWidth().padding(styling.infoPadding), + ) + } + + Text(block.content, style = styling.textStyle) + + if (block.mimeType != null && styling.infoPosition.verticalAlignment == Alignment.Bottom) { + FencedBlockInfo( + block.mimeType.displayName(), + styling.infoPosition.horizontalAlignment + ?: error("No horizontal alignment for position ${styling.infoPosition.name}"), + styling.infoTextStyle, + Modifier.fillMaxWidth().padding(styling.infoPadding), + ) + } + } + } + } + + @Composable + private fun FencedBlockInfo( + infoText: String, + alignment: Alignment.Horizontal, + textStyle: TextStyle, + modifier: Modifier, + ) { + Column(modifier, horizontalAlignment = alignment) { + DisableSelection { Text(infoText, style = textStyle) } + } + } + + @Composable + override fun render(block: Image, styling: MarkdownStyling.Image) { + // TODO implement image rendering support (will require image loading) + Text( + "⚠️ Images are not supported yet", + Modifier.border(1.dp, Color.Red).padding(horizontal = 8.dp, vertical = 4.dp), + color = Color.Red, + ) + } + + @Composable + override fun renderThematicBreak(styling: MarkdownStyling.ThematicBreak) { + Box(Modifier.padding(styling.padding).height(styling.lineWidth).background(styling.lineColor)) + } + + @Composable + override fun render(block: HtmlBlock, styling: MarkdownStyling.HtmlBlock) { + // HTML blocks are intentionally not rendered + } + + @Composable + private fun rememberRenderedContent(block: BlockWithInlineMarkdown, styling: InlinesStyling) = + remember(block.inlineContent, styling) { + inlineRenderer.renderAsAnnotatedString(block.inlineContent, styling) + } + + @OptIn(ExperimentalTextApi::class) + @Composable + private fun SimpleClickableText( + text: AnnotatedString, + textStyle: TextStyle, + modifier: Modifier = Modifier, + ) { + var pointerIcon by remember { mutableStateOf(PointerIcon.Default) } + + ClickableText( + text = text, + style = textStyle.merge(LocalContentColor.current), + modifier = modifier.pointerHoverIcon(pointerIcon, true), + onHover = { offset -> + pointerIcon = + if (offset == null || text.getUrlAnnotations(offset, offset).isEmpty()) { + PointerIcon.Default + } else { + PointerIcon.Hand + } + }, + ) { offset -> + val span = text.getUrlAnnotations(offset, offset).firstOrNull() ?: return@ClickableText + onUrlClick(span.item.url) + } + } + + @Composable + private fun HorizontallyScrollingContainer( + isScrollable: Boolean, + modifier: Modifier = Modifier, + content: @Composable () -> Unit, + ) { + var isHovered by remember { mutableStateOf(false) } + + Layout( + content = { + val scrollState = rememberScrollState() + Box( + Modifier.layoutId("mainContent") + .then(if (isScrollable) Modifier.horizontalScroll(scrollState) else Modifier), + ) { + content() + } + + val canScroll by derivedStateOf { + scrollState.canScrollBackward || scrollState.canScrollForward + } + + if (isScrollable && canScroll) { + val alpha by animateFloatAsState( + if (isHovered) 1f else 0f, + tween(durationMillis = 150, easing = LinearEasing), + ) + + HorizontalScrollbar( + rememberScrollbarAdapter(scrollState), + Modifier.layoutId("containerHScrollbar") + .padding(start = 2.dp, end = 2.dp, bottom = 2.dp) + .alpha(alpha), + ) + } + }, + modifier.onHover { isHovered = it }, + { measurables, incomingConstraints -> + val contentMeasurable = + measurables.singleOrNull { it.layoutId == "mainContent" } + ?: error("There must be one and only one child with ID 'mainContent'") + + val contentPlaceable = contentMeasurable.measure(incomingConstraints) + + val scrollbarMeasurable = measurables.find { it.layoutId == "containerHScrollbar" } + val scrollbarPlaceable = scrollbarMeasurable?.measure(incomingConstraints) + val scrollbarHeight = scrollbarPlaceable?.measuredHeight ?: 0 + + layout(contentPlaceable.width, contentPlaceable.height + scrollbarHeight) { + contentPlaceable.place(0, 0) + scrollbarPlaceable?.place(0, contentPlaceable.height) + } + }, + ) + } +} diff --git a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultStyling.kt b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultStyling.kt new file mode 100644 index 000000000..6bf8cf20b --- /dev/null +++ b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultStyling.kt @@ -0,0 +1,639 @@ +package org.jetbrains.jewel.markdown.rendering + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.ui.Alignment +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathEffect +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.jetbrains.jewel.markdown.rendering.MarkdownStyling.BlockQuote +import org.jetbrains.jewel.markdown.rendering.MarkdownStyling.Code +import org.jetbrains.jewel.markdown.rendering.MarkdownStyling.Code.Fenced +import org.jetbrains.jewel.markdown.rendering.MarkdownStyling.Code.Fenced.InfoPosition +import org.jetbrains.jewel.markdown.rendering.MarkdownStyling.Code.Indented +import org.jetbrains.jewel.markdown.rendering.MarkdownStyling.Heading +import org.jetbrains.jewel.markdown.rendering.MarkdownStyling.HtmlBlock +import org.jetbrains.jewel.markdown.rendering.MarkdownStyling.Image +import org.jetbrains.jewel.markdown.rendering.MarkdownStyling.List +import org.jetbrains.jewel.markdown.rendering.MarkdownStyling.List.Ordered +import org.jetbrains.jewel.markdown.rendering.MarkdownStyling.List.Unordered +import org.jetbrains.jewel.markdown.rendering.MarkdownStyling.Paragraph +import org.jetbrains.jewel.markdown.rendering.MarkdownStyling.ThematicBreak + +public fun MarkdownStyling.Companion.light( + blockVerticalSpacing: Dp = 16.dp, + paragraph: Paragraph = Paragraph.light(), + heading: Heading = Heading.light(), + blockQuote: BlockQuote = BlockQuote.light(), + code: Code = Code.light(), + list: List = List.light(), + image: Image = Image.default(), + thematicBreak: ThematicBreak = ThematicBreak.light(), + htmlBlock: HtmlBlock = HtmlBlock.light(), +): MarkdownStyling = + MarkdownStyling( + blockVerticalSpacing, + paragraph, + heading, + blockQuote, + code, + list, + image, + thematicBreak, + htmlBlock, + ) + +public fun MarkdownStyling.Companion.dark( + blockVerticalSpacing: Dp = 16.dp, + paragraph: Paragraph = Paragraph.dark(), + heading: Heading = Heading.dark(), + blockQuote: BlockQuote = BlockQuote.dark(), + code: Code = Code.dark(), + list: List = List.dark(), + image: Image = Image.default(), + thematicBreak: ThematicBreak = ThematicBreak.dark(), + htmlBlock: HtmlBlock = HtmlBlock.dark(), +): MarkdownStyling = + MarkdownStyling( + blockVerticalSpacing, + paragraph, + heading, + blockQuote, + code, + list, + image, + thematicBreak, + htmlBlock, + ) + +public fun Paragraph.Companion.light( + inlinesStyling: InlinesStyling = InlinesStyling.light(), +): Paragraph = Paragraph(inlinesStyling) + +public fun Paragraph.Companion.dark( + inlinesStyling: InlinesStyling = InlinesStyling.dark(), +): Paragraph = Paragraph(inlinesStyling) + +public fun Heading.Companion.light( + h1: Heading.H1 = Heading.H1.light(), + h2: Heading.H2 = Heading.H2.light(), + h3: Heading.H3 = Heading.H3.light(), + h4: Heading.H4 = Heading.H4.light(), + h5: Heading.H5 = Heading.H5.light(), + h6: Heading.H6 = Heading.H6.light(), +): Heading = Heading(h1, h2, h3, h4, h5, h6) + +public fun Heading.Companion.dark( + h1: Heading.H1 = Heading.H1.dark(), + h2: Heading.H2 = Heading.H2.dark(), + h3: Heading.H3 = Heading.H3.dark(), + h4: Heading.H4 = Heading.H4.dark(), + h5: Heading.H5 = Heading.H5.dark(), + h6: Heading.H6 = Heading.H6.dark(), +): Heading = Heading(h1, h2, h3, h4, h5, h6) + +public fun Heading.H1.Companion.light( + inlinesStyling: InlinesStyling = + InlinesStyling.light( + defaultLightTextStyle.copy( + fontSize = defaultTextSize * 2, + lineHeight = defaultTextSize * 2 * 1.25, + fontWeight = FontWeight.SemiBold, + ), + ), + underlineWidth: Dp = 1.dp, + underlineColor: Color = Color(0xFFD8DEE4), + underlineGap: Dp = 10.dp, + padding: PaddingValues = PaddingValues(top = 24.dp, bottom = 16.dp), +): Heading.H1 = Heading.H1(inlinesStyling, underlineWidth, underlineColor, underlineGap, padding) + +public fun Heading.H1.Companion.dark( + inlinesStyling: InlinesStyling = + InlinesStyling.dark( + defaultDarkTextStyle.copy( + fontSize = defaultTextSize * 2, + lineHeight = defaultTextSize * 2 * 1.25, + fontWeight = FontWeight.SemiBold, + ), + ), + underlineWidth: Dp = 1.dp, + underlineColor: Color = Color(0xFF21262d), + underlineGap: Dp = 10.dp, + padding: PaddingValues = PaddingValues(top = 24.dp, bottom = 16.dp), +): Heading.H1 = Heading.H1(inlinesStyling, underlineWidth, underlineColor, underlineGap, padding) + +public fun Heading.H2.Companion.light( + inlinesStyling: InlinesStyling = + InlinesStyling.light( + defaultLightTextStyle.copy( + fontSize = defaultTextSize * 1.5, + lineHeight = defaultTextSize * 1.5 * 1.25, + fontWeight = FontWeight.SemiBold, + ), + ), + underlineWidth: Dp = 1.dp, + underlineColor: Color = Color(0xFFD8DEE4), + underlineGap: Dp = 6.dp, + padding: PaddingValues = PaddingValues(top = 24.dp, bottom = 16.dp), +): Heading.H2 = Heading.H2(inlinesStyling, underlineWidth, underlineColor, underlineGap, padding) + +public fun Heading.H2.Companion.dark( + inlinesStyling: InlinesStyling = + InlinesStyling.dark( + defaultDarkTextStyle.copy( + fontSize = defaultTextSize * 1.5, + lineHeight = defaultTextSize * 1.5 * 1.25, + fontWeight = FontWeight.SemiBold, + ), + ), + underlineWidth: Dp = 1.dp, + underlineColor: Color = Color(0xFF21262d), + underlineGap: Dp = 6.dp, + padding: PaddingValues = PaddingValues(top = 24.dp, bottom = 16.dp), +): Heading.H2 = Heading.H2(inlinesStyling, underlineWidth, underlineColor, underlineGap, padding) + +// This doesn't match Int UI specs as there is no spec for HTML rendering +public fun Heading.H3.Companion.light( + inlinesStyling: InlinesStyling = + InlinesStyling.light( + defaultLightTextStyle.copy( + fontSize = defaultTextSize * 1.25, + lineHeight = defaultTextSize * 1.25 * 1.25, + fontWeight = FontWeight.SemiBold, + ), + ), + underlineWidth: Dp = 0.dp, + underlineColor: Color = Color.Unspecified, + underlineGap: Dp = 0.dp, + padding: PaddingValues = PaddingValues(top = 24.dp, bottom = 16.dp), +): Heading.H3 = Heading.H3(inlinesStyling, underlineWidth, underlineColor, underlineGap, padding) + +// This doesn't match Int UI specs as there is no spec for HTML rendering +public fun Heading.H3.Companion.dark( + inlinesStyling: InlinesStyling = + InlinesStyling.dark( + defaultDarkTextStyle.copy( + fontSize = defaultTextSize * 1.25, + lineHeight = defaultTextSize * 1.25 * 1.25, + fontWeight = FontWeight.SemiBold, + ), + ), + underlineWidth: Dp = 0.dp, + underlineColor: Color = Color.Unspecified, + underlineGap: Dp = 0.dp, + padding: PaddingValues = PaddingValues(top = 24.dp, bottom = 16.dp), +): Heading.H3 = Heading.H3(inlinesStyling, underlineWidth, underlineColor, underlineGap, padding) + +// This doesn't match Int UI specs as there is no spec for HTML rendering +public fun Heading.H4.Companion.light( + inlinesStyling: InlinesStyling = + InlinesStyling.light( + defaultLightTextStyle.copy( + fontSize = defaultTextSize, + lineHeight = defaultTextSize * 1.25, + fontWeight = FontWeight.SemiBold, + ), + ), + underlineWidth: Dp = 0.dp, + underlineColor: Color = Color.Unspecified, + underlineGap: Dp = 0.dp, + padding: PaddingValues = PaddingValues(top = 24.dp, bottom = 16.dp), +): Heading.H4 = Heading.H4(inlinesStyling, underlineWidth, underlineColor, underlineGap, padding) + +// This doesn't match Int UI specs as there is no spec for HTML rendering +public fun Heading.H4.Companion.dark( + inlinesStyling: InlinesStyling = + InlinesStyling.dark( + defaultDarkTextStyle.copy( + fontSize = defaultTextSize, + lineHeight = defaultTextSize * 1.25, + fontWeight = FontWeight.SemiBold, + ), + ), + underlineWidth: Dp = 0.dp, + underlineColor: Color = Color.Unspecified, + underlineGap: Dp = 0.dp, + padding: PaddingValues = PaddingValues(top = 24.dp, bottom = 16.dp), +): Heading.H4 = Heading.H4(inlinesStyling, underlineWidth, underlineColor, underlineGap, padding) + +// H5 is identical to H4 and H6 +public fun Heading.H5.Companion.light( + inlinesStyling: InlinesStyling = + InlinesStyling.light( + defaultLightTextStyle.copy( + fontSize = defaultTextSize * .875, + lineHeight = defaultTextSize * .875 * 1.25, + fontWeight = FontWeight.SemiBold, + ), + ), + underlineWidth: Dp = 0.dp, + underlineColor: Color = Color.Unspecified, + underlineGap: Dp = 0.dp, + padding: PaddingValues = PaddingValues(top = 24.dp, bottom = 16.dp), +): Heading.H5 = Heading.H5(inlinesStyling, underlineWidth, underlineColor, underlineGap, padding) + +// H5 is identical to H4 and H6 +public fun Heading.H5.Companion.dark( + inlinesStyling: InlinesStyling = + InlinesStyling.dark( + defaultDarkTextStyle.copy( + fontSize = defaultTextSize * .875, + lineHeight = defaultTextSize * .875 * 1.25, + fontWeight = FontWeight.SemiBold, + ), + ), + underlineWidth: Dp = 0.dp, + underlineColor: Color = Color.Unspecified, + underlineGap: Dp = 0.dp, + padding: PaddingValues = PaddingValues(top = 24.dp, bottom = 16.dp), +): Heading.H5 = Heading.H5(inlinesStyling, underlineWidth, underlineColor, underlineGap, padding) + +// H6 is identical to H4 and H5 +public fun Heading.H6.Companion.light( + inlinesStyling: InlinesStyling = + InlinesStyling.light( + defaultLightTextStyle.copy( + color = Color(0xFF656d76), + fontSize = defaultTextSize * .85, + lineHeight = defaultTextSize * .85 * 1.25, + fontWeight = FontWeight.SemiBold, + ), + ), + underlineWidth: Dp = 0.dp, + underlineColor: Color = Color.Unspecified, + underlineGap: Dp = 0.dp, + padding: PaddingValues = PaddingValues(top = 24.dp, bottom = 16.dp), +): Heading.H6 = Heading.H6(inlinesStyling, underlineWidth, underlineColor, underlineGap, padding) + +// H6 is identical to H4 and H5 +public fun Heading.H6.Companion.dark( + inlinesStyling: InlinesStyling = + InlinesStyling.dark( + defaultDarkTextStyle.copy( + color = Color(0xFF848d97), + fontSize = defaultTextSize * .85, + lineHeight = defaultTextSize * .85 * 1.25, + fontWeight = FontWeight.SemiBold, + ), + ), + underlineWidth: Dp = 0.dp, + underlineColor: Color = Color.Unspecified, + underlineGap: Dp = 0.dp, + padding: PaddingValues = PaddingValues(top = 24.dp, bottom = 16.dp), +): Heading.H6 = Heading.H6(inlinesStyling, underlineWidth, underlineColor, underlineGap, padding) + +public fun BlockQuote.Companion.light( + padding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp), + lineWidth: Dp = 4.dp, + lineColor: Color = Color(0xFFD0D7DE), + pathEffect: PathEffect? = null, + strokeCap: StrokeCap = StrokeCap.Square, + textColor: Color = Color(0xFF656d76), +): BlockQuote = BlockQuote(padding, lineWidth, lineColor, pathEffect, strokeCap, textColor) + +public fun BlockQuote.Companion.dark( + padding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp), + lineWidth: Dp = 4.dp, + lineColor: Color = Color.DarkGray, + pathEffect: PathEffect? = null, + strokeCap: StrokeCap = StrokeCap.Square, + textColor: Color = Color(0xFF848d97), +): BlockQuote = BlockQuote(padding, lineWidth, lineColor, pathEffect, strokeCap, textColor) + +public fun List.Companion.light( + ordered: Ordered = Ordered.light(), + unordered: Unordered = Unordered.light(), +): List = List(ordered, unordered) + +public fun List.Companion.dark( + ordered: Ordered = Ordered.dark(), + unordered: Unordered = Unordered.dark(), +): List = List(ordered, unordered) + +public fun Ordered.Companion.light( + numberStyle: TextStyle = defaultLightTextStyle, + numberContentGap: Dp = 8.dp, + numberMinWidth: Dp = 16.dp, + numberTextAlign: TextAlign = TextAlign.End, + itemVerticalSpacing: Dp = 16.dp, + itemVerticalSpacingTight: Dp = 4.dp, + padding: PaddingValues = PaddingValues(start = 16.dp), +): Ordered = + Ordered( + numberStyle, + numberContentGap, + numberMinWidth, + numberTextAlign, + itemVerticalSpacing, + itemVerticalSpacingTight, + padding, + ) + +public fun Ordered.Companion.dark( + numberStyle: TextStyle = defaultDarkTextStyle, + numberContentGap: Dp = 8.dp, + numberMinWidth: Dp = 16.dp, + numberTextAlign: TextAlign = TextAlign.End, + itemVerticalSpacing: Dp = 16.dp, + itemVerticalSpacingTight: Dp = 4.dp, + padding: PaddingValues = PaddingValues(start = 16.dp), +): Ordered = + Ordered( + numberStyle, + numberContentGap, + numberMinWidth, + numberTextAlign, + itemVerticalSpacing, + itemVerticalSpacingTight, + padding, + ) + +public fun Unordered.Companion.light( + bullet: Char? = '•', + bulletStyle: TextStyle = defaultLightTextStyle.copy(fontWeight = FontWeight.Black), + bulletContentGap: Dp = 16.dp, + itemVerticalSpacing: Dp = 16.dp, + itemVerticalSpacingTight: Dp = 4.dp, + padding: PaddingValues = PaddingValues(start = 16.dp), +): Unordered = + Unordered( + bullet, + bulletStyle, + bulletContentGap, + itemVerticalSpacing, + itemVerticalSpacingTight, + padding, + ) + +public fun Unordered.Companion.dark( + bullet: Char? = '•', + bulletStyle: TextStyle = defaultDarkTextStyle.copy(fontWeight = FontWeight.Black), + bulletContentGap: Dp = 16.dp, + itemVerticalSpacing: Dp = 16.dp, + itemVerticalSpacingTight: Dp = 4.dp, + padding: PaddingValues = PaddingValues(start = 16.dp), +): Unordered = + Unordered( + bullet, + bulletStyle, + bulletContentGap, + itemVerticalSpacing, + itemVerticalSpacingTight, + padding, + ) + +public fun Code.Companion.light( + indented: Indented = Indented.light(), + fenced: Fenced = Fenced.light(), +): Code = Code(indented, fenced) + +public fun Code.Companion.dark( + indented: Indented = Indented.dark(), + fenced: Fenced = Fenced.dark(), +): Code = Code(indented, fenced) + +public fun Indented.Companion.light( + textStyle: TextStyle = + defaultLightTextStyle.copy( + color = Color(0xFF1F2328), + fontFamily = FontFamily.Monospace, + fontSize = defaultTextSize * .85, + lineHeight = defaultTextSize * .85 * 1.45, + ), + padding: PaddingValues = PaddingValues(16.dp), + shape: Shape = RectangleShape, + background: Color = Color(0xFFf6f8fa), + borderWidth: Dp = 0.dp, + borderColor: Color = Color.Unspecified, + fillWidth: Boolean = true, + scrollsHorizontally: Boolean = true, +): Indented = + Indented( + textStyle, + padding, + shape, + background, + borderWidth, + borderColor, + fillWidth, + scrollsHorizontally, + ) + +public fun Indented.Companion.dark( + textStyle: TextStyle = + defaultDarkTextStyle.copy( + color = Color(0xFFe6edf3), + fontFamily = FontFamily.Monospace, + fontSize = defaultTextSize * .85, + lineHeight = defaultTextSize * .85 * 1.45, + ), + padding: PaddingValues = PaddingValues(16.dp), + shape: Shape = RectangleShape, + background: Color = Color(0xff161b22), + borderWidth: Dp = 0.dp, + borderColor: Color = Color.Unspecified, + fillWidth: Boolean = true, + scrollsHorizontally: Boolean = true, +): Indented = + Indented( + textStyle, + padding, + shape, + background, + borderWidth, + borderColor, + fillWidth, + scrollsHorizontally, + ) + +public fun Fenced.Companion.light( + textStyle: TextStyle = + defaultLightTextStyle.copy( + color = Color(0xFF1F2328), + fontFamily = FontFamily.Monospace, + fontSize = defaultTextSize * .85, + lineHeight = defaultTextSize * .85 * 1.45, + ), + padding: PaddingValues = PaddingValues(16.dp), + shape: Shape = RectangleShape, + background: Color = Color(0xFFf6f8fa), + borderWidth: Dp = 0.dp, + borderColor: Color = Color.Unspecified, + fillWidth: Boolean = true, + scrollsHorizontally: Boolean = true, + infoTextStyle: TextStyle = TextStyle(color = Color.Gray, fontSize = 12.sp), + infoPadding: PaddingValues = PaddingValues(bottom = 16.dp), + infoPosition: InfoPosition = InfoPosition.Hide, +): Fenced = + Fenced( + textStyle, + padding, + shape, + background, + borderWidth, + borderColor, + fillWidth, + scrollsHorizontally, + infoTextStyle, + infoPadding, + infoPosition, + ) + +public fun Fenced.Companion.dark( + textStyle: TextStyle = + defaultDarkTextStyle.copy( + color = Color(0xFFe6edf3), + fontFamily = FontFamily.Monospace, + fontSize = defaultTextSize * .85, + lineHeight = defaultTextSize * .85 * 1.45, + ), + padding: PaddingValues = PaddingValues(16.dp), + shape: Shape = RectangleShape, + background: Color = Color(0xff161b22), + borderWidth: Dp = 0.dp, + borderColor: Color = Color.Unspecified, + fillWidth: Boolean = true, + scrollsHorizontally: Boolean = true, + infoTextStyle: TextStyle = TextStyle(color = Color.Gray, fontSize = 12.sp), + infoPadding: PaddingValues = PaddingValues(start = 8.dp, end = 8.dp, bottom = 8.dp), + infoPosition: InfoPosition = InfoPosition.Hide, +): Fenced = + Fenced( + textStyle, + padding, + shape, + background, + borderWidth, + borderColor, + fillWidth, + scrollsHorizontally, + infoTextStyle, + infoPadding, + infoPosition, + ) + +public fun Image.Companion.default( + alignment: Alignment = Alignment.Center, + contentScale: ContentScale = ContentScale.Fit, + padding: PaddingValues = PaddingValues(), + shape: Shape = RectangleShape, + background: Color = Color.Unspecified, + borderWidth: Dp = 0.dp, + borderColor: Color = Color.Unspecified, +): Image = Image(alignment, contentScale, padding, shape, background, borderWidth, borderColor) + +public fun ThematicBreak.Companion.light( + padding: PaddingValues = PaddingValues(), + lineWidth: Dp = 2.dp, + lineColor: Color = Color.LightGray, +): ThematicBreak = ThematicBreak(padding, lineWidth, lineColor) + +public fun ThematicBreak.Companion.dark( + padding: PaddingValues = PaddingValues(), + lineWidth: Dp = 2.dp, + lineColor: Color = Color.DarkGray, +): ThematicBreak = ThematicBreak(padding, lineWidth, lineColor) + +public fun HtmlBlock.Companion.light( + textStyle: TextStyle = + defaultLightTextStyle.copy(color = Color.DarkGray, fontFamily = FontFamily.Monospace), + padding: PaddingValues = PaddingValues(8.dp), + shape: Shape = RoundedCornerShape(4.dp), + background: Color = Color.LightGray, + borderWidth: Dp = 1.dp, + borderColor: Color = Color.Gray, + fillWidth: Boolean = true, +): HtmlBlock = HtmlBlock(textStyle, padding, shape, background, borderWidth, borderColor, fillWidth) + +public fun HtmlBlock.Companion.dark( + textStyle: TextStyle = + defaultDarkTextStyle.copy(color = Color.Gray, fontFamily = FontFamily.Monospace), + padding: PaddingValues = PaddingValues(8.dp), + shape: Shape = RoundedCornerShape(4.dp), + background: Color = Color.DarkGray, + borderWidth: Dp = 1.dp, + borderColor: Color = Color.Gray, + fillWidth: Boolean = true, +): HtmlBlock = HtmlBlock(textStyle, padding, shape, background, borderWidth, borderColor, fillWidth) + +private val defaultTextSize = 14.sp + +private val defaultLightTextStyle = + TextStyle.Default.copy( + color = Color.Unspecified, + fontSize = defaultTextSize, + lineHeight = defaultTextSize * 1.5, + fontWeight = FontWeight.Normal, + ) + +private val defaultDarkTextStyle = + defaultLightTextStyle.copy(color = Color.Unspecified) + +public fun InlinesStyling.Companion.light( + textStyle: TextStyle = defaultLightTextStyle, + inlineCode: SpanStyle = + textStyle + .copy( + fontSize = textStyle.fontSize * .85, + background = Color(0xFFEFF1F2), + fontFamily = FontFamily.Monospace, + ) + .toSpanStyle(), + link: SpanStyle = + textStyle.copy(color = Color(0xFF0969DA), textDecoration = TextDecoration.Underline).toSpanStyle(), + emphasis: SpanStyle = textStyle.copy(fontStyle = FontStyle.Italic).toSpanStyle(), + strongEmphasis: SpanStyle = textStyle.copy(fontWeight = FontWeight.Bold).toSpanStyle(), + inlineHtml: SpanStyle = textStyle.toSpanStyle(), + renderInlineHtml: Boolean = false, +): InlinesStyling = + InlinesStyling( + textStyle, + inlineCode, + link, + emphasis, + strongEmphasis, + inlineHtml, + renderInlineHtml, + ) + +public fun InlinesStyling.Companion.dark( + textStyle: TextStyle = defaultDarkTextStyle, + inlineCode: SpanStyle = + textStyle + .copy( + fontSize = textStyle.fontSize * .85, + background = Color(0xFF343941), + fontFamily = FontFamily.Monospace, + ) + .toSpanStyle(), + link: SpanStyle = + textStyle + .copy(color = Color(0xFF2F81F7), textDecoration = TextDecoration.Underline) + .toSpanStyle(), + emphasis: SpanStyle = textStyle.copy(fontStyle = FontStyle.Italic).toSpanStyle(), + strongEmphasis: SpanStyle = textStyle.copy(fontWeight = FontWeight.Bold).toSpanStyle(), + inlineHtml: SpanStyle = textStyle.toSpanStyle(), + renderInlineHtml: Boolean = false, +): InlinesStyling = + InlinesStyling( + textStyle, + inlineCode, + link, + emphasis, + strongEmphasis, + inlineHtml, + renderInlineHtml, + ) diff --git a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer.kt b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer.kt new file mode 100644 index 000000000..49d69ebaa --- /dev/null +++ b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer.kt @@ -0,0 +1,23 @@ +package org.jetbrains.jewel.markdown.rendering + +import androidx.compose.ui.text.AnnotatedString +import org.jetbrains.jewel.foundation.ExperimentalJewelApi +import org.jetbrains.jewel.markdown.InlineMarkdown +import org.jetbrains.jewel.markdown.extensions.MarkdownProcessorExtension + +@ExperimentalJewelApi +public interface InlineMarkdownRenderer { + + /** + * Render the [inlineMarkdown] as an [AnnotatedString], using the [styling] + * provided. + */ + public fun renderAsAnnotatedString(inlineMarkdown: InlineMarkdown, styling: InlinesStyling): AnnotatedString + + public companion object { + + /** Create a default inline renderer, with the [extensions] provided. */ + public fun default(extensions: List = emptyList()): InlineMarkdownRenderer = + DefaultInlineMarkdownRenderer(extensions) + } +} diff --git a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer.kt b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer.kt new file mode 100644 index 000000000..c99bbc116 --- /dev/null +++ b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer.kt @@ -0,0 +1,106 @@ +package org.jetbrains.jewel.markdown.rendering + +import androidx.compose.runtime.Composable +import org.jetbrains.jewel.foundation.ExperimentalJewelApi +import org.jetbrains.jewel.markdown.MarkdownBlock +import org.jetbrains.jewel.markdown.MarkdownBlock.BlockQuote +import org.jetbrains.jewel.markdown.MarkdownBlock.CodeBlock +import org.jetbrains.jewel.markdown.MarkdownBlock.CodeBlock.FencedCodeBlock +import org.jetbrains.jewel.markdown.MarkdownBlock.CodeBlock.IndentedCodeBlock +import org.jetbrains.jewel.markdown.MarkdownBlock.Heading.H1 +import org.jetbrains.jewel.markdown.MarkdownBlock.Heading.H2 +import org.jetbrains.jewel.markdown.MarkdownBlock.Heading.H3 +import org.jetbrains.jewel.markdown.MarkdownBlock.Heading.H4 +import org.jetbrains.jewel.markdown.MarkdownBlock.Heading.H5 +import org.jetbrains.jewel.markdown.MarkdownBlock.Heading.H6 +import org.jetbrains.jewel.markdown.MarkdownBlock.HtmlBlock +import org.jetbrains.jewel.markdown.MarkdownBlock.Image +import org.jetbrains.jewel.markdown.MarkdownBlock.ListBlock +import org.jetbrains.jewel.markdown.MarkdownBlock.ListBlock.OrderedList +import org.jetbrains.jewel.markdown.MarkdownBlock.ListBlock.UnorderedList +import org.jetbrains.jewel.markdown.MarkdownBlock.ListItem +import org.jetbrains.jewel.markdown.extensions.MarkdownRendererExtension + +@ExperimentalJewelApi +public interface MarkdownBlockRenderer { + + @Composable + public fun render(blocks: List) + + @Composable + public fun render(block: MarkdownBlock) + + @Composable + public fun render(block: MarkdownBlock.Paragraph, styling: MarkdownStyling.Paragraph) + + @Composable + public fun render(block: MarkdownBlock.Heading, styling: MarkdownStyling.Heading) + + @Composable + public fun render(block: H1, styling: MarkdownStyling.Heading.H1) + + @Composable + public fun render(block: H2, styling: MarkdownStyling.Heading.H2) + + @Composable + public fun render(block: H3, styling: MarkdownStyling.Heading.H3) + + @Composable + public fun render(block: H4, styling: MarkdownStyling.Heading.H4) + + @Composable + public fun render(block: H5, styling: MarkdownStyling.Heading.H5) + + @Composable + public fun render(block: H6, styling: MarkdownStyling.Heading.H6) + + @Composable + public fun render(block: BlockQuote, styling: MarkdownStyling.BlockQuote) + + @Composable + public fun render(block: ListBlock, styling: MarkdownStyling.List) + + @Composable + public fun render(block: OrderedList, styling: MarkdownStyling.List.Ordered) + + @Composable + public fun render(block: UnorderedList, styling: MarkdownStyling.List.Unordered) + + @Composable + public fun render(block: ListItem) + + @Composable + public fun render(block: CodeBlock, styling: MarkdownStyling.Code) + + @Composable + public fun render(block: IndentedCodeBlock, styling: MarkdownStyling.Code.Indented) + + @Composable + public fun render(block: FencedCodeBlock, styling: MarkdownStyling.Code.Fenced) + + @Composable + public fun render(block: Image, styling: MarkdownStyling.Image) + + @Composable + public fun renderThematicBreak(styling: MarkdownStyling.ThematicBreak) + + @Composable + public fun render(block: HtmlBlock, styling: MarkdownStyling.HtmlBlock) + + public companion object { + + public fun light( + styling: MarkdownStyling = MarkdownStyling.light(), + rendererExtensions: List = emptyList(), + inlineRenderer: InlineMarkdownRenderer = InlineMarkdownRenderer.default(), + onUrlClick: (String) -> Unit = {}, + ): MarkdownBlockRenderer = DefaultMarkdownBlockRenderer(styling, rendererExtensions, inlineRenderer, onUrlClick) + + public fun dark( + styling: MarkdownStyling = MarkdownStyling.dark(), + rendererExtensions: List = emptyList(), + inlineRenderer: InlineMarkdownRenderer = InlineMarkdownRenderer.default(), + onUrlClick: (String) -> Unit = {}, + ): MarkdownBlockRenderer = DefaultMarkdownBlockRenderer(styling, rendererExtensions, inlineRenderer, onUrlClick) + } +} diff --git a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/MarkdownStyling.kt b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/MarkdownStyling.kt new file mode 100644 index 000000000..8c7a33142 --- /dev/null +++ b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/MarkdownStyling.kt @@ -0,0 +1,330 @@ +package org.jetbrains.jewel.markdown.rendering + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.ui.Alignment +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathEffect +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.Dp +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 +import org.jetbrains.jewel.markdown.rendering.MarkdownStyling.Code.Fenced.InfoPosition.BottomEnd +import org.jetbrains.jewel.markdown.rendering.MarkdownStyling.Code.Fenced.InfoPosition.BottomStart +import org.jetbrains.jewel.markdown.rendering.MarkdownStyling.Code.Fenced.InfoPosition.Hide +import org.jetbrains.jewel.markdown.rendering.MarkdownStyling.Code.Fenced.InfoPosition.TopCenter +import org.jetbrains.jewel.markdown.rendering.MarkdownStyling.Code.Fenced.InfoPosition.TopEnd +import org.jetbrains.jewel.markdown.rendering.MarkdownStyling.Code.Fenced.InfoPosition.TopStart + +@GenerateDataFunctions +public class MarkdownStyling( + public val blockVerticalSpacing: Dp, + public val paragraph: Paragraph, + public val heading: Heading, + public val blockQuote: BlockQuote, + public val code: Code, + public val list: List, + public val image: Image, + public val thematicBreak: ThematicBreak, + public val htmlBlock: HtmlBlock, +) { + + @GenerateDataFunctions + public class Paragraph( + override val inlinesStyling: InlinesStyling, + ) : WithInlinesStyling { + + public companion object + } + + @GenerateDataFunctions + public class Heading( + public val h1: H1, + public val h2: H2, + public val h3: H3, + public val h4: H4, + public val h5: H5, + public val h6: H6, + ) { + + @GenerateDataFunctions + public class H1( + override val inlinesStyling: InlinesStyling, + override val underlineWidth: Dp, + override val underlineColor: Color, + override val underlineGap: Dp, + public val padding: PaddingValues, + ) : WithInlinesStyling, WithUnderline { + + public companion object + } + + @GenerateDataFunctions + public class H2( + override val inlinesStyling: InlinesStyling, + override val underlineWidth: Dp, + override val underlineColor: Color, + override val underlineGap: Dp, + public val padding: PaddingValues, + ) : WithInlinesStyling, WithUnderline { + + public companion object + } + + @GenerateDataFunctions + public class H3( + override val inlinesStyling: InlinesStyling, + override val underlineWidth: Dp, + override val underlineColor: Color, + override val underlineGap: Dp, + public val padding: PaddingValues, + ) : WithInlinesStyling, WithUnderline { + + public companion object + } + + @GenerateDataFunctions + public class H4( + override val inlinesStyling: InlinesStyling, + override val underlineWidth: Dp, + override val underlineColor: Color, + override val underlineGap: Dp, + public val padding: PaddingValues, + ) : WithInlinesStyling, WithUnderline { + + public companion object + } + + @GenerateDataFunctions + public class H5( + override val inlinesStyling: InlinesStyling, + override val underlineWidth: Dp, + override val underlineColor: Color, + override val underlineGap: Dp, + public val padding: PaddingValues, + ) : WithInlinesStyling, WithUnderline { + + public companion object + } + + @GenerateDataFunctions + public class H6( + override val inlinesStyling: InlinesStyling, + override val underlineWidth: Dp, + override val underlineColor: Color, + override val underlineGap: Dp, + public val padding: PaddingValues, + ) : WithInlinesStyling, WithUnderline { + + public companion object + } + + public companion object + } + + @GenerateDataFunctions + public class BlockQuote( + public val padding: PaddingValues, + public val lineWidth: Dp, + public val lineColor: Color, + public val pathEffect: PathEffect?, + public val strokeCap: StrokeCap, + public val textColor: Color, + ) { + + public companion object + } + + @GenerateDataFunctions + public class List( + public val ordered: Ordered, + public val unordered: Unordered, + ) { + + @GenerateDataFunctions + public class Ordered( + public val numberStyle: TextStyle, + public val numberContentGap: Dp, + public val numberMinWidth: Dp, + public val numberTextAlign: TextAlign, + public val itemVerticalSpacing: Dp, + public val itemVerticalSpacingTight: Dp, + public val padding: PaddingValues, + ) { + + public companion object + } + + @GenerateDataFunctions + public class Unordered( + public val bullet: Char?, + public val bulletStyle: TextStyle, + public val bulletContentGap: Dp, + public val itemVerticalSpacing: Dp, + public val itemVerticalSpacingTight: Dp, + public val padding: PaddingValues, + ) { + + public companion object + } + + public companion object + } + + @GenerateDataFunctions + public class Code( + public val indented: Indented, + public val fenced: Fenced, + ) { + + @GenerateDataFunctions + public class Indented( + public val textStyle: TextStyle, + public val padding: PaddingValues, + public val shape: Shape, + public val background: Color, + public val borderWidth: Dp, + public val borderColor: Color, + public val fillWidth: Boolean, + public val scrollsHorizontally: Boolean, + ) { + + public companion object + } + + @GenerateDataFunctions + public class Fenced( + public val textStyle: TextStyle, + public val padding: PaddingValues, + public val shape: Shape, + public val background: Color, + public val borderWidth: Dp, + public val borderColor: Color, + public val fillWidth: Boolean, + public val scrollsHorizontally: Boolean, + public val infoTextStyle: TextStyle, + public val infoPadding: PaddingValues, + public val infoPosition: InfoPosition, + ) { + + public enum class InfoPosition { + TopStart, + TopCenter, + TopEnd, + BottomStart, + BottomCenter, + BottomEnd, + Hide, + } + + public companion object + } + + public companion object + } + + @GenerateDataFunctions + public class Image( + public val alignment: Alignment, + public val contentScale: ContentScale, + public val padding: PaddingValues, + public val shape: Shape, + public val background: Color, + public val borderWidth: Dp, + public val borderColor: Color, + ) { + + public companion object + } + + @GenerateDataFunctions + public class ThematicBreak( + public val padding: PaddingValues, + public val lineWidth: Dp, + public val lineColor: Color, + ) { + + public companion object + } + + @GenerateDataFunctions + public class HtmlBlock( + public val textStyle: TextStyle, + public val padding: PaddingValues, + public val shape: Shape, + public val background: Color, + public val borderWidth: Dp, + public val borderColor: Color, + public val fillWidth: Boolean, + ) { + + public companion object + } + + public companion object +} + +public interface WithInlinesStyling { + + public val inlinesStyling: InlinesStyling +} + +public interface WithUnderline { + + public val underlineWidth: Dp + public val underlineColor: Color + public val underlineGap: Dp +} + +@GenerateDataFunctions +public class InlinesStyling( + public val textStyle: TextStyle, + public val inlineCode: SpanStyle, + public val link: SpanStyle, + public val emphasis: SpanStyle, + public val strongEmphasis: SpanStyle, + public val inlineHtml: SpanStyle, + public val renderInlineHtml: Boolean, +) { + + public companion object +} + +internal val InfoPosition.verticalAlignment + get() = + when (this) { + TopStart, + TopCenter, + TopEnd, + -> Alignment.Top + + BottomStart, + BottomCenter, + BottomEnd, + -> Alignment.Bottom + + Hide -> null + } + +internal val InfoPosition.horizontalAlignment + get() = + when (this) { + TopStart, + BottomStart, + -> Alignment.Start + + TopCenter, + BottomCenter, + -> Alignment.CenterHorizontally + + TopEnd, + BottomEnd, + -> Alignment.End + + Hide -> null + } diff --git a/markdown/core/src/test/kotlin/org/jetbrains/jewel/markdown/MarkdownProcessorDocumentParsingTest.kt b/markdown/core/src/test/kotlin/org/jetbrains/jewel/markdown/MarkdownProcessorDocumentParsingTest.kt new file mode 100644 index 000000000..ac8df27ea --- /dev/null +++ b/markdown/core/src/test/kotlin/org/jetbrains/jewel/markdown/MarkdownProcessorDocumentParsingTest.kt @@ -0,0 +1,12579 @@ +package org.jetbrains.jewel.markdown + +import org.jetbrains.jewel.markdown.MarkdownBlock.ThematicBreak +import org.jetbrains.jewel.markdown.processing.MarkdownProcessor +import org.junit.Test + +/** + * This class tests that all the snippets in the CommonMark 0.30 specs are + * rendered correctly into MarkdownBlocks, matching what the CommonMark + * 0.20 HTML renderer tests also validate. + * + * Note that the reference HTML output is only there as information; our + * parsing logic performs various transformations that CommonMark wouldn't. + * For more info, refer to [MarkdownProcessor.processMarkdownDocument]. + */ +@Suppress( + "HtmlDeprecatedAttribute", + "HtmlRequiredAltAttribute", + "HtmlUnknownAttribute", + "HtmlUnknownTarget", + "MarkdownLinkDestinationWithSpaces", + "MarkdownUnresolvedFileReference", + "MarkdownUnresolvedLinkLabel", + "MarkdownUnresolvedHeaderReference", + "LargeClass", // Detekt hates huge test suites I guess +) // All used in purposefully odd Markdown +class MarkdownProcessorDocumentParsingTest { + + private val processor = MarkdownProcessor() + + @Test + fun `should parse spec sample 1 correctly (Tabs)`() { + val parsed = processor.processMarkdownDocument("\tfoo\tbaz\t\tbim") + + /* + * Expected HTML: + *
foo→baz→→bim
+         * 
+ */ + parsed.assertEquals(indentedCodeBlock("foo\tbaz\t\tbim")) + } + + @Test + fun `should parse spec sample 2 correctly (Tabs)`() { + val parsed = processor.processMarkdownDocument(" \tfoo\tbaz\t\tbim") + + /* + * Expected HTML: + *
foo→baz→\tbim
+         * 
+ */ + parsed.assertEquals(indentedCodeBlock("foo\tbaz\t\tbim")) + } + + @Test + fun `should parse spec sample 3 correctly (Tabs)`() { + val parsed = processor.processMarkdownDocument(" a\ta\n ὐ\ta") + + /* + * Expected HTML: + *
a→a
+         * ὐ→a
+         * 
+ */ + parsed.assertEquals(indentedCodeBlock("a\ta\nὐ\ta")) + } + + @Test + fun `should parse spec sample 4 correctly (Tabs)`() { + val parsed = processor.processMarkdownDocument(" - foo\n\n\tbar") + + /* + * Expected HTML: + *
    + *
  • + *

    foo

    + *

    bar

    + *
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem( + paragraph("foo"), + paragraph("bar"), + ), + isTight = false, + ), + ) + } + + @Test + fun `should parse spec sample 5 correctly (Tabs)`() { + val parsed = processor.processMarkdownDocument("- foo\n\n\t\tbar") + + /* + * Expected HTML: + *
    + *
  • + *

    foo

    + *
      bar
    +         * 
    + *
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem( + paragraph("foo"), + indentedCodeBlock(" bar"), + ), + isTight = false, + ), + ) + } + + @Test + fun `should parse spec sample 6 correctly (Tabs)`() { + val parsed = processor.processMarkdownDocument(">\t\tfoo") + + /* + * Expected HTML: + *
+ *
  foo
+         * 
+ *
+ */ + parsed.assertEquals(blockQuote(indentedCodeBlock(" foo"))) + } + + @Test + fun `should parse spec sample 7 correctly (Tabs)`() { + val parsed = processor.processMarkdownDocument("-\t\tfoo") + + /* + * Expected HTML: + *
    + *
  • + *
      foo
    +         * 
    + *
  • + *
+ */ + parsed.assertEquals(unorderedList(listItem(indentedCodeBlock(" foo")))) + } + + @Test + fun `should parse spec sample 8 correctly (Tabs)`() { + val parsed = processor.processMarkdownDocument(" foo\n\tbar") + + /* + * Expected HTML: + *
foo
+         * bar
+         * 
+ */ + parsed.assertEquals(indentedCodeBlock("foo\nbar")) + } + + @Test + fun `should parse spec sample 9 correctly (Tabs)`() { + val parsed = processor.processMarkdownDocument(" - foo\n - bar\n\t - baz") + + /* + * Expected HTML: + *
    + *
  • foo + *
      + *
    • bar + *
        + *
      • baz
      • + *
      + *
    • + *
    + *
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem( + paragraph("foo"), + unorderedList( + listItem( + paragraph("bar"), + unorderedList(listItem(paragraph("baz"))), + ), + ), + ), + ), + ) + } + + @Test + fun `should parse spec sample 10 correctly (Tabs)`() { + val parsed = processor.processMarkdownDocument("#\tFoo") + + /* + * Expected HTML: + *

Foo

+ */ + parsed.assertEquals(heading(level = 1, "Foo")) + } + + @Test + fun `should parse spec sample 11 correctly (Tabs)`() { + val parsed = processor.processMarkdownDocument("*\t*\t*\t") + + /* + * Expected HTML: + *
+ */ + parsed.assertEquals(ThematicBreak) + } + + @Test + fun `should parse spec sample 12 correctly (Backslash escapes)`() { + val parsed = + processor.processMarkdownDocument( + "\\!\\\"\\#\\\$\\%\\&\\'\\(\\)\\*\\+\\,\\-\\.\\/\\:\\;\\<\\=\\>\\?\\@" + + "\\[\\\\\\\\]\\^\\_\\`\\{\\|\\}\\~\n", + // ^^ + // Note: this was slightly edited by adding a backslash here + // because the `\[\\\]` sequence wouldn't be representable otherwise + // (CommonMark un-escapes characters, and it's a lossy transform) + ) + + /* + * Expected HTML: + *

!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~

+ */ + parsed.assertEquals(paragraph("\\!\"#\$%&'\\(\\)\\*+,-./:;\\<=\\>?@\\[\\\\\\]^\\_\\`{|}\\~")) + } + + @Test + fun `should parse spec sample 13 correctly (Backslash escapes)`() { + val parsed = processor.processMarkdownDocument("\\\t\\A\\a\\ \\3\\φ\\«") + + /* + * Expected HTML: + *

\→\A\a\ \3\φ\«

+ */ + parsed.assertEquals(paragraph("\\\t\\A\\a\\ \\3\\φ\\«")) + } + + @Test + fun `should parse spec sample 14 correctly (Backslash escapes)`() { + val parsed = + processor.processMarkdownDocument( + """ + |\*not emphasized* + |\
not a tag + |\[not a link](/foo) + |\`not code` + |1\. not a list + |\* not a list + |\# not a heading + |\[foo]: /url "not a reference" + |\ö not a character entity + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

*not emphasized* + * <br/> not a tag + * [not a link](/foo) + * `not code` + * 1. not a list + * * not a list + * # not a heading + * [foo]: /url "not a reference" + * &ouml; not a character entity

+ */ + parsed.assertEquals( + paragraph( + "\\*not emphasized\\* " + + "\\
not a tag " + + "\\[not a link\\]\\(/foo\\) " + + "\\`not code\\` " + + "1. not a list " + + "\\* not a list " + + "# not a heading " + + "\\[foo\\]: /url \"not a reference\" " + + "ö not a character entity", + ), + ) + } + + @Test + fun `should parse spec sample 15 correctly (Backslash escapes)`() { + val parsed = processor.processMarkdownDocument("\\\\*emphasis*") + + /* + * Expected HTML: + *

\emphasis

+ */ + parsed.assertEquals(paragraph("\\*emphasis*")) + } + + @Test + fun `should parse spec sample 16 correctly (Backslash escapes)`() { + val parsed = + processor.processMarkdownDocument( + """ + |foo\ + |bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo
+ * bar

+ */ + parsed.assertEquals(paragraph("foo\nbar")) + } + + @Test + fun `should parse spec sample 17 correctly (Backslash escapes)`() { + val parsed = processor.processMarkdownDocument("`` \\[\\` ``") + + /* + * Expected HTML: + *

\[\`

+ */ + parsed.assertEquals(paragraph("``\\[\\```")) + } + + @Test + fun `should parse spec sample 18 correctly (Backslash escapes)`() { + val parsed = processor.processMarkdownDocument(" \\[\\]") + + /* + * Expected HTML: + *
\[\]
+         * 
+ */ + parsed.assertEquals(indentedCodeBlock("\\[\\]")) + } + + @Test + fun `should parse spec sample 19 correctly (Backslash escapes)`() { + val parsed = + processor.processMarkdownDocument( + """ + |~~~ + |\[\] + |~~~ + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
\[\]
+         * 
+ */ + parsed.assertEquals(fencedCodeBlock("\\[\\]")) + } + + @Test + fun `should parse spec sample 20 correctly (Backslash escapes)`() { + val parsed = processor.processMarkdownDocument("") + + /* + * Expected HTML: + *

http://example.com?find=\*

+ */ + parsed.assertEquals(paragraph("[http://example.com?find=\\*](http://example.com?find=\\*)")) + } + + @Test + fun `should parse spec sample 21 correctly (Backslash escapes)`() { + val parsed = processor.processMarkdownDocument("") + + /* + * Expected HTML: + * + */ + parsed.assertEquals(htmlBlock("")) + } + + @Test + fun `should parse spec sample 22 correctly (Backslash escapes)`() { + val parsed = processor.processMarkdownDocument("[foo](/bar\\* \"ti\\*tle\")") + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("[foo](/bar* \"ti*tle\")")) + } + + @Test + fun `should parse spec sample 22b correctly (Backslash escapes)`() { + val parsed = processor.processMarkdownDocument("[](/bar\\* \"ti\\*tle\")") + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("[/bar*](/bar* \"ti*tle\")")) + } + + @Test + fun `should parse spec sample 23 correctly (Backslash escapes)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo] + | + |[foo]: /bar\* "ti\*tle" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("[foo](/bar* \"ti*tle\")")) + } + + @Test + fun `should parse spec sample 24 correctly (Backslash escapes)`() { + val parsed = + processor.processMarkdownDocument( + """ + |``` foo\+bar + |foo + |``` + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
foo
+         * 
+ */ + parsed.assertEquals(fencedCodeBlock("foo", MimeType.Known.fromMarkdownLanguageName("foo\\+bar"))) + } + + @Test + fun `should parse spec sample 25 correctly (Entity and numeric character references)`() { + @Suppress("CheckDtdRefs") // Malformed on purpose + val parsed = + processor.processMarkdownDocument( + """ + |  & © Æ Ď + |¾ ℋ ⅆ + |∲ ≧̸ + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

  & © Æ Ď + * ¾ ℋ ⅆ + * ∲ ≧̸

+ */ + parsed.assertEquals(paragraph("  & © Æ Ď ¾ ℋ ⅆ ∲ ≧̸")) + } + + @Test + fun `should parse spec sample 26 correctly (Entity and numeric character references)`() { + val parsed = processor.processMarkdownDocument("# Ӓ Ϡ �") + + /* + * Expected HTML: + *

# Ӓ Ϡ �

+ */ + parsed.assertEquals(paragraph("# Ӓ Ϡ �")) + } + + @Test + fun `should parse spec sample 27 correctly (Entity and numeric character references)`() { + val parsed = processor.processMarkdownDocument("" ആ ಫ") + + /* + * Expected HTML: + *

" ആ ಫ

+ */ + parsed.assertEquals(paragraph("\" ആ ಫ")) + } + + @Suppress("CheckDtdRefs") + @Test + fun `should parse spec sample 28 correctly (Entity and numeric character references)`() { + val parsed = + processor.processMarkdownDocument( + """ + |  &x; &#; &#x; + |� + |&#abcdef0; + |&ThisIsNotDefined; &hi?; + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

&nbsp &x; &#; &#x; + * &#87654321; + * &#abcdef0; + * &ThisIsNotDefined; &hi?;

+ */ + parsed.assertEquals( + paragraph("  &x; &#; &#x; � &#abcdef0; &ThisIsNotDefined; &hi?;"), + ) + } + + @Test + fun `should parse spec sample 29 correctly (Entity and numeric character references)`() { + val parsed = processor.processMarkdownDocument("©") + + /* + * Expected HTML: + *

&copy

+ */ + parsed.assertEquals(paragraph("©")) + } + + @Suppress("CheckDtdRefs") // Malformed on purpose + @Test + fun `should parse spec sample 30 correctly (Entity and numeric character references)`() { + val parsed = processor.processMarkdownDocument("&MadeUpEntity;") + + /* + * Expected HTML: + *

&MadeUpEntity;

+ */ + parsed.assertEquals(paragraph("&MadeUpEntity;")) + } + + @Test + fun `should parse spec sample 31 correctly (Entity and numeric character references)`() { + val parsed = processor.processMarkdownDocument("") + + /* + * Expected HTML: + * + */ + parsed.assertEquals(htmlBlock("")) + } + + @Test + fun `should parse spec sample 32 correctly (Entity and numeric character references)`() { + val parsed = processor.processMarkdownDocument("[foo](/föö \"föö\")") + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("[foo](/föö \"föö\")")) + } + + @Test + fun `should parse spec sample 33 correctly (Entity and numeric character references)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo] + | + |[foo]: /föö "föö" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ */ + + parsed.assertEquals(paragraph("[foo](/föö \"föö\")")) + } + + @Test + fun `should parse spec sample 34 correctly (Entity and numeric character references)`() { + val parsed = + processor.processMarkdownDocument( + """ + |``` föö + |foo + |``` + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
foo
+         * 
+ */ + parsed.assertEquals(fencedCodeBlock("foo", mimeType = MimeType.Known.fromMarkdownLanguageName("föö"))) + } + + @Test + fun `should parse spec sample 35 correctly (Entity and numeric character references)`() { + val parsed = processor.processMarkdownDocument("`föö`") + + /* + * Expected HTML: + *

f&ouml;&ouml;

+ */ + parsed.assertEquals(paragraph("`föö`")) + } + + @Test + fun `should parse spec sample 36 correctly (Entity and numeric character references)`() { + val parsed = processor.processMarkdownDocument(" föfö") + + /* + * Expected HTML: + *
f&ouml;f&ouml;
+         * 
+ */ + parsed.assertEquals(indentedCodeBlock("föfö")) + } + + @Test + fun `should parse spec sample 37 correctly (Entity and numeric character references)`() { + val parsed = processor.processMarkdownDocument("*foo*\n*foo*") + + /* + * Expected HTML: + *

*foo* + * foo

+ */ + parsed.assertEquals(paragraph("\\*foo\\* *foo*")) + } + + @Test + fun `should parse spec sample 38 correctly (Entity and numeric character references)`() { + val parsed = + processor.processMarkdownDocument( + """ + |* foo + | + |* foo + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

* foo

+ *
    + *
  • foo
  • + *
+ */ + parsed.assertEquals( + paragraph("\\* foo"), + unorderedList(listItem(paragraph("foo")), bulletMarker = '*'), + ) + } + + @Test + fun `should parse spec sample 39 correctly (Entity and numeric character references)`() { + val parsed = processor.processMarkdownDocument("foo bar") + + /* + * Expected HTML: + *

foo + * + * bar

+ */ + parsed.assertEquals(paragraph("foo\n\nbar")) + } + + @Test + fun `should parse spec sample 40 correctly (Entity and numeric character references)`() { + val parsed = processor.processMarkdownDocument(" foo") + + /* + * Expected HTML: + *

\tfoo

+ */ + parsed.assertEquals(paragraph("\tfoo")) + } + + @Test + fun `should parse spec sample 41 correctly (Entity and numeric character references)`() { + val parsed = processor.processMarkdownDocument("[a](url "tit")") + + /* + * Expected HTML: + *

[a](url "tit")

+ */ + parsed.assertEquals(paragraph("\\[a\\]\\(url \"tit\"\\)")) + } + + @Test + fun `should parse spec sample 42 correctly (Precedence)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- `one + |- two` + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • `one
  • + *
  • two`
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem(paragraph("\\`one")), + listItem(paragraph("two\\`")), + ), + ) + } + + @Test + fun `should parse spec sample 43 correctly (Thematic breaks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |*** + |--- + |___ + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *
+ *
+ */ + parsed.assertEquals( + ThematicBreak, + ThematicBreak, + ThematicBreak, + ) + } + + @Test + fun `should parse spec sample 44 correctly (Thematic breaks)`() { + val parsed = processor.processMarkdownDocument("+++") + + /* + * Expected HTML: + *

+++

+ */ + parsed.assertEquals(paragraph("+++")) + } + + @Test + fun `should parse spec sample 45 correctly (Thematic breaks)`() { + val parsed = processor.processMarkdownDocument("===") + + /* + * Expected HTML: + *

===

+ */ + parsed.assertEquals(paragraph("===")) + } + + @Test + fun `should parse spec sample 46 correctly (Thematic breaks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |-- + |** + |__ + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

-- + * ** + * __

+ */ + parsed.assertEquals(paragraph("-- \\*\\* \\_\\_")) + } + + @Test + fun `should parse spec sample 47 correctly (Thematic breaks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | *** + | *** + | *** + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *
+ *
+ */ + parsed.assertEquals( + ThematicBreak, + ThematicBreak, + ThematicBreak, + ) + } + + @Test + fun `should parse spec sample 48 correctly (Thematic breaks)`() { + val parsed = processor.processMarkdownDocument(" ***") + + /* + * Expected HTML: + *
***
+         * 
+ */ + parsed.assertEquals(indentedCodeBlock("***")) + } + + @Test + fun `should parse spec sample 49 correctly (Thematic breaks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |Foo + | *** + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo + * ***

+ */ + parsed.assertEquals(paragraph("Foo \\*\\*\\*")) + } + + @Test + fun `should parse spec sample 50 correctly (Thematic breaks)`() { + val parsed = processor.processMarkdownDocument("_____________________________________") + + /* + * Expected HTML: + *
+ */ + parsed.assertEquals(ThematicBreak) + } + + @Test + fun `should parse spec sample 51 correctly (Thematic breaks)`() { + val parsed = processor.processMarkdownDocument(" - - -") + + /* + * Expected HTML: + *
+ */ + parsed.assertEquals(ThematicBreak) + } + + @Test + fun `should parse spec sample 52 correctly (Thematic breaks)`() { + val parsed = processor.processMarkdownDocument(" ** * ** * ** * **") + + /* + * Expected HTML: + *
+ */ + parsed.assertEquals(ThematicBreak) + } + + @Test + fun `should parse spec sample 53 correctly (Thematic breaks)`() { + val parsed = processor.processMarkdownDocument("- - - -") + + /* + * Expected HTML: + *
+ */ + parsed.assertEquals(ThematicBreak) + } + + @Test + fun `should parse spec sample 54 correctly (Thematic breaks)`() { + val parsed = processor.processMarkdownDocument("- - - - ") + + /* + * Expected HTML: + *
+ */ + parsed.assertEquals(ThematicBreak) + } + + @Test + fun `should parse spec sample 55 correctly (Thematic breaks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |_ _ _ _ a + | + |a------ + | + |---a--- + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

_ _ _ _ a

+ *

a------

+ *

---a---

+ */ + parsed.assertEquals( + paragraph("\\_ \\_ \\_ \\_ a"), + paragraph("a------"), + paragraph("---a---"), + ) + } + + @Test + fun `should parse spec sample 56 correctly (Thematic breaks)`() { + val parsed = processor.processMarkdownDocument(" *-*") + + /* + * Expected HTML: + *

-

+ */ + parsed.assertEquals(paragraph("*-*")) + } + + @Test + fun `should parse spec sample 57 correctly (Thematic breaks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- foo + |*** + |- bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • foo
  • + *
+ *
+ *
    + *
  • bar
  • + *
+ */ + parsed.assertEquals( + unorderedList(listItem(paragraph("foo"))), + ThematicBreak, + unorderedList(listItem(paragraph("bar"))), + ) + } + + @Test + fun `should parse spec sample 58 correctly (Thematic breaks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |Foo + |*** + |bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo

+ *
+ *

bar

+ */ + parsed.assertEquals( + paragraph("Foo"), + ThematicBreak, + paragraph("bar"), + ) + } + + @Test + fun `should parse spec sample 59 correctly (Thematic breaks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |Foo + |--- + |bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo

+ *

bar

+ */ + parsed.assertEquals( + heading(2, "Foo"), + paragraph("bar"), + ) + } + + @Test + fun `should parse spec sample 60 correctly (Thematic breaks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |* Foo + |* * * + |* Bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • Foo
  • + *
+ *
+ *
    + *
  • Bar
  • + *
+ */ + parsed.assertEquals( + unorderedList(listItem(paragraph("Foo")), bulletMarker = '*'), + ThematicBreak, + unorderedList(listItem(paragraph("Bar")), bulletMarker = '*'), + ) + } + + @Test + fun `should parse spec sample 61 correctly (Thematic breaks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- Foo + |- * * * + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • Foo
  • + *
  • + *
    + *
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem(paragraph("Foo")), + listItem(ThematicBreak), + ), + ) + } + + @Test + fun `should parse spec sample 62 correctly (ATX headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |# foo + |## foo + |### foo + |#### foo + |##### foo + |###### foo + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ *

foo

+ *

foo

+ *

foo

+ *
foo
+ *
foo
+ */ + parsed.assertEquals( + heading(1, "foo"), + heading(2, "foo"), + heading(3, "foo"), + heading(4, "foo"), + heading(5, "foo"), + heading(6, "foo"), + ) + } + + @Test + fun `should parse spec sample 63 correctly (ATX headings)`() { + val parsed = processor.processMarkdownDocument("####### foo") + + /* + * Expected HTML: + *

####### foo

+ */ + parsed.assertEquals(paragraph("####### foo")) + } + + @Test + fun `should parse spec sample 64 correctly (ATX headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |#5 bolt + | + |#hashtag + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

#5 bolt

+ *

#hashtag

+ */ + parsed.assertEquals( + paragraph("#5 bolt"), + paragraph("#hashtag"), + ) + } + + @Test + fun `should parse spec sample 65 correctly (ATX headings)`() { + val parsed = processor.processMarkdownDocument("\\## foo") + + /* + * Expected HTML: + *

## foo

+ */ + parsed.assertEquals(paragraph("## foo")) + } + + @Test + fun `should parse spec sample 66 correctly (ATX headings)`() { + val parsed = processor.processMarkdownDocument("# foo *bar* \\*baz\\*") + + /* + * Expected HTML: + *

foo bar *baz*

+ */ + parsed.assertEquals( + heading(level = 1, "foo *bar* \\*baz\\*"), + ) + } + + @Test + fun `should parse spec sample 67 correctly (ATX headings)`() { + val parsed = processor.processMarkdownDocument("# foo ") + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(heading(level = 1, "foo")) + } + + @Test + fun `should parse spec sample 68 correctly (ATX headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + | ### foo + | ## foo + | # foo + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ *

foo

+ *

foo

+ */ + parsed.assertEquals( + heading(level = 3, "foo"), + heading(level = 2, "foo"), + heading(level = 1, "foo"), + ) + } + + @Test + fun `should parse spec sample 69 correctly (ATX headings)`() { + val parsed = processor.processMarkdownDocument(" # foo") + + /* + * Expected HTML: + *
# foo
+         * 
+ */ + parsed.assertEquals(indentedCodeBlock("# foo")) + } + + @Test + fun `should parse spec sample 70 correctly (ATX headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |foo + | # bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo + * # bar

+ */ + parsed.assertEquals(paragraph("foo # bar")) + } + + @Test + fun `should parse spec sample 71 correctly (ATX headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |## foo ## + | ### bar ### + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ *

bar

+ */ + parsed.assertEquals( + heading(level = 2, "foo"), + heading(level = 3, "bar"), + ) + } + + @Test + fun `should parse spec sample 72 correctly (ATX headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |# foo ################################## + |##### foo ## + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ *
foo
+ */ + parsed.assertEquals( + heading(level = 1, "foo"), + heading(level = 5, "foo"), + ) + } + + @Test + fun `should parse spec sample 73 correctly (ATX headings)`() { + val parsed = processor.processMarkdownDocument("### foo ### ") + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(heading(level = 3, "foo")) + } + + @Test + fun `should parse spec sample 74 correctly (ATX headings)`() { + val parsed = processor.processMarkdownDocument("### foo ### b") + + /* + * Expected HTML: + *

foo ### b

+ */ + parsed.assertEquals(heading(level = 3, "foo ### b")) + } + + @Test + fun `should parse spec sample 75 correctly (ATX headings)`() { + val parsed = processor.processMarkdownDocument("# foo#") + + /* + * Expected HTML: + *

foo#

+ */ + parsed.assertEquals(heading(level = 1, "foo#")) + } + + @Test + fun `should parse spec sample 76 correctly (ATX headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |### foo \### + |## foo #\## + |# foo \# + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo ###

+ *

foo ###

+ *

foo #

+ */ + parsed.assertEquals( + heading(level = 3, "foo ###"), + heading(level = 2, "foo ###"), + heading(level = 1, "foo #"), + ) + } + + @Test + fun `should parse spec sample 77 correctly (ATX headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |**** + |## foo + |**** + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *

foo

+ *
+ */ + parsed.assertEquals( + ThematicBreak, + heading(level = 2, "foo"), + ThematicBreak, + ) + } + + @Test + fun `should parse spec sample 78 correctly (ATX headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |Foo bar + |# baz + |Bar foo + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo bar

+ *

baz

+ *

Bar foo

+ */ + parsed.assertEquals( + paragraph("Foo bar"), + heading(level = 1, "baz"), + paragraph("Bar foo"), + ) + } + + @Test + fun `should parse spec sample 79 correctly (ATX headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |## + |# + |### ### + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

+ *

+ *

+ */ + parsed.assertEquals( + heading(level = 2, ""), + heading(level = 1, ""), + heading(level = 3, ""), + ) + } + + @Test + fun `should parse spec sample 80 correctly (Setext headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |Foo *bar* + |========= + | + |Foo *bar* + |--------- + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo bar

+ *

Foo bar

+ */ + + parsed.assertEquals( + heading(level = 1, "Foo *bar*"), + heading(level = 2, "Foo *bar*"), + ) + } + + @Test + fun `should parse spec sample 81 correctly (Setext headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |Foo *bar + |baz* + |==== + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo bar + * baz

+ */ + parsed.assertEquals(heading(level = 1, "Foo *bar baz*")) + } + + @Test + fun `should parse spec sample 82 correctly (Setext headings)`() { + val parsed = processor.processMarkdownDocument(" Foo *bar\nbaz*\t\n====") + + /* + * Expected HTML: + *

Foo bar + * baz

+ */ + parsed.assertEquals(heading(level = 1, "Foo *bar baz*")) + } + + @Test + fun `should parse spec sample 83 correctly (Setext headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |Foo + |------------------------- + | + |Foo + |= + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo

+ *

Foo

+ */ + parsed.assertEquals( + heading(level = 2, "Foo"), + heading(level = 1, "Foo"), + ) + } + + @Test + fun `should parse spec sample 84 correctly (Setext headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + | Foo + |--- + | + | Foo + |----- + | + | Foo + | === + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo

+ *

Foo

+ *

Foo

+ */ + parsed.assertEquals( + heading(level = 2, "Foo"), + heading(level = 2, "Foo"), + heading(level = 1, "Foo"), + ) + } + + @Test + fun `should parse spec sample 85 correctly (Setext headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + | Foo + | --- + | + | Foo + |--- + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
Foo
+         * ---
+         *
+         * Foo
+         * 
+ *
+ */ + parsed.assertEquals( + indentedCodeBlock("Foo\n---\n\nFoo"), + ThematicBreak, + ) + } + + @Test + fun `should parse spec sample 86 correctly (Setext headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |Foo + | ---- + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo

+ */ + parsed.assertEquals(heading(level = 2, "Foo")) + } + + @Test + fun `should parse spec sample 87 correctly (Setext headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |Foo + | --- + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo + * ---

+ */ + parsed.assertEquals(paragraph("Foo ---")) + } + + @Test + fun `should parse spec sample 88 correctly (Setext headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |Foo + |= = + | + |Foo + |--- - + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo + * = =

+ *

Foo

+ *
+ */ + parsed.assertEquals( + paragraph("Foo = ="), + paragraph("Foo"), + ThematicBreak, + ) + } + + @Test + fun `should parse spec sample 89 correctly (Setext headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |Foo + |----- + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo

+ */ + parsed.assertEquals(heading(level = 2, "Foo")) + } + + @Test + fun `should parse spec sample 90 correctly (Setext headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |Foo\ + |---- + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo\

+ */ + parsed.assertEquals(heading(level = 2, "Foo\\")) + } + + @Test + fun `should parse spec sample 91 correctly (Setext headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |`Foo + |---- + |` + | + | + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

`Foo

+ *

`

+ *

<a title="a lot

+ *

of dashes"/>

+ */ + parsed.assertEquals( + heading(level = 2, "\\`Foo"), + paragraph("\\`"), + heading(level = 2, "\\
"), + ) + } + + @Test + fun `should parse spec sample 92 correctly (Setext headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |> Foo + |--- + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *

Foo

+ *
+ *
+ */ + parsed.assertEquals( + blockQuote(paragraph("Foo")), + ThematicBreak, + ) + } + + @Test + fun `should parse spec sample 93 correctly (Setext headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |> foo + |bar + |=== + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *

foo + * bar + * ===

+ *
+ */ + parsed.assertEquals(blockQuote(paragraph("foo bar ==="))) + } + + @Test + fun `should parse spec sample 94 correctly (Setext headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- Foo + |--- + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • Foo
  • + *
+ *
+ */ + parsed.assertEquals( + unorderedList(listItem(paragraph("Foo"))), + ThematicBreak, + ) + } + + @Test + fun `should parse spec sample 95 correctly (Setext headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |Foo + |Bar + |--- + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo + * Bar

+ */ + parsed.assertEquals(heading(level = 2, "Foo Bar")) + } + + @Test + fun `should parse spec sample 96 correctly (Setext headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |--- + |Foo + |--- + |Bar + |--- + |Baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *

Foo

+ *

Bar

+ *

Baz

+ */ + parsed.assertEquals( + ThematicBreak, + heading(level = 2, "Foo"), + heading(level = 2, "Bar"), + paragraph("Baz"), + ) + } + + @Test + fun `should parse spec sample 97 correctly (Setext headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + | + |==== + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

====

+ */ + parsed.assertEquals(paragraph("====")) + } + + @Test + fun `should parse spec sample 98 correctly (Setext headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |--- + |--- + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *
+ */ + parsed.assertEquals( + ThematicBreak, + ThematicBreak, + ) + } + + @Test + fun `should parse spec sample 99 correctly (Setext headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- foo + |----- + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • foo
  • + *
+ *
+ */ + parsed.assertEquals( + unorderedList(listItem(paragraph("foo"))), + ThematicBreak, + ) + } + + @Test + fun `should parse spec sample 100 correctly (Setext headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + | foo + |--- + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
foo
+         * 
+ *
+ */ + parsed.assertEquals( + indentedCodeBlock("foo"), + ThematicBreak, + ) + } + + @Test + fun `should parse spec sample 101 correctly (Setext headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |> foo + |----- + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *

foo

+ *
+ *
+ */ + parsed.assertEquals( + blockQuote(paragraph("foo")), + ThematicBreak, + ) + } + + @Test + fun `should parse spec sample 102 correctly (Setext headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |\> foo + |------ + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

> foo

+ */ + parsed.assertEquals(heading(level = 2, "\\> foo")) + } + + @Test + fun `should parse spec sample 103 correctly (Setext headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |Foo + | + |bar + |--- + |baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo

+ *

bar

+ *

baz

+ */ + parsed.assertEquals( + paragraph("Foo"), + heading(level = 2, "bar"), + paragraph("baz"), + ) + } + + @Test + fun `should parse spec sample 104 correctly (Setext headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |Foo + |bar + | + |--- + | + |baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo + * bar

+ *
+ *

baz

+ */ + parsed.assertEquals( + paragraph("Foo bar"), + ThematicBreak, + paragraph("baz"), + ) + } + + @Test + fun `should parse spec sample 105 correctly (Setext headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |Foo + |bar + |* * * + |baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo + * bar

+ *
+ *

baz

+ */ + parsed.assertEquals( + paragraph("Foo bar"), + ThematicBreak, + paragraph("baz"), + ) + } + + @Test + fun `should parse spec sample 106 correctly (Setext headings)`() { + val parsed = + processor.processMarkdownDocument( + """ + |Foo + |bar + |\--- + |baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo + * bar + * --- + * baz

+ */ + parsed.assertEquals(paragraph("Foo bar --- baz")) + } + + @Test + fun `should parse spec sample 107 correctly (Indented code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | a simple + | indented code block + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
a simple
+         *   indented code block
+         * 
+ */ + parsed.assertEquals(indentedCodeBlock("a simple\n indented code block")) + } + + @Test + fun `should parse spec sample 108 correctly (Indented code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | - foo + | + | bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • + *

    foo

    + *

    bar

    + *
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem(paragraph("foo"), paragraph("bar")), + isTight = false, + ), + ) + } + + @Test + fun `should parse spec sample 109 correctly (Indented code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |1. foo + | + | - bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  1. + *

    foo

    + *
      + *
    • bar
    • + *
    + *
  2. + *
+ */ + parsed.assertEquals( + orderedList( + listItem( + paragraph("foo"), + unorderedList(listItem(paragraph("bar"))), + ), + isTight = false, + ), + ) + } + + @Test + fun `should parse spec sample 110 correctly (Indented code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |
+ | *hi* + | + | - one + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
<a/>
+         * *hi*
+         *
+         * - one
+         * 
+ */ + parsed.assertEquals(indentedCodeBlock("
\n*hi*\n\n- one")) + } + + @Test + fun `should parse spec sample 111 correctly (Indented code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | chunk1 + | + | chunk2 + | + | + | + | chunk3 + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
chunk1
+         *
+         * chunk2
+         *
+         *
+         *
+         * chunk3
+         * 
+ */ + parsed.assertEquals(indentedCodeBlock("chunk1\n\nchunk2\n\n\n\nchunk3")) + } + + @Test + fun `should parse spec sample 112 correctly (Indented code blocks)`() { + val parsed = processor.processMarkdownDocument(" chunk1\n \n chunk2") + + /* + * Expected HTML: + *
chunk1
+         * ••
+         *   chunk2
+         * 
+ */ + parsed.assertEquals(indentedCodeBlock("chunk1\n \n chunk2")) + } + + @Test + fun `should parse spec sample 113 correctly (Indented code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |Foo + | bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo + * bar

+ */ + parsed.assertEquals(paragraph("Foo bar")) + } + + @Test + fun `should parse spec sample 114 correctly (Indented code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | foo + |bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
foo
+         * 
+ *

bar

+ */ + parsed.assertEquals( + indentedCodeBlock("foo"), + paragraph("bar"), + ) + } + + @Test + fun `should parse spec sample 115 correctly (Indented code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |# Heading + | foo + |Heading + |------ + | foo + |---- + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Heading

+ *
foo
+         * 
+ *

Heading

+ *
foo
+         * 
+ *
+ */ + parsed.assertEquals( + heading(level = 1, "Heading"), + indentedCodeBlock("foo"), + heading(level = 2, "Heading"), + indentedCodeBlock("foo"), + ThematicBreak, + ) + } + + @Test + fun `should parse spec sample 116 correctly (Indented code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | foo + | bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    foo
+         * bar
+         * 
+ */ + parsed.assertEquals(indentedCodeBlock(" foo\nbar")) + } + + @Test + fun `should parse spec sample 117 correctly (Indented code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | + | + | foo + | + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
foo
+         * 
+ */ + parsed.assertEquals(indentedCodeBlock("foo")) + } + + @Test + fun `should parse spec sample 118 correctly (Indented code blocks)`() { + val parsed = processor.processMarkdownDocument(" foo ") + + /* + * Expected HTML: + *
foo••
+         * 
+ */ + parsed.assertEquals(indentedCodeBlock("foo ")) + } + + @Test + fun `should parse spec sample 119 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |``` + |< + | > + |``` + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
<
+         *  >
+         * 
+ */ + parsed.assertEquals(fencedCodeBlock("<\n >")) + } + + @Test + fun `should parse spec sample 120 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |~~~ + |< + | > + |~~~ + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
<
+         *  >
+         * 
+ */ + parsed.assertEquals(fencedCodeBlock("<\n >")) + } + + @Test + fun `should parse spec sample 121 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |`` + |foo + |`` + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("`foo`")) + } + + @Test + fun `should parse spec sample 122 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |``` + |aaa + |~~~ + |``` + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
aaa
+         * ~~~
+         * 
+ */ + parsed.assertEquals(fencedCodeBlock("aaa\n~~~")) + } + + @Test + fun `should parse spec sample 123 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |~~~ + |aaa + |``` + |~~~ + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
aaa
+         * ```
+         * 
+ */ + parsed.assertEquals(fencedCodeBlock("aaa\n```")) + } + + @Test + fun `should parse spec sample 124 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |```` + |aaa + |``` + |`````` + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
aaa
+         * ```
+         * 
+ */ + parsed.assertEquals(fencedCodeBlock("aaa\n```")) + } + + @Test + fun `should parse spec sample 125 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |~~~~ + |aaa + |~~~ + |~~~~ + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
aaa
+         * ~~~
+         * 
+ */ + parsed.assertEquals(fencedCodeBlock("aaa\n~~~")) + } + + @Test + fun `should parse spec sample 126 correctly (Fenced code blocks)`() { + val parsed = processor.processMarkdownDocument("```") + + /* + * Expected HTML: + *
+ */ + parsed.assertEquals(fencedCodeBlock("")) + } + + @Test + fun `should parse spec sample 127 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |````` + | + |``` + |aaa + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

+         * ```
+         * aaa
+         * 
+ */ + parsed.assertEquals(fencedCodeBlock("\n```\naaa")) + } + + @Test + fun `should parse spec sample 128 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |> ``` + |> aaa + | + |bbb + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *
aaa
+         * 
+ *
+ *

bbb

+ */ + parsed.assertEquals( + blockQuote(fencedCodeBlock("aaa")), + paragraph("bbb"), + ) + } + + @Test + fun `should parse spec sample 129 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |``` + | + | + |``` + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

+         * ••
+         * 
+ */ + parsed.assertEquals(fencedCodeBlock("\n ")) + } + + @Test + fun `should parse spec sample 130 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |``` + |``` + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ */ + parsed.assertEquals(fencedCodeBlock("")) + } + + @Test + fun `should parse spec sample 131 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | ``` + | aaa + |aaa + |``` + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
aaa
+         * aaa
+         * 
+ */ + parsed.assertEquals(fencedCodeBlock("aaa\naaa")) + } + + @Test + fun `should parse spec sample 132 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | ``` + |aaa + | aaa + |aaa + | ``` + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
aaa
+         * aaa
+         * aaa
+         * 
+ */ + parsed.assertEquals(fencedCodeBlock("aaa\naaa\naaa")) + } + + @Test + fun `should parse spec sample 133 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | ``` + | aaa + | aaa + | aaa + | ``` + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
aaa
+         *  aaa
+         * aaa
+         * 
+ */ + parsed.assertEquals(fencedCodeBlock("aaa\n aaa\naaa")) + } + + @Test + fun `should parse spec sample 134 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | ``` + | aaa + | ``` + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
```
+         * aaa
+         * ```
+         * 
+ */ + parsed.assertEquals(indentedCodeBlock("```\naaa\n```")) + } + + @Test + fun `should parse spec sample 135 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |``` + |aaa + | ``` + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
aaa
+         * 
+ */ + parsed.assertEquals(fencedCodeBlock("aaa")) + } + + @Test + fun `should parse spec sample 136 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | ``` + |aaa + | ``` + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
aaa
+         * 
+ */ + parsed.assertEquals(fencedCodeBlock("aaa")) + } + + @Test + fun `should parse spec sample 137 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |``` + |aaa + | ``` + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
aaa
+         *     ```
+         * 
+ */ + parsed.assertEquals(fencedCodeBlock("aaa\n ```")) + } + + @Test + fun `should parse spec sample 138 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |``` ``` + |aaa + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

+ * aaa

+ */ + parsed.assertEquals(paragraph("` ` aaa")) + } + + @Test + fun `should parse spec sample 139 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |~~~~~~ + |aaa + |~~~ ~~ + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
aaa
+         * ~~~ ~~
+         * 
+ */ + parsed.assertEquals(fencedCodeBlock("aaa\n~~~ ~~")) + } + + @Test + fun `should parse spec sample 140 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |foo + |``` + |bar + |``` + |baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ *
bar
+         * 
+ *

baz

+ */ + parsed.assertEquals( + paragraph("foo"), + fencedCodeBlock("bar"), + paragraph("baz"), + ) + } + + @Test + fun `should parse spec sample 141 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |foo + |--- + |~~~ + |bar + |~~~ + |# baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ *
bar
+         * 
+ *

baz

+ */ + parsed.assertEquals( + heading(level = 2, "foo"), + fencedCodeBlock("bar"), + heading(level = 1, "baz"), + ) + } + + @Test + fun `should parse spec sample 142 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |```ruby + |def foo(x) + | return 3 + |end + |``` + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
def foo(x)
+         *   return 3
+         * end
+         * 
+ */ + parsed.assertEquals( + fencedCodeBlock( + "def foo(x)\n return 3\nend", + mimeType = MimeType.Known.fromMarkdownLanguageName("ruby"), + ), + ) + } + + @Test + fun `should parse spec sample 143 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |~~~~ ruby startline=3 $%@#$ + |def foo(x) + | return 3 + |end + |~~~~~~~ + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
def foo(x)
+         *   return 3
+         * end
+         * 
+ */ + parsed.assertEquals( + fencedCodeBlock( + "def foo(x)\n return 3\nend", + mimeType = MimeType.Known.fromMarkdownLanguageName("ruby"), + ), + ) + } + + @Test + fun `should parse spec sample 144 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |````; + |```` + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ */ + parsed.assertEquals( + fencedCodeBlock( + "", + mimeType = MimeType.Known.fromMarkdownLanguageName(";"), + ), + ) + } + + @Test + fun `should parse spec sample 145 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |``` aa ``` + |foo + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

aa + * foo

+ */ + parsed.assertEquals(paragraph("`aa` foo")) + } + + @Test + fun `should parse spec sample 146 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |~~~ aa ``` ~~~ + |foo + |~~~ + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
foo
+         * 
+ */ + parsed.assertEquals( + fencedCodeBlock( + "foo", + mimeType = MimeType.Known.fromMarkdownLanguageName("aa"), + ), + ) + } + + @Test + fun `should parse spec sample 147 correctly (Fenced code blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |``` + |``` aaa + |``` + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
``` aaa
+         * 
+ */ + parsed.assertEquals(fencedCodeBlock("``` aaa")) + } + + @Test + fun `should parse spec sample 148 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |
+ |
+            |**Hello**,
+            |
+            |_world_.
+            |
+ |
+ """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *
+         * **Hello**,
+         * 

world. + *

+ *
+ */ + parsed.assertEquals( + htmlBlock("
\n
\n**Hello**,"),
+            paragraph("_world_. 
"), + htmlBlock("
"), + ) + } + + @Test + fun `should parse spec sample 149 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | + | + | + | + |
+ | hi + |
+ | + |okay. + """ + .trimMargin(), + ) + + /* + * Expected HTML: + * + * + * + * + *
+ * hi + *
+ *

okay.

+ */ + parsed.assertEquals( + htmlBlock( + """ + | + | + | + | + |
+ | hi + |
+ """ + .trimMargin(), + ), + paragraph("okay."), + ) + } + + @Test + fun `should parse spec sample 150 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |
\n*foo*")) + } + + @Test + fun `should parse spec sample 152 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |
+ | + |*Markdown* + | + |
+ """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *

Markdown

+ *
+ */ + parsed.assertEquals( + htmlBlock("
"), + paragraph("*Markdown*"), + htmlBlock("
"), + ) + } + + @Test + fun `should parse spec sample 153 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |
+ |
+ """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *
+ */ + parsed.assertEquals( + htmlBlock( + """ + |
+ |
+ """ + .trimMargin(), + ), + ) + } + + @Test + fun `should parse spec sample 154 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |
+ |
+ """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *
+ */ + parsed.assertEquals( + htmlBlock( + """ + |
+ |
+ """ + .trimMargin(), + ), + ) + } + + @Test + fun `should parse spec sample 155 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |
+ |*foo* + | + |*bar* + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ * *foo* + *

bar

+ */ + parsed.assertEquals( + htmlBlock("
\n*foo*"), + paragraph("*bar*"), + ) + } + + @Test + fun `should parse spec sample 156 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |") + + /* + * Expected HTML: + * + */ + parsed.assertEquals(htmlBlock("")) + } + + @Test + fun `should parse spec sample 160 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |
+ |foo + |
+ """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ * foo + *
+ */ + parsed.assertEquals( + htmlBlock( + """ + |
+ |foo + |
+ """ + .trimMargin(), + ), + ) + } + + @Test + fun `should parse spec sample 161 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |
+ |``` c + |int x = 33; + |``` + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ * ``` c + * int x = 33; + * ``` + */ + parsed.assertEquals( + htmlBlock( + """ + |
+ |``` c + |int x = 33; + |``` + """ + .trimMargin(), + ), + ) + } + + @Test + fun `should parse spec sample 162 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | + |*bar* + | + """ + .trimMargin(), + ) + + /* + * Expected HTML: + * + * *bar* + * + */ + parsed.assertEquals( + htmlBlock( + """ + | + |*bar* + | + """ + .trimMargin(), + ), + ) + } + + @Test + fun `should parse spec sample 163 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | + |*bar* + | + """ + .trimMargin(), + ) + + /* + * Expected HTML: + * + * *bar* + * + */ + parsed.assertEquals( + htmlBlock( + """ + | + |*bar* + | + """ + .trimMargin(), + ), + ) + } + + @Test + fun `should parse spec sample 164 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | + |*bar* + | + """ + .trimMargin(), + ) + + /* + * Expected HTML: + * + * *bar* + * + */ + parsed.assertEquals( + htmlBlock( + """ + | + |*bar* + | + """ + .trimMargin(), + ), + ) + } + + @Test + fun `should parse spec sample 165 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | + |*bar* + """ + .trimMargin(), + ) + + /* + * Expected HTML: + * + * *bar* + */ + parsed.assertEquals( + htmlBlock( + """ + | + |*bar* + """ + .trimMargin(), + ), + ) + } + + @Test + fun `should parse spec sample 166 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | + |*foo* + | + """ + .trimMargin(), + ) + + /* + * Expected HTML: + * + * *foo* + * + */ + parsed.assertEquals( + htmlBlock( + """ + | + |*foo* + | + """ + .trimMargin(), + ), + ) + } + + @Test + fun `should parse spec sample 167 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | + | + |*foo* + | + | + """ + .trimMargin(), + ) + + /* + * Expected HTML: + * + *

foo

+ *
+ */ + parsed.assertEquals( + htmlBlock(""), + paragraph("*foo*"), + htmlBlock(""), + ) + } + + @Test + fun `should parse spec sample 168 correctly (HTML blocks)`() { + val parsed = processor.processMarkdownDocument("*foo*") + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("*foo*")) + } + + @Test + fun `should parse spec sample 169 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |

+            |import Text.HTML.TagSoup
+            |
+            |main :: IO ()
+            |main = print $ parseTags tags
+            |
+ |okay + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

+         * import Text.HTML.TagSoup
+         *
+         * main :: IO ()
+         * main = print $ parseTags tags
+         * 
+ *

okay

+ */ + parsed.assertEquals( + htmlBlock( + """ + |

+            |import Text.HTML.TagSoup
+            |
+            |main :: IO ()
+            |main = print $ parseTags tags
+            |
+ """ + .trimMargin(), + ), + paragraph("okay"), + ) + } + + @Test + fun `should parse spec sample 170 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | + |okay + """ + .trimMargin(), + ) + + /* + * Expected HTML: + * + *

okay

+ */ + parsed.assertEquals( + htmlBlock( + """ + | + """ + .trimMargin(), + ), + paragraph("okay"), + ) + } + + @Test + fun `should parse spec sample 171 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | + """ + .trimMargin(), + ) + + /* + * Expected HTML: + * + */ + parsed.assertEquals( + htmlBlock( + """ + | + """ + .trimMargin(), + ), + ) + } + + @Test + fun `should parse spec sample 172 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | + |okay + """ + .trimMargin(), + ) + + /* + * Expected HTML: + * + *

okay

+ */ + parsed.assertEquals( + htmlBlock( + """ + | + """ + .trimMargin(), + ), + paragraph("okay"), + ) + } + + @Test + fun `should parse spec sample 173 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | + |*foo* + """ + .trimMargin(), + ) + + /* + * Expected HTML: + * + *

foo

+ */ + parsed.assertEquals( + htmlBlock(""), + paragraph("*foo*"), + ) + } + + @Test + fun `should parse spec sample 177 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |*bar* + |*baz* + """ + .trimMargin(), + ) + + /* + * Expected HTML: + * *bar* + *

baz

+ */ + parsed.assertEquals( + htmlBlock("*bar*"), + paragraph("*baz*"), + ) + } + + @Test + fun `should parse spec sample 178 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |1. *bar* + """ + .trimMargin(), + ) + + /* + * Expected HTML: + * 1. *bar* + */ + parsed.assertEquals( + htmlBlock( + """ + |1. *bar* + """ + .trimMargin(), + ), + ) + } + + @Test + fun `should parse spec sample 179 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | + |okay + """ + .trimMargin(), + ) + + /* + * Expected HTML: + * + *

okay

+ */ + parsed.assertEquals( + htmlBlock( + """ + | + """ + .trimMargin(), + ), + paragraph("okay"), + ) + } + + @Test + fun `should parse spec sample 180 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |'; + | + |?> + |okay + """ + .trimMargin(), + ) + + /* + * Expected HTML: + * '; + * + * ?> + *

okay

+ */ + parsed.assertEquals( + htmlBlock( + """ + |'; + | + |?> + """ + .trimMargin(), + ), + paragraph("okay"), + ) + } + + @Test + fun `should parse spec sample 181 correctly (HTML blocks)`() { + val parsed = processor.processMarkdownDocument("") + + /* + * Expected HTML: + * + */ + parsed.assertEquals(htmlBlock("")) + } + + @Test + fun `should parse spec sample 182 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | + |okay + """ + .trimMargin(), + ) + + /* + * Expected HTML: + * + *

okay

+ */ + parsed.assertEquals( + htmlBlock( + """ + | + """ + .trimMargin(), + ), + paragraph("okay"), + ) + } + + @Test + fun `should parse spec sample 183 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | + | + | + """ + .trimMargin(), + ) + + /* + * Expected HTML: + * + *
<!-- foo -->
+         * 
+ */ + parsed.assertEquals( + htmlBlock(" "), + indentedCodeBlock(""), + ) + } + + @Test + fun `should parse spec sample 184 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |
+ | + |
+ """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *
<div>
+         * 
+ */ + parsed.assertEquals( + htmlBlock("
"), + indentedCodeBlock("
"), + ) + } + + @Test + fun `should parse spec sample 185 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |Foo + |
+ |bar + |
+ """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo

+ *
+ * bar + *
+ */ + parsed.assertEquals( + paragraph("Foo"), + htmlBlock( + """ + |
+ |bar + |
+ """ + .trimMargin(), + ), + ) + } + + @Test + fun `should parse spec sample 186 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |
+ |bar + |
+ |*foo* + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ * bar + *
+ * *foo* + */ + parsed.assertEquals( + htmlBlock( + """ + |
+ |bar + |
+ |*foo* + """ + .trimMargin(), + ), + ) + } + + @Test + fun `should parse spec sample 187 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |Foo + | + |baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo + * + * baz

+ */ + parsed.assertEquals(paragraph("Foo baz")) + } + + @Test + fun `should parse spec sample 188 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |
+ | + |*Emphasized* text. + | + |
+ """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *

Emphasized text.

+ *
+ */ + parsed.assertEquals( + htmlBlock("
"), + paragraph("*Emphasized* text."), + htmlBlock("
"), + ) + } + + @Test + fun `should parse spec sample 189 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |
+ |*Emphasized* text. + |
+ """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ * *Emphasized* text. + *
+ */ + parsed.assertEquals( + htmlBlock( + """ + |
+ |*Emphasized* text. + |
+ """ + .trimMargin(), + ), + ) + } + + @Test + fun `should parse spec sample 190 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | + | + | + | + | + | + | + | + |
+ |Hi + |
+ """ + .trimMargin(), + ) + + /* + * Expected HTML: + * + * + * + * + *
+ * Hi + *
+ */ + parsed.assertEquals( + htmlBlock(""), + htmlBlock(""), + htmlBlock(""), + htmlBlock(""), + htmlBlock("
\nHi\n
"), + ) + } + + @Test + fun `should parse spec sample 191 correctly (HTML blocks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | + | + | + | + | + | + | + | + |
+ | Hi + |
+ """ + .trimMargin(), + ) + + /* + * Expected HTML: + * + * + *
<td>
+         *   Hi
+         * </td>
+         * 
+ * + *
+ */ + parsed.assertEquals( + htmlBlock(""), + htmlBlock(" "), + indentedCodeBlock(""), + htmlBlock(" "), + htmlBlock("
\n Hi\n
"), + ) + } + + @Test + fun `should parse spec sample 192 correctly (Link reference definitions)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo]: /url "title" + | + |[foo] + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("[foo](/url \"title\")")) + } + + @Test + fun `should parse spec sample 193 correctly (Link reference definitions)`() { + val parsed = + processor.processMarkdownDocument( + """ + | [foo]: + | /url + | 'the title' + | + |[foo] + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("[foo](/url \"the title\")")) + } + + @Test + fun `should parse spec sample 194 correctly (Link reference definitions)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[Foo*bar\]]:my_(url) 'title (with parens)' + | + |[Foo*bar\]] + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo*bar]

+ */ + parsed.assertEquals(paragraph("[Foo\\*bar\\]](my_\\(url\\) \"title (with parens)\")")) + } + + @Test + fun `should parse spec sample 195 correctly (Link reference definitions)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[Foo bar]: + | + |'title' + | + |[Foo bar] + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo bar

+ */ + parsed.assertEquals(paragraph("[Foo bar]( \"title\")")) + } + + @Test + fun `should parse spec sample 196 correctly (Link reference definitions)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo]: /url ' + |title + |line1 + |line2 + |' + | + |[foo] + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("[foo](/url \"title\nline1\nline2\")")) + } + + @Test + fun `should parse spec sample 197 correctly (Link reference definitions)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo]: /url 'title + | + |with blank line' + | + |[foo] + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

[foo]: /url 'title

+ *

with blank line'

+ *

[foo]

+ */ + parsed.assertEquals( + paragraph("\\[foo\\]: /url 'title"), + paragraph("with blank line'"), + paragraph("\\[foo\\]"), + ) + } + + @Test + fun `should parse spec sample 198 correctly (Link reference definitions)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo]: + |/url + | + |[foo] + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("[foo](/url)")) + } + + @Test + fun `should parse spec sample 199 correctly (Link reference definitions)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo]: + | + |[foo] + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

[foo]:

+ *

[foo]

+ */ + parsed.assertEquals( + paragraph("\\[foo\\]:"), + paragraph("\\[foo\\]"), + ) + } + + @Test + fun `should parse spec sample 200 correctly (Link reference definitions)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo]: <> + | + |[foo] + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("[foo]()")) + } + + @Test + fun `should parse spec sample 201 correctly (Link reference definitions)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo]: (baz) + | + |[foo] + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

[foo]: (baz)

+ *

[foo]

+ */ + parsed.assertEquals( + paragraph("\\[foo\\]: \\(baz\\)"), + paragraph("\\[foo\\]"), + ) + } + + @Test + fun `should parse spec sample 202 correctly (Link reference definitions)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo]: /url\bar\*baz "foo\"bar\baz" + | + |[foo] + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("[foo](/url\\bar*baz \"foo\\\"bar\\baz\")")) + } + + @Test + fun `should parse spec sample 203 correctly (Link reference definitions)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo] + | + |[foo]: url + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("[foo](url)")) + } + + @Test + fun `should parse spec sample 204 correctly (Link reference definitions)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo] + | + |[foo]: first + |[foo]: second + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("[foo](first)")) + } + + @Test + fun `should parse spec sample 205 correctly (Link reference definitions)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[FOO]: /url + | + |[Foo] + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo

+ */ + parsed.assertEquals(paragraph("[Foo](/url)")) + } + + @Test + fun `should parse spec sample 206 correctly (Link reference definitions)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[ΑΓΩ]: /φου + | + |[αγω] + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

αγω

+ */ + parsed.assertEquals(paragraph("[αγω](/φου)")) + } + + @Test + fun `should parse spec sample 207 correctly (Link reference definitions)`() { + val parsed = processor.processMarkdownDocument("[foo]: /url") + + /* + * Expected HTML: + * [intentionally blank] + */ + parsed.assertEquals() + } + + @Test + fun `should parse spec sample 208 correctly (Link reference definitions)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[ + |foo + |]: /url + |bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

bar

+ */ + parsed.assertEquals(paragraph("bar")) + } + + @Test + fun `should parse spec sample 209 correctly (Link reference definitions)`() { + val parsed = processor.processMarkdownDocument("[foo]: /url \"title\" ok") + + /* + * Expected HTML: + *

[foo]: /url "title" ok

+ */ + parsed.assertEquals(paragraph("\\[foo\\]: /url \"title\" ok")) + } + + @Test + fun `should parse spec sample 210 correctly (Link reference definitions)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo]: /url + |"title" ok + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

"title" ok

+ */ + parsed.assertEquals(paragraph("\"title\" ok")) + } + + @Test + fun `should parse spec sample 211 correctly (Link reference definitions)`() { + val parsed = + processor.processMarkdownDocument( + """ + | [foo]: /url "title" + | + |[foo] + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
[foo]: /url "title"
+         * 
+ *

[foo]

+ */ + parsed.assertEquals( + indentedCodeBlock("[foo]: /url \"title\""), + paragraph("\\[foo\\]"), + ) + } + + @Test + fun `should parse spec sample 212 correctly (Link reference definitions)`() { + val parsed = + processor.processMarkdownDocument( + """ + |``` + |[foo]: /url + |``` + | + |[foo] + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
[foo]: /url
+         * 
+ *

[foo]

+ */ + parsed.assertEquals( + fencedCodeBlock("[foo]: /url"), + paragraph("\\[foo\\]"), + ) + } + + @Test + fun `should parse spec sample 213 correctly (Link reference definitions)`() { + val parsed = + processor.processMarkdownDocument( + """ + |Foo + |[bar]: /baz + | + |[bar] + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo + * [bar]: /baz

+ *

[bar]

+ */ + parsed.assertEquals( + paragraph("Foo \\[bar\\]: /baz"), + paragraph("\\[bar\\]"), + ) + } + + @Test + fun `should parse spec sample 214 correctly (Link reference definitions)`() { + val parsed = + processor.processMarkdownDocument( + """ + |# [Foo] + |[foo]: /url + |> bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo

+ *
+ *

bar

+ *
+ */ + parsed.assertEquals( + heading(level = 1, "[Foo](/url)"), + blockQuote(paragraph("bar")), + ) + } + + @Test + fun `should parse spec sample 215 correctly (Link reference definitions)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo]: /url + |bar + |=== + |[foo] + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

bar

+ *

foo

+ */ + parsed.assertEquals( + heading(level = 1, "bar"), + paragraph("[foo](/url)"), + ) + } + + @Test + fun `should parse spec sample 216 correctly (Link reference definitions)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo]: /url + |=== + |[foo] + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

=== + * foo

+ */ + parsed.assertEquals(paragraph("=== [foo](/url)")) + } + + @Test + fun `should parse spec sample 217 correctly (Link reference definitions)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo]: /foo-url "foo" + |[bar]: /bar-url + | "bar" + |[baz]: /baz-url + | + |[foo], + |[bar], + |[baz] + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo, + * bar, + * baz

+ */ + parsed.assertEquals( + paragraph("[foo](/foo-url \"foo\"), [bar](/bar-url \"bar\"), [baz](/baz-url)"), + ) + } + + @Test + fun `should parse spec sample 218 correctly (Link reference definitions)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo] + | + |> [foo]: /url + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ *
+ *
+ */ + parsed.assertEquals( + paragraph("[foo](/url)"), + blockQuote(), + ) + } + + @Test + fun `should parse spec sample 219 correctly (Paragraphs)`() { + val parsed = + processor.processMarkdownDocument( + """ + |aaa + | + |bbb + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

aaa

+ *

bbb

+ */ + parsed.assertEquals( + paragraph("aaa"), + paragraph("bbb"), + ) + } + + @Test + fun `should parse spec sample 220 correctly (Paragraphs)`() { + val parsed = + processor.processMarkdownDocument( + """ + |aaa + |bbb + | + |ccc + |ddd + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

aaa + * bbb

+ *

ccc + * ddd

+ */ + parsed.assertEquals( + paragraph("aaa bbb"), + paragraph("ccc ddd"), + ) + } + + @Test + fun `should parse spec sample 221 correctly (Paragraphs)`() { + val parsed = + processor.processMarkdownDocument( + """ + |aaa + | + | + |bbb + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

aaa

+ *

bbb

+ */ + parsed.assertEquals( + paragraph("aaa"), + paragraph("bbb"), + ) + } + + @Test + fun `should parse spec sample 222 correctly (Paragraphs)`() { + val parsed = + processor.processMarkdownDocument( + """ + | aaa + | bbb + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

aaa + * bbb

+ */ + parsed.assertEquals(paragraph("aaa bbb")) + } + + @Test + fun `should parse spec sample 223 correctly (Paragraphs)`() { + val parsed = + processor.processMarkdownDocument( + """ + |aaa + | bbb + | ccc + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

aaa + * bbb + * ccc

+ */ + parsed.assertEquals(paragraph("aaa bbb ccc")) + } + + @Test + fun `should parse spec sample 224 correctly (Paragraphs)`() { + val parsed = + processor.processMarkdownDocument( + """ + | aaa + |bbb + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

aaa + * bbb

+ */ + parsed.assertEquals(paragraph("aaa bbb")) + } + + @Test + fun `should parse spec sample 225 correctly (Paragraphs)`() { + val parsed = + processor.processMarkdownDocument( + """ + | aaa + |bbb + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
aaa
+         * 
+ *

bbb

+ */ + parsed.assertEquals( + indentedCodeBlock("aaa"), + paragraph("bbb"), + ) + } + + @Test + fun `should parse spec sample 226 correctly (Paragraphs)`() { + val parsed = + processor.processMarkdownDocument( + """ + |aaa + |bbb + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

aaa
+ * bbb

+ */ + parsed.assertEquals(paragraph("aaa\nbbb")) + } + + @Test + fun `should parse spec sample 227 correctly (Blank lines)`() { + val parsed = + processor.processMarkdownDocument( + """ + | + | + |aaa + | + | + |# aaa + | + | + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

aaa

+ *

aaa

+ */ + parsed.assertEquals( + paragraph("aaa"), + heading(level = 1, "aaa"), + ) + } + + @Test + fun `should parse spec sample 228 correctly (Block quotes)`() { + val parsed = + processor.processMarkdownDocument( + """ + |> # Foo + |> bar + |> baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *

Foo

+ *

bar + * baz

+ *
+ */ + parsed.assertEquals( + blockQuote( + heading(level = 1, "Foo"), + paragraph("bar baz"), + ), + ) + } + + @Test + fun `should parse spec sample 229 correctly (Block quotes)`() { + val parsed = + processor.processMarkdownDocument( + """ + |># Foo + |>bar + |> baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *

Foo

+ *

bar + * baz

+ *
+ */ + parsed.assertEquals( + blockQuote( + heading(level = 1, "Foo"), + paragraph("bar baz"), + ), + ) + } + + @Test + fun `should parse spec sample 230 correctly (Block quotes)`() { + val parsed = + processor.processMarkdownDocument( + """ + | > # Foo + | > bar + | > baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *

Foo

+ *

bar + * baz

+ *
+ */ + parsed.assertEquals( + blockQuote( + heading(level = 1, "Foo"), + paragraph("bar baz"), + ), + ) + } + + @Test + fun `should parse spec sample 231 correctly (Block quotes)`() { + val parsed = + processor.processMarkdownDocument( + """ + | > # Foo + | > bar + | > baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
> # Foo
+         * > bar
+         * > baz
+         * 
+ */ + parsed.assertEquals(indentedCodeBlock("> # Foo\n> bar\n> baz")) + } + + @Test + fun `should parse spec sample 232 correctly (Block quotes)`() { + val parsed = + processor.processMarkdownDocument( + """ + |> # Foo + |> bar + |baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *

Foo

+ *

bar + * baz

+ *
+ */ + parsed.assertEquals( + blockQuote( + heading(level = 1, "Foo"), + paragraph("bar baz"), + ), + ) + } + + @Test + fun `should parse spec sample 233 correctly (Block quotes)`() { + val parsed = + processor.processMarkdownDocument( + """ + |> bar + |baz + |> foo + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *

bar + * baz + * foo

+ *
+ */ + parsed.assertEquals(blockQuote(paragraph("bar baz foo"))) + } + + @Test + fun `should parse spec sample 234 correctly (Block quotes)`() { + val parsed = + processor.processMarkdownDocument( + """ + |> foo + |--- + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *

foo

+ *
+ *
+ */ + parsed.assertEquals( + blockQuote(paragraph("foo")), + ThematicBreak, + ) + } + + @Test + fun `should parse spec sample 235 correctly (Block quotes)`() { + val parsed = + processor.processMarkdownDocument( + """ + |> - foo + |- bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *
    + *
  • foo
  • + *
+ *
+ *
    + *
  • bar
  • + *
+ */ + parsed.assertEquals( + blockQuote(unorderedList(listItem(paragraph("foo")))), + unorderedList(listItem(paragraph("bar"))), + ) + } + + @Test + fun `should parse spec sample 236 correctly (Block quotes)`() { + val parsed = + processor.processMarkdownDocument( + """ + |> foo + | bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *
foo
+         * 
+ *
+ *
bar
+         * 
+ */ + parsed.assertEquals( + blockQuote(indentedCodeBlock("foo")), + indentedCodeBlock("bar"), + ) + } + + @Test + fun `should parse spec sample 237 correctly (Block quotes)`() { + val parsed = + processor.processMarkdownDocument( + """ + |> ``` + |foo + |``` + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *
+ *
+ *

foo

+ *
+ */ + parsed.assertEquals( + blockQuote(fencedCodeBlock("")), + paragraph("foo"), + fencedCodeBlock(""), + ) + } + + @Test + fun `should parse spec sample 238 correctly (Block quotes)`() { + val parsed = + processor.processMarkdownDocument( + """ + |> foo + | - bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *

foo + * - bar

+ *
+ */ + parsed.assertEquals(blockQuote(paragraph("foo - bar"))) + } + + @Test + fun `should parse spec sample 239 correctly (Block quotes)`() { + val parsed = processor.processMarkdownDocument(">") + + /* + * Expected HTML: + *
+ *
+ */ + parsed.assertEquals(blockQuote()) + } + + @Test + fun `should parse spec sample 240 correctly (Block quotes)`() { + val parsed = + processor.processMarkdownDocument( + """ + |> + |> + |> + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *
+ */ + parsed.assertEquals(blockQuote()) + } + + @Test + fun `should parse spec sample 241 correctly (Block quotes)`() { + val parsed = + processor.processMarkdownDocument( + """ + |> + |> foo + |> + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *

foo

+ *
+ */ + parsed.assertEquals(blockQuote(paragraph("foo"))) + } + + @Test + fun `should parse spec sample 242 correctly (Block quotes)`() { + val parsed = + processor.processMarkdownDocument( + """ + |> foo + | + |> bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *

foo

+ *
+ *
+ *

bar

+ *
+ */ + parsed.assertEquals( + blockQuote(paragraph("foo")), + blockQuote(paragraph("bar")), + ) + } + + @Test + fun `should parse spec sample 243 correctly (Block quotes)`() { + val parsed = + processor.processMarkdownDocument( + """ + |> foo + |> bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *

foo + * bar

+ *
+ */ + parsed.assertEquals(blockQuote(paragraph("foo bar"))) + } + + @Test + fun `should parse spec sample 244 correctly (Block quotes)`() { + val parsed = + processor.processMarkdownDocument( + """ + |> foo + |> + |> bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *

foo

+ *

bar

+ *
+ */ + parsed.assertEquals( + blockQuote( + paragraph("foo"), + paragraph("bar"), + ), + ) + } + + @Test + fun `should parse spec sample 245 correctly (Block quotes)`() { + val parsed = + processor.processMarkdownDocument( + """ + |foo + |> bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ *
+ *

bar

+ *
+ */ + parsed.assertEquals( + paragraph("foo"), + blockQuote(paragraph("bar")), + ) + } + + @Test + fun `should parse spec sample 246 correctly (Block quotes)`() { + val parsed = + processor.processMarkdownDocument( + """ + |> aaa + |*** + |> bbb + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *

aaa

+ *
+ *
+ *
+ *

bbb

+ *
+ */ + parsed.assertEquals( + blockQuote(paragraph("aaa")), + ThematicBreak, + blockQuote(paragraph("bbb")), + ) + } + + @Test + fun `should parse spec sample 247 correctly (Block quotes)`() { + val parsed = + processor.processMarkdownDocument( + """ + |> bar + |baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *

bar + * baz

+ *
+ */ + parsed.assertEquals(blockQuote(paragraph("bar baz"))) + } + + @Test + fun `should parse spec sample 248 correctly (Block quotes)`() { + val parsed = + processor.processMarkdownDocument( + """ + |> bar + | + |baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *

bar

+ *
+ *

baz

+ */ + parsed.assertEquals( + blockQuote(paragraph("bar")), + paragraph("baz"), + ) + } + + @Test + fun `should parse spec sample 249 correctly (Block quotes)`() { + val parsed = + processor.processMarkdownDocument( + """ + |> bar + |> + |baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *

bar

+ *
+ *

baz

+ */ + parsed.assertEquals( + blockQuote(paragraph("bar")), + paragraph("baz"), + ) + } + + @Test + fun `should parse spec sample 250 correctly (Block quotes)`() { + val parsed = + processor.processMarkdownDocument( + """ + |> > > foo + |bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *
+ *
+ *

foo + * bar

+ *
+ *
+ *
+ */ + parsed.assertEquals( + blockQuote( + blockQuote( + blockQuote(paragraph("foo bar")), + ), + ), + ) + } + + @Test + fun `should parse spec sample 251 correctly (Block quotes)`() { + val parsed = + processor.processMarkdownDocument( + """ + |>>> foo + |> bar + |>>baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *
+ *
+ *

foo + * bar + * baz

+ *
+ *
+ *
+ */ + parsed.assertEquals( + blockQuote( + blockQuote( + blockQuote(paragraph("foo bar baz")), + ), + ), + ) + } + + @Test + fun `should parse spec sample 252 correctly (Block quotes)`() { + val parsed = + processor.processMarkdownDocument( + """ + |> code + | + |> not code + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *
code
+         * 
+ *
+ *
+ *

not code

+ *
+ */ + parsed.assertEquals( + blockQuote(indentedCodeBlock("code")), + blockQuote(paragraph("not code")), + ) + } + + @Test + fun `should parse spec sample 253 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |A paragraph + |with two lines. + | + | indented code + | + |> A block quote. + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

A paragraph + * with two lines.

+ *
indented code
+         * 
+ *
+ *

A block quote.

+ *
+ */ + parsed.assertEquals( + paragraph("A paragraph with two lines."), + indentedCodeBlock("indented code"), + blockQuote(paragraph("A block quote.")), + ) + } + + @Test + fun `should parse spec sample 254 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |1. A paragraph + | with two lines. + | + | indented code + | + | > A block quote. + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  1. + *

    A paragraph + * with two lines.

    + *
    indented code
    +         * 
    + *
    + *

    A block quote.

    + *
    + *
  2. + *
+ */ + parsed.assertEquals( + orderedList( + listItem( + paragraph("A paragraph with two lines."), + indentedCodeBlock("indented code"), + blockQuote(paragraph("A block quote.")), + ), + isTight = false, + ), + ) + } + + @Test + fun `should parse spec sample 255 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- one + | + | two + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • one
  • + *
+ *

two

+ */ + parsed.assertEquals( + unorderedList(listItem(paragraph("one"))), + paragraph("two"), + ) + } + + @Test + fun `should parse spec sample 256 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- one + | + | two + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • + *

    one

    + *

    two

    + *
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem(paragraph("one"), paragraph("two")), + isTight = false, + ), + ) + } + + @Test + fun `should parse spec sample 257 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + | - one + | + | two + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • one
  • + *
+ *
 two
+         * 
+ */ + parsed.assertEquals( + unorderedList(listItem(paragraph("one"))), + indentedCodeBlock(" two"), + ) + } + + @Test + fun `should parse spec sample 258 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + | - one + | + | two + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • + *

    one

    + *

    two

    + *
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem(paragraph("one"), paragraph("two")), + isTight = false, + ), + ) + } + + @Test + fun `should parse spec sample 259 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + | > > 1. one + |>> + |>> two + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *
+ *
    + *
  1. + *

    one

    + *

    two

    + *
  2. + *
+ *
+ *
+ */ + parsed.assertEquals( + blockQuote( + blockQuote( + orderedList( + listItem(paragraph("one"), paragraph("two")), + isTight = false, + ), + ), + ), + ) + } + + @Test + fun `should parse spec sample 260 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |>>- one + |>> + | > > two + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *
+ *
    + *
  • one
  • + *
+ *

two

+ *
+ *
+ */ + parsed.assertEquals( + blockQuote( + blockQuote( + unorderedList(listItem(paragraph("one"))), + paragraph("two"), + ), + ), + ) + } + + @Test + fun `should parse spec sample 261 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |-one + | + |2.two + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

-one

+ *

2.two

+ */ + parsed.assertEquals( + paragraph("-one"), + paragraph("2.two"), + ) + } + + @Test + fun `should parse spec sample 262 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- foo + | + | + | bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • + *

    foo

    + *

    bar

    + *
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem( + paragraph("foo"), + paragraph("bar"), + ), + isTight = false, + ), + ) + } + + @Test + fun `should parse spec sample 263 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |1. foo + | + | ``` + | bar + | ``` + | + | baz + | + | > bam + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  1. + *

    foo

    + *
    bar
    +         * 
    + *

    baz

    + *
    + *

    bam

    + *
    + *
  2. + *
+ */ + parsed.assertEquals( + orderedList( + listItem( + paragraph("foo"), + fencedCodeBlock("bar"), + paragraph("baz"), + blockQuote(paragraph("bam")), + ), + isTight = false, + ), + ) + } + + @Test + fun `should parse spec sample 264 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- Foo + | + | bar + | + | + | baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • + *

    Foo

    + *
    bar
    +         *
    +         *
    +         * baz
    +         * 
    + *
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem(paragraph("Foo"), indentedCodeBlock("bar\n\n\nbaz")), + isTight = false, + ), + ) + } + + @Test + fun `should parse spec sample 265 correctly (List items)`() { + val parsed = processor.processMarkdownDocument("123456789. ok") + + /* + * Expected HTML: + *
    + *
  1. ok
  2. + *
+ */ + parsed.assertEquals(orderedList(listItem(paragraph("ok")), startFrom = 123456789)) + } + + @Test + fun `should parse spec sample 266 correctly (List items)`() { + val parsed = processor.processMarkdownDocument("1234567890. not ok") + + /* + * Expected HTML: + *

1234567890. not ok

+ */ + parsed.assertEquals(paragraph("1234567890. not ok")) + } + + @Test + fun `should parse spec sample 267 correctly (List items)`() { + val parsed = processor.processMarkdownDocument("0. ok") + + /* + * Expected HTML: + *
    + *
  1. ok
  2. + *
+ */ + parsed.assertEquals(orderedList(listItem(paragraph("ok")), startFrom = 0)) + } + + @Test + fun `should parse spec sample 268 correctly (List items)`() { + val parsed = processor.processMarkdownDocument("003. ok") + + /* + * Expected HTML: + *
    + *
  1. ok
  2. + *
+ */ + parsed.assertEquals(orderedList(listItem(paragraph("ok")), startFrom = 3)) + } + + @Test + fun `should parse spec sample 269 correctly (List items)`() { + val parsed = processor.processMarkdownDocument("-1. not ok") + + /* + * Expected HTML: + *

-1. not ok

+ */ + parsed.assertEquals(paragraph("-1. not ok")) + } + + @Test + fun `should parse spec sample 270 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- foo + | + | bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • + *

    foo

    + *
    bar
    +         * 
    + *
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem(paragraph("foo"), indentedCodeBlock("bar")), + isTight = false, + ), + ) + } + + @Test + fun `should parse spec sample 271 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + | 10. foo + | + | bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  1. + *

    foo

    + *
    bar
    +         * 
    + *
  2. + *
+ */ + parsed.assertEquals( + orderedList( + listItem(paragraph("foo"), indentedCodeBlock("bar")), + startFrom = 10, + isTight = false, + ), + ) + } + + @Test + fun `should parse spec sample 272 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + | indented code + | + |paragraph + | + | more code + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
indented code
+         * 
+ *

paragraph

+ *
more code
+         * 
+ */ + parsed.assertEquals( + indentedCodeBlock("indented code"), + paragraph("paragraph"), + indentedCodeBlock("more code"), + ) + } + + @Test + fun `should parse spec sample 273 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |1. indented code + | + | paragraph + | + | more code + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  1. + *
    indented code
    +         * 
    + *

    paragraph

    + *
    more code
    +         * 
    + *
  2. + *
+ */ + parsed.assertEquals( + orderedList( + listItem( + indentedCodeBlock("indented code"), + paragraph("paragraph"), + indentedCodeBlock("more code"), + ), + isTight = false, + ), + ) + } + + @Test + fun `should parse spec sample 274 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |1. indented code + | + | paragraph + | + | more code + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  1. + *
     indented code
    +         * 
    + *

    paragraph

    + *
    more code
    +         * 
    + *
  2. + *
+ */ + parsed.assertEquals( + orderedList( + listItem( + indentedCodeBlock(" indented code"), + paragraph("paragraph"), + indentedCodeBlock("more code"), + ), + isTight = false, + ), + ) + } + + @Test + fun `should parse spec sample 275 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + | foo + | + |bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ *

bar

+ */ + parsed.assertEquals( + paragraph("foo"), + paragraph("bar"), + ) + } + + @Test + fun `should parse spec sample 276 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- foo + | + | bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • foo
  • + *
+ *

bar

+ */ + parsed.assertEquals( + unorderedList(listItem(paragraph("foo"))), + paragraph("bar"), + ) + } + + @Test + fun `should parse spec sample 277 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- foo + | + | bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • + *

    foo

    + *

    bar

    + *
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem(paragraph("foo"), paragraph("bar")), + isTight = false, + ), + ) + } + + @Test + fun `should parse spec sample 278 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- + | foo + |- + | ``` + | bar + | ``` + |- + | baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • foo
  • + *
  • + *
    bar
    +         * 
    + *
  • + *
  • + *
    baz
    +         * 
    + *
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem(paragraph("foo")), + listItem(fencedCodeBlock("bar")), + listItem(indentedCodeBlock("baz")), + ), + ) + } + + @Test + fun `should parse spec sample 279 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- + | foo + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • foo
  • + *
+ */ + parsed.assertEquals(unorderedList(listItem(paragraph("foo")))) + } + + @Test + fun `should parse spec sample 280 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- + | + | foo + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • + *
+ *

foo

+ */ + parsed.assertEquals( + unorderedList(listItem()), + paragraph("foo"), + ) + } + + @Test + fun `should parse spec sample 281 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- foo + |- + |- bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • foo
  • + *
  • + *
  • bar
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem(paragraph("foo")), + listItem(), + listItem(paragraph("bar")), + ), + ) + } + + @Test + fun `should parse spec sample 282 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- foo + |- + |- bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • foo
  • + *
  • + *
  • bar
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem(paragraph("foo")), + listItem(), + listItem(paragraph("bar")), + ), + ) + } + + @Test + fun `should parse spec sample 283 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |1. foo + |2. + |3. bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  1. foo
  2. + *
  3. + *
  4. bar
  5. + *
+ */ + parsed.assertEquals( + orderedList( + listItem(paragraph("foo")), + listItem(), + listItem(paragraph("bar")), + ), + ) + } + + @Test + fun `should parse spec sample 284 correctly (List items)`() { + val parsed = processor.processMarkdownDocument("*") + + /* + * Expected HTML: + *
    + *
  • + *
+ */ + parsed.assertEquals(unorderedList(listItem(), bulletMarker = '*')) + } + + @Test + fun `should parse spec sample 285 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |foo + |* + | + |foo + |1. + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo + * *

+ *

foo + * 1.

+ */ + parsed.assertEquals( + paragraph("foo \\*"), + paragraph("foo 1."), + ) + } + + @Test + fun `should parse spec sample 286 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + | 1. A paragraph + | with two lines. + | + | indented code + | + | > A block quote. + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  1. + *

    A paragraph + * with two lines.

    + *
    indented code
    +         * 
    + *
    + *

    A block quote.

    + *
    + *
  2. + *
+ */ + parsed.assertEquals( + orderedList( + listItem( + paragraph("A paragraph with two lines."), + indentedCodeBlock("indented code"), + blockQuote(paragraph("A block quote.")), + ), + isTight = false, + ), + ) + } + + @Test + fun `should parse spec sample 287 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + | 1. A paragraph + | with two lines. + | + | indented code + | + | > A block quote. + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  1. + *

    A paragraph + * with two lines.

    + *
    indented code
    +         * 
    + *
    + *

    A block quote.

    + *
    + *
  2. + *
+ */ + parsed.assertEquals( + orderedList( + listItem( + paragraph("A paragraph with two lines."), + indentedCodeBlock("indented code"), + blockQuote(paragraph("A block quote.")), + ), + isTight = false, + ), + ) + } + + @Test + fun `should parse spec sample 288 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + | 1. A paragraph + | with two lines. + | + | indented code + | + | > A block quote. + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  1. + *

    A paragraph + * with two lines.

    + *
    indented code
    +         * 
    + *
    + *

    A block quote.

    + *
    + *
  2. + *
+ */ + parsed.assertEquals( + orderedList( + listItem( + paragraph("A paragraph with two lines."), + indentedCodeBlock("indented code"), + blockQuote(paragraph("A block quote.")), + ), + isTight = false, + ), + ) + } + + @Test + fun `should parse spec sample 289 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + | 1. A paragraph + | with two lines. + | + | indented code + | + | > A block quote. + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
1.  A paragraph
+         *     with two lines.
+         *
+         *         indented code
+         *
+         *     > A block quote.
+         * 
+ */ + parsed.assertEquals( + indentedCodeBlock( + """ + |1. A paragraph + | with two lines. + | + | indented code + | + | > A block quote. + """ + .trimMargin(), + ), + ) + } + + @Test + fun `should parse spec sample 290 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + | 1. A paragraph + |with two lines. + | + | indented code + | + | > A block quote. + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  1. + *

    A paragraph + * with two lines.

    + *
    indented code
    +         * 
    + *
    + *

    A block quote.

    + *
    + *
  2. + *
+ */ + parsed.assertEquals( + orderedList( + listItem( + paragraph("A paragraph with two lines."), + indentedCodeBlock("indented code"), + blockQuote(paragraph("A block quote.")), + ), + isTight = false, + ), + ) + } + + @Test + fun `should parse spec sample 291 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + | 1. A paragraph + | with two lines. + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  1. A paragraph + * with two lines.
  2. + *
+ */ + parsed.assertEquals( + orderedList( + listItem(paragraph("A paragraph with two lines.")), + ), + ) + } + + @Test + fun `should parse spec sample 292 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |> 1. > Blockquote + |continued here. + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *
    + *
  1. + *
    + *

    Blockquote + * continued here.

    + *
    + *
  2. + *
+ *
+ */ + parsed.assertEquals( + blockQuote( + orderedList(listItem(blockQuote(paragraph("Blockquote continued here.")))), + ), + ) + } + + @Test + fun `should parse spec sample 293 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |> 1. > Blockquote + |> continued here. + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
+ *
    + *
  1. + *
    + *

    Blockquote + * continued here.

    + *
    + *
  2. + *
+ *
+ */ + parsed.assertEquals( + blockQuote( + orderedList(listItem(blockQuote(paragraph("Blockquote continued here.")))), + ), + ) + } + + @Test + fun `should parse spec sample 294 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- foo + | - bar + | - baz + | - boo + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • foo + *
      + *
    • bar + *
        + *
      • baz + *
          + *
        • boo
        • + *
        + *
      • + *
      + *
    • + *
    + *
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem( + paragraph("foo"), + unorderedList( + listItem( + paragraph("bar"), + unorderedList( + listItem( + paragraph("baz"), + unorderedList(listItem(paragraph("boo"))), + ), + ), + ), + ), + ), + ), + ) + } + + @Test + fun `should parse spec sample 295 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- foo + | - bar + | - baz + | - boo + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • foo
  • + *
  • bar
  • + *
  • baz
  • + *
  • boo
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem(paragraph("foo")), + listItem(paragraph("bar")), + listItem(paragraph("baz")), + listItem(paragraph("boo")), + ), + ) + } + + @Test + fun `should parse spec sample 296 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |10) foo + | - bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  1. foo + *
      + *
    • bar
    • + *
    + *
  2. + *
+ */ + parsed.assertEquals( + orderedList( + listItem( + paragraph("foo"), + unorderedList(listItem(paragraph("bar"))), + ), + startFrom = 10, + delimiter = ')', + ), + ) + } + + @Test + fun `should parse spec sample 297 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |10) foo + | - bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  1. foo
  2. + *
+ *
    + *
  • bar
  • + *
+ */ + parsed.assertEquals( + orderedList( + listItem(paragraph("foo")), + startFrom = 10, + delimiter = ')', + ), + unorderedList(listItem(paragraph("bar"))), + ) + } + + @Test + fun `should parse spec sample 298 correctly (List items)`() { + val parsed = processor.processMarkdownDocument("- - foo") + + /* + * Expected HTML: + *
    + *
  • + *
      + *
    • foo
    • + *
    + *
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem( + unorderedList(listItem(paragraph("foo"))), + ), + ), + ) + } + + @Test + fun `should parse spec sample 299 correctly (List items)`() { + val parsed = processor.processMarkdownDocument("1. - 2. foo") + + /* + * Expected HTML: + *
    + *
  1. + *
      + *
    • + *
        + *
      1. foo
      2. + *
      + *
    • + *
    + *
  2. + *
+ */ + parsed.assertEquals( + orderedList( + listItem( + unorderedList( + listItem( + orderedList( + listItem(paragraph("foo")), + startFrom = 2, + ), + ), + ), + ), + ), + ) + } + + @Test + fun `should parse spec sample 300 correctly (List items)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- # Foo + |- Bar + | --- + | baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • + *

    Foo

    + *
  • + *
  • + *

    Bar

    + * baz
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem(heading(level = 1, "Foo")), + listItem(heading(level = 2, "Bar"), paragraph("baz")), + ), + ) + } + + @Test + fun `should parse spec sample 301 correctly (Lists)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- foo + |- bar + |+ baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • foo
  • + *
  • bar
  • + *
+ *
    + *
  • baz
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem(paragraph("foo")), + listItem(paragraph("bar")), + ), + unorderedList( + listItem(paragraph("baz")), + bulletMarker = '+', + ), + ) + } + + @Test + fun `should parse spec sample 302 correctly (Lists)`() { + val parsed = + processor.processMarkdownDocument( + """ + |1. foo + |2. bar + |3) baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  1. foo
  2. + *
  3. bar
  4. + *
+ *
    + *
  1. baz
  2. + *
+ */ + parsed.assertEquals( + orderedList( + listItem(paragraph("foo")), + listItem(paragraph("bar")), + ), + orderedList( + listItem(paragraph("baz")), + startFrom = 3, + delimiter = ')', + ), + ) + } + + @Test + fun `should parse spec sample 303 correctly (Lists)`() { + val parsed = + processor.processMarkdownDocument( + """ + |Foo + |- bar + |- baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo

+ *
    + *
  • bar
  • + *
  • baz
  • + *
+ */ + parsed.assertEquals( + paragraph("Foo"), + unorderedList( + listItem(paragraph("bar")), + listItem(paragraph("baz")), + ), + ) + } + + @Test + fun `should parse spec sample 304 correctly (Lists)`() { + val parsed = + processor.processMarkdownDocument( + """ + |The number of windows in my house is + |14. The number of doors is 6. + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

The number of windows in my house is + * 14. The number of doors is 6.

+ */ + parsed.assertEquals( + paragraph("The number of windows in my house is 14. The number of doors is 6."), + ) + } + + @Test + fun `should parse spec sample 305 correctly (Lists)`() { + val parsed = + processor.processMarkdownDocument( + """ + |The number of windows in my house is + |1. The number of doors is 6. + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

The number of windows in my house is

+ *
    + *
  1. The number of doors is 6.
  2. + *
+ */ + parsed.assertEquals( + paragraph("The number of windows in my house is"), + orderedList(listItem(paragraph("The number of doors is 6."))), + ) + } + + @Test + fun `should parse spec sample 306 correctly (Lists)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- foo + | + |- bar + | + | + |- baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • + *

    foo

    + *
  • + *
  • + *

    bar

    + *
  • + *
  • + *

    baz

    + *
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem(paragraph("foo")), + listItem(paragraph("bar")), + listItem(paragraph("baz")), + isTight = false, + ), + ) + } + + @Test + fun `should parse spec sample 307 correctly (Lists)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- foo + | - bar + | - baz + | + | + | bim + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • foo + *
      + *
    • bar + *
        + *
      • + *

        baz

        + *

        bim

        + *
      • + *
      + *
    • + *
    + *
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem( + paragraph("foo"), + unorderedList( + listItem( + paragraph("bar"), + unorderedList( + listItem( + paragraph("baz"), + paragraph("bim"), + ), + isTight = false, + ), + ), + ), + ), + ), + ) + } + + @Test + fun `should parse spec sample 308 correctly (Lists)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- foo + |- bar + | + | + | + |- baz + |- bim + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • foo
  • + *
  • bar
  • + *
+ * + *
    + *
  • baz
  • + *
  • bim
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem(paragraph("foo")), + listItem(paragraph("bar")), + ), + htmlBlock(""), + unorderedList( + listItem(paragraph("baz")), + listItem(paragraph("bim")), + ), + ) + } + + @Test + fun `should parse spec sample 309 correctly (Lists)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- foo + | + | notcode + | + |- foo + | + | + | + | code + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • + *

    foo

    + *

    notcode

    + *
  • + *
  • + *

    foo

    + *
  • + *
+ * + *
code
+         * 
+ */ + parsed.assertEquals( + unorderedList( + listItem(paragraph("foo"), paragraph("notcode")), + listItem(paragraph("foo")), + isTight = false, + ), + htmlBlock(""), + indentedCodeBlock("code"), + ) + } + + @Test + fun `should parse spec sample 310 correctly (Lists)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- a + | - b + | - c + | - d + | - e + | - f + |- g + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • a
  • + *
  • b
  • + *
  • c
  • + *
  • d
  • + *
  • e
  • + *
  • f
  • + *
  • g
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem(paragraph("a")), + listItem(paragraph("b")), + listItem(paragraph("c")), + listItem(paragraph("d")), + listItem(paragraph("e")), + listItem(paragraph("f")), + listItem(paragraph("g")), + ), + ) + } + + @Test + fun `should parse spec sample 311 correctly (Lists)`() { + val parsed = + processor.processMarkdownDocument( + """ + |1. a + | + | 2. b + | + | 3. c + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  1. + *

    a

    + *
  2. + *
  3. + *

    b

    + *
  4. + *
  5. + *

    c

    + *
  6. + *
+ */ + parsed.assertEquals( + orderedList( + listItem(paragraph("a")), + listItem(paragraph("b")), + listItem(paragraph("c")), + isTight = false, + ), + ) + } + + @Test + fun `should parse spec sample 312 correctly (Lists)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- a + | - b + | - c + | - d + | - e + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • a
  • + *
  • b
  • + *
  • c
  • + *
  • d + * - e
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem(paragraph("a")), + listItem(paragraph("b")), + listItem(paragraph("c")), + listItem(paragraph("d - e")), + ), + ) + } + + @Test + fun `should parse spec sample 313 correctly (Lists)`() { + val parsed = + processor.processMarkdownDocument( + """ + |1. a + | + | 2. b + | + | 3. c + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  1. + *

    a

    + *
  2. + *
  3. + *

    b

    + *
  4. + *
+ *
3. c
+         * 
+ */ + parsed.assertEquals( + orderedList( + listItem(paragraph("a")), + listItem(paragraph("b")), + isTight = false, + ), + indentedCodeBlock("3. c"), + ) + } + + @Test + fun `should parse spec sample 314 correctly (Lists)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- a + |- b + | + |- c + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • + *

    a

    + *
  • + *
  • + *

    b

    + *
  • + *
  • + *

    c

    + *
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem(paragraph("a")), + listItem(paragraph("b")), + listItem(paragraph("c")), + isTight = false, + ), + ) + } + + @Test + fun `should parse spec sample 315 correctly (Lists)`() { + val parsed = + processor.processMarkdownDocument( + """ + |* a + |* + | + |* c + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • + *

    a

    + *
  • + *
  • + *
  • + *

    c

    + *
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem(paragraph("a")), + listItem(), + listItem(paragraph("c")), + isTight = false, + bulletMarker = '*', + ), + ) + } + + @Test + fun `should parse spec sample 316 correctly (Lists)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- a + |- b + | + | c + |- d + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • + *

    a

    + *
  • + *
  • + *

    b

    + *

    c

    + *
  • + *
  • + *

    d

    + *
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem(paragraph("a")), + listItem(paragraph("b"), paragraph("c")), + listItem(paragraph("d")), + isTight = false, + ), + ) + } + + @Test + fun `should parse spec sample 317 correctly (Lists)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- a + |- b + | + | [ref]: /url + |- d + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • + *

    a

    + *
  • + *
  • + *

    b

    + *
  • + *
  • + *

    d

    + *
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem(paragraph("a")), + listItem(paragraph("b")), + listItem(paragraph("d")), + isTight = false, + ), + ) + } + + @Test + fun `should parse spec sample 318 correctly (Lists)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- a + |- ``` + | b + | + | + | ``` + |- c + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • a
  • + *
  • + *
    b
    +         *
    +         *
    +         * 
    + *
  • + *
  • c
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem(paragraph("a")), + listItem(fencedCodeBlock("b")), + listItem(paragraph("c")), + ), + ) + } + + @Test + fun `should parse spec sample 319 correctly (Lists)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- a + | - b + | + | c + |- d + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • a + *
      + *
    • + *

      b

      + *

      c

      + *
    • + *
    + *
  • + *
  • d
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem( + paragraph("a"), + unorderedList( + listItem( + paragraph("b"), + paragraph("c"), + ), + isTight = false, + ), + ), + listItem(paragraph("d")), + ), + ) + } + + @Test + fun `should parse spec sample 320 correctly (Lists)`() { + val parsed = + processor.processMarkdownDocument( + """ + |* a + | > b + | > + |* c + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • a + *
    + *

    b

    + *
    + *
  • + *
  • c
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem(paragraph("a"), blockQuote(paragraph("b"))), + listItem(paragraph("c")), + bulletMarker = '*', + ), + ) + } + + @Test + fun `should parse spec sample 321 correctly (Lists)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- a + | > b + | ``` + | c + | ``` + |- d + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • a + *
    + *

    b

    + *
    + *
    c
    +         * 
    + *
  • + *
  • d
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem( + paragraph("a"), + blockQuote(paragraph("b")), + fencedCodeBlock("c"), + ), + listItem(paragraph("d")), + ), + ) + } + + @Test + fun `should parse spec sample 322 correctly (Lists)`() { + val parsed = processor.processMarkdownDocument("- a") + + /* + * Expected HTML: + *
    + *
  • a
  • + *
+ */ + parsed.assertEquals(unorderedList(listItem(paragraph("a")))) + } + + @Test + fun `should parse spec sample 323 correctly (Lists)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- a + | - b + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • a + *
      + *
    • b
    • + *
    + *
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem( + paragraph("a"), + unorderedList(listItem(paragraph("b"))), + ), + ), + ) + } + + @Test + fun `should parse spec sample 324 correctly (Lists)`() { + val parsed = + processor.processMarkdownDocument( + """ + |1. ``` + | foo + | ``` + | + | bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  1. + *
    foo
    +         * 
    + *

    bar

    + *
  2. + *
+ */ + parsed.assertEquals( + orderedList( + listItem( + fencedCodeBlock("foo"), + paragraph("bar"), + ), + isTight = false, + ), + ) + } + + @Test + fun `should parse spec sample 325 correctly (Lists)`() { + val parsed = + processor.processMarkdownDocument( + """ + |* foo + | * bar + | + | baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • + *

    foo

    + *
      + *
    • bar
    • + *
    + *

    baz

    + *
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem( + paragraph("foo"), + unorderedList( + listItem(paragraph("bar")), + bulletMarker = '*', + ), + paragraph("baz"), + ), + bulletMarker = '*', + isTight = false, + ), + ) + } + + @Test + fun `should parse spec sample 326 correctly (Lists)`() { + val parsed = + processor.processMarkdownDocument( + """ + |- a + | - b + | - c + | + |- d + | - e + | - f + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *
    + *
  • + *

    a

    + *
      + *
    • b
    • + *
    • c
    • + *
    + *
  • + *
  • + *

    d

    + *
      + *
    • e
    • + *
    • f
    • + *
    + *
  • + *
+ */ + parsed.assertEquals( + unorderedList( + listItem( + paragraph("a"), + unorderedList( + listItem(paragraph("b")), + listItem(paragraph("c")), + ), + ), + listItem( + paragraph("d"), + unorderedList( + listItem(paragraph("e")), + listItem(paragraph("f")), + ), + ), + isTight = false, + ), + ) + } + + @Test + fun `should parse spec sample 327 correctly (Inlines)`() { + val parsed = processor.processMarkdownDocument("`hi`lo`") + + /* + * Expected HTML: + *

hilo`

+ */ + parsed.assertEquals(paragraph("`hi`lo\\`")) + } + + @Test + fun `should parse spec sample 328 correctly (Code spans)`() { + val parsed = processor.processMarkdownDocument("`foo`") + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("`foo`")) + } + + @Test + fun `should parse spec sample 329 correctly (Code spans)`() { + val parsed = processor.processMarkdownDocument("`` foo ` bar ``") + + /* + * Expected HTML: + *

foo ` bar

+ */ + parsed.assertEquals(paragraph("``foo ` bar``")) + } + + @Test + fun `should parse spec sample 330 correctly (Code spans)`() { + val parsed = processor.processMarkdownDocument("` `` `") + + /* + * Expected HTML: + *

``

+ */ + parsed.assertEquals(paragraph("````````")) + } + + @Test + fun `should parse spec sample 331 correctly (Code spans)`() { + val parsed = processor.processMarkdownDocument("` `` `") + + /* + * Expected HTML: + *

``

+ */ + parsed.assertEquals(paragraph("``` `` ```")) + } + + @Test + fun `should parse spec sample 332 correctly (Code spans)`() { + val parsed = processor.processMarkdownDocument("` a`") + + /* + * Expected HTML: + *

a

+ */ + parsed.assertEquals(paragraph("` a`")) + } + + @Test + fun `should parse spec sample 333 correctly (Code spans)`() { + val parsed = processor.processMarkdownDocument("` b `") + + /* + * Expected HTML: + *

 b 

+ */ + parsed.assertEquals(paragraph("` b `")) + } + + @Test + fun `should parse spec sample 334 correctly (Code spans)`() { + val parsed = + processor.processMarkdownDocument( + """ + |` ` + |` ` + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

  + *

+ */ + parsed.assertEquals(paragraph("` ` ` `")) + } + + @Test + fun `should parse spec sample 335 correctly (Code spans)`() { + val parsed = + processor.processMarkdownDocument( + """ + |`` + |foo + |bar + |baz + |`` + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo bar baz

+ */ + parsed.assertEquals(paragraph("`foo bar baz`")) + } + + @Test + fun `should parse spec sample 336 correctly (Code spans)`() { + val parsed = + processor.processMarkdownDocument( + """ + |`` + |foo + |`` + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("`foo `")) + } + + @Test + fun `should parse spec sample 337 correctly (Code spans)`() { + val parsed = + processor.processMarkdownDocument( + """ + |`foo bar + |baz` + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo bar baz

+ */ + parsed.assertEquals(paragraph("`foo bar baz`")) + } + + @Test + fun `should parse spec sample 338 correctly (Code spans)`() { + val parsed = processor.processMarkdownDocument("`foo\\`bar`") + + /* + * Expected HTML: + *

foo\bar`

+ */ + parsed.assertEquals(paragraph("`foo\\`bar\\`")) + } + + @Test + fun `should parse spec sample 339 correctly (Code spans)`() { + val parsed = processor.processMarkdownDocument("``foo`bar``") + + /* + * Expected HTML: + *

foo`bar

+ */ + parsed.assertEquals(paragraph("``foo`bar``")) + } + + @Test + fun `should parse spec sample 340 correctly (Code spans)`() { + val parsed = processor.processMarkdownDocument("` foo `` bar `") + + /* + * Expected HTML: + *

foo `` bar

+ */ + parsed.assertEquals(paragraph("```foo `` bar```")) + } + + @Test + fun `should parse spec sample 341 correctly (Code spans)`() { + val parsed = processor.processMarkdownDocument("*foo`*`") + + /* + * Expected HTML: + *

*foo*

+ */ + parsed.assertEquals(paragraph("\\*foo`*`")) + } + + @Test + fun `should parse spec sample 342 correctly (Code spans)`() { + val parsed = processor.processMarkdownDocument("[not a `link](/foo`)") + + /* + * Expected HTML: + *

[not a link](/foo)

+ */ + parsed.assertEquals(paragraph("\\[not a `link](/foo`\\)")) + } + + @Test + fun `should parse spec sample 343 correctly (Code spans)`() { + val parsed = processor.processMarkdownDocument("``") + + /* + * Expected HTML: + *

<a href="">`

+ */ + parsed.assertEquals(paragraph("`
\\`")) + } + + @Test + fun `should parse spec sample 344 correctly (Code spans)`() { + val parsed = processor.processMarkdownDocument("`") + + /* + * Expected HTML: + *

`

+ */ + parsed.assertEquals(paragraph("\\`")) + } + + @Test + fun `should parse spec sample 345 correctly (Code spans)`() { + val parsed = processor.processMarkdownDocument("``") + + /* + * Expected HTML: + *

<http://foo.bar.baz>`

+ */ + parsed.assertEquals(paragraph("`\\`")) + } + + @Test + fun `should parse spec sample 346 correctly (Code spans)`() { + val parsed = processor.processMarkdownDocument("`") + + /* + * Expected HTML: + *

http://foo.bar.`baz`

+ */ + parsed.assertEquals(paragraph("[http://foo.bar.\\`baz](http://foo.bar.`baz)\\`")) + } + + @Test + fun `should parse spec sample 347 correctly (Code spans)`() { + val parsed = processor.processMarkdownDocument("```foo``") + + /* + * Expected HTML: + *

```foo``

+ */ + parsed.assertEquals(paragraph("\\`\\`\\`foo\\`\\`")) + } + + @Test + fun `should parse spec sample 348 correctly (Code spans)`() { + val parsed = processor.processMarkdownDocument("`foo") + + /* + * Expected HTML: + *

`foo

+ */ + parsed.assertEquals(paragraph("\\`foo")) + } + + @Test + fun `should parse spec sample 349 correctly (Code spans)`() { + val parsed = processor.processMarkdownDocument("`foo``bar``") + + /* + * Expected HTML: + *

`foobar

+ */ + parsed.assertEquals(paragraph("\\`foo`bar`")) + } + + @Test + fun `should parse spec sample 350 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("*foo bar*") + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("*foo bar*")) + } + + @Test + fun `should parse spec sample 351 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("a * foo bar*") + + /* + * Expected HTML: + *

a * foo bar*

+ */ + parsed.assertEquals(paragraph("a \\* foo bar\\*")) + } + + @Test + fun `should parse spec sample 352 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("a*\"foo\"*") + + /* + * Expected HTML: + *

a*"foo"*

+ */ + parsed.assertEquals(paragraph("a\\*\"foo\"\\*")) + } + + @Test + fun `should parse spec sample 353 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("* a *") + + /* + * Expected HTML: + *

* a *

+ */ + parsed.assertEquals(paragraph("\\* a \\*")) + } + + @Test + fun `should parse spec sample 354 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("foo*bar*") + + /* + * Expected HTML: + *

foobar

+ */ + parsed.assertEquals(paragraph("foo*bar*")) + } + + @Test + fun `should parse spec sample 355 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("5*6*78") + + /* + * Expected HTML: + *

5678

+ */ + parsed.assertEquals(paragraph("5*6*78")) + } + + @Test + fun `should parse spec sample 356 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("_foo bar_") + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("_foo bar_")) + } + + @Test + fun `should parse spec sample 357 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("_ foo bar_") + + /* + * Expected HTML: + *

_ foo bar_

+ */ + parsed.assertEquals(paragraph("\\_ foo bar\\_")) + } + + @Test + fun `should parse spec sample 358 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("a_\"foo\"_") + + /* + * Expected HTML: + *

a_"foo"_

+ */ + parsed.assertEquals(paragraph("a\\_\"foo\"\\_")) + } + + @Test + fun `should parse spec sample 359 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("foo_bar_") + + /* + * Expected HTML: + *

foo_bar_

+ */ + parsed.assertEquals(paragraph("foo\\_bar\\_")) + } + + @Test + fun `should parse spec sample 360 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("5_6_78") + + /* + * Expected HTML: + *

5_6_78

+ */ + parsed.assertEquals(paragraph("5\\_6\\_78")) + } + + @Test + fun `should parse spec sample 361 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("пристаням_стремятся_") + + /* + * Expected HTML: + *

пристаням_стремятся_

+ */ + parsed.assertEquals(paragraph("пристаням\\_стремятся\\_")) + } + + @Test + fun `should parse spec sample 362 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("aa_\"bb\"_cc") + + /* + * Expected HTML: + *

aa_"bb"_cc

+ */ + parsed.assertEquals(paragraph("aa\\_\"bb\"\\_cc")) + } + + @Test + fun `should parse spec sample 363 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("foo-_(bar)_") + + /* + * Expected HTML: + *

foo-(bar)

+ */ + parsed.assertEquals(paragraph("foo-_\\(bar\\)_")) + } + + @Test + fun `should parse spec sample 364 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("_foo*") + + /* + * Expected HTML: + *

_foo*

+ */ + parsed.assertEquals(paragraph("\\_foo\\*")) + } + + @Test + fun `should parse spec sample 365 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("*foo bar *") + + /* + * Expected HTML: + *

*foo bar *

+ */ + parsed.assertEquals(paragraph("\\*foo bar \\*")) + } + + @Test + fun `should parse spec sample 366 correctly (Emphasis and strong emphasis)`() { + val parsed = + processor.processMarkdownDocument( + """ + |*foo bar + |* + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

*foo bar + * *

+ */ + parsed.assertEquals(paragraph("\\*foo bar \\*")) + } + + @Test + fun `should parse spec sample 367 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("*(*foo)") + + /* + * Expected HTML: + *

*(*foo)

+ */ + parsed.assertEquals(paragraph("\\*\\(\\*foo\\)")) + } + + @Test + fun `should parse spec sample 368 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("*(*foo*)*") + + /* + * Expected HTML: + *

(foo)

+ */ + parsed.assertEquals(paragraph("*\\(*foo*\\)*")) + } + + @Test + fun `should parse spec sample 369 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("*foo*bar") + + /* + * Expected HTML: + *

foobar

+ */ + parsed.assertEquals(paragraph("*foo*bar")) + } + + @Test + fun `should parse spec sample 370 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("_foo bar _") + + /* + * Expected HTML: + *

_foo bar _

+ */ + parsed.assertEquals(paragraph("\\_foo bar \\_")) + } + + @Test + fun `should parse spec sample 371 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("_(_foo)") + + /* + * Expected HTML: + *

_(_foo)

+ */ + parsed.assertEquals(paragraph("\\_\\(\\_foo\\)")) + } + + @Test + fun `should parse spec sample 372 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("_(_foo_)_") + + /* + * Expected HTML: + *

(foo)

+ */ + parsed.assertEquals(paragraph("_\\(_foo_\\)_")) + } + + @Test + fun `should parse spec sample 373 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("_foo_bar") + + /* + * Expected HTML: + *

_foo_bar

+ */ + parsed.assertEquals(paragraph("\\_foo\\_bar")) + } + + @Test + fun `should parse spec sample 374 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("_пристаням_стремятся") + + /* + * Expected HTML: + *

_пристаням_стремятся

+ */ + parsed.assertEquals(paragraph("\\_пристаням\\_стремятся")) + } + + @Test + fun `should parse spec sample 375 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("_foo_bar_baz_") + + /* + * Expected HTML: + *

foo_bar_baz

+ */ + parsed.assertEquals(paragraph("_foo\\_bar\\_baz_")) + } + + @Test + fun `should parse spec sample 376 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("_(bar)_.") + + /* + * Expected HTML: + *

(bar).

+ */ + parsed.assertEquals(paragraph("_\\(bar\\)_.")) + } + + @Test + fun `should parse spec sample 377 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("**foo bar**") + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("**foo bar**")) + } + + @Test + fun `should parse spec sample 378 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("** foo bar**") + + /* + * Expected HTML: + *

** foo bar**

+ */ + parsed.assertEquals(paragraph("\\*\\* foo bar\\*\\*")) + } + + @Test + fun `should parse spec sample 379 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("a**\"foo\"**") + + /* + * Expected HTML: + *

a**"foo"**

+ */ + parsed.assertEquals(paragraph("a\\*\\*\"foo\"\\*\\*")) + } + + @Test + fun `should parse spec sample 380 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("foo**bar**") + + /* + * Expected HTML: + *

foobar

+ */ + parsed.assertEquals(paragraph("foo**bar**")) + } + + @Test + fun `should parse spec sample 381 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("__foo bar__") + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("__foo bar__")) + } + + @Test + fun `should parse spec sample 382 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("__ foo bar__") + + /* + * Expected HTML: + *

__ foo bar__

+ */ + parsed.assertEquals(paragraph("\\_\\_ foo bar\\_\\_")) + } + + @Test + fun `should parse spec sample 383 correctly (Emphasis and strong emphasis)`() { + val parsed = + processor.processMarkdownDocument( + """ + |__ + |foo bar__ + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

__ + * foo bar__

+ */ + parsed.assertEquals(paragraph("\\_\\_ foo bar\\_\\_")) + } + + @Test + fun `should parse spec sample 384 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("a__\"foo\"__") + + /* + * Expected HTML: + *

a__"foo"__

+ */ + parsed.assertEquals(paragraph("a\\_\\_\"foo\"\\_\\_")) + } + + @Test + fun `should parse spec sample 385 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("foo__bar__") + + /* + * Expected HTML: + *

foo__bar__

+ */ + parsed.assertEquals(paragraph("foo\\_\\_bar\\_\\_")) + } + + @Test + fun `should parse spec sample 386 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("5__6__78") + + /* + * Expected HTML: + *

5__6__78

+ */ + parsed.assertEquals(paragraph("5\\_\\_6\\_\\_78")) + } + + @Test + fun `should parse spec sample 387 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("пристаням__стремятся__") + + /* + * Expected HTML: + *

пристаням__стремятся__

+ */ + parsed.assertEquals(paragraph("пристаням\\_\\_стремятся\\_\\_")) + } + + @Test + fun `should parse spec sample 388 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("__foo, __bar__, baz__") + + /* + * Expected HTML: + *

foo, bar, baz

+ */ + parsed.assertEquals(paragraph("__foo, __bar__, baz__")) + } + + @Test + fun `should parse spec sample 389 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("foo-__(bar)__") + + /* + * Expected HTML: + *

foo-(bar)

+ */ + parsed.assertEquals(paragraph("foo-__\\(bar\\)__")) + } + + @Test + fun `should parse spec sample 390 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("**foo bar **") + + /* + * Expected HTML: + *

**foo bar **

+ */ + parsed.assertEquals(paragraph("\\*\\*foo bar \\*\\*")) + } + + @Test + fun `should parse spec sample 391 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("**(**foo)") + + /* + * Expected HTML: + *

**(**foo)

+ */ + parsed.assertEquals(paragraph("\\*\\*\\(\\*\\*foo\\)")) + } + + @Test + fun `should parse spec sample 392 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("*(**foo**)*") + + /* + * Expected HTML: + *

(foo)

+ */ + parsed.assertEquals(paragraph("*\\(**foo**\\)*")) + } + + @Test + fun `should parse spec sample 393 correctly (Emphasis and strong emphasis)`() { + val parsed = + processor.processMarkdownDocument( + """ + |**Gomphocarpus (*Gomphocarpus physocarpus*, syn. + |*Asclepias physocarpa*)** + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Gomphocarpus (Gomphocarpus physocarpus, syn. + * Asclepias physocarpa)

+ */ + parsed.assertEquals( + paragraph( + "**Gomphocarpus \\(*Gomphocarpus physocarpus*, syn. *Asclepias physocarpa*\\)**", + ), + ) + } + + @Test + fun `should parse spec sample 394 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("**foo \"*bar*\" foo**") + + /* + * Expected HTML: + *

foo "bar" foo

+ */ + parsed.assertEquals(paragraph("**foo \"*bar*\" foo**")) + } + + @Test + fun `should parse spec sample 395 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("**foo**bar") + + /* + * Expected HTML: + *

foobar

+ */ + parsed.assertEquals(paragraph("**foo**bar")) + } + + @Test + fun `should parse spec sample 396 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("__foo bar __") + + /* + * Expected HTML: + *

__foo bar __

+ */ + parsed.assertEquals(paragraph("\\_\\_foo bar \\_\\_")) + } + + @Test + fun `should parse spec sample 397 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("__(__foo)") + + /* + * Expected HTML: + *

__(__foo)

+ */ + parsed.assertEquals(paragraph("\\_\\_\\(\\_\\_foo\\)")) + } + + @Test + fun `should parse spec sample 398 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("_(__foo__)_") + + /* + * Expected HTML: + *

(foo)

+ */ + parsed.assertEquals(paragraph("_\\(__foo__\\)_")) + } + + @Test + fun `should parse spec sample 399 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("__foo__bar") + + /* + * Expected HTML: + *

__foo__bar

+ */ + parsed.assertEquals(paragraph("\\_\\_foo\\_\\_bar")) + } + + @Test + fun `should parse spec sample 400 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("__пристаням__стремятся") + + /* + * Expected HTML: + *

__пристаням__стремятся

+ */ + parsed.assertEquals(paragraph("\\_\\_пристаням\\_\\_стремятся")) + } + + @Test + fun `should parse spec sample 401 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("__foo__bar__baz__") + + /* + * Expected HTML: + *

foo__bar__baz

+ */ + parsed.assertEquals(paragraph("__foo\\_\\_bar\\_\\_baz__")) + } + + @Test + fun `should parse spec sample 402 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("__(bar)__.") + + /* + * Expected HTML: + *

(bar).

+ */ + parsed.assertEquals(paragraph("__\\(bar\\)__.")) + } + + @Test + fun `should parse spec sample 403 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("*foo [bar](/url)*") + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("*foo [bar](/url)*")) + } + + @Test + fun `should parse spec sample 404 correctly (Emphasis and strong emphasis)`() { + val parsed = + processor.processMarkdownDocument( + """ + |*foo + |bar* + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo + * bar

+ */ + parsed.assertEquals(paragraph("*foo bar*")) + } + + @Test + fun `should parse spec sample 405 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("_foo __bar__ baz_") + + /* + * Expected HTML: + *

foo bar baz

+ */ + parsed.assertEquals(paragraph("_foo __bar__ baz_")) + } + + @Test + fun `should parse spec sample 406 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("_foo _bar_ baz_") + + /* + * Expected HTML: + *

foo bar baz

+ */ + parsed.assertEquals(paragraph("_foo _bar_ baz_")) + } + + @Test + fun `should parse spec sample 407 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("__foo_ bar_") + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("__foo_ bar_")) + } + + @Test + fun `should parse spec sample 408 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("*foo *bar**") + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("*foo *bar**")) + } + + @Test + fun `should parse spec sample 409 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("*foo **bar** baz*") + + /* + * Expected HTML: + *

foo bar baz

+ */ + parsed.assertEquals(paragraph("*foo **bar** baz*")) + } + + @Test + fun `should parse spec sample 410 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("*foo**bar**baz*") + + /* + * Expected HTML: + *

foobarbaz

+ */ + parsed.assertEquals(paragraph("*foo**bar**baz*")) + } + + @Test + fun `should parse spec sample 411 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("*foo**bar*") + + /* + * Expected HTML: + *

foo**bar

+ */ + parsed.assertEquals(paragraph("*foo\\*\\*bar*")) + } + + @Test + fun `should parse spec sample 412 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("***foo** bar*") + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("***foo** bar*")) + } + + @Test + fun `should parse spec sample 413 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("*foo **bar***") + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("*foo **bar***")) + } + + @Test + fun `should parse spec sample 414 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("*foo**bar***") + + /* + * Expected HTML: + *

foobar

+ */ + parsed.assertEquals(paragraph("*foo**bar***")) + } + + @Test + fun `should parse spec sample 415 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("foo***bar***baz") + + /* + * Expected HTML: + *

foobarbaz

+ */ + parsed.assertEquals(paragraph("foo***bar***baz")) + } + + @Test + fun `should parse spec sample 416 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("foo******bar*********baz") + + /* + * Expected HTML: + *

foobar***baz

+ */ + parsed.assertEquals(paragraph("foo******bar******\\*\\*\\*baz")) + } + + @Test + fun `should parse spec sample 417 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("*foo **bar *baz* bim** bop*") + + /* + * Expected HTML: + *

foo bar baz bim bop

+ */ + parsed.assertEquals(paragraph("*foo **bar *baz* bim** bop*")) + } + + @Test + fun `should parse spec sample 418 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("*foo [*bar*](/url)*") + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("*foo [*bar*](/url)*")) + } + + @Test + fun `should parse spec sample 419 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("** is not an empty emphasis") + + /* + * Expected HTML: + *

** is not an empty emphasis

+ */ + parsed.assertEquals(paragraph("\\*\\* is not an empty emphasis")) + } + + @Test + fun `should parse spec sample 420 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("**** is not an empty strong emphasis") + + /* + * Expected HTML: + *

**** is not an empty strong emphasis

+ */ + parsed.assertEquals(paragraph("\\*\\*\\*\\* is not an empty strong emphasis")) + } + + @Test + fun `should parse spec sample 421 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("**foo [bar](/url)**") + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("**foo [bar](/url)**")) + } + + @Test + fun `should parse spec sample 422 correctly (Emphasis and strong emphasis)`() { + val parsed = + processor.processMarkdownDocument( + """ + |**foo + |bar** + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo + * bar

+ */ + parsed.assertEquals(paragraph("**foo bar**")) + } + + @Test + fun `should parse spec sample 423 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("__foo _bar_ baz__") + + /* + * Expected HTML: + *

foo bar baz

+ */ + parsed.assertEquals(paragraph("__foo _bar_ baz__")) + } + + @Test + fun `should parse spec sample 424 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("__foo __bar__ baz__") + + /* + * Expected HTML: + *

foo bar baz

+ */ + parsed.assertEquals(paragraph("__foo __bar__ baz__")) + } + + @Test + fun `should parse spec sample 425 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("____foo__ bar__") + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("____foo__ bar__")) + } + + @Test + fun `should parse spec sample 426 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("**foo **bar****") + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("**foo **bar****")) + } + + @Test + fun `should parse spec sample 427 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("**foo *bar* baz**") + + /* + * Expected HTML: + *

foo bar baz

+ */ + parsed.assertEquals(paragraph("**foo *bar* baz**")) + } + + @Test + fun `should parse spec sample 428 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("**foo*bar*baz**") + + /* + * Expected HTML: + *

foobarbaz

+ */ + parsed.assertEquals(paragraph("**foo*bar*baz**")) + } + + @Test + fun `should parse spec sample 429 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("***foo* bar**") + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("***foo* bar**")) + } + + @Test + fun `should parse spec sample 430 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("**foo *bar***") + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("**foo *bar***")) + } + + @Test + fun `should parse spec sample 431 correctly (Emphasis and strong emphasis)`() { + val parsed = + processor.processMarkdownDocument( + """ + |**foo *bar **baz** + |bim* bop** + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo bar baz + * bim bop

+ */ + parsed.assertEquals(paragraph("**foo *bar **baz** bim* bop**")) + } + + @Test + fun `should parse spec sample 432 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("**foo [*bar*](/url)**") + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("**foo [*bar*](/url)**")) + } + + @Test + fun `should parse spec sample 433 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("__ is not an empty emphasis") + + /* + * Expected HTML: + *

__ is not an empty emphasis

+ */ + parsed.assertEquals(paragraph("\\_\\_ is not an empty emphasis")) + } + + @Test + fun `should parse spec sample 434 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("____ is not an empty strong emphasis") + + /* + * Expected HTML: + *

____ is not an empty strong emphasis

+ */ + parsed.assertEquals(paragraph("\\_\\_\\_\\_ is not an empty strong emphasis")) + } + + @Test + fun `should parse spec sample 435 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("foo ***") + + /* + * Expected HTML: + *

foo ***

+ */ + parsed.assertEquals(paragraph("foo \\*\\*\\*")) + } + + @Test + fun `should parse spec sample 436 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("foo *\\**") + + /* + * Expected HTML: + *

foo *

+ */ + parsed.assertEquals(paragraph("foo *\\**")) + } + + @Test + fun `should parse spec sample 437 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("foo *_*") + + /* + * Expected HTML: + *

foo _

+ */ + parsed.assertEquals(paragraph("foo *\\_*")) + } + + @Test + fun `should parse spec sample 438 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("foo *****") + + /* + * Expected HTML: + *

foo *****

+ */ + parsed.assertEquals(paragraph("foo \\*\\*\\*\\*\\*")) + } + + @Test + fun `should parse spec sample 439 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("foo **\\***") + + /* + * Expected HTML: + *

foo *

+ */ + parsed.assertEquals(paragraph("foo **\\***")) + } + + @Test + fun `should parse spec sample 440 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("foo **_**") + + /* + * Expected HTML: + *

foo _

+ */ + parsed.assertEquals(paragraph("foo **\\_**")) + } + + @Test + fun `should parse spec sample 441 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("**foo*") + + /* + * Expected HTML: + *

*foo

+ */ + parsed.assertEquals(paragraph("\\**foo*")) + } + + @Test + fun `should parse spec sample 442 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("*foo**") + + /* + * Expected HTML: + *

foo*

+ */ + parsed.assertEquals(paragraph("*foo*\\*")) + } + + @Test + fun `should parse spec sample 443 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("***foo**") + + /* + * Expected HTML: + *

*foo

+ */ + parsed.assertEquals(paragraph("\\***foo**")) + } + + @Test + fun `should parse spec sample 444 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("****foo*") + + /* + * Expected HTML: + *

***foo

+ */ + parsed.assertEquals(paragraph("\\*\\*\\**foo*")) + } + + @Test + fun `should parse spec sample 445 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("**foo***") + + /* + * Expected HTML: + *

foo*

+ */ + parsed.assertEquals(paragraph("**foo**\\*")) + } + + @Test + fun `should parse spec sample 446 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("*foo****") + + /* + * Expected HTML: + *

foo***

+ */ + parsed.assertEquals(paragraph("*foo*\\*\\*\\*")) + } + + @Test + fun `should parse spec sample 447 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("foo ___") + + /* + * Expected HTML: + *

foo ___

+ */ + parsed.assertEquals(paragraph("foo \\_\\_\\_")) + } + + @Test + fun `should parse spec sample 448 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("foo _\\__") + + /* + * Expected HTML: + *

foo _

+ */ + parsed.assertEquals(paragraph("foo _\\__")) + } + + @Test + fun `should parse spec sample 449 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("foo _*_") + + /* + * Expected HTML: + *

foo *

+ */ + parsed.assertEquals(paragraph("foo _\\*_")) + } + + @Test + fun `should parse spec sample 450 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("foo _____") + + /* + * Expected HTML: + *

foo _____

+ */ + parsed.assertEquals(paragraph("foo \\_\\_\\_\\_\\_")) + } + + @Test + fun `should parse spec sample 451 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("foo __\\___") + + /* + * Expected HTML: + *

foo _

+ */ + parsed.assertEquals(paragraph("foo __\\___")) + } + + @Test + fun `should parse spec sample 452 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("foo __*__") + + /* + * Expected HTML: + *

foo *

+ */ + parsed.assertEquals(paragraph("foo __\\*__")) + } + + @Test + fun `should parse spec sample 453 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("__foo_") + + /* + * Expected HTML: + *

_foo

+ */ + parsed.assertEquals(paragraph("\\__foo_")) + } + + @Test + fun `should parse spec sample 454 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("_foo__") + + /* + * Expected HTML: + *

foo_

+ */ + parsed.assertEquals(paragraph("_foo_\\_")) + } + + @Test + fun `should parse spec sample 455 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("___foo__") + + /* + * Expected HTML: + *

_foo

+ */ + parsed.assertEquals(paragraph("\\___foo__")) + } + + @Test + fun `should parse spec sample 456 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("____foo_") + + /* + * Expected HTML: + *

___foo

+ */ + parsed.assertEquals(paragraph("\\_\\_\\__foo_")) + } + + @Test + fun `should parse spec sample 457 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("__foo___") + + /* + * Expected HTML: + *

foo_

+ */ + parsed.assertEquals(paragraph("__foo__\\_")) + } + + @Test + fun `should parse spec sample 458 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("_foo____") + + /* + * Expected HTML: + *

foo___

+ */ + parsed.assertEquals(paragraph("_foo_\\_\\_\\_")) + } + + @Test + fun `should parse spec sample 459 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("**foo**") + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("**foo**")) + } + + @Test + fun `should parse spec sample 460 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("*_foo_*") + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("*_foo_*")) + } + + @Test + fun `should parse spec sample 460b correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("*_foo *bar*_*") + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("*_foo *bar*_*")) + } + + @Test + fun `should parse spec sample 460c correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("**foo *bar***") + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("**foo *bar***")) + } + + @Test + fun `should parse spec sample 460d correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("*_foo *bar* a_*") + + /* + * Expected HTML: + *

foo bar a

+ */ + parsed.assertEquals(paragraph("*_foo *bar* a_*")) + } + + @Test + fun `should parse spec sample 460e correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("**foo *bar* a**") + + /* + * Expected HTML: + *

foo bar a

+ */ + parsed.assertEquals(paragraph("**foo *bar* a**")) + } + + @Test + fun `should parse spec sample 460f correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("*_*foo *bar* a*_*") + + /* + * Expected HTML: + *

foo bar a

+ */ + parsed.assertEquals(paragraph("*_*foo *bar* a*_*")) + } + + @Test + fun `should parse spec sample 461 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("__foo__") + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("__foo__")) + } + + @Test + fun `should parse spec sample 462 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("_*foo*_") + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("_*foo*_")) + } + + @Test + fun `should parse spec sample 462b correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("_*foo _bar_*_") + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("_*foo _bar_*_")) + } + + @Test + fun `should parse spec sample 462c correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("__foo _bar___") + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("__foo _bar___")) + } + + @Test + fun `should parse spec sample 462d correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("_*foo _bar_ a*_") + + /* + * Expected HTML: + *

foo bar a

+ */ + parsed.assertEquals(paragraph("_*foo _bar_ a*_")) + } + + @Test + fun `should parse spec sample 462e correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("__foo _bar_ a__") + + /* + * Expected HTML: + *

foo bar a

+ */ + parsed.assertEquals(paragraph("__foo _bar_ a__")) + } + + @Test + fun `should parse spec sample 462f correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("_*foo *bar* a*_") + + /* + * Expected HTML: + *

foo bar a

+ */ + parsed.assertEquals(paragraph("_*foo *bar* a*_")) + } + + @Test + fun `should parse spec sample 463 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("****foo****") + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("****foo****")) + } + + @Test + fun `should parse spec sample 464 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("____foo____") + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("____foo____")) + } + + @Test + fun `should parse spec sample 465 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("******foo******") + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("******foo******")) + } + + @Test + fun `should parse spec sample 466 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("***foo***") + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("***foo***")) + } + + @Test + fun `should parse spec sample 467 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("_____foo_____") + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("_____foo_____")) + } + + @Test + fun `should parse spec sample 468 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("*foo _bar* baz_") + + /* + * Expected HTML: + *

foo _bar baz_

+ */ + parsed.assertEquals(paragraph("*foo \\_bar* baz\\_")) + } + + @Test + fun `should parse spec sample 469 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("*foo __bar *baz bim__ bam*") + + /* + * Expected HTML: + *

foo bar *baz bim bam

+ */ + parsed.assertEquals(paragraph("*foo __bar \\*baz bim__ bam*")) + } + + @Test + fun `should parse spec sample 470 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("**foo **bar baz**") + + /* + * Expected HTML: + *

**foo bar baz

+ */ + parsed.assertEquals(paragraph("\\*\\*foo **bar baz**")) + } + + @Test + fun `should parse spec sample 471 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("*foo *bar baz*") + + /* + * Expected HTML: + *

*foo bar baz

+ */ + parsed.assertEquals(paragraph("\\*foo *bar baz*")) + } + + @Test + fun `should parse spec sample 472 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("*[bar*](/url)") + + /* + * Expected HTML: + *

*bar*

+ */ + parsed.assertEquals(paragraph("\\*[bar\\*](/url)")) + } + + @Test + fun `should parse spec sample 473 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("_foo [bar_](/url)") + + /* + * Expected HTML: + *

_foo bar_

+ */ + parsed.assertEquals(paragraph("\\_foo [bar\\_](/url)")) + } + + @Test + fun `should parse spec sample 474 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("*") + + /* + * Expected HTML: + *

*

+ */ + parsed.assertEquals(paragraph("\\*")) + } + + @Test + fun `should parse spec sample 475 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("**") + + /* + * Expected HTML: + *

**

+ */ + parsed.assertEquals(paragraph("\\*\\*")) + } + + @Test + fun `should parse spec sample 476 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("__") + + /* + * Expected HTML: + *

__

+ */ + parsed.assertEquals(paragraph("\\_\\_")) + } + + @Test + fun `should parse spec sample 477 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("*a `*`*") + + /* + * Expected HTML: + *

a *

+ */ + parsed.assertEquals(paragraph("*a `*`*")) + } + + @Test + fun `should parse spec sample 478 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("_a `_`_") + + /* + * Expected HTML: + *

a _

+ */ + parsed.assertEquals(paragraph("_a `_`_")) + } + + @Test + fun `should parse spec sample 479 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("**a") + + /* + * Expected HTML: + *

**ahttp://foo.bar/?q=**

+ */ + parsed.assertEquals(paragraph("\\*\\*a[http://foo.bar/?q=\\*\\*](http://foo.bar/?q=**)")) + } + + @Test + fun `should parse spec sample 480 correctly (Emphasis and strong emphasis)`() { + val parsed = processor.processMarkdownDocument("__a") + + /* + * Expected HTML: + *

__ahttp://foo.bar/?q=__

+ */ + parsed.assertEquals(paragraph("\\_\\_a[http://foo.bar/?q=\\_\\_](http://foo.bar/?q=__)")) + } + + @Test + fun `should parse spec sample 481 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[link](/uri \"title\")") + + /* + * Expected HTML: + *

link

+ */ + parsed.assertEquals(paragraph("[link](/uri \"title\")")) + } + + @Test + fun `should parse spec sample 482 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[link](/uri)") + + /* + * Expected HTML: + *

link

+ */ + parsed.assertEquals(paragraph("[link](/uri)")) + } + + @Test + fun `should parse spec sample 483 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[](./target.md)") + + /* + * Expected HTML: + *

+ */ + parsed.assertEquals(paragraph("[./target.md](./target.md)")) + } + + @Test + fun `should parse spec sample 484 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[link]()") + + /* + * Expected HTML: + *

link

+ */ + parsed.assertEquals(paragraph("[link]()")) + } + + @Test + fun `should parse spec sample 485 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[link](<>)") + + /* + * Expected HTML: + *

link

+ */ + parsed.assertEquals(paragraph("[link]()")) + } + + @Test + fun `should parse spec sample 486 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[]()") + + /* + * Expected HTML: + *

+ */ + parsed.assertEquals() + } + + @Test + fun `should parse spec sample 487 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[link](/my uri)") + + /* + * Expected HTML: + *

[link](/my uri)

+ */ + parsed.assertEquals(paragraph("\\[link\\]\\(/my uri\\)")) + } + + @Test + fun `should parse spec sample 488 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[link](
)") + + /* + * Expected HTML: + *

link

+ */ + parsed.assertEquals(paragraph("[link](
)")) + } + + @Test + fun `should parse spec sample 489 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[link](foo + |bar) + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

[link](foo + * bar)

+ */ + parsed.assertEquals(paragraph("\\[link\\]\\(foo bar\\)")) + } + + @Test + fun `should parse spec sample 490 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[link]() + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

[link]()

+ */ + parsed.assertEquals(paragraph("\\[link\\]\\(\\)")) + } + + @Test + fun `should parse spec sample 491 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[a]()") + + /* + * Expected HTML: + *

a

+ */ + parsed.assertEquals(paragraph("[a](b\\)c)")) + } + + @Test + fun `should parse spec sample 492 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[link]()") + + /* + * Expected HTML: + *

[link](<foo>)

+ */ + parsed.assertEquals(paragraph("\\[link\\]\\(\\\\)")) + } + + @Test + fun `should parse spec sample 493 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[a]( + |[a](c) + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

[a](<b)c + * [a](<b)c> + * [a](c)

+ */ + parsed.assertEquals( + paragraph( + "\\[a\\]\\(\\ \\[a\\]\\(c\\)", + ), + ) + } + + @Test + fun `should parse spec sample 494 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[link](\\(foo\\))") + + /* + * Expected HTML: + *

link

+ */ + parsed.assertEquals(paragraph("[link](\\(foo\\))")) + } + + @Test + fun `should parse spec sample 495 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[link](foo(and(bar)))") + + /* + * Expected HTML: + *

link

+ */ + parsed.assertEquals(paragraph("[link](foo\\(and\\(bar\\)\\))")) + } + + @Test + fun `should parse spec sample 496 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[link](foo(and(bar))") + + /* + * Expected HTML: + *

[link](foo(and(bar))

+ */ + parsed.assertEquals(paragraph("\\[link\\]\\(foo\\(and\\(bar\\)\\)")) + } + + @Test + fun `should parse spec sample 497 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[link](foo\\(and\\(bar\\))") + + /* + * Expected HTML: + *

link

+ */ + parsed.assertEquals(paragraph("[link](foo\\(and\\(bar\\))")) + } + + @Test + fun `should parse spec sample 498 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[link]()") + + /* + * Expected HTML: + *

link

+ */ + parsed.assertEquals(paragraph("[link](foo\\(and\\(bar\\))")) + } + + @Test + fun `should parse spec sample 499 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[link](foo\\)\\:)") + + /* + * Expected HTML: + *

link

+ */ + parsed.assertEquals(paragraph("[link](foo\\):)")) + } + + @Test + fun `should parse spec sample 500 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[link](#fragment) + | + |[link](http://example.com#fragment) + | + |[link](http://example.com?foo=3#frag) + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

link

+ *

link

+ *

link

+ */ + parsed.assertEquals( + paragraph("[link](#fragment)"), + paragraph("[link](http://example.com#fragment)"), + paragraph("[link](http://example.com?foo=3#frag)"), + ) + } + + @Test + fun `should parse spec sample 501 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[link](foo\\bar)") + + /* + * Expected HTML: + *

link

+ */ + parsed.assertEquals(paragraph("[link](foo\\bar)")) + } + + @Test + fun `should parse spec sample 502 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[link](foo%20bä)") + + /* + * Expected HTML: + *

link

+ */ + parsed.assertEquals(paragraph("[link](foo%20bä)")) + } + + @Test + fun `should parse spec sample 503 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[link](\"title\")") + + /* + * Expected HTML: + *

link

+ */ + parsed.assertEquals(paragraph("[link](\"title\")")) + } + + @Test + fun `should parse spec sample 504 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[link](/url "title") + |[link](/url 'title') + |[link](/url (title)) + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

link + * link + * link

+ */ + parsed.assertEquals( + paragraph("[link](/url \"title\") [link](/url \"title\") [link](/url \"title\")"), + ) + } + + @Test + fun `should parse spec sample 505 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[link](/url \"title \\\""\")") + + /* + * Expected HTML: + *

link

+ */ + parsed.assertEquals(paragraph("[link](/url \"title \\\"\\\"\")")) + } + + @Test + fun `should parse spec sample 506 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[link](/url \"title\")") + + /* + * Expected HTML: + *

link

+ */ + parsed.assertEquals(paragraph("[link]()")) + } + + @Test + fun `should parse spec sample 507 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[link](/url \"title \"and\" title\")") + + /* + * Expected HTML: + *

[link](/url "title "and" title")

+ */ + parsed.assertEquals(paragraph("\\[link\\]\\(/url \"title \"and\" title\"\\)")) + } + + @Test + fun `should parse spec sample 508 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[link](/url 'title \"and\" title')") + + /* + * Expected HTML: + *

link

+ */ + parsed.assertEquals(paragraph("[link](/url \"title \\\"and\\\" title\")")) + } + + @Test + fun `should parse spec sample 509 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[link]( /uri + | "title" ) + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

link

+ */ + parsed.assertEquals(paragraph("[link](/uri \"title\")")) + } + + @Test + fun `should parse spec sample 510 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[link] (/uri)") + + /* + * Expected HTML: + *

[link] (/uri)

+ */ + parsed.assertEquals(paragraph("\\[link\\] \\(/uri\\)")) + } + + @Test + fun `should parse spec sample 511 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[link [foo [bar]]](/uri)") + + /* + * Expected HTML: + *

link [foo [bar]]

+ */ + parsed.assertEquals(paragraph("[link \\[foo \\[bar\\]\\]](/uri)")) + } + + @Test + fun `should parse spec sample 512 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[link] bar](/uri)") + + /* + * Expected HTML: + *

[link] bar](/uri)

+ */ + parsed.assertEquals(paragraph("\\[link\\] bar\\]\\(/uri\\)")) + } + + @Test + fun `should parse spec sample 513 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[link [bar](/uri)") + + /* + * Expected HTML: + *

[link bar

+ */ + parsed.assertEquals(paragraph("\\[link [bar](/uri)")) + } + + @Test + fun `should parse spec sample 514 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[link \\[bar](/uri)") + + /* + * Expected HTML: + *

link [bar

+ */ + parsed.assertEquals(paragraph("[link \\[bar](/uri)")) + } + + @Test + fun `should parse spec sample 515 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[link *foo **bar** `#`*](/uri)") + + /* + * Expected HTML: + *

link foo bar #

+ */ + parsed.assertEquals(paragraph("[link *foo **bar** `#`*](/uri)")) + } + + @Test + fun `should parse spec sample 516 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[![moon](moon.jpg)](/uri)") + + /* + * Expected HTML: + *

moon

+ */ + parsed.assertEquals(paragraph("[![moon](moon.jpg)](/uri)")) + } + + @Test + fun `should parse spec sample 517 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[foo [bar](/uri)](/uri)") + + /* + * Expected HTML: + *

[foo bar](/uri)

+ */ + parsed.assertEquals(paragraph("\\[foo [bar](/uri)\\]\\(/uri\\)")) + } + + @Test + fun `should parse spec sample 518 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[foo *[bar [baz](/uri)](/uri)*](/uri)") + + /* + * Expected HTML: + *

[foo [bar baz](/uri)](/uri)

+ */ + parsed.assertEquals(paragraph("\\[foo *\\[bar [baz](/uri)\\]\\(/uri\\)*\\]\\(/uri\\)")) + } + + @Test + fun `should parse spec sample 519 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("![[[foo](uri1)](uri2)](uri3)") + + /* + * Expected HTML: + *

[foo](uri2)

+ */ + parsed.assertEquals(paragraph("![\\[\"foo\" (uri1)\\]\\(uri2\\)](uri3)")) + } + + @Test + fun `should parse spec sample 520 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("*[foo*](/uri)") + + /* + * Expected HTML: + *

*foo*

+ */ + parsed.assertEquals(paragraph("\\*[foo\\*](/uri)")) + } + + @Test + fun `should parse spec sample 521 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[foo *bar](baz*)") + + /* + * Expected HTML: + *

foo *bar

+ */ + parsed.assertEquals(paragraph("[foo \\*bar](baz*)")) + } + + @Test + fun `should parse spec sample 522 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("*foo [bar* baz]") + + /* + * Expected HTML: + *

foo [bar baz]

+ */ + parsed.assertEquals(paragraph("*foo \\[bar* baz\\]")) + } + + @Test + fun `should parse spec sample 523 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[foo ") + + /* + * Expected HTML: + *

[foo

+ */ + parsed.assertEquals(paragraph("\\[foo ")) + } + + @Test + fun `should parse spec sample 524 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[foo`](/uri)`") + + /* + * Expected HTML: + *

[foo](/uri)

+ */ + parsed.assertEquals(paragraph("\\[foo`](/uri)`")) + } + + @Test + fun `should parse spec sample 525 correctly (Links)`() { + val parsed = processor.processMarkdownDocument("[foo") + + /* + * Expected HTML: + *

[foohttp://example.com/?search=](uri)

+ */ + parsed.assertEquals( + paragraph( + "\\[foo[http://example.com/?search=\\]\\(uri\\)](http://example.com/?search=]\\(uri\\))", + ), + ) + } + + @Test + fun `should parse spec sample 526 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo][bar] + | + |[bar]: /url "title" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("[foo](/url \"title\")")) + } + + @Test + fun `should parse spec sample 527 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[link [foo [bar]]][ref] + | + |[ref]: /uri + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

link [foo [bar]]

+ */ + parsed.assertEquals(paragraph("[link \\[foo \\[bar\\]\\]](/uri)")) + } + + @Test + fun `should parse spec sample 528 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[link \[bar][ref] + | + |[ref]: /uri + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

link [bar

+ */ + parsed.assertEquals(paragraph("[link \\[bar](/uri)")) + } + + @Test + fun `should parse spec sample 529 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[link *foo **bar** `#`*][ref] + | + |[ref]: /uri + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

link foo bar #

+ */ + parsed.assertEquals(paragraph("[link *foo **bar** `#`*](/uri)")) + } + + @Test + fun `should parse spec sample 530 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[![moon](moon.jpg)][ref] + | + |[ref]: /uri + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

moon

+ */ + parsed.assertEquals(paragraph("[![moon](moon.jpg)](/uri)")) + } + + @Test + fun `should parse spec sample 531 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo [bar](/uri)][ref] + | + |[ref]: /uri + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

[foo bar]ref

+ */ + parsed.assertEquals(paragraph("\\[foo [bar](/uri)\\][ref](/uri)")) + } + + @Test + fun `should parse spec sample 532 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo *bar [baz][ref]*][ref] + | + |[ref]: /uri + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

[foo bar baz]ref

+ */ + parsed.assertEquals(paragraph("\\[foo *bar [baz](/uri)*\\][ref](/uri)")) + } + + @Test + fun `should parse spec sample 533 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |*[foo*][ref] + | + |[ref]: /uri + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

*foo*

+ */ + parsed.assertEquals(paragraph("\\*[foo\\*](/uri)")) + } + + @Test + fun `should parse spec sample 534 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo *bar][ref]* + | + |[ref]: /uri + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo *bar*

+ */ + parsed.assertEquals(paragraph("[foo \\*bar](/uri)\\*")) + } + + @Test + fun `should parse spec sample 535 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo + | + |[ref]: /uri + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

[foo

+ */ + parsed.assertEquals(paragraph("\\[foo ")) + } + + @Test + fun `should parse spec sample 536 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo`][ref]` + | + |[ref]: /uri + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

[foo][ref]

+ */ + parsed.assertEquals(paragraph("\\[foo`][ref]`")) + } + + @Test + fun `should parse spec sample 537 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo + | + |[ref]: /uri + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

[foohttp://example.com/?search=][ref]

+ */ + parsed.assertEquals( + paragraph( + "\\[foo[http://example.com/?search=\\]\\[ref\\]](http://example.com/?search=][ref])", + ), + ) + } + + @Test + fun `should parse spec sample 538 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo][BaR] + | + |[bar]: /url "title" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("[foo](/url \"title\")")) + } + + @Test + fun `should parse spec sample 539 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[ẞ] + | + |[SS]: /url + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

+ */ + parsed.assertEquals(paragraph("[ẞ](/url)")) + } + + @Test + fun `should parse spec sample 540 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[Foo + | bar]: /url + | + |[Baz][Foo bar] + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Baz

+ */ + parsed.assertEquals(paragraph("[Baz](/url)")) + } + + @Test + fun `should parse spec sample 541 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo] [bar] + | + |[bar]: /url "title" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

[foo] bar

+ */ + parsed.assertEquals(paragraph("\\[foo\\] [bar](/url \"title\")")) + } + + @Test + fun `should parse spec sample 542 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo] + |[bar] + | + |[bar]: /url "title" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

[foo] + * bar

+ */ + parsed.assertEquals(paragraph("\\[foo\\] [bar](/url \"title\")")) + } + + @Test + fun `should parse spec sample 543 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo]: /url1 + | + |[foo]: /url2 + | + |[bar][foo] + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

bar

+ */ + parsed.assertEquals(paragraph("[bar](/url1)")) + } + + @Test + fun `should parse spec sample 544 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[bar][foo\!] + | + |[foo!]: /url + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

[bar][foo!]

+ */ + parsed.assertEquals(paragraph("\\[bar\\]\\[foo\\!\\]")) + } + + @Test + fun `should parse spec sample 545 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo][ref[] + | + |[ref[]: /uri + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

[foo][ref[]

+ *

[ref[]: /uri

+ */ + parsed.assertEquals( + paragraph("\\[foo\\]\\[ref\\[\\]"), + paragraph("\\[ref\\[\\]: /uri"), + ) + } + + @Test + fun `should parse spec sample 546 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo][ref[bar]] + | + |[ref[bar]]: /uri + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

[foo][ref[bar]]

+ *

[ref[bar]]: /uri

+ */ + parsed.assertEquals( + paragraph("\\[foo\\]\\[ref\\[bar\\]\\]"), + paragraph("\\[ref\\[bar\\]\\]: /uri"), + ) + } + + @Test + fun `should parse spec sample 547 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[[[foo]]] + | + |[[[foo]]]: /url + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

[[[foo]]]

+ *

[[[foo]]]: /url

+ */ + parsed.assertEquals( + paragraph("\\[\\[\\[foo\\]\\]\\]"), + paragraph("\\[\\[\\[foo\\]\\]\\]: /url"), + ) + } + + @Test + fun `should parse spec sample 548 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo][ref\[] + | + |[ref\[]: /uri + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("[foo](/uri)")) + } + + @Test + fun `should parse spec sample 549 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[bar\\]: /uri + | + |[bar\\] + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

bar\

+ */ + parsed.assertEquals(paragraph("[bar\\\\](/uri)")) + } + + @Test + fun `should parse spec sample 550 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[] + | + |[]: /uri + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

[]

+ *

[]: /uri

+ */ + parsed.assertEquals( + paragraph("\\[\\]"), + paragraph("\\[\\]: /uri"), + ) + } + + @Test + fun `should parse spec sample 551 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[ + | ] + | + |[ + | ]: /uri + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

[ + * ]

+ *

[ + * ]: /uri

+ */ + parsed.assertEquals( + paragraph("\\[ \\]"), + paragraph("\\[ \\]: /uri"), + ) + } + + @Test + fun `should parse spec sample 552 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo][] + | + |[foo]: /url "title" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("[foo](/url \"title\")")) + } + + @Test + fun `should parse spec sample 553 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[*foo* bar][] + | + |[*foo* bar]: /url "title" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("[*foo* bar](/url \"title\")")) + } + + @Test + fun `should parse spec sample 554 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[Foo][] + | + |[foo]: /url "title" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo

+ */ + parsed.assertEquals(paragraph("[Foo](/url \"title\")")) + } + + @Test + fun `should parse spec sample 555 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo] + |[] + | + |[foo]: /url "title" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo + * []

+ */ + parsed.assertEquals(paragraph("[foo](/url \"title\") \\[\\]")) + } + + @Test + fun `should parse spec sample 556 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo] + | + |[foo]: /url "title" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("[foo](/url \"title\")")) + } + + @Test + fun `should parse spec sample 557 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[*foo* bar] + | + |[*foo* bar]: /url "title" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("[*foo* bar](/url \"title\")")) + } + + @Test + fun `should parse spec sample 558 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[[*foo* bar]] + | + |[*foo* bar]: /url "title" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

[foo bar]

+ */ + parsed.assertEquals(paragraph("\\[[*foo* bar](/url \"title\")\\]")) + } + + @Test + fun `should parse spec sample 559 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[[bar [foo] + | + |[foo]: /url + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

[[bar foo

+ */ + parsed.assertEquals(paragraph("\\[\\[bar [foo](/url)")) + } + + @Test + fun `should parse spec sample 560 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[Foo] + | + |[foo]: /url "title" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo

+ */ + parsed.assertEquals(paragraph("[Foo](/url \"title\")")) + } + + @Test + fun `should parse spec sample 561 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo] bar + | + |[foo]: /url + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("[foo](/url) bar")) + } + + @Test + fun `should parse spec sample 562 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |\[foo] + | + |[foo]: /url "title" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

[foo]

+ */ + parsed.assertEquals(paragraph("\\[foo\\]")) + } + + @Test + fun `should parse spec sample 563 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo*]: /url + | + |*[foo*] + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

*foo*

+ */ + parsed.assertEquals(paragraph("\\*[foo\\*](/url)")) + } + + @Test + fun `should parse spec sample 564 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo][bar] + | + |[foo]: /url1 + |[bar]: /url2 + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("[foo](/url2)")) + } + + @Test + fun `should parse spec sample 565 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo][] + | + |[foo]: /url1 + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("[foo](/url1)")) + } + + @Test + fun `should parse spec sample 566 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo]() + | + |[foo]: /url1 + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("[foo]()")) + } + + @Test + fun `should parse spec sample 567 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo](not a link) + | + |[foo]: /url1 + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo(not a link)

+ */ + parsed.assertEquals(paragraph("[foo](/url1)\\(not a link\\)")) + } + + @Test + fun `should parse spec sample 568 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo][bar][baz] + | + |[baz]: /url + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

[foo]bar

+ */ + parsed.assertEquals(paragraph("\\[foo\\][bar](/url)")) + } + + @Test + fun `should parse spec sample 569 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo][bar][baz] + | + |[baz]: /url1 + |[bar]: /url2 + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foobaz

+ */ + parsed.assertEquals(paragraph("[foo](/url2)[baz](/url1)")) + } + + @Test + fun `should parse spec sample 570 correctly (Links)`() { + val parsed = + processor.processMarkdownDocument( + """ + |[foo][bar][baz] + | + |[baz]: /url1 + |[foo]: /url2 + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

[foo]bar

+ */ + parsed.assertEquals(paragraph("\\[foo\\][bar](/url1)")) + } + + @Test + fun `should parse spec sample 571 correctly (Images)`() { + val parsed = processor.processMarkdownDocument("![foo](/url \"title\")") + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("![foo](/url \"title\")")) + } + + @Test + fun `should parse spec sample 572 correctly (Images)`() { + val parsed = + processor.processMarkdownDocument( + """ + |![foo *bar*] + | + |[foo *bar*]: train.jpg "train & tracks" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("![foo *bar*](train.jpg \"train & tracks\")")) + } + + @Test + fun `should parse spec sample 573 correctly (Images)`() { + val parsed = processor.processMarkdownDocument("![foo ![bar](/url)](/url2)") + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("![foo ![bar](/url)](/url2)")) + } + + @Test + fun `should parse spec sample 574 correctly (Images)`() { + val parsed = processor.processMarkdownDocument("![foo [bar](/url)](/url2)") + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("![foo \"bar\" (/url)](/url2)")) + } + + @Test + fun `should parse spec sample 575 correctly (Images)`() { + val parsed = + processor.processMarkdownDocument( + """ + |![foo *bar*][] + | + |[foo *bar*]: train.jpg "train & tracks" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("![foo *bar*](train.jpg \"train & tracks\")")) + } + + @Test + fun `should parse spec sample 576 correctly (Images)`() { + val parsed = + processor.processMarkdownDocument( + """ + |![foo *bar*][foobar] + | + |[FOOBAR]: train.jpg "train & tracks" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("![foo *bar*](train.jpg \"train & tracks\")")) + } + + @Test + fun `should parse spec sample 577 correctly (Images)`() { + val parsed = processor.processMarkdownDocument("![foo](train.jpg)") + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("![foo](train.jpg)")) + } + + @Test + fun `should parse spec sample 578 correctly (Images)`() { + val parsed = processor.processMarkdownDocument("My ![foo bar](/path/to/train.jpg \"title\" )") + + /* + * Expected HTML: + *

My foo bar

+ */ + parsed.assertEquals(paragraph("My ![foo bar](/path/to/train.jpg \"title\")")) + } + + @Test + fun `should parse spec sample 579 correctly (Images)`() { + val parsed = processor.processMarkdownDocument("![foo]()") + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("![foo](url)")) + } + + @Test + fun `should parse spec sample 580 correctly (Images)`() { + val parsed = processor.processMarkdownDocument("![](/url)") + + /* + * Expected HTML: + *

+ */ + parsed.assertEquals(paragraph("![](/url)")) + } + + @Test + fun `should parse spec sample 581 correctly (Images)`() { + val parsed = + processor.processMarkdownDocument( + """ + |![foo][bar] + | + |[bar]: /url + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("![foo](/url)")) + } + + @Test + fun `should parse spec sample 582 correctly (Images)`() { + val parsed = + processor.processMarkdownDocument( + """ + |![foo][bar] + | + |[BAR]: /url + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("![foo](/url)")) + } + + @Test + fun `should parse spec sample 583 correctly (Images)`() { + val parsed = + processor.processMarkdownDocument( + """ + |![foo][] + | + |[foo]: /url "title" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("![foo](/url \"title\")")) + } + + @Test + fun `should parse spec sample 584 correctly (Images)`() { + val parsed = + processor.processMarkdownDocument( + """ + |![*foo* bar][] + | + |[*foo* bar]: /url "title" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("![*foo* bar](/url \"title\")")) + } + + @Test + fun `should parse spec sample 585 correctly (Images)`() { + val parsed = + processor.processMarkdownDocument( + """ + |![Foo][] + | + |[foo]: /url "title" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo

+ */ + parsed.assertEquals(paragraph("![Foo](/url \"title\")")) + } + + @Test + fun `should parse spec sample 586 correctly (Images)`() { + val parsed = + processor.processMarkdownDocument( + """ + |![foo] + |[] + | + |[foo]: /url "title" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo + * []

+ */ + parsed.assertEquals(paragraph("![foo](/url \"title\") \\[\\]")) + } + + @Test + fun `should parse spec sample 587 correctly (Images)`() { + val parsed = + processor.processMarkdownDocument( + """ + |![foo] + | + |[foo]: /url "title" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("![foo](/url \"title\")")) + } + + @Test + fun `should parse spec sample 588 correctly (Images)`() { + val parsed = + processor.processMarkdownDocument( + """ + |![*foo* bar] + | + |[*foo* bar]: /url "title" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo bar

+ */ + parsed.assertEquals(paragraph("![*foo* bar](/url \"title\")")) + } + + @Test + fun `should parse spec sample 589 correctly (Images)`() { + val parsed = + processor.processMarkdownDocument( + """ + |![[foo]] + | + |[[foo]]: /url "title" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

![[foo]]

+ *

[[foo]]: /url "title"

+ */ + parsed.assertEquals( + paragraph("\\!\\[\\[foo\\]\\]"), + paragraph("\\[\\[foo\\]\\]: /url \"title\""), + ) + } + + @Test + fun `should parse spec sample 590 correctly (Images)`() { + val parsed = + processor.processMarkdownDocument( + """ + |![Foo] + | + |[foo]: /url "title" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

Foo

+ */ + parsed.assertEquals(paragraph("![Foo](/url \"title\")")) + } + + @Test + fun `should parse spec sample 591 correctly (Images)`() { + val parsed = + processor.processMarkdownDocument( + """ + |!\[foo] + | + |[foo]: /url "title" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

![foo]

+ */ + parsed.assertEquals(paragraph("\\!\\[foo\\]")) + } + + @Test + fun `should parse spec sample 592 correctly (Images)`() { + val parsed = + processor.processMarkdownDocument( + """ + |\![foo] + | + |[foo]: /url "title" + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

!foo

+ */ + parsed.assertEquals(paragraph("\\![foo](/url \"title\")")) + } + + @Test + fun `should parse spec sample 593 correctly (Autolinks)`() { + val parsed = processor.processMarkdownDocument("") + + /* + * Expected HTML: + *

http://foo.bar.baz

+ */ + parsed.assertEquals(paragraph("[http://foo.bar.baz](http://foo.bar.baz)")) + } + + @Test + fun `should parse spec sample 594 correctly (Autolinks)`() { + val parsed = processor.processMarkdownDocument("") + + /* + * Expected HTML: + *

http://foo.bar.baz/test?q=hello&id=22&boolean

+ */ + parsed.assertEquals( + paragraph( + "[http://foo.bar.baz/test?q=hello&id=22&boolean](http://foo.bar.baz/test?q=hello&id=22&boolean)", + ), + ) + } + + @Test + fun `should parse spec sample 595 correctly (Autolinks)`() { + val parsed = processor.processMarkdownDocument("") + + /* + * Expected HTML: + *

irc://foo.bar:2233/baz

+ */ + parsed.assertEquals(paragraph("[irc://foo.bar:2233/baz](irc://foo.bar:2233/baz)")) + } + + @Test + fun `should parse spec sample 596 correctly (Autolinks)`() { + val parsed = processor.processMarkdownDocument("") + + /* + * Expected HTML: + *

MAILTO:FOO@BAR.BAZ

+ */ + parsed.assertEquals(paragraph("[MAILTO:FOO@BAR.BAZ](MAILTO:FOO@BAR.BAZ)")) + } + + @Test + fun `should parse spec sample 597 correctly (Autolinks)`() { + val parsed = processor.processMarkdownDocument("") + + /* + * Expected HTML: + *

a+b+c:d

+ */ + parsed.assertEquals(paragraph("[a+b+c:d](a+b+c:d)")) + } + + @Test + fun `should parse spec sample 598 correctly (Autolinks)`() { + val parsed = processor.processMarkdownDocument("") + + /* + * Expected HTML: + *

made-up-scheme://foo,bar

+ */ + parsed.assertEquals(paragraph("[made-up-scheme://foo,bar](made-up-scheme://foo,bar)")) + } + + @Test + fun `should parse spec sample 599 correctly (Autolinks)`() { + val parsed = processor.processMarkdownDocument("") + + /* + * Expected HTML: + *

http://../

+ */ + parsed.assertEquals(paragraph("[http://../](http://../)")) + } + + @Test + fun `should parse spec sample 600 correctly (Autolinks)`() { + val parsed = processor.processMarkdownDocument("") + + /* + * Expected HTML: + *

localhost:5001/foo

+ */ + parsed.assertEquals(paragraph("[localhost:5001/foo](localhost:5001/foo)")) + } + + @Test + fun `should parse spec sample 601 correctly (Autolinks)`() { + val parsed = processor.processMarkdownDocument("") + + /* + * Expected HTML: + *

<http://foo.bar/baz bim>

+ */ + parsed.assertEquals(paragraph("\\")) + } + + @Test + fun `should parse spec sample 602 correctly (Autolinks)`() { + val parsed = processor.processMarkdownDocument("") + + /* + * Expected HTML: + *

http://example.com/\[\

+ */ + parsed.assertEquals(paragraph("[http://example.com/\\[\\\\](http://example.com/\\[\\)")) + } + + @Test + fun `should parse spec sample 603 correctly (Autolinks)`() { + val parsed = processor.processMarkdownDocument("") + + /* + * Expected HTML: + *

foo@bar.example.com

+ */ + parsed.assertEquals(paragraph("[foo@bar.example.com](mailto:foo@bar.example.com)")) + } + + @Test + fun `should parse spec sample 604 correctly (Autolinks)`() { + val parsed = processor.processMarkdownDocument("") + + /* + * Expected HTML: + *

foo+special@Bar.baz-bar0.com

+ */ + parsed.assertEquals( + paragraph( + "[foo+special@Bar.baz-bar0.com](mailto:foo+special@Bar.baz-bar0.com)", + ), + ) + } + + @Test + fun `should parse spec sample 605 correctly (Autolinks)`() { + val parsed = processor.processMarkdownDocument("") + + /* + * Expected HTML: + *

<foo+@bar.example.com>

+ */ + parsed.assertEquals(paragraph("\\")) + } + + @Test + fun `should parse spec sample 606 correctly (Autolinks)`() { + val parsed = processor.processMarkdownDocument("<>") + + /* + * Expected HTML: + *

<>

+ */ + parsed.assertEquals(paragraph("\\<\\>")) + } + + @Test + fun `should parse spec sample 607 correctly (Autolinks)`() { + val parsed = processor.processMarkdownDocument("< http://foo.bar >") + + /* + * Expected HTML: + *

< http://foo.bar >

+ */ + parsed.assertEquals(paragraph("\\< http://foo.bar \\>")) + } + + @Test + fun `should parse spec sample 608 correctly (Autolinks)`() { + val parsed = processor.processMarkdownDocument("") + + /* + * Expected HTML: + *

<m:abc>

+ */ + parsed.assertEquals(paragraph("\\")) + } + + @Test + fun `should parse spec sample 609 correctly (Autolinks)`() { + val parsed = processor.processMarkdownDocument("") + + /* + * Expected HTML: + *

<foo.bar.baz>

+ */ + parsed.assertEquals(paragraph("\\")) + } + + @Test + fun `should parse spec sample 610 correctly (Autolinks)`() { + val parsed = processor.processMarkdownDocument("http://example.com") + + /* + * Expected HTML: + *

http://example.com

+ */ + parsed.assertEquals(paragraph("http://example.com")) + } + + @Test + fun `should parse spec sample 611 correctly (Autolinks)`() { + val parsed = processor.processMarkdownDocument("foo@bar.example.com") + + /* + * Expected HTML: + *

foo@bar.example.com

+ */ + parsed.assertEquals(paragraph("foo@bar.example.com")) + } + + @Test + fun `should parse spec sample 612 correctly (Raw HTML)`() { + val parsed = processor.processMarkdownDocument("") + + /* + * Expected HTML: + *

+ */ + parsed.assertEquals(paragraph("")) + } + + @Test + fun `should parse spec sample 613 correctly (Raw HTML)`() { + val parsed = processor.processMarkdownDocument("") + + /* + * Expected HTML: + *

+ */ + parsed.assertEquals(paragraph("")) + } + + @Test + fun `should parse spec sample 614 correctly (Raw HTML)`() { + val parsed = + processor.processMarkdownDocument( + """ + | + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

+ */ + parsed.assertEquals(paragraph("")) + } + + @Test + fun `should parse spec sample 615 correctly (Raw HTML)`() { + val parsed = + processor.processMarkdownDocument( + """ + | + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

+ */ + parsed.assertEquals( + paragraph( + "", + ), + ) + } + + @Test + fun `should parse spec sample 616 correctly (Raw HTML)`() { + val parsed = processor.processMarkdownDocument("Foo ") + + /* + * Expected HTML: + *

Foo

+ */ + parsed.assertEquals(paragraph("Foo ")) + } + + @Test + fun `should parse spec sample 617 correctly (Raw HTML)`() { + val parsed = processor.processMarkdownDocument("<33> <__>") + + /* + * Expected HTML: + *

<33> <__>

+ */ + parsed.assertEquals(paragraph("\\<33\\> \\<\\_\\_\\>")) + } + + @Test + fun `should parse spec sample 618 correctly (Raw HTML)`() { + val parsed = processor.processMarkdownDocument("
") + + /* + * Expected HTML: + *

<a h*#ref="hi">

+ */ + parsed.assertEquals(paragraph("\\
")) + } + + @Test + fun `should parse spec sample 619 correctly (Raw HTML)`() { + val parsed = processor.processMarkdownDocument(" ") + + /* + * Expected HTML: + *

<a href="hi'> <a href=hi'>

+ */ + parsed.assertEquals(paragraph("\\
\\")) + } + + @Test + fun `should parse spec sample 620 correctly (Raw HTML)`() { + val parsed = + processor.processMarkdownDocument( + """ + |< a>< + |foo> + | + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

< a>< + * foo><bar/ > + * <foo bar=baz + * bim!bop />

+ */ + parsed.assertEquals( + paragraph( + "\\< a\\>\\< foo\\>\\ \\", + ), + ) + } + + @Test + fun `should parse spec sample 621 correctly (Raw HTML)`() { + val parsed = processor.processMarkdownDocument("
") + + /* + * Expected HTML: + *

<a href='bar'title=title>

+ */ + parsed.assertEquals(paragraph("\\
")) + } + + @Test + fun `should parse spec sample 622 correctly (Raw HTML)`() { + val parsed = processor.processMarkdownDocument("
") + + /* + * Expected HTML: + *

+ */ + parsed.assertEquals(paragraph("")) + } + + @Test + fun `should parse spec sample 623 correctly (Raw HTML)`() { + val parsed = processor.processMarkdownDocument("") + + /* + * Expected HTML: + *

</a href="foo">

+ */ + parsed.assertEquals(paragraph("\\")) + } + + @Test + fun `should parse spec sample 624 correctly (Raw HTML)`() { + val parsed = + processor.processMarkdownDocument( + """ + |foo + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo ")) + } + + @Test + fun `should parse spec sample 625 correctly (Raw HTML)`() { + val parsed = processor.processMarkdownDocument("foo ") + + /* + * Expected HTML: + *

foo <!-- not a comment -- two hyphens -->

+ */ + parsed.assertEquals(paragraph("foo \\<\\!-- not a comment -- two hyphens --\\>")) + } + + @Test + fun `should parse spec sample 626 correctly (Raw HTML)`() { + val parsed = + processor.processMarkdownDocument( + """ + |foo foo --> + | + |foo + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo <!--> foo -->

+ *

foo <!-- foo--->

+ */ + parsed.assertEquals( + paragraph("foo \\<\\!--\\> foo --\\>"), + paragraph("foo \\<\\!-- foo---\\>"), + ) + } + + @Test + fun `should parse spec sample 627 correctly (Raw HTML)`() { + val parsed = processor.processMarkdownDocument("foo ") + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("foo ")) + } + + @Test + fun `should parse spec sample 628 correctly (Raw HTML)`() { + val parsed = processor.processMarkdownDocument("foo ") + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("foo ")) + } + + @Test + fun `should parse spec sample 629 correctly (Raw HTML)`() { + val parsed = processor.processMarkdownDocument("foo &<]]>") + + /* + * Expected HTML: + *

foo &<]]>

+ */ + parsed.assertEquals(paragraph("foo &<]]>")) + } + + @Test + fun `should parse spec sample 630 correctly (Raw HTML)`() { + val parsed = processor.processMarkdownDocument("foo ") + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("foo ")) + } + + @Test + fun `should parse spec sample 631 correctly (Raw HTML)`() { + val parsed = processor.processMarkdownDocument("foo ") + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("foo ")) + } + + @Test + fun `should parse spec sample 632 correctly (Raw HTML)`() { + val parsed = processor.processMarkdownDocument("") + + /* + * Expected HTML: + *

<a href=""">

+ */ + parsed.assertEquals(paragraph("\\
")) + } + + @Test + fun `should parse spec sample 633 correctly (Hard line breaks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |foo + |baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo
+ * baz

+ */ + parsed.assertEquals(paragraph("foo\nbaz")) + } + + @Test + fun `should parse spec sample 634 correctly (Hard line breaks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |foo\ + |baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo
+ * baz

+ */ + parsed.assertEquals(paragraph("foo\nbaz")) + } + + @Test + fun `should parse spec sample 635 correctly (Hard line breaks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |foo + |baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo
+ * baz

+ */ + parsed.assertEquals(paragraph("foo\nbaz")) + } + + @Test + fun `should parse spec sample 636 correctly (Hard line breaks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |foo + | bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo
+ * bar

+ */ + parsed.assertEquals(paragraph("foo\nbar")) + } + + @Test + fun `should parse spec sample 637 correctly (Hard line breaks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |foo\ + | bar + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo
+ * bar

+ */ + parsed.assertEquals(paragraph("foo\nbar")) + } + + @Test + fun `should parse spec sample 638 correctly (Hard line breaks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |*foo + |bar* + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo
+ * bar

+ */ + parsed.assertEquals(paragraph("*foo\nbar*")) + } + + @Test + fun `should parse spec sample 639 correctly (Hard line breaks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |*foo\ + |bar* + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo
+ * bar

+ */ + parsed.assertEquals(paragraph("*foo\nbar*")) + } + + @Test + fun `should parse spec sample 640 correctly (Hard line breaks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |`code + |span` + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

code span

+ */ + parsed.assertEquals(paragraph("`code span`")) + } + + @Test + fun `should parse spec sample 641 correctly (Hard line breaks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |`code\ + |span` + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

code\ span

+ */ + parsed.assertEquals(paragraph("`code\\ span`")) + } + + @Test + fun `should parse spec sample 642 correctly (Hard line breaks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |
+ """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

+ */ + parsed.assertEquals(paragraph("")) + } + + @Test + fun `should parse spec sample 643 correctly (Hard line breaks)`() { + val parsed = + processor.processMarkdownDocument( + """ + | + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

+ */ + parsed.assertEquals(paragraph("")) + } + + @Test + fun `should parse spec sample 644 correctly (Hard line breaks)`() { + val parsed = processor.processMarkdownDocument("foo\\") + + /* + * Expected HTML: + *

foo\

+ */ + parsed.assertEquals(paragraph("foo\\")) + } + + @Test + fun `should parse spec sample 645 correctly (Hard line breaks)`() { + val parsed = processor.processMarkdownDocument("foo ") + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(paragraph("foo")) + } + + @Test + fun `should parse spec sample 646 correctly (Hard line breaks)`() { + val parsed = processor.processMarkdownDocument("### foo\\") + + /* + * Expected HTML: + *

foo\

+ */ + parsed.assertEquals(heading(level = 3, "foo\\")) + } + + @Test + fun `should parse spec sample 647 correctly (Hard line breaks)`() { + val parsed = processor.processMarkdownDocument("### foo ") + + /* + * Expected HTML: + *

foo

+ */ + parsed.assertEquals(heading(level = 3, "foo")) + } + + @Test + fun `should parse spec sample 648 correctly (Soft line breaks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |foo + |baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo + * baz

+ */ + parsed.assertEquals(paragraph("foo baz")) + } + + @Test + fun `should parse spec sample 649 correctly (Soft line breaks)`() { + val parsed = + processor.processMarkdownDocument( + """ + |foo + | baz + """ + .trimMargin(), + ) + + /* + * Expected HTML: + *

foo + * baz

+ */ + parsed.assertEquals(paragraph("foo baz")) + } + + @Test + fun `should parse spec sample 650 correctly (Textual content)`() { + val parsed = processor.processMarkdownDocument("hello $.;'there") + + /* + * Expected HTML: + *

hello $.;'there

+ */ + parsed.assertEquals(paragraph("hello \$.;'there")) + } + + @Test + fun `should parse spec sample 651 correctly (Textual content)`() { + val parsed = processor.processMarkdownDocument("Foo χρῆν") + + /* + * Expected HTML: + *

Foo χρῆν

+ */ + parsed.assertEquals(paragraph("Foo χρῆν")) + } + + @Test + fun `should parse spec sample 652 correctly (Textual content)`() { + val parsed = processor.processMarkdownDocument("Multiple spaces") + + /* + * Expected HTML: + *

Multiple spaces

+ */ + parsed.assertEquals(paragraph("Multiple spaces")) + } +} diff --git a/markdown/core/src/test/kotlin/org/jetbrains/jewel/markdown/TestUtils.kt b/markdown/core/src/test/kotlin/org/jetbrains/jewel/markdown/TestUtils.kt new file mode 100644 index 000000000..2827bcbd8 --- /dev/null +++ b/markdown/core/src/test/kotlin/org/jetbrains/jewel/markdown/TestUtils.kt @@ -0,0 +1,243 @@ +package org.jetbrains.jewel.markdown + +import org.intellij.lang.annotations.Language +import org.jetbrains.jewel.markdown.MarkdownBlock.BlockQuote +import org.jetbrains.jewel.markdown.MarkdownBlock.CodeBlock +import org.jetbrains.jewel.markdown.MarkdownBlock.CodeBlock.FencedCodeBlock +import org.jetbrains.jewel.markdown.MarkdownBlock.CodeBlock.IndentedCodeBlock +import org.jetbrains.jewel.markdown.MarkdownBlock.Heading +import org.jetbrains.jewel.markdown.MarkdownBlock.Heading.H1 +import org.jetbrains.jewel.markdown.MarkdownBlock.Heading.H2 +import org.jetbrains.jewel.markdown.MarkdownBlock.Heading.H3 +import org.jetbrains.jewel.markdown.MarkdownBlock.Heading.H4 +import org.jetbrains.jewel.markdown.MarkdownBlock.Heading.H5 +import org.jetbrains.jewel.markdown.MarkdownBlock.Heading.H6 +import org.jetbrains.jewel.markdown.MarkdownBlock.HtmlBlock +import org.jetbrains.jewel.markdown.MarkdownBlock.Image +import org.jetbrains.jewel.markdown.MarkdownBlock.ListBlock +import org.jetbrains.jewel.markdown.MarkdownBlock.ListBlock.OrderedList +import org.jetbrains.jewel.markdown.MarkdownBlock.ListBlock.UnorderedList +import org.jetbrains.jewel.markdown.MarkdownBlock.ListItem +import org.jetbrains.jewel.markdown.MarkdownBlock.Paragraph +import org.jetbrains.jewel.markdown.MarkdownBlock.ThematicBreak +import org.junit.Assert + +fun List.assertEquals(vararg expected: MarkdownBlock) { + val differences = findDifferences(expected.toList(), indentSize = 0) + Assert.assertTrue( + "The following differences were found:\n\n" + + "${differences.joinToString("\n").replace('\t', '→')}\n\n", + differences.isEmpty(), + ) +} + +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", + ) + } + } +} + +private fun MarkdownBlock.findDifferenceWith( + expected: MarkdownBlock, + indentSize: Int, +): List { + val indent = " ".repeat(indentSize) + if (this.javaClass != expected.javaClass) { + return listOf( + "$indent * Block type mismatch.\n\n" + + "$indent Actual: ${javaClass.name}\n" + + "$indent Expected: ${expected.javaClass.name}\n", + ) + } + + return when (this) { + is Paragraph -> diffParagraph(this, expected, indent) + is BlockQuote -> content.findDifferences((expected as BlockQuote).content, indentSize) + is HtmlBlock -> diffHtmlBlock(this, expected, indent) + is FencedCodeBlock -> diffFencedCodeBlock(this, expected, indent) + is IndentedCodeBlock -> diffIndentedCodeBlock(this, expected, indent) + is Heading -> diffHeading(this, expected, indent) + is Image -> diffImage(this, expected, indent) + is ListBlock -> diffList(this, expected, indentSize, indent) + is ListItem -> content.findDifferences((expected as ListItem).content, indentSize) + is ThematicBreak -> emptyList() // They can only differ in their node + else -> error("Unsupported MarkdownBlock: ${this.javaClass.name}") + } +} + +private fun diffParagraph(actual: Paragraph, expected: MarkdownBlock, indent: String) = buildList { + if (actual.inlineContent != (expected as Paragraph).inlineContent) { + add( + "$indent * Paragraph raw content mismatch.\n\n" + + "$indent Actual: ${actual.inlineContent}\n" + + "$indent Expected: ${expected.inlineContent}\n", + ) + } +} + +private fun diffHtmlBlock(actual: HtmlBlock, expected: MarkdownBlock, indent: String) = buildList { + if (actual.content != (expected as HtmlBlock).content) { + add( + "$indent * HTML block content mismatch.\n\n" + + "$indent Actual: ${actual.content}\n" + + "$indent Expected: ${expected.content}\n", + ) + } +} + +private fun diffFencedCodeBlock(actual: FencedCodeBlock, expected: MarkdownBlock, indent: String) = + buildList { + if (actual.mimeType != (expected as FencedCodeBlock).mimeType) { + add( + "$indent * Fenced code block mime type mismatch.\n\n" + + "$indent Actual: ${actual.mimeType}\n" + + "$indent Expected: ${expected.mimeType}", + ) + } + + if (actual.content != expected.content) { + add( + "$indent * Fenced code block content mismatch.\n\n" + + "$indent Actual: ${actual.content}\n" + + "$indent Expected: ${expected.content}", + ) + } + } + +private fun diffIndentedCodeBlock(actual: CodeBlock, expected: MarkdownBlock, indent: String) = + buildList { + if (actual.content != (expected as IndentedCodeBlock).content) { + add( + "$indent * Indented code block content mismatch.\n\n" + + "$indent Actual: ${actual.content}\n" + + "$indent Expected: ${expected.content}", + ) + } + } + +private fun diffHeading(actual: Heading, expected: MarkdownBlock, indent: String) = buildList { + if (actual.inlineContent != (expected as Heading).inlineContent) { + add( + "$indent * Heading raw content mismatch.\n\n" + + "$indent Actual: ${actual.inlineContent}\n" + + "$indent Expected: ${expected.inlineContent}", + ) + } +} + +private fun diffImage(actual: Image, expected: MarkdownBlock, indent: String) = buildList { + if (actual.url != (expected as Image).url) { + add( + "$indent * Image URL mismatch.\n\n" + + "$indent Actual: ${actual.url}\n" + + "$indent Expected: ${expected.url}", + ) + } + + if (actual.altString != expected.altString) { + add( + "$indent * Image alt string mismatch.\n\n" + + "$indent Actual: ${actual.altString}\n" + + "$indent Expected: ${expected.altString}", + ) + } +} + +private fun diffList(actual: ListBlock, expected: MarkdownBlock, indentSize: Int, indent: String) = + buildList { + addAll(actual.items.findDifferences((expected as ListBlock).items, indentSize)) + + if (actual.isTight != expected.isTight) { + add( + "$indent * List isTight mismatch.\n\n" + + "$indent Actual: ${actual.isTight}\n" + + "$indent Expected: ${expected.isTight}", + ) + } + + when (actual) { + is OrderedList -> { + if (actual.startFrom != (expected as OrderedList).startFrom) { + add( + "$indent * List startFrom mismatch.\n\n" + + "$indent Actual: ${actual.startFrom}\n" + + "$indent Expected: ${expected.startFrom}", + ) + } + + if (actual.delimiter != expected.delimiter) { + add( + "$indent * List delimiter mismatch.\n\n" + + "$indent Actual: ${actual.delimiter}\n" + + "$indent Expected: ${expected.delimiter}", + ) + } + } + + is UnorderedList -> { + if (actual.bulletMarker != (expected as UnorderedList).bulletMarker) { + add( + "$indent * List bulletMarker mismatch.\n\n" + + "$indent Actual: ${actual.bulletMarker}\n" + + "$indent Expected: ${expected.bulletMarker}", + ) + } + } + } + } + +fun paragraph(@Language("Markdown") content: String) = Paragraph(InlineMarkdown(content)) + +fun indentedCodeBlock(content: String) = IndentedCodeBlock(content) + +fun fencedCodeBlock(content: String, mimeType: MimeType? = null) = + FencedCodeBlock(content, mimeType) + +fun blockQuote(vararg contents: MarkdownBlock) = BlockQuote(contents.toList()) + +fun unorderedList( + vararg items: ListItem, + isTight: Boolean = true, + bulletMarker: Char = '-', +) = UnorderedList(items.toList(), isTight, bulletMarker) + +fun orderedList( + vararg items: ListItem, + isTight: Boolean = true, + startFrom: Int = 1, + delimiter: Char = '.', +) = OrderedList(items.toList(), isTight, startFrom, delimiter) + +fun listItem(vararg items: MarkdownBlock) = ListItem(items.toList()) + +fun heading(level: Int, @Language("Markdown") content: String) = + when (level) { + 1 -> H1(InlineMarkdown(content)) + 2 -> H2(InlineMarkdown(content)) + 3 -> H3(InlineMarkdown(content)) + 4 -> H4(InlineMarkdown(content)) + 5 -> H5(InlineMarkdown(content)) + 6 -> H6(InlineMarkdown(content)) + else -> error("Invalid heading level $level") + } + +fun htmlBlock(content: String) = HtmlBlock(content) diff --git a/markdown/extension-gfm-alerts/api/extension-gfm-alerts.api b/markdown/extension-gfm-alerts/api/extension-gfm-alerts.api new file mode 100644 index 000000000..6eaf2d215 --- /dev/null +++ b/markdown/extension-gfm-alerts/api/extension-gfm-alerts.api @@ -0,0 +1,246 @@ +public abstract interface class org/jetbrains/jewel/markdown/extensions/github/alerts/Alert : org/jetbrains/jewel/markdown/MarkdownBlock$Extension { + public abstract fun getContent ()Ljava/util/List; +} + +public final class org/jetbrains/jewel/markdown/extensions/github/alerts/Alert$Caution : org/jetbrains/jewel/markdown/extensions/github/alerts/Alert { + public static final field $stable I + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/Alert$Caution; + public static synthetic fun copy$default (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/Alert$Caution;Ljava/util/List;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/Alert$Caution; + public fun equals (Ljava/lang/Object;)Z + public fun getContent ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/extensions/github/alerts/Alert$Important : org/jetbrains/jewel/markdown/extensions/github/alerts/Alert { + public static final field $stable I + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/Alert$Important; + public static synthetic fun copy$default (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/Alert$Important;Ljava/util/List;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/Alert$Important; + public fun equals (Ljava/lang/Object;)Z + public fun getContent ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/extensions/github/alerts/Alert$Note : org/jetbrains/jewel/markdown/extensions/github/alerts/Alert { + public static final field $stable I + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/Alert$Note; + public static synthetic fun copy$default (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/Alert$Note;Ljava/util/List;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/Alert$Note; + public fun equals (Ljava/lang/Object;)Z + public fun getContent ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/extensions/github/alerts/Alert$Tip : org/jetbrains/jewel/markdown/extensions/github/alerts/Alert { + public static final field $stable I + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/Alert$Tip; + public static synthetic fun copy$default (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/Alert$Tip;Ljava/util/List;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/Alert$Tip; + public fun equals (Ljava/lang/Object;)Z + public fun getContent ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/extensions/github/alerts/Alert$Warning : org/jetbrains/jewel/markdown/extensions/github/alerts/Alert { + public static final field $stable I + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/Alert$Warning; + public static synthetic fun copy$default (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/Alert$Warning;Ljava/util/List;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/Alert$Warning; + public fun equals (Ljava/lang/Object;)Z + public fun getContent ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/extensions/github/alerts/AlertStyling { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/extensions/github/alerts/AlertStyling$Companion; + public fun (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/NoteAlertStyling;Lorg/jetbrains/jewel/markdown/extensions/github/alerts/TipAlertStyling;Lorg/jetbrains/jewel/markdown/extensions/github/alerts/ImportantAlertStyling;Lorg/jetbrains/jewel/markdown/extensions/github/alerts/WarningAlertStyling;Lorg/jetbrains/jewel/markdown/extensions/github/alerts/CautionAlertStyling;)V + public fun equals (Ljava/lang/Object;)Z + public final fun getCaution ()Lorg/jetbrains/jewel/markdown/extensions/github/alerts/CautionAlertStyling; + public final fun getImportant ()Lorg/jetbrains/jewel/markdown/extensions/github/alerts/ImportantAlertStyling; + public final fun getNote ()Lorg/jetbrains/jewel/markdown/extensions/github/alerts/NoteAlertStyling; + public final fun getTip ()Lorg/jetbrains/jewel/markdown/extensions/github/alerts/TipAlertStyling; + public final fun getWarning ()Lorg/jetbrains/jewel/markdown/extensions/github/alerts/WarningAlertStyling; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/extensions/github/alerts/AlertStyling$Companion { +} + +public abstract interface class org/jetbrains/jewel/markdown/extensions/github/alerts/BaseAlertStyling { + public abstract fun getLineColor-0d7_KjU ()J + public abstract fun getLineWidth-D9Ej5fM ()F + public abstract fun getPadding ()Landroidx/compose/foundation/layout/PaddingValues; + public abstract fun getPathEffect ()Landroidx/compose/ui/graphics/PathEffect; + public abstract fun getStrokeCap-KaPHkGw ()I + public abstract fun getTextColor-0d7_KjU ()J + public abstract fun getTitleIconPath ()Ljava/lang/String; + public abstract fun getTitleIconTint-0d7_KjU ()J + public abstract fun getTitleTextStyle ()Landroidx/compose/ui/text/TextStyle; +} + +public final class org/jetbrains/jewel/markdown/extensions/github/alerts/CautionAlertStyling : org/jetbrains/jewel/markdown/extensions/github/alerts/BaseAlertStyling { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/extensions/github/alerts/CautionAlertStyling$Companion; + public synthetic fun (Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;ILandroidx/compose/ui/text/TextStyle;Ljava/lang/String;JJLkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z + public fun getLineColor-0d7_KjU ()J + public fun getLineWidth-D9Ej5fM ()F + public fun getPadding ()Landroidx/compose/foundation/layout/PaddingValues; + public fun getPathEffect ()Landroidx/compose/ui/graphics/PathEffect; + public fun getStrokeCap-KaPHkGw ()I + public fun getTextColor-0d7_KjU ()J + public fun getTitleIconPath ()Ljava/lang/String; + public fun getTitleIconTint-0d7_KjU ()J + public fun getTitleTextStyle ()Landroidx/compose/ui/text/TextStyle; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/extensions/github/alerts/CautionAlertStyling$Companion { +} + +public final class org/jetbrains/jewel/markdown/extensions/github/alerts/GitHubAlertBlockRenderer : org/jetbrains/jewel/markdown/extensions/MarkdownBlockRendererExtension { + 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 canRender (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Extension;)Z + public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Extension;Lorg/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer;Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer;Landroidx/compose/runtime/Composer;I)V +} + +public final class org/jetbrains/jewel/markdown/extensions/github/alerts/GitHubAlertDefaultStylingKt { + public static final fun dark (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/AlertStyling$Companion;Lorg/jetbrains/jewel/markdown/extensions/github/alerts/NoteAlertStyling;Lorg/jetbrains/jewel/markdown/extensions/github/alerts/TipAlertStyling;Lorg/jetbrains/jewel/markdown/extensions/github/alerts/ImportantAlertStyling;Lorg/jetbrains/jewel/markdown/extensions/github/alerts/WarningAlertStyling;Lorg/jetbrains/jewel/markdown/extensions/github/alerts/CautionAlertStyling;)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/AlertStyling; + public static synthetic fun dark$default (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/AlertStyling$Companion;Lorg/jetbrains/jewel/markdown/extensions/github/alerts/NoteAlertStyling;Lorg/jetbrains/jewel/markdown/extensions/github/alerts/TipAlertStyling;Lorg/jetbrains/jewel/markdown/extensions/github/alerts/ImportantAlertStyling;Lorg/jetbrains/jewel/markdown/extensions/github/alerts/WarningAlertStyling;Lorg/jetbrains/jewel/markdown/extensions/github/alerts/CautionAlertStyling;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/AlertStyling; + public static final fun dark-gaOEZmc (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/CautionAlertStyling$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;ILandroidx/compose/ui/text/TextStyle;Ljava/lang/String;JJ)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/CautionAlertStyling; + public static final fun dark-gaOEZmc (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/ImportantAlertStyling$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;ILandroidx/compose/ui/text/TextStyle;Ljava/lang/String;JJ)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/ImportantAlertStyling; + public static final fun dark-gaOEZmc (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/NoteAlertStyling$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;ILandroidx/compose/ui/text/TextStyle;Ljava/lang/String;JJ)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/NoteAlertStyling; + public static final fun dark-gaOEZmc (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/TipAlertStyling$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;ILandroidx/compose/ui/text/TextStyle;Ljava/lang/String;JJ)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/TipAlertStyling; + public static final fun dark-gaOEZmc (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/WarningAlertStyling$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;ILandroidx/compose/ui/text/TextStyle;Ljava/lang/String;JJ)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/WarningAlertStyling; + public static synthetic fun dark-gaOEZmc$default (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/CautionAlertStyling$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;ILandroidx/compose/ui/text/TextStyle;Ljava/lang/String;JJILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/CautionAlertStyling; + public static synthetic fun dark-gaOEZmc$default (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/ImportantAlertStyling$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;ILandroidx/compose/ui/text/TextStyle;Ljava/lang/String;JJILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/ImportantAlertStyling; + public static synthetic fun dark-gaOEZmc$default (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/NoteAlertStyling$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;ILandroidx/compose/ui/text/TextStyle;Ljava/lang/String;JJILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/NoteAlertStyling; + public static synthetic fun dark-gaOEZmc$default (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/TipAlertStyling$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;ILandroidx/compose/ui/text/TextStyle;Ljava/lang/String;JJILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/TipAlertStyling; + public static synthetic fun dark-gaOEZmc$default (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/WarningAlertStyling$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;ILandroidx/compose/ui/text/TextStyle;Ljava/lang/String;JJILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/WarningAlertStyling; + public static final fun light (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/AlertStyling$Companion;Lorg/jetbrains/jewel/markdown/extensions/github/alerts/NoteAlertStyling;Lorg/jetbrains/jewel/markdown/extensions/github/alerts/TipAlertStyling;Lorg/jetbrains/jewel/markdown/extensions/github/alerts/ImportantAlertStyling;Lorg/jetbrains/jewel/markdown/extensions/github/alerts/WarningAlertStyling;Lorg/jetbrains/jewel/markdown/extensions/github/alerts/CautionAlertStyling;)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/AlertStyling; + public static synthetic fun light$default (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/AlertStyling$Companion;Lorg/jetbrains/jewel/markdown/extensions/github/alerts/NoteAlertStyling;Lorg/jetbrains/jewel/markdown/extensions/github/alerts/TipAlertStyling;Lorg/jetbrains/jewel/markdown/extensions/github/alerts/ImportantAlertStyling;Lorg/jetbrains/jewel/markdown/extensions/github/alerts/WarningAlertStyling;Lorg/jetbrains/jewel/markdown/extensions/github/alerts/CautionAlertStyling;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/AlertStyling; + public static final fun light-gaOEZmc (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/CautionAlertStyling$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;ILandroidx/compose/ui/text/TextStyle;Ljava/lang/String;JJ)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/CautionAlertStyling; + public static final fun light-gaOEZmc (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/ImportantAlertStyling$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;ILandroidx/compose/ui/text/TextStyle;Ljava/lang/String;JJ)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/ImportantAlertStyling; + public static final fun light-gaOEZmc (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/NoteAlertStyling$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;ILandroidx/compose/ui/text/TextStyle;Ljava/lang/String;JJ)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/NoteAlertStyling; + public static final fun light-gaOEZmc (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/TipAlertStyling$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;ILandroidx/compose/ui/text/TextStyle;Ljava/lang/String;JJ)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/TipAlertStyling; + public static final fun light-gaOEZmc (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/WarningAlertStyling$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;ILandroidx/compose/ui/text/TextStyle;Ljava/lang/String;JJ)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/WarningAlertStyling; + public static synthetic fun light-gaOEZmc$default (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/CautionAlertStyling$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;ILandroidx/compose/ui/text/TextStyle;Ljava/lang/String;JJILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/CautionAlertStyling; + public static synthetic fun light-gaOEZmc$default (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/ImportantAlertStyling$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;ILandroidx/compose/ui/text/TextStyle;Ljava/lang/String;JJILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/ImportantAlertStyling; + public static synthetic fun light-gaOEZmc$default (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/NoteAlertStyling$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;ILandroidx/compose/ui/text/TextStyle;Ljava/lang/String;JJILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/NoteAlertStyling; + public static synthetic fun light-gaOEZmc$default (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/TipAlertStyling$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;ILandroidx/compose/ui/text/TextStyle;Ljava/lang/String;JJILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/TipAlertStyling; + public static synthetic fun light-gaOEZmc$default (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/WarningAlertStyling$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;ILandroidx/compose/ui/text/TextStyle;Ljava/lang/String;JJILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/WarningAlertStyling; +} + +public final class org/jetbrains/jewel/markdown/extensions/github/alerts/GitHubAlertProcessorExtension : org/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension { + public static final field $stable I + public static final field INSTANCE Lorg/jetbrains/jewel/markdown/extensions/github/alerts/GitHubAlertProcessorExtension; + public fun getParserExtension ()Lorg/commonmark/parser/Parser$ParserExtension; + public fun getProcessorExtension ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownBlockProcessorExtension; + public fun getTextRendererExtension ()Lorg/commonmark/renderer/text/TextContentRenderer$TextContentRendererExtension; +} + +public final class org/jetbrains/jewel/markdown/extensions/github/alerts/GitHubAlertRendererExtension : org/jetbrains/jewel/markdown/extensions/MarkdownRendererExtension { + 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 final class org/jetbrains/jewel/markdown/extensions/github/alerts/ImportantAlertStyling : org/jetbrains/jewel/markdown/extensions/github/alerts/BaseAlertStyling { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/extensions/github/alerts/ImportantAlertStyling$Companion; + public synthetic fun (Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;ILandroidx/compose/ui/text/TextStyle;Ljava/lang/String;JJLkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z + public fun getLineColor-0d7_KjU ()J + public fun getLineWidth-D9Ej5fM ()F + public fun getPadding ()Landroidx/compose/foundation/layout/PaddingValues; + public fun getPathEffect ()Landroidx/compose/ui/graphics/PathEffect; + public fun getStrokeCap-KaPHkGw ()I + public fun getTextColor-0d7_KjU ()J + public fun getTitleIconPath ()Ljava/lang/String; + public fun getTitleIconTint-0d7_KjU ()J + public fun getTitleTextStyle ()Landroidx/compose/ui/text/TextStyle; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/extensions/github/alerts/ImportantAlertStyling$Companion { +} + +public final class org/jetbrains/jewel/markdown/extensions/github/alerts/NoteAlertStyling : org/jetbrains/jewel/markdown/extensions/github/alerts/BaseAlertStyling { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/extensions/github/alerts/NoteAlertStyling$Companion; + public synthetic fun (Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;ILandroidx/compose/ui/text/TextStyle;Ljava/lang/String;JJLkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z + public fun getLineColor-0d7_KjU ()J + public fun getLineWidth-D9Ej5fM ()F + public fun getPadding ()Landroidx/compose/foundation/layout/PaddingValues; + public fun getPathEffect ()Landroidx/compose/ui/graphics/PathEffect; + public fun getStrokeCap-KaPHkGw ()I + public fun getTextColor-0d7_KjU ()J + public fun getTitleIconPath ()Ljava/lang/String; + public fun getTitleIconTint-0d7_KjU ()J + public fun getTitleTextStyle ()Landroidx/compose/ui/text/TextStyle; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/extensions/github/alerts/NoteAlertStyling$Companion { +} + +public final class org/jetbrains/jewel/markdown/extensions/github/alerts/TipAlertStyling : org/jetbrains/jewel/markdown/extensions/github/alerts/BaseAlertStyling { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/extensions/github/alerts/TipAlertStyling$Companion; + public synthetic fun (Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;ILandroidx/compose/ui/text/TextStyle;Ljava/lang/String;JJLkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z + public fun getLineColor-0d7_KjU ()J + public fun getLineWidth-D9Ej5fM ()F + public fun getPadding ()Landroidx/compose/foundation/layout/PaddingValues; + public fun getPathEffect ()Landroidx/compose/ui/graphics/PathEffect; + public fun getStrokeCap-KaPHkGw ()I + public fun getTextColor-0d7_KjU ()J + public fun getTitleIconPath ()Ljava/lang/String; + public fun getTitleIconTint-0d7_KjU ()J + public fun getTitleTextStyle ()Landroidx/compose/ui/text/TextStyle; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/extensions/github/alerts/TipAlertStyling$Companion { +} + +public final class org/jetbrains/jewel/markdown/extensions/github/alerts/WarningAlertStyling : org/jetbrains/jewel/markdown/extensions/github/alerts/BaseAlertStyling { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/extensions/github/alerts/WarningAlertStyling$Companion; + public synthetic fun (Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;ILandroidx/compose/ui/text/TextStyle;Ljava/lang/String;JJLkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z + public fun getLineColor-0d7_KjU ()J + public fun getLineWidth-D9Ej5fM ()F + public fun getPadding ()Landroidx/compose/foundation/layout/PaddingValues; + public fun getPathEffect ()Landroidx/compose/ui/graphics/PathEffect; + public fun getStrokeCap-KaPHkGw ()I + public fun getTextColor-0d7_KjU ()J + public fun getTitleIconPath ()Ljava/lang/String; + public fun getTitleIconTint-0d7_KjU ()J + public fun getTitleTextStyle ()Landroidx/compose/ui/text/TextStyle; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/extensions/github/alerts/WarningAlertStyling$Companion { +} + diff --git a/markdown/extension-gfm-alerts/build.gradle.kts b/markdown/extension-gfm-alerts/build.gradle.kts new file mode 100644 index 000000000..bd624c946 --- /dev/null +++ b/markdown/extension-gfm-alerts/build.gradle.kts @@ -0,0 +1,19 @@ +plugins { + jewel + `jewel-publish` + `jewel-check-public-api` + alias(libs.plugins.composeDesktop) +} + +dependencies { + api(projects.markdown.core) + + implementation(libs.commonmark.core) + + testImplementation(compose.desktop.uiTestJUnit4) +} + +publicApiValidation { + // We don't foresee changes to the data models for now + excludedClassRegexes = setOf("org.jetbrains.jewel.markdown.extensions.github.alerts.Alert\\$.*") +} diff --git a/markdown/extension-gfm-alerts/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/alerts/Alert.kt b/markdown/extension-gfm-alerts/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/alerts/Alert.kt new file mode 100644 index 000000000..4a5e7f7e5 --- /dev/null +++ b/markdown/extension-gfm-alerts/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/alerts/Alert.kt @@ -0,0 +1,18 @@ +package org.jetbrains.jewel.markdown.extensions.github.alerts + +import org.jetbrains.jewel.markdown.MarkdownBlock + +public sealed interface Alert : MarkdownBlock.Extension { + + public val content: List + + public data class Note(override val content: List) : Alert + + public data class Tip(override val content: List) : Alert + + public data class Important(override val content: List) : Alert + + public data class Warning(override val content: List) : Alert + + public data class Caution(override val content: List) : Alert +} diff --git a/markdown/extension-gfm-alerts/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/alerts/GitHubAlertBlockRenderer.kt b/markdown/extension-gfm-alerts/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/alerts/GitHubAlertBlockRenderer.kt new file mode 100644 index 000000000..23aa425f1 --- /dev/null +++ b/markdown/extension-gfm-alerts/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/alerts/GitHubAlertBlockRenderer.kt @@ -0,0 +1,119 @@ +package org.jetbrains.jewel.markdown.extensions.github.alerts + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.isSpecified +import androidx.compose.ui.graphics.takeOrElse +import androidx.compose.ui.unit.LayoutDirection.Ltr +import androidx.compose.ui.unit.dp +import org.jetbrains.jewel.foundation.theme.LocalContentColor +import org.jetbrains.jewel.markdown.MarkdownBlock.Extension +import org.jetbrains.jewel.markdown.extensions.MarkdownBlockRendererExtension +import org.jetbrains.jewel.markdown.extensions.github.alerts.Alert.Caution +import org.jetbrains.jewel.markdown.extensions.github.alerts.Alert.Important +import org.jetbrains.jewel.markdown.extensions.github.alerts.Alert.Note +import org.jetbrains.jewel.markdown.extensions.github.alerts.Alert.Tip +import org.jetbrains.jewel.markdown.extensions.github.alerts.Alert.Warning +import org.jetbrains.jewel.markdown.rendering.InlineMarkdownRenderer +import org.jetbrains.jewel.markdown.rendering.MarkdownBlockRenderer +import org.jetbrains.jewel.markdown.rendering.MarkdownStyling +import org.jetbrains.jewel.ui.component.Icon +import org.jetbrains.jewel.ui.component.Text + +public class GitHubAlertBlockRenderer( + private val styling: AlertStyling, + private val rootStyling: MarkdownStyling, +) : MarkdownBlockRendererExtension { + + override fun canRender(block: Extension): Boolean = + block is Alert + + @Composable + override fun render( + block: Extension, + blockRenderer: MarkdownBlockRenderer, + inlineRenderer: InlineMarkdownRenderer, + ) { + // Smart cast doesn't work in this case, and then the detection for redundant suppression is + // also borked + @Suppress("MoveVariableDeclarationIntoWhen", "RedundantSuppression") + val alert = block as? Alert + + when (alert) { + is Caution -> Alert(alert, styling.caution, blockRenderer) + is Important -> Alert(alert, styling.important, blockRenderer) + is Note -> Alert(alert, styling.note, blockRenderer) + is Tip -> Alert(alert, styling.tip, blockRenderer) + is Warning -> Alert(alert, styling.warning, blockRenderer) + else -> error("Unsupported block of type ${block.javaClass.name} cannot be rendered") + } + } + + @Composable + private fun Alert(block: Alert, styling: BaseAlertStyling, blockRenderer: MarkdownBlockRenderer) { + Column( + Modifier.drawBehind { + val isLtr = layoutDirection == Ltr + val lineWidthPx = styling.lineWidth.toPx() + val x = if (isLtr) lineWidthPx / 2 else size.width - lineWidthPx / 2 + + drawLine( + styling.lineColor, + Offset(x, 0f), + Offset(x, size.height), + lineWidthPx, + styling.strokeCap, + styling.pathEffect, + ) + } + .padding(styling.padding), + verticalArrangement = Arrangement.spacedBy(rootStyling.blockVerticalSpacing), + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + val titleIconProvider = styling.titleIconPath + if (titleIconProvider != null) { + val colorFilter = + remember(styling.titleIconTint) { + if (styling.titleIconTint.isSpecified) { + ColorFilter.tint(styling.titleIconTint) + } else { + null + } + } + + Icon( + titleIconProvider, + contentDescription = null, + iconClass = AlertStyling::class.java, + colorFilter = colorFilter, + ) + } + + CompositionLocalProvider( + LocalContentColor provides + styling.titleTextStyle.color.takeOrElse { LocalContentColor.current }, + ) { + Text(block.javaClass.simpleName, style = styling.titleTextStyle) + } + } + CompositionLocalProvider( + LocalContentColor provides styling.textColor.takeOrElse { LocalContentColor.current }, + ) { + blockRenderer.render(block.content) + } + } + } +} diff --git a/markdown/extension-gfm-alerts/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/alerts/GitHubAlertDefaultStyling.kt b/markdown/extension-gfm-alerts/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/alerts/GitHubAlertDefaultStyling.kt new file mode 100644 index 000000000..69150c0ba --- /dev/null +++ b/markdown/extension-gfm-alerts/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/alerts/GitHubAlertDefaultStyling.kt @@ -0,0 +1,257 @@ +package org.jetbrains.jewel.markdown.extensions.github.alerts + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathEffect +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +public fun AlertStyling.Companion.light( + note: NoteAlertStyling = NoteAlertStyling.light(), + tip: TipAlertStyling = TipAlertStyling.light(), + important: ImportantAlertStyling = ImportantAlertStyling.light(), + warning: WarningAlertStyling = WarningAlertStyling.light(), + caution: CautionAlertStyling = CautionAlertStyling.light(), +): AlertStyling = AlertStyling(note, tip, important, warning, caution) + +public fun AlertStyling.Companion.dark( + note: NoteAlertStyling = NoteAlertStyling.dark(), + tip: TipAlertStyling = TipAlertStyling.dark(), + important: ImportantAlertStyling = ImportantAlertStyling.dark(), + warning: WarningAlertStyling = WarningAlertStyling.dark(), + caution: CautionAlertStyling = CautionAlertStyling.dark(), +): AlertStyling = AlertStyling(note, tip, important, warning, caution) + +public fun NoteAlertStyling.Companion.light( + padding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp), + lineWidth: Dp = 3.dp, + lineColor: Color = Color(0xFF0969DA), + pathEffect: PathEffect? = null, + strokeCap: StrokeCap = StrokeCap.Square, + titleTextStyle: TextStyle = TextStyle(fontWeight = FontWeight.Medium, color = lineColor), + titleIconPath: String? = "icons/markdown/extensions/github/alerts/alert-note.svg", + titleIconTint: Color = lineColor, + textColor: Color = Color.Unspecified, +): NoteAlertStyling = + NoteAlertStyling( + padding, + lineWidth, + lineColor, + pathEffect, + strokeCap, + titleTextStyle, + titleIconPath, + titleIconTint, + textColor, + ) + +public fun NoteAlertStyling.Companion.dark( + padding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp), + lineWidth: Dp = 3.dp, + lineColor: Color = Color(0xFF1F6EEB), + pathEffect: PathEffect? = null, + strokeCap: StrokeCap = StrokeCap.Square, + titleTextStyle: TextStyle = TextStyle(fontWeight = FontWeight.Medium, color = lineColor), + titleIconPath: String? = "icons/markdown/extensions/github/alerts/alert-note.svg", + titleIconTint: Color = lineColor, + textColor: Color = Color.Unspecified, +): NoteAlertStyling = + NoteAlertStyling( + padding, + lineWidth, + lineColor, + pathEffect, + strokeCap, + titleTextStyle, + titleIconPath, + titleIconTint, + textColor, + ) + +public fun TipAlertStyling.Companion.light( + padding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp), + lineWidth: Dp = 3.dp, + lineColor: Color = Color(0xFF1F883D), + pathEffect: PathEffect? = null, + strokeCap: StrokeCap = StrokeCap.Square, + titleTextStyle: TextStyle = TextStyle(fontWeight = FontWeight.Medium, color = lineColor), + titleIconPath: String? = "icons/markdown/extensions/github/alerts/alert-tip.svg", + titleIconTint: Color = lineColor, + textColor: Color = Color.Unspecified, +): TipAlertStyling = + TipAlertStyling( + padding, + lineWidth, + lineColor, + pathEffect, + strokeCap, + titleTextStyle, + titleIconPath, + titleIconTint, + textColor, + ) + +public fun TipAlertStyling.Companion.dark( + padding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp), + lineWidth: Dp = 3.dp, + lineColor: Color = Color(0xFF238636), + pathEffect: PathEffect? = null, + strokeCap: StrokeCap = StrokeCap.Square, + titleTextStyle: TextStyle = TextStyle(fontWeight = FontWeight.Medium, color = lineColor), + titleIconPath: String? = "icons/markdown/extensions/github/alerts/alert-tip.svg", + titleIconTint: Color = lineColor, + textColor: Color = Color.Unspecified, +): TipAlertStyling = + TipAlertStyling( + padding, + lineWidth, + lineColor, + pathEffect, + strokeCap, + titleTextStyle, + titleIconPath, + titleIconTint, + textColor, + ) + +public fun ImportantAlertStyling.Companion.light( + padding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp), + lineWidth: Dp = 3.dp, + lineColor: Color = Color(0xFF8250DF), + pathEffect: PathEffect? = null, + strokeCap: StrokeCap = StrokeCap.Square, + titleTextStyle: TextStyle = TextStyle(fontWeight = FontWeight.Medium, color = lineColor), + titleIconPath: String? = "icons/markdown/extensions/github/alerts/alert-important.svg", + titleIconTint: Color = lineColor, + textColor: Color = Color.Unspecified, +): ImportantAlertStyling = + ImportantAlertStyling( + padding, + lineWidth, + lineColor, + pathEffect, + strokeCap, + titleTextStyle, + titleIconPath, + titleIconTint, + textColor, + ) + +public fun ImportantAlertStyling.Companion.dark( + padding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp), + lineWidth: Dp = 3.dp, + lineColor: Color = Color(0xFF8957E5), + pathEffect: PathEffect? = null, + strokeCap: StrokeCap = StrokeCap.Square, + titleTextStyle: TextStyle = TextStyle(fontWeight = FontWeight.Medium, color = lineColor), + titleIconPath: String? = "icons/markdown/extensions/github/alerts/alert-important.svg", + titleIconTint: Color = lineColor, + textColor: Color = Color.Unspecified, +): ImportantAlertStyling = + ImportantAlertStyling( + padding, + lineWidth, + lineColor, + pathEffect, + strokeCap, + titleTextStyle, + titleIconPath, + titleIconTint, + textColor, + ) + +public fun WarningAlertStyling.Companion.light( + padding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp), + lineWidth: Dp = 3.dp, + lineColor: Color = Color(0xFF9A6601), + pathEffect: PathEffect? = null, + strokeCap: StrokeCap = StrokeCap.Square, + titleTextStyle: TextStyle = TextStyle(fontWeight = FontWeight.Medium, color = lineColor), + titleIconPath: String? = "icons/markdown/extensions/github/alerts/alert-warning.svg", + titleIconTint: Color = lineColor, + textColor: Color = Color.Unspecified, +): WarningAlertStyling = + WarningAlertStyling( + padding, + lineWidth, + lineColor, + pathEffect, + strokeCap, + titleTextStyle, + titleIconPath, + titleIconTint, + textColor, + ) + +public fun WarningAlertStyling.Companion.dark( + padding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp), + lineWidth: Dp = 3.dp, + lineColor: Color = Color(0xFF9E6A02), + pathEffect: PathEffect? = null, + strokeCap: StrokeCap = StrokeCap.Square, + titleTextStyle: TextStyle = TextStyle(fontWeight = FontWeight.Medium, color = lineColor), + titleIconPath: String? = "icons/markdown/extensions/github/alerts/alert-warning.svg", + titleIconTint: Color = lineColor, + textColor: Color = Color.Unspecified, +): WarningAlertStyling = + WarningAlertStyling( + padding, + lineWidth, + lineColor, + pathEffect, + strokeCap, + titleTextStyle, + titleIconPath, + titleIconTint, + textColor, + ) + +public fun CautionAlertStyling.Companion.light( + padding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp), + lineWidth: Dp = 3.dp, + lineColor: Color = Color(0xFFCF222E), + pathEffect: PathEffect? = null, + strokeCap: StrokeCap = StrokeCap.Square, + titleTextStyle: TextStyle = TextStyle(fontWeight = FontWeight.Medium, color = lineColor), + titleIconPath: String? = "icons/markdown/extensions/github/alerts/alert-caution.svg", + titleIconTint: Color = lineColor, + textColor: Color = Color.Unspecified, +): CautionAlertStyling = + CautionAlertStyling( + padding, + lineWidth, + lineColor, + pathEffect, + strokeCap, + titleTextStyle, + titleIconPath, + titleIconTint, + textColor, + ) + +public fun CautionAlertStyling.Companion.dark( + padding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp), + lineWidth: Dp = 3.dp, + lineColor: Color = Color(0xFFDA3633), + pathEffect: PathEffect? = null, + strokeCap: StrokeCap = StrokeCap.Square, + titleTextStyle: TextStyle = TextStyle(fontWeight = FontWeight.Medium, color = lineColor), + titleIconPath: String? = "icons/markdown/extensions/github/alerts/alert-caution.svg", + titleIconTint: Color = lineColor, + textColor: Color = Color.Unspecified, +): CautionAlertStyling { + return CautionAlertStyling( + padding, + lineWidth, + lineColor, + pathEffect, + strokeCap, + titleTextStyle, + titleIconPath, + titleIconTint, + textColor, + ) +} diff --git a/markdown/extension-gfm-alerts/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/alerts/GitHubAlertProcessorExtension.kt b/markdown/extension-gfm-alerts/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/alerts/GitHubAlertProcessorExtension.kt new file mode 100644 index 000000000..7da342a49 --- /dev/null +++ b/markdown/extension-gfm-alerts/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/alerts/GitHubAlertProcessorExtension.kt @@ -0,0 +1,174 @@ +package org.jetbrains.jewel.markdown.extensions.github.alerts + +import org.commonmark.internal.util.Parsing +import org.commonmark.node.Block +import org.commonmark.node.CustomBlock +import org.commonmark.node.Node +import org.commonmark.parser.Parser.Builder +import org.commonmark.parser.Parser.ParserExtension +import org.commonmark.parser.block.AbstractBlockParser +import org.commonmark.parser.block.BlockContinue +import org.commonmark.parser.block.BlockStart +import org.commonmark.parser.block.ParserState +import org.commonmark.renderer.NodeRenderer +import org.commonmark.renderer.text.TextContentNodeRendererContext +import org.commonmark.renderer.text.TextContentRenderer +import org.commonmark.renderer.text.TextContentRenderer.TextContentRendererExtension +import org.jetbrains.jewel.markdown.MarkdownBlock +import org.jetbrains.jewel.markdown.extensions.MarkdownBlockProcessorExtension +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.extensions.github.alerts.AlertBlock.Caution +import org.jetbrains.jewel.markdown.extensions.github.alerts.AlertBlock.Important +import org.jetbrains.jewel.markdown.extensions.github.alerts.AlertBlock.Note +import org.jetbrains.jewel.markdown.extensions.github.alerts.AlertBlock.Tip +import org.jetbrains.jewel.markdown.extensions.github.alerts.AlertBlock.Warning +import org.jetbrains.jewel.markdown.processing.MarkdownProcessor +import org.jetbrains.jewel.markdown.rendering.MarkdownStyling + +public object GitHubAlertProcessorExtension : MarkdownProcessorExtension { + + override val parserExtension: ParserExtension = GitHubAlertCommonMarkExtension + override val textRendererExtension: TextContentRendererExtension = + GitHubAlertCommonMarkExtension + + override val processorExtension: MarkdownBlockProcessorExtension = GitHubAlertProcessorExtension + + private object GitHubAlertProcessorExtension : MarkdownBlockProcessorExtension { + + override fun canProcess(block: CustomBlock): Boolean = block is AlertBlock + + override fun processMarkdownBlock(block: CustomBlock, processor: MarkdownProcessor): MarkdownBlock.Extension? { + val children = processor.processChildren(block) + + if (children.isEmpty()) return null + + return when (block) { + is Caution -> Alert.Caution(children) + is Important -> Alert.Important(children) + is Note -> Alert.Note(children) + is Tip -> Alert.Tip(children) + is Warning -> Alert.Warning(children) + else -> error("Unsupported custom block of type ${block.javaClass.name}") + } + } + } +} + +public class GitHubAlertRendererExtension( + alertStyling: AlertStyling, + rootStyling: MarkdownStyling, +) : MarkdownRendererExtension { + + override val blockRenderer: MarkdownBlockRendererExtension = + GitHubAlertBlockRenderer(alertStyling, rootStyling) +} + +private object GitHubAlertCommonMarkExtension : ParserExtension, TextContentRendererExtension { + + private val AlertStartRegex = + ">\\s+\\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)]\\s*".toRegex(RegexOption.IGNORE_CASE) + + override fun extend(parserBuilder: Builder) { + parserBuilder.customBlockParserFactory { state, _ -> + val line = state.line.content.substring(state.column) + val matchResult = AlertStartRegex.matchEntire(line) + + if (matchResult != null) { + val type = matchResult.groupValues[1] + BlockStart.of(AlertParser(type)) + .atColumn(state.column + state.indent + matchResult.value.length) + } else { + BlockStart.none() + } + } + } + + override fun extend(rendererBuilder: TextContentRenderer.Builder) { + rendererBuilder.nodeRendererFactory { AlertTextContentNodeRenderer(it) } + } +} + +private class AlertParser(type: String) : AbstractBlockParser() { + + private val block = + when (type.lowercase()) { + "note" -> Note() + "tip" -> Tip() + "important" -> Important() + "warning" -> Warning() + "caution" -> Caution() + else -> error("Unsupported highlighted blockquote type: '$type'") + } + + override fun getBlock() = block + + override fun isContainer() = true + + override fun canContain(childBlock: Block?) = childBlock !is AlertBlock + + override fun tryContinue(parserState: ParserState): BlockContinue? { + val nextNonSpace: Int = parserState.nextNonSpaceIndex + + return if (parserState.isMarker(nextNonSpace)) { + var newColumn: Int = parserState.column + parserState.indent + 1 + // optional following space or tab + if (Parsing.isSpaceOrTab(parserState.line.content, nextNonSpace + 1)) { + newColumn++ + } + BlockContinue.atColumn(newColumn) + } else { + BlockContinue.none() + } + } + + private fun ParserState.isMarker(index: Int): Boolean { + val line = line.content + return indent < Parsing.CODE_BLOCK_INDENT && index < line.length && line[index] == '>' + } +} + +private class AlertTextContentNodeRenderer(private val context: TextContentNodeRendererContext) : + NodeRenderer { + + private val writer = context.writer + + override fun getNodeTypes(): Set> = setOf(AlertBlock::class.java) + + override fun render(node: Node) { + val premise = + when (node as? AlertBlock) { + is Caution -> "\uD83D\uDED1 Caution! " + is Important -> "⚠\uFE0F Important! " + is Note -> "ℹ\uFE0F Note: " + is Tip -> "\uD83D\uDCA1 Tip: " + is Warning -> "⚠\uFE0F Warning: " + null -> error("Unsupported node type ${node.javaClass.name}") + } + + writer.write(premise) + renderChildren(node) + } + + private fun renderChildren(node: Node) { + var child = node.firstChild + while (child != null) { + context.render(node) + child = child.next + } + } +} + +internal sealed class AlertBlock : CustomBlock() { + + class Note : AlertBlock() + + class Tip : AlertBlock() + + class Important : AlertBlock() + + class Warning : AlertBlock() + + class Caution : AlertBlock() +} diff --git a/markdown/extension-gfm-alerts/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/alerts/GitHubAlertStyling.kt b/markdown/extension-gfm-alerts/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/alerts/GitHubAlertStyling.kt new file mode 100644 index 000000000..41ea20244 --- /dev/null +++ b/markdown/extension-gfm-alerts/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/alerts/GitHubAlertStyling.kt @@ -0,0 +1,114 @@ +package org.jetbrains.jewel.markdown.extensions.github.alerts + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathEffect +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.Dp +import org.jetbrains.jewel.foundation.GenerateDataFunctions + +@GenerateDataFunctions +public class AlertStyling( + public val note: NoteAlertStyling, + public val tip: TipAlertStyling, + public val important: ImportantAlertStyling, + public val warning: WarningAlertStyling, + public val caution: CautionAlertStyling, +) { + + public companion object +} + +public sealed interface BaseAlertStyling { + + public val padding: PaddingValues + public val lineWidth: Dp + public val lineColor: Color + public val pathEffect: PathEffect? + public val strokeCap: StrokeCap + public val titleTextStyle: TextStyle + public val titleIconPath: String? + public val titleIconTint: Color + public val textColor: Color +} + +@GenerateDataFunctions +public class NoteAlertStyling( + override val padding: PaddingValues, + override val lineWidth: Dp, + override val lineColor: Color, + override val pathEffect: PathEffect?, + override val strokeCap: StrokeCap, + override val titleTextStyle: TextStyle, + override val titleIconPath: String?, + override val titleIconTint: Color, + override val textColor: Color, +) : BaseAlertStyling { + + public companion object +} + +@GenerateDataFunctions +public class TipAlertStyling( + override val padding: PaddingValues, + override val lineWidth: Dp, + override val lineColor: Color, + override val pathEffect: PathEffect?, + override val strokeCap: StrokeCap, + override val titleTextStyle: TextStyle, + override val titleIconPath: String?, + override val titleIconTint: Color, + override val textColor: Color, +) : BaseAlertStyling { + + public companion object +} + +@GenerateDataFunctions +public class ImportantAlertStyling( + override val padding: PaddingValues, + override val lineWidth: Dp, + override val lineColor: Color, + override val pathEffect: PathEffect?, + override val strokeCap: StrokeCap, + override val titleTextStyle: TextStyle, + override val titleIconPath: String?, + override val titleIconTint: Color, + override val textColor: Color, +) : BaseAlertStyling { + + public companion object +} + +@GenerateDataFunctions +public class WarningAlertStyling( + override val padding: PaddingValues, + override val lineWidth: Dp, + override val lineColor: Color, + override val pathEffect: PathEffect?, + override val strokeCap: StrokeCap, + override val titleTextStyle: TextStyle, + override val titleIconPath: String?, + override val titleIconTint: Color, + override val textColor: Color, +) : BaseAlertStyling { + + public companion object +} + +@GenerateDataFunctions +public class CautionAlertStyling( + override val padding: PaddingValues, + override val lineWidth: Dp, + override val lineColor: Color, + override val pathEffect: PathEffect?, + override val strokeCap: StrokeCap, + override val titleTextStyle: TextStyle, + override val titleIconPath: String?, + override val titleIconTint: Color, + override val textColor: Color, +) : BaseAlertStyling { + + public companion object +} diff --git a/markdown/extension-gfm-alerts/src/main/resources/icons/markdown/extensions/github/alerts/alert-caution.svg b/markdown/extension-gfm-alerts/src/main/resources/icons/markdown/extensions/github/alerts/alert-caution.svg new file mode 100644 index 000000000..8be3249b0 --- /dev/null +++ b/markdown/extension-gfm-alerts/src/main/resources/icons/markdown/extensions/github/alerts/alert-caution.svg @@ -0,0 +1,3 @@ + + + diff --git a/markdown/extension-gfm-alerts/src/main/resources/icons/markdown/extensions/github/alerts/alert-important.svg b/markdown/extension-gfm-alerts/src/main/resources/icons/markdown/extensions/github/alerts/alert-important.svg new file mode 100644 index 000000000..d8f8df739 --- /dev/null +++ b/markdown/extension-gfm-alerts/src/main/resources/icons/markdown/extensions/github/alerts/alert-important.svg @@ -0,0 +1,3 @@ + + + diff --git a/markdown/extension-gfm-alerts/src/main/resources/icons/markdown/extensions/github/alerts/alert-note.svg b/markdown/extension-gfm-alerts/src/main/resources/icons/markdown/extensions/github/alerts/alert-note.svg new file mode 100644 index 000000000..aab00417a --- /dev/null +++ b/markdown/extension-gfm-alerts/src/main/resources/icons/markdown/extensions/github/alerts/alert-note.svg @@ -0,0 +1,3 @@ + + + diff --git a/markdown/extension-gfm-alerts/src/main/resources/icons/markdown/extensions/github/alerts/alert-tip.svg b/markdown/extension-gfm-alerts/src/main/resources/icons/markdown/extensions/github/alerts/alert-tip.svg new file mode 100644 index 000000000..5536a5231 --- /dev/null +++ b/markdown/extension-gfm-alerts/src/main/resources/icons/markdown/extensions/github/alerts/alert-tip.svg @@ -0,0 +1,3 @@ + + + diff --git a/markdown/extension-gfm-alerts/src/main/resources/icons/markdown/extensions/github/alerts/alert-warning.svg b/markdown/extension-gfm-alerts/src/main/resources/icons/markdown/extensions/github/alerts/alert-warning.svg new file mode 100644 index 000000000..cf9d405fb --- /dev/null +++ b/markdown/extension-gfm-alerts/src/main/resources/icons/markdown/extensions/github/alerts/alert-warning.svg @@ -0,0 +1,3 @@ + + + diff --git a/markdown/extension-gfm-alerts/src/test/kotlin/org/jetbrains/jewel/markdown/extensions/github/alerts/GitHubAlertBlockExtensionTest.kt b/markdown/extension-gfm-alerts/src/test/kotlin/org/jetbrains/jewel/markdown/extensions/github/alerts/GitHubAlertBlockExtensionTest.kt new file mode 100644 index 000000000..1b61bd84b --- /dev/null +++ b/markdown/extension-gfm-alerts/src/test/kotlin/org/jetbrains/jewel/markdown/extensions/github/alerts/GitHubAlertBlockExtensionTest.kt @@ -0,0 +1,547 @@ +package org.jetbrains.jewel.markdown.extensions.github.alerts + +import org.commonmark.node.BlockQuote +import org.commonmark.node.Document +import org.commonmark.node.Node +import org.commonmark.node.Paragraph +import org.commonmark.parser.Parser +import org.commonmark.renderer.text.TextContentRenderer +import org.jetbrains.jewel.markdown.extensions.github.alerts.AlertBlock.Caution +import org.jetbrains.jewel.markdown.extensions.github.alerts.AlertBlock.Important +import org.jetbrains.jewel.markdown.extensions.github.alerts.AlertBlock.Note +import org.jetbrains.jewel.markdown.extensions.github.alerts.AlertBlock.Tip +import org.jetbrains.jewel.markdown.extensions.github.alerts.AlertBlock.Warning +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test + +class GitHubAlertBlockExtensionTest { + + private val parser = + Parser.builder() + .extensions(listOf(GitHubAlertProcessorExtension.parserExtension)) + .build() + + private val renderer = + TextContentRenderer.builder() + .extensions(listOf(GitHubAlertProcessorExtension.textRendererExtension)) + .build() + + @Test + fun `should parse note alert`() { + val rawMarkdown = + """ + |> [!NOTE] + |> Highlights information that users should take into account, even when skimming. + """ + .trimMargin() + val parsed = parser.parse(rawMarkdown) + + assertTrue("Parse result is not a document", parsed is Document) + + val children = parsed.children + assertEquals("Parsed document should only have one direct child", 1, children.size) + + val firstChild = children.first() + assertTrue( + "Parsed node should be a Note node, but was ${firstChild.javaClass.name}", + firstChild is Note, + ) + + val contents = firstChild.children + assertEquals("Parsed node should only have one direct child", 1, contents.size) + + val firstContent = contents.first() + assertTrue( + "Parsed content should be a Paragraph, but was ${firstContent.javaClass.name}", + firstContent is Paragraph, + ) + (firstContent as Paragraph) + assertEquals( + "Content text should be parsed correctly\n", + "Highlights information that users should take into account, even when skimming.", + renderer.render(firstContent), + ) + } + + @Test + fun `should parse tip alert`() { + val rawMarkdown = + """ + |> [!TIP] + |> Optional information to help a user be more successful. + """ + .trimMargin() + val parsed = parser.parse(rawMarkdown) + + assertTrue("Parse result is not a document", parsed is Document) + + val children = parsed.children + assertEquals("Parsed document should only have one direct child", 1, children.size) + + val firstChild = children.first() + assertTrue( + "Parsed node should be a Tip node, but was ${firstChild.javaClass.name}", + firstChild is Tip, + ) + + val contents = firstChild.children + assertEquals("Parsed node should only have one direct child", 1, contents.size) + + val firstContent = contents.first() + assertTrue( + "Parsed content should be a Paragraph, but was ${firstContent.javaClass.name}", + firstContent is Paragraph, + ) + assertEquals( + "Content text should be parsed correctly.\n", + "Optional information to help a user be more successful.", + renderer.render(firstContent), + ) + } + + @Test + fun `should parse important alert`() { + val rawMarkdown = + """ + |> [!IMPORTANT] + |> Crucial information necessary for users to succeed. + """ + .trimMargin() + val parsed = parser.parse(rawMarkdown) + + assertTrue("Parse result is not a document", parsed is Document) + + val children = parsed.children + assertEquals("Parsed document should only have one direct child", 1, children.size) + + val firstChild = children.first() + assertTrue( + "Parsed node should be a Important node, but was ${firstChild.javaClass.name}", + firstChild is Important, + ) + + val contents = firstChild.children + assertEquals("Parsed node should only have one direct child", 1, contents.size) + + val firstContent = contents.first() + assertTrue( + "Parsed content should be a Paragraph, but was ${firstContent.javaClass.name}", + firstContent is Paragraph, + ) + assertEquals( + "Content text should be parsed correctly.\n", + "Crucial information necessary for users to succeed.", + renderer.render(firstContent), + ) + } + + @Test + fun `should parse warning alert`() { + val rawMarkdown = + """ + |> [!WARNING] + |> Critical content demanding immediate user attention due to potential risks. + """ + .trimMargin() + val parsed = parser.parse(rawMarkdown) + + assertTrue("Parse result is not a document", parsed is Document) + + val children = parsed.children + assertEquals("Parsed document should only have one direct child", 1, children.size) + + val firstChild = children.first() + assertTrue( + "Parsed node should be a Warning node, but was ${firstChild.javaClass.name}", + firstChild is Warning, + ) + + val contents = firstChild.children + assertEquals("Parsed node should only have one direct child", 1, contents.size) + + val firstContent = contents.first() + assertTrue( + "Parsed content should be a Paragraph, but was ${firstContent.javaClass.name}", + firstContent is Paragraph, + ) + assertEquals( + "Content text should be parsed correctly.\n", + "Critical content demanding immediate user attention due to potential risks.", + renderer.render(firstContent), + ) + } + + @Test + fun `should parse caution alert`() { + val rawMarkdown = + """ + |> [!CAUTION] + |> Negative potential consequences of an action. + """ + .trimMargin() + val parsed = parser.parse(rawMarkdown) + + assertTrue("Parse result is not a document", parsed is Document) + + val children = parsed.children + assertEquals("Parsed document should only have one direct child", 1, children.size) + + val firstChild = children.first() + assertTrue( + "Parsed node should be a Caution node, but was ${firstChild.javaClass.name}", + firstChild is Caution, + ) + + val contents = firstChild.children + assertEquals("Parsed node should only have one direct child", 1, contents.size) + + val firstContent = contents.first() + assertTrue( + "Parsed content should be a Paragraph, but was ${firstContent.javaClass.name}", + firstContent is Paragraph, + ) + assertEquals( + "Content text should be parsed correctly.\n", + "Negative potential consequences of an action.", + renderer.render(firstContent), + ) + } + + @Test + fun `should parse lowercase note alert`() { + val rawMarkdown = + """ + |> [!note] + |> Highlights information that users should take into account, even when skimming. + """ + .trimMargin() + val parsed = parser.parse(rawMarkdown) + + assertTrue("Parse result is not a document", parsed is Document) + + val children = parsed.children + assertEquals("Parsed document should only have one direct child", 1, children.size) + + val firstChild = children.first() + assertTrue( + "Parsed node should be a Note node, but was ${firstChild.javaClass.name}", + firstChild is Note, + ) + + val contents = firstChild.children + assertEquals("Parsed node should only have one direct child", 1, contents.size) + + val firstContent = contents.first() + assertTrue( + "Parsed content should be a Paragraph, but was ${firstContent.javaClass.name}", + firstContent is Paragraph, + ) + assertEquals( + "Content text should be parsed correctly\n", + "Highlights information that users should take into account, even when skimming.", + renderer.render(firstContent), + ) + } + + @Test + fun `should parse lowercase tip alert`() { + val rawMarkdown = + """ + |> [!tip] + |> Optional information to help a user be more successful. + """ + .trimMargin() + val parsed = parser.parse(rawMarkdown) + + assertTrue("Parse result is not a document", parsed is Document) + + val children = parsed.children + assertEquals("Parsed document should only have one direct child", 1, children.size) + + val firstChild = children.first() + assertTrue( + "Parsed node should be a Tip node, but was ${firstChild.javaClass.name}", + firstChild is Tip, + ) + + val contents = firstChild.children + assertEquals("Parsed node should only have one direct child", 1, contents.size) + + val firstContent = contents.first() + assertTrue( + "Parsed content should be a Paragraph, but was ${firstContent.javaClass.name}", + firstContent is Paragraph, + ) + assertEquals( + "Content text should be parsed correctly.\n", + "Optional information to help a user be more successful.", + renderer.render(firstContent), + ) + } + + @Test + fun `should parse lowercase important alert`() { + val rawMarkdown = + """ + |> [!important] + |> Crucial information necessary for users to succeed. + """ + .trimMargin() + val parsed = parser.parse(rawMarkdown) + + assertTrue("Parse result is not a document", parsed is Document) + + val children = parsed.children + assertEquals("Parsed document should only have one direct child", 1, children.size) + + val firstChild = children.first() + assertTrue( + "Parsed node should be a Important node, but was ${firstChild.javaClass.name}", + firstChild is Important, + ) + + val contents = firstChild.children + assertEquals("Parsed node should only have one direct child", 1, contents.size) + + val firstContent = contents.first() + assertTrue( + "Parsed content should be a Paragraph, but was ${firstContent.javaClass.name}", + firstContent is Paragraph, + ) + assertEquals( + "Content text should be parsed correctly.\n", + "Crucial information necessary for users to succeed.", + renderer.render(firstContent), + ) + } + + @Test + fun `should parse lowercase warning alert`() { + val rawMarkdown = + """ + |> [!warning] + |> Critical content demanding immediate user attention due to potential risks. + """ + .trimMargin() + val parsed = parser.parse(rawMarkdown) + + assertTrue("Parse result is not a document", parsed is Document) + + val children = parsed.children + assertEquals("Parsed document should only have one direct child", 1, children.size) + + val firstChild = children.first() + assertTrue( + "Parsed node should be a Warning node, but was ${firstChild.javaClass.name}", + firstChild is Warning, + ) + + val contents = firstChild.children + assertEquals("Parsed node should only have one direct child", 1, contents.size) + + val firstContent = contents.first() + assertTrue( + "Parsed content should be a Paragraph, but was ${firstContent.javaClass.name}", + firstContent is Paragraph, + ) + assertEquals( + "Content text should be parsed correctly.\n", + "Critical content demanding immediate user attention due to potential risks.", + renderer.render(firstContent), + ) + } + + @Test + fun `should parse lowercase caution alert`() { + val rawMarkdown = + """ + |> [!caution] + |> Negative potential consequences of an action. + """ + .trimMargin() + val parsed = parser.parse(rawMarkdown) + + assertTrue("Parse result is not a document", parsed is Document) + + val children = parsed.children + assertEquals("Parsed document should only have one direct child", 1, children.size) + + val firstChild = children.first() + assertTrue( + "Parsed node should be a Caution node, but was ${firstChild.javaClass.name}", + firstChild is Caution, + ) + + val contents = firstChild.children + assertEquals("Parsed node should only have one direct child", 1, contents.size) + + val firstContent = contents.first() + assertTrue( + "Parsed content should be a Paragraph, but was ${firstContent.javaClass.name}", + firstContent is Paragraph, + ) + assertEquals( + "Content text should be parsed correctly.\n", + "Negative potential consequences of an action.", + renderer.render(firstContent), + ) + } + + @Test + fun `should trim trailing and leading empty lines`() { + val rawMarkdown = + """ + |> [!CAUTION] + |> + |> Negative potential consequences of an action. + |> + """ + .trimMargin() + val parsed = parser.parse(rawMarkdown) + + assertTrue("Parse result is not a document", parsed is Document) + + val children = parsed.children + assertEquals("Parsed document should only have one direct child", 1, children.size) + + val firstChild = children.first() + assertTrue( + "Parsed node should be a Caution node, but was ${firstChild.javaClass.name}", + firstChild is Caution, + ) + + val contents = firstChild.children + assertEquals("Parsed node should only have one direct child", 1, contents.size) + + val firstContent = contents.first() + assertTrue( + "Parsed content should be a Paragraph, but was ${firstContent.javaClass.name}", + firstContent is Paragraph, + ) + assertEquals( + "Content text should be parsed correctly.\n", + "Negative potential consequences of an action.", + renderer.render(firstContent), + ) + } + + @Test + fun `should parse malformed entry as blockquote - space after exclamation mark`() { + val rawMarkdown = + """ + |> [! CAUTION] + |> Negative potential consequences of an action. + """ + .trimMargin() + val parsed = parser.parse(rawMarkdown) + + assertTrue("Parse result is not a document", parsed is Document) + + val children = parsed.children + assertEquals("Parsed document should only have one direct child", 1, children.size) + + val firstChild = children.first() + assertTrue( + "Parsed node should be a BlockQuote node, but was ${firstChild.javaClass.name}", + firstChild is BlockQuote, + ) + } + + @Test + fun `should parse malformed entry as blockquote - type not on first line`() { + val rawMarkdown = + """ + |> + |> [! CAUTION] + |> Negative potential consequences of an action. + """ + .trimMargin() + val parsed = parser.parse(rawMarkdown) + + assertTrue("Parse result is not a document", parsed is Document) + + val children = parsed.children + assertEquals("Parsed document should only have one direct child", 1, children.size) + + val firstChild = children.first() + assertTrue( + "Parsed node should be a BlockQuote node, but was ${firstChild.javaClass.name}", + firstChild is BlockQuote, + ) + } + + @Test + fun `should parse malformed entry as blockquote - space before exclamation mark`() { + val rawMarkdown = + """ + |> [ !CAUTION] + |> Negative potential consequences of an action. + """ + .trimMargin() + val parsed = parser.parse(rawMarkdown) + + assertTrue("Parse result is not a document", parsed is Document) + + val children = parsed.children + assertEquals("Parsed document should only have one direct child", 1, children.size) + + val firstChild = children.first() + assertTrue( + "Parsed node should be a BlockQuote node, but was ${firstChild.javaClass.name}", + firstChild is BlockQuote, + ) + } + + @Test + fun `should parse malformed entry as blockquote - space after type`() { + val rawMarkdown = + """ + |> [!CAUTION ] + |> Negative potential consequences of an action. + """ + .trimMargin() + val parsed = parser.parse(rawMarkdown) + + assertTrue("Parse result is not a document", parsed is Document) + + val children = parsed.children + assertEquals("Parsed document should only have one direct child", 1, children.size) + + val firstChild = children.first() + assertTrue( + "Parsed node should be a BlockQuote node, but was ${firstChild.javaClass.name}", + firstChild is BlockQuote, + ) + } + + @Test + fun `should parse malformed entry as blockquote - missing newline`() { + val rawMarkdown = + """ + |> [!CAUTION] Negative potential consequences of an action. + """ + .trimMargin() + val parsed = parser.parse(rawMarkdown) + + assertTrue("Parse result is not a document", parsed is Document) + + val children = parsed.children + assertEquals("Parsed document should only have one direct child", 1, children.size) + + val firstChild = children.first() + assertTrue( + "Parsed node should be a BlockQuote node, but was ${firstChild.javaClass.name}", + firstChild is BlockQuote, + ) + } +} + +private val Node.children: List + get() = buildList { + var nextChild = firstChild + while (nextChild != null) { + add(nextChild) + nextChild = nextChild.next + } + } diff --git a/samples/standalone/build.gradle.kts b/samples/standalone/build.gradle.kts index 219e85078..d4e396d91 100644 --- a/samples/standalone/build.gradle.kts +++ b/samples/standalone/build.gradle.kts @@ -9,8 +9,12 @@ plugins { dependencies { implementation(libs.kotlin.reflect) + implementation(libs.filePicker) implementation(projects.intUi.intUiStandalone) implementation(projects.intUi.intUiDecoratedWindow) + implementation(projects.markdown.core) + implementation(projects.markdown.extensionGfmAlerts) + implementation(compose.desktop.currentOs) { exclude(group = "org.jetbrains.compose.material") } diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/Main.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/Main.kt index e911bb658..1596668ae 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/Main.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/Main.kt @@ -55,7 +55,7 @@ fun main() { ) { DecoratedWindow( onCloseRequest = { exitApplication() }, - title = "Jewel component catalog", + title = "Jewel standalone sample", icon = icon, ) { TitleBarView() diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/MarkdownView.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/MarkdownView.kt new file mode 100644 index 000000000..6cbc62ffe --- /dev/null +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/MarkdownView.kt @@ -0,0 +1,33 @@ +package org.jetbrains.jewel.samples.standalone.view + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import org.jetbrains.jewel.foundation.modifier.trackActivation +import org.jetbrains.jewel.foundation.theme.JewelTheme +import org.jetbrains.jewel.samples.standalone.view.markdown.JewelReadme +import org.jetbrains.jewel.samples.standalone.view.markdown.MarkdownEditor +import org.jetbrains.jewel.samples.standalone.view.markdown.MarkdownPreview +import org.jetbrains.jewel.samples.standalone.viewmodel.View +import org.jetbrains.jewel.ui.Orientation +import org.jetbrains.jewel.ui.component.Divider + +@View(title = "Markdown", position = 2, icon = "icons/markdown.svg") +@Composable +fun MarkdownDemo() { + Row(Modifier.trackActivation().fillMaxSize().background(JewelTheme.globalColors.paneBackground)) { + var currentMarkdown by remember { mutableStateOf(JewelReadme) } + MarkdownEditor(currentMarkdown, { currentMarkdown = it }, Modifier.fillMaxHeight().weight(1f)) + + Divider(Orientation.Vertical, Modifier.fillMaxHeight()) + + MarkdownPreview(currentMarkdown, Modifier.fillMaxHeight().weight(1f)) + } +} diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/JewelReadme.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/JewelReadme.kt new file mode 100644 index 000000000..54afa0886 --- /dev/null +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/JewelReadme.kt @@ -0,0 +1,369 @@ +package org.jetbrains.jewel.samples.standalone.view.markdown + +import org.intellij.lang.annotations.Language + +@Language("Markdown") +internal val JewelReadme = """ +# Jewel: a Compose for Desktop theme + +Jewel logo + +Jewel aims at recreating the IntelliJ Platform's _New UI_ Swing Look and Feel in Compose for Desktop, providing a +desktop-optimized theme and set of components. + +> [!WARNING] +> +> This project is in active development, and caution is advised when considering it for production uses. You _can_ use +> it, but you should expect APIs to change often, things to move around and/or break, and all that jazz. Binary +> compatibility is not guaranteed across releases, and APIs are still in flux and subject to change. +> +> Writing 3rd party IntelliJ Plugins in Compose for Desktop is currently **not officially supported** by the IntelliJ +> Platform. It should work, but your mileage may vary, and if things break you're on your own. +> +> Use at your own risk! + +Jewel provides an implementation of the IntelliJ Platform themes that can be used in any Compose for Desktop +application. Additionally, it has a Swing LaF Bridge that only works in the IntelliJ Platform (i.e., used to create IDE +plugins), but automatically mirrors the current Swing LaF into Compose for a native-looking, consistent UI. + +## Getting started + +To use Jewel in your app, you only need to add the relevant dependency. There are two scenarios: standalone Compose for +Desktop app, and IntelliJ Platform plugin. + +For now, Jewel artifacts aren't available on Maven Central. You need to add a custom Maven repository to your build: + +```kotlin +repositories { + maven("https://packages.jetbrains.team/maven/p/kpm/public/") + // Any other repositories you need (e.g., mavenCentral()) +} +``` + +If you're writing a **standalone app**, then you should depend on the `int-ui-standalone` artifact: + +```kotlin +dependencies { + implementation("org.jetbrains.jewel:jewel-int-ui-standalone:[jewel version]") + + // Optional, for custom decorated windows: + implementation("org.jetbrains.jewel:jewel-int-ui-decorated-window:[jewel version]") +} +``` + +For an **IntelliJ Platform plugin**, then you should depend on the appropriate `ide-laf-bridge` artifact: + +```kotlin +dependencies { + // The platform version is a supported major IJP version (e.g., 232 or 233 for 2023.2 and 2023.3 respectively) + implementation("org.jetbrains.jewel:jewel-ide-laf-bridge-[platform version]:[jewel version]") +} +``` + +
+ +> [!TIP] +>
+> +> +> +> If you want to learn more about Jewel and Compose for Desktop and why they're a great, modern solution for your +> desktop +> UI needs, check out [this talk](https://www.droidcon.com/2023/11/15/meet-jewelcreate-ide-plugins-in-compose/) by Jewel +> contributors Sebastiano and Chris. +> +> It covers why Compose is a viable choice, and an overview of the Jewel project, plus +> some real-life use cases.
+ +
+ +## Project structure + +The project is split in modules: + +1. `buildSrc` contains the build logic, including: + * The `jewel` and `jewel-publish` configuration plugins + * The `jewel-check-public-api` and `jewel-linting` configuration plugins + * The Theme Palette generator plugin + * The Studio Releases generator plugin +2. `foundation` contains the foundational Jewel functionality: + * Basic components without strong styling (e.g., `SelectableLazyColumn`, `BasicLazyTree`) + * The `JewelTheme` interface with a few basic composition locals + * The state management primitives + * The Jewel annotations + * A few other primitives +3. `ui` contains all the styled components and custom painters logic +4. `decorated-window` contains basic, unstyled functionality to have custom window decoration on the JetBrains Runtime +5. `int-ui` contains two modules: + * `int-ui-standalone` has a standalone version of the Int UI styling values that can be used in any Compose for + Desktop app + * `int-ui-decorated-window` has a standalone version of the Int UI styling values for the custom window decoration + that can be used in any Compose for Desktop app +6. `ide-laf-bridge` contains the Swing LaF bridge to use in IntelliJ Platform plugins (see more below) +7. `samples` contains the example apps, which showcase the available components: + * `standalone` is a regular CfD app, using the standalone theme definitions and custom window decoration + * `ide-plugin` is an IntelliJ plugin that showcases the use of the Swing Bridge + +## Branching strategy and IJ Platforms + +Code on the main branch is developed and tested against the current latest IntelliJ Platform version. + +When the EAP for a new major version starts, we cut a `releases/xxx` release branch, where `xxx` is the tracked major +IJP version. At that point, the main branch starts tracking the latest available major IJP version, and changes are +cherry-picked into each release branch as needed. All active release branches have the same functionality (where +supported by the corresponding IJP version), but might differ in platform version-specific fixes and internals. + +The standalone Int UI theme will always work the same way as the latest major IJP version; release branches will not +include the `int-ui` module, which is always released from the main branch. + +Releases of Jewel are always cut from a tag on the main branch; the HEAD of each `releases/xxx` branch is then tagged +as `[mainTag]-xxx`, and used to publish the artifacts for that major IJP version. + +> ![IMPORTANT] +> We only support the latest build of IJP for each major IJP version. If the latest 233 version is 2023.3.3, for +> example, we will only guarantee that Jewel works on that. Versions 2023.3.0–2023.3.2 might or might not work. + +### Int UI Standalone theme + +The standalone theme can be used in any Compose for Desktop app. You use it as a normal theme, and you can customise it +to your heart's content. By default, it matches the official Int UI specs. + +For an example on how to set up a standalone app, you can refer to +the [`standalone` sample](samples/standalone/build.gradle.kts). + +> [!WARNING] +> Note that Jewel **requires** the JetBrains Runtime to work correctly. Some features like font loading depend on it, +> as it has extra features and patches for UI functionalities that aren't available in other JDKs. +> We **do not support** running Jewel on any other JDK. + +To use Jewel components in a non-IntelliJ Platform environment, you need to wrap your UI hierarchy in a `IntUiTheme` +composable: + +```kotlin +IntUiTheme(isDark = false) { + // ... +} +``` + +If you want more control over the theming, you can use other `IntUiTheme` overloads, like the standalone sample does. + +#### Custom window decoration + +The JetBrains Runtime allows windows to have a custom decoration instead of the regular title bar. + +![A screenshot of the custom window decoration in the standalone sample](art/docs/custom-chrome.png) + +The standalone sample app shows how to easily get something that looks like a JetBrains IDE; if you want to go _very_ +custom, you only need to depend on the `decorated-window` module, which contains all the required primitives, but not +the Int UI styling. + +To get an IntelliJ-like custom title bar, you need to pass the window decoration styling to your theme call, and add the +`DecoratedWindow` composable at the top level of the theme: + +```kotlin +IntUiTheme( + themeDefinition, + componentStyling = { + themeDefinition.decoratedWindowComponentStyling( + titleBarStyle = TitleBarStyle.light() + ) + }, +) { + DecoratedWindow( + onCloseRequest = { exitApplication() }, + ) { + // ... + } +} +``` + +### Running on the IntelliJ Platform: the Swing bridge + +Jewel includes a crucial element for proper integration with the IDE: a bridge between the Swing components — theme +and LaF — and the Compose world. + +This bridge ensures that we pick up the colours, typography, metrics, and images as defined in the current IntelliJ +theme, and apply them to the Compose components as well. This means Jewel will automatically adapt to IntelliJ Platform +themes that use the [standard theming](https://plugins.jetbrains.com/docs/intellij/themes-getting-started.html) +mechanisms. + +> [!NOTE] +> IntelliJ themes that use non-standard mechanisms (such as providing custom UI implementations for Swing components) +> are not, and can never, be supported. + +If you're writing an IntelliJ Platform plugin, you should use the `SwingBridgeTheme` instead of the standalone theme: + +```kotlin +SwingBridgeTheme { + // ... +} +``` + +#### Supported IntelliJ Platform versions + +To use Jewel in the IntelliJ Platform, you should depend on the appropriate `jewel-ide-laf-bridge-*` artifact, which +will bring in the necessary transitive dependencies. These are the currently supported versions of the IntelliJ Platform +and the branch on which the corresponding bridge code lives: + +| IntelliJ Platform version(s) | Branch to use | + |------------------------------|-------------------| +| 2024.1 (EAP 3+) | `main` | +| 2023.3 | `releases/233` | +| 2023.2 | `releases/232` | +| 2023.1 or older | **Not supported** | + +For an example on how to set up an IntelliJ Plugin, you can refer to +the [`ide-plugin` sample](samples/ide-plugin/build.gradle.kts). + +#### Accessing icons + +When you want to draw an icon from the resources, you can either use the `Icon` composable and pass it the resource path +and the corresponding class to look up the classpath from, or go one lever deeper and use the lower level, +`Painter`-based API. + +The `Icon` approach looks like this: + +```kotlin +// Load the "close" icon from the IDE's AllIcons class +Icon( + "actions/close.svg", + iconClass = AllIcons::class.java, + contentDescription = "Close", +) +``` + +To obtain a `Painter`, instead, you'd use: + +```kotlin +val painterProvider = rememberResourcePainterProvider( + path = "actions/close.svg", + iconClass = AllIcons::class.java +) +val painter by painterProvider.getPainter() +``` + +#### Icon runtime patching + +Jewel emulates the under-the-hood machinations that happen in the IntelliJ Platform when loading icons. Specifically, +the resource will be subject to some transformations before being loaded. + +For example, in the IDE, if New UI is active, the icon path may be replaced with a different one. Some key colors in SVG +icons will also be replaced based on the current theme. See +[the docs](https://plugins.jetbrains.com/docs/intellij/work-with-icons-and-images.html#new-ui-icons). + +Beyond that, even in standalone, Jewel will pick up icons with the appropriate dark/light variant for the current theme, +and for bitmap icons it will try to pick the 2x variants based on the `LocalDensity`. + +If you have a _stateful_ icon, that is if you need to display different icons based on some state, you can use the +`PainterProvider.getPainter(PainterHint...)` overload. You can then use one of the state-mapping `PainterHint` to let +Jewel load the appropriate icon automatically: + +```kotlin +// myState implements SelectableComponentState and has a ToggleableState property +val myPainter by myPainterProvider.getPainter( + if (myState.toggleableState == ToggleableState.Indeterminate) { + IndeterminateHint + } else { + PainterHint.None + }, + Selected(myState), + Stateful(myState), +) +``` + +Where the `IndeterminateHint` looks like this: + +```kotlin +private object IndeterminateHint : PainterSuffixHint() { + override fun suffix(): String = "Indeterminate" +} +``` + +Assuming the PainterProvider has a base path of `components/myIcon.svg`, Jewel will automatically translate it to the +right path based on the state. If you want to learn more about this system, look at the `PainterHint` interface and its +implementations. + +### Fonts + +To load a system font, you can obtain it by its family name: + +```kotlin +val myFamily = FontFamily("My Family") +``` + +If you want to use a font embedded in the JetBrains Runtime, you can use the `EmbeddedFontFamily` API instead: + +```kotlin +import javax.swing.text.StyledEditorKit.FontFamilyAction + +// Will return null if no matching font family exists in the JBR +val myEmbeddedFamily = EmbeddedFontFamily("Embedded family") + +// It's recommended to load a fallback family when dealing with embedded familes +val myFamily = myEmbeddedFamily ?: FontFamily("Fallback family") +``` + +You can obtain a `FontFamily` from any `java.awt.Font` — including from `JBFont`s — by using the `asComposeFontFamily()` +API: + +```kotlin +val myAwtFamily = myFont.asComposeFontFamily() + +// This will attempt to resolve the logical AWT font +val myLogicalFamily = Font("Dialog").asComposeFontFamily() + +// This only works in the IntelliJ Platform, +// since JBFont is only available there +val myLabelFamily = JBFont.label().asComposeFontFamily() +``` + +### Swing interoperability + +As this is Compose for Desktop, you get a good degree of interoperability with Swing. To avoid glitches and z-order +issues, you should enable the +[experimental Swing rendering pipeline](https://blog.jetbrains.com/kotlin/2023/08/compose-multiplatform-1-5-0-release/#enhanced-swing-interop) +before you initialize Compose content. + +The `ToolWindow.addComposeTab()` extension function provided by the `ide-laf-bridge` module will take care of that for +you. However, if you want to also enable it in other scenarios and in standalone applications, you can call the +`enableNewSwingCompositing()` function in your Compose entry points (that is, right before creating a `ComposePanel`). + +> [!NOTE] +> The new Swing rendering pipeline is experimental and may have performance repercussions when using infinitely +> repeating animations. This is a known issue by the Compose Multiplatform team, that requires changes in the Java +> runtime to fix. Once the required changes are made in the JetBrains Runtime, we'll remove this notice. + +## Written with Jewel + +Here is a small selection of projects that use Compose for Desktop and Jewel: + +* [Package Search](https://github.com/JetBrains/package-search-intellij-plugin) (IntelliJ Platform plugin) +* [Kotlin Explorer](https://github.com/romainguy/kotlin-explorer) (standalone app) +* ...and more to come! + +## Need help? + +You can find help on the [`#jewel`](https://app.slack.com/client/T09229ZC6/C05T8U2C31T) channel on the Kotlin Slack. +If you don't already have access to the Kotlin Slack, you can request it +[here](https://surveys.jetbrains.com/s3/kotlin-slack-sign-up). + +## License + +Jewel is licensed under the [Apache 2.0 license](https://github.com/JetBrains/jewel/blob/main/LICENSE). + +``` +Copyright 2022–4 JetBrains s.r.o. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` +""".trimIndent() diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownCatalog.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownCatalog.kt new file mode 100644 index 000000000..0e6453c72 --- /dev/null +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownCatalog.kt @@ -0,0 +1,1777 @@ +package org.jetbrains.jewel.samples.standalone.view.markdown + +import org.intellij.lang.annotations.Language + +@Language("Markdown") +internal val MarkdownCatalog = + """ +## GitHub Markdown catalog + + +### My fixtures + +#### Left-aligned image + + + +#### Right-aligned image + + + +#### Alerts + +> [!NOTE] +> Highlights information that users should take into account, even when skimming. + +> [!TIP] +> Optional information to help a user be more successful. + +> [!IMPORTANT] +> Crucial information necessary for users to succeed. + +> [!WARNING] +> Critical content demanding immediate user attention due to potential risks. + +> [!CAUTION] +> Negative potential consequences of an action. + +### Code snippet + +https://github.com/sindresorhus/generate-github-markdown-css/blob/6d6a328dc9706d4e6f1bcc524ed1ad1b0448a3ea/index.js#L25-L28 + +--- + +--- + +Borrowed from https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet + +--- + +This is intended as a quick reference and showcase. For more complete info, see [John Gruber's original spec](http://daringfireball.net/projects/markdown/) and the [Github-flavored Markdown info page](http://github.github.com/github-flavored-markdown/). + +Note that there is also a [Cheatsheet specific to Markdown Here](./Markdown-Here-Cheatsheet) if that's what you're looking for. + +PLEASE DO NOT EDIT THIS PAGE! You can play around with Markdown on our [live demo page](http://www.markdown-here.com/livedemo.html). + +##### Table of Contents +[Headers](#headers) +[Emphasis](#emphasis) +[Lists](#lists) +[Links](#links) +[Images](#images) +[Code and Syntax Highlighting](#code) +[Tables](#tables) +[Blockquotes](#blockquotes) +[Inline HTML](#html) +[Horizontal Rule](#hr) +[Line Breaks](#lines) +[Youtube videos](#videos) + + +## Headers + +```no-highlight +# H1 +## H2 +### H3 +#### H4 +##### H5 +###### H6 + +Alternatively, for H1 and H2, an underline-ish style: + +Alt-H1 +====== + +Alt-H2 +------ +``` + +# H1 +## H2 +### H3 +#### H4 +##### H5 +###### H6 + +Alternatively, for H1 and H2, an underline-ish style: + +Alt-H1 +====== + +Alt-H2 +------ + + +## Emphasis + +```no-highlight +Emphasis, aka italics, with *asterisks* or _underscores_. + +Strong emphasis, aka bold, with **asterisks** or __underscores__. + +Combined emphasis with **asterisks and _underscores_**. + +Strikethrough uses two tildes. ~~Scratch this.~~ +``` + +Emphasis, aka italics, with *asterisks* or _underscores_. + +Strong emphasis, aka bold, with **asterisks** or __underscores__. + +Combined emphasis with **asterisks and _underscores_**. + +Strikethrough uses two tildes. ~~Scratch this.~~ + + + +## Lists + +(In this example, leading and trailing spaces are shown with with dots: ⋅) + +```no-highlight +1. First ordered list item +2. Another item +⋅⋅* Unordered sub-list. +1. Actual numbers don't matter, just that it's a number +⋅⋅1. Ordered sub-list +4. And another item. + +⋅⋅⋅You can have properly indented paragraphs within list items. Notice the blank line above, and the leading spaces (at least one, but we'll use three here to also align the raw Markdown). + +⋅⋅⋅To have a line break without a paragraph, you will need to use two trailing spaces.⋅⋅ +⋅⋅⋅Note that this line is separate, but within the same paragraph.⋅⋅ +⋅⋅⋅(This is contrary to the typical GFM line break behaviour, where trailing spaces are not required.) + +* Unordered list can use asterisks +- Or minuses ++ Or pluses +``` + +1. First ordered list item +2. Another item + * Unordered sub-list. +1. Actual numbers don't matter, just that it's a number + 1. Ordered sub-list +4. And another item. + + You can have properly indented paragraphs within list items. Notice the blank line above, and the leading spaces (at least one, but we'll use three here to also align the raw Markdown). + + To have a line break without a paragraph, you will need to use two trailing spaces. + Note that this line is separate, but within the same paragraph. + (This is contrary to the typical GFM line break behaviour, where trailing spaces are not required.) + +* Unordered list can use asterisks +- Or minuses ++ Or pluses + +1. foo + 1. bar + 1. baz + 1. faz +2. foo2 + + +- foo + - bar + - baz + - faz +- foo2 + + +1. foo + - bar + 1. baz + - faz + +- foo + 1. bar + - baz + 1. faz + + + +1. Lists in a list item: + - Indented four spaces. + * indented eight spaces. + - Four spaces again. +2. Multiple paragraphs in a list items: + It's best to indent the paragraphs four spaces + You can get away with three, but it can get + confusing when you nest other things. + Stick to four. + + We indented the first line an extra space to align + it with these paragraphs. In real use, we might do + that to the entire list so that all items line up. + + This paragraph is still part of the list item, but it looks messy to humans. So it's a good idea to wrap your nested paragraphs manually, as we did with the first two. + +3. Blockquotes in a list item: + + > Skip a line and + > indent the >'s four spaces. + +4. Preformatted text in a list item: + + Skip a line and indent eight spaces. + That's four spaces for the list + and four to trigger the code block. + + + + +## Inline HTML + +To reboot your computer, press ctrl+alt+del. + + + + +## Links + +There are two ways to create links. + +```no-highlight +[I'm an inline-style link](https://www.google.com) + +[I'm an inline-style link with title](https://www.google.com "Google's Homepage") + +[I'm a reference-style link][Arbitrary case-insensitive reference text] + +[I'm a relative reference to a repository file](../blob/master/LICENSE) + +[You can use numbers for reference-style link definitions][1] + +Or leave it empty and use the [link text itself] + +Some text to show that the reference links can follow later. + +[arbitrary case-insensitive reference text]: https://www.mozilla.org +[1]: http://slashdot.org +[link text itself]: http://www.reddit.com +``` + +[I'm an inline-style link](https://www.google.com) + +[I'm an inline-style link with title](https://www.google.com "Google's Homepage") + +[I'm a reference-style link][Arbitrary case-insensitive reference text] + +[I'm a relative reference to a repository file](../blob/master/LICENSE) + +[You can use numbers for reference-style link definitions][1] + +Or leave it empty and use the [link text itself] + +Some text to show that the reference links can follow later. + +[arbitrary case-insensitive reference text]: https://www.mozilla.org +[1]: http://slashdot.org +[link text itself]: http://www.reddit.com + + +## Images + +```no-highlight +Here's our logo (hover to see the title text): + +Inline-style: +![alt text](https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png "Logo Title Text 1") + +Reference-style: +![alt text][logo] + +[logo]: https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png "Logo Title Text 2" +``` + +Here's our logo (hover to see the title text): + +Inline-style: +![alt text](https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png "Logo Title Text 1") + +Reference-style: +![alt text][logo] + +[logo]: https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png "Logo Title Text 2" + + +## Code and Syntax Highlighting + +Code blocks are part of the Markdown spec, but syntax highlighting isn't. However, many renderers -- like Github's and *Markdown Here* -- support syntax highlighting. Which languages are supported and how those language names should be written will vary from renderer to renderer. *Markdown Here* supports highlighting for dozens of languages (and not-really-languages, like diffs and HTTP headers); to see the complete list, and how to write the language names, see the [highlight.js demo page](http://softwaremaniacs.org/media/soft/highlight/test.html). + +```no-highlight +Inline `code` has `back-ticks around` it. +``` + +Inline `code` has `back-ticks around` it. + +Blocks of code are either fenced by lines with three back-ticks ```, or are indented with four spaces. I recommend only using the fenced code blocks -- they're easier and only they support syntax highlighting. + +
```javascript
+var s = "JavaScript syntax highlighting";
+alert(s);
+```
+
+```python
+s = "Python syntax highlighting"
+print s
+```
+
+```
+No language indicated, so no syntax highlighting.
+But let's throw in a <b>tag</b>.
+```
+
+ + + +```javascript +var s = "JavaScript syntax highlighting"; +alert(s); +``` + +```python +s = "Python syntax highlighting" +print s +``` + +``` +No language indicated, so no syntax highlighting in Markdown Here (varies on Github). +But let's throw in a tag. +``` + + + +## Tables + +Tables aren't part of the core Markdown spec, but they are part of GFM and *Markdown Here* supports them. They are an easy way of adding tables to your email -- a task that would otherwise require copy-pasting from another application. + +```no-highlight +Colons can be used to align columns. + +| Tables | Are | Cool | +| ------------- |:-------------:| -----:| +| col 3 is | right-aligned | | +| col 2 is | centered | | +| zebra stripes | are neat | | + +The outer pipes (|) are optional, and you don't need to make the raw Markdown line up prettily. You can also use inline Markdown. + +Markdown | Less | Pretty +--- | --- | --- +*Still* | `renders` | **nicely** +1 | 2 | 3 +``` + +Colons can be used to align columns. + +| Tables | Are | Cool | +| ------------- |:-------------:| -----:| +| col 3 is | right-aligned | | +| col 2 is | centered | | +| zebra stripes | are neat | | + +The outer pipes (|) are optional, and you don't need to make the raw Markdown line up prettily. You can also use inline Markdown. + +Markdown | Less | Pretty +--- | --- | --- +*Still* | `renders` | **nicely** +1 | 2 | 3 + + +## Blockquotes + +```no-highlight +> Blockquotes are very handy in email to emulate reply text. +> This line is part of the same quote. + +Quote break. + +> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote. +``` + +> Blockquotes are very handy in email to emulate reply text. +> This line is part of the same quote. + +Quote break. + +> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote. + + +## Inline HTML + +You can also use raw HTML in your Markdown, and it'll mostly work pretty well. + +```no-highlight +
+
Definition list
+
Is something people use sometimes.
+ +
Markdown in HTML
+
Does *not* work **very** well. Use HTML tags.
+
+``` + +
+
Definition list
+
Is something people use sometimes.
+ +
Markdown in HTML
+
Does *not* work **very** well. Use HTML tags.
+
+ + +## Horizontal Rule + +``` +Three or more... + +--- + +Hyphens + +*** + +Asterisks + +___ + +Underscores +``` + +Three or more... + +--- + +Hyphens + +*** + +Asterisks + +___ + +Underscores + + +## Line Breaks + +My basic recommendation for learning how line breaks work is to experiment and discover -- hit <Enter> once (i.e., insert one newline), then hit it twice (i.e., insert two newlines), see what happens. You'll soon learn to get what you want. "Markdown Toggle" is your friend. + +Here are some things to try out: + +``` +Here's a line for us to start with. + +This line is separated from the one above by two newlines, so it will be a *separate paragraph*. + +This line is also a separate paragraph, but... +This line is only separated by a single newline, so it's a separate line in the *same paragraph*. +``` + +Here's a line for us to start with. + +This line is separated from the one above by two newlines, so it will be a *separate paragraph*. + +This line is also begins a separate paragraph, but... +This line is only separated by a single newline, so it's a separate line in the *same paragraph*. + +(Technical note: *Markdown Here* uses GFM line breaks, so there's no need to use MD's two-space line breaks.) + + +## Youtube videos + +They can't be added directly but you can add an image with a link to the video like this: + +```no-highlight + +``` + +Or, in pure Markdown, but losing the image sizing and border: + +```no-highlight +[![IMAGE ALT TEXT HERE](http://img.youtube.com/vi/YOUTUBE_VIDEO_ID_HERE/0.jpg)](http://www.youtube.com/watch?v=YOUTUBE_VIDEO_ID_HERE) +``` + +Referencing a bug by #bugID in your git commit links it to the slip. For example #1. + + + +## Task List + +- [ ] foo + - [ ] foo + - [x] foo +- [x] foo + + + + + + +[test]: http://google.com/ "Google" + +# A heading + +Just a note, I've found that I can't test my markdown parser vs others. +For example, both markdown.js and showdown code blocks in lists wrong. They're +also completely [inconsistent][test] with regards to paragraphs in list items. + +A link. Not anymore. + + + +* List Item 1 + +* List Item 2 + * New List Item 1 + Hi, this is a list item. + * New List Item 2 + Another item + Code goes here. + Lots of it... + * New List Item 3 + The last item + +* List Item 3 +The final item. + +* List Item 4 +The real final item. + +Paragraph. + +> * bq Item 1 +> * bq Item 2 +> * New bq Item 1 +> * New bq Item 2 +> Text here + +* * * + +> Another blockquote! +> I really need to get +> more creative with +> mockup text.. +> markdown.js breaks here again + +Another Heading +------------- + +Hello *world*. Here is a [link](//hello). +And an image ![alt](src). + + Code goes here. + Lots of it... + + + + +> A list within a blockquote: +> +> * asterisk 1 +> * asterisk 2 +> * asterisk 3 + + + + + +***This is strong and em.*** + +So is ***this*** word. + +___This is strong and em.___ + +So is ___this___ word. + + + + + +## Unordered + +Asterisks tight: + +* asterisk 1 +* asterisk 2 +* asterisk 3 + + +Asterisks loose: + +* asterisk 1 + +* asterisk 2 + +* asterisk 3 + +* * * + +Pluses tight: + ++ Plus 1 ++ Plus 2 ++ Plus 3 + + +Pluses loose: + ++ Plus 1 + ++ Plus 2 + ++ Plus 3 + +* * * + + +Minuses tight: + +- Minus 1 +- Minus 2 +- Minus 3 + + +Minuses loose: + +- Minus 1 + +- Minus 2 + +- Minus 3 + + +## Ordered + +Tight: + +1. First +2. Second +3. Third + +and: + +1. One +2. Two +3. Three + + +Loose using tabs: + +1. First + +2. Second + +3. Third + +and using spaces: + +1. One + +2. Two + +3. Three + +Multiple paragraphs: + +1. Item 1, graf one. + + Item 2. graf two. The quick brown fox jumped over the lazy dog's + back. + +2. Item 2. + +3. Item 3. + + + +## Nested + +* Tab + * Tab + * Tab + +Here's another: + +1. First +2. Second: + * Fee + * Fie + * Foe +3. Third + +Same thing but with paragraphs: + +1. First + +2. Second: + * Fee + * Fie + * Foe + +3. Third + + +This was an error in Markdown 1.0.1: + +* this + + * sub + + that + + + + + + +> foo +> +> > bar +> +> foo + + + + + + +Markdown: Syntax +================ + + + + +* [Overview](#overview) + * [Philosophy](#philosophy) + * [Inline HTML](#html) + * [Automatic Escaping for Special Characters](#autoescape) +* [Block Elements](#block) + * [Paragraphs and Line Breaks](#p) + * [Headers](#header) + * [Blockquotes](#blockquote) + * [Lists](#list) + * [Code Blocks](#precode) + * [Horizontal Rules](#hr) +* [Span Elements](#span) + * [Links](#link) + * [Emphasis](#em) + * [Code](#code) + * [Images](#img) +* [Miscellaneous](#misc) + * [Backslash Escapes](#backslash) + * [Automatic Links](#autolink) + + +**Note:** This document is itself written using Markdown; you +can [see the source for it by adding '.text' to the URL][src]. + + [src]: /projects/markdown/syntax.text + +* * * + +

Overview

+ +

Philosophy

+ +Markdown is intended to be as easy-to-read and easy-to-write as is feasible. + +Readability, however, is emphasized above all else. A Markdown-formatted +document should be publishable as-is, as plain text, without looking +like it's been marked up with tags or formatting instructions. While +Markdown's syntax has been influenced by several existing text-to-HTML +filters -- including [Setext] [1], [atx] [2], [Textile] [3], [reStructuredText] [4], +[Grutatext] [5], and [EtText] [6] -- the single biggest source of +inspiration for Markdown's syntax is the format of plain text email. + + [1]: http://docutils.sourceforge.net/mirror/setext.html + [2]: http://www.aaronsw.com/2002/atx/ + [3]: http://textism.com/tools/textile/ + [4]: http://docutils.sourceforge.net/rst.html + [5]: http://www.triptico.com/software/grutatxt.html + [6]: http://ettext.taint.org/doc/ + +To this end, Markdown's syntax is comprised entirely of punctuation +characters, which punctuation characters have been carefully chosen so +as to look like what they mean. E.g., asterisks around a word actually +look like \*emphasis\*. Markdown lists look like, well, lists. Even +blockquotes look like quoted passages of text, assuming you've ever +used email. + + + +

Inline HTML

+ +Markdown's syntax is intended for one purpose: to be used as a +format for *writing* for the web. + +Markdown is not a replacement for HTML, or even close to it. Its +syntax is very small, corresponding only to a very small subset of +HTML tags. The idea is *not* to create a syntax that makes it easier +to insert HTML tags. In my opinion, HTML tags are already easy to +insert. The idea for Markdown is to make it easy to read, write, and +edit prose. HTML is a *publishing* format; Markdown is a *writing* +format. Thus, Markdown's formatting syntax only addresses issues that +can be conveyed in plain text. + +For any markup that is not covered by Markdown's syntax, you simply +use HTML itself. There's no need to preface it or delimit it to +indicate that you're switching from Markdown to HTML; you just use +the tags. + +The only restrictions are that block-level HTML elements -- e.g. `
`, +``, `
`, `

`, etc. -- must be separated from surrounding +content by blank lines, and the start and end tags of the block should +not be indented with tabs or spaces. Markdown is smart enough not +to add extra (unwanted) `

` tags around HTML block-level tags. + +For example, to add an HTML table to a Markdown article: + + This is a regular paragraph. + +

+ + + +
Foo
+ + This is another regular paragraph. + +Note that Markdown formatting syntax is not processed within block-level +HTML tags. E.g., you can't use Markdown-style `*emphasis*` inside an +HTML block. + +Span-level HTML tags -- e.g. ``, ``, or `` -- can be +used anywhere in a Markdown paragraph, list item, or header. If you +want, you can even use HTML tags instead of Markdown formatting; e.g. if +you'd prefer to use HTML `` or `` tags instead of Markdown's +link or image syntax, go right ahead. + +Unlike block-level HTML tags, Markdown syntax *is* processed within +span-level tags. + + +

Automatic Escaping for Special Characters

+ +In HTML, there are two characters that demand special treatment: `<` +and `&`. Left angle brackets are used to start tags; ampersands are +used to denote HTML entities. If you want to use them as literal +characters, you must escape them as entities, e.g. `<`, and +`&`. + +Ampersands in particular are bedeviling for web writers. If you want to +write about 'AT&T', you need to write '`AT&T`'. You even need to +escape ampersands within URLs. Thus, if you want to link to: + + http://images.google.com/images?num=30&q=larry+bird + +you need to encode the URL as: + + http://images.google.com/images?num=30&q=larry+bird + +in your anchor tag `href` attribute. Needless to say, this is easy to +forget, and is probably the single most common source of HTML validation +errors in otherwise well-marked-up web sites. + +Markdown allows you to use these characters naturally, taking care of +all the necessary escaping for you. If you use an ampersand as part of +an HTML entity, it remains unchanged; otherwise it will be translated +into `&`. + +So, if you want to include a copyright symbol in your article, you can write: + + © + +and Markdown will leave it alone. But if you write: + + AT&T + +Markdown will translate it to: + + AT&T + +Similarly, because Markdown supports [inline HTML](#html), if you use +angle brackets as delimiters for HTML tags, Markdown will treat them as +such. But if you write: + + 4 < 5 + +Markdown will translate it to: + + 4 < 5 + +However, inside Markdown code spans and blocks, angle brackets and +ampersands are *always* encoded automatically. This makes it easy to use +Markdown to write about HTML code. (As opposed to raw HTML, which is a +terrible format for writing about HTML syntax, because every single `<` +and `&` in your example code needs to be escaped.) + + +* * * + + +

Block Elements

+ + +

Paragraphs and Line Breaks

+ +A paragraph is simply one or more consecutive lines of text, separated +by one or more blank lines. (A blank line is any line that looks like a +blank line -- a line containing nothing but spaces or tabs is considered +blank.) Normal paragraphs should not be intended with spaces or tabs. + +The implication of the "one or more consecutive lines of text" rule is +that Markdown supports "hard-wrapped" text paragraphs. This differs +significantly from most other text-to-HTML formatters (including Movable +Type's "Convert Line Breaks" option) which translate every line break +character in a paragraph into a `
` tag. + +When you *do* want to insert a `
` break tag using Markdown, you +end a line with two or more spaces, then type return. + +Yes, this takes a tad more effort to create a `
`, but a simplistic +"every line break is a `
`" rule wouldn't work for Markdown. +Markdown's email-style [blockquoting][bq] and multi-paragraph [list items][l] +work best -- and look better -- when you format them with hard breaks. + + [bq]: #blockquote + [l]: #list + + + + + +Markdown supports two styles of headers, [Setext] [1] and [atx] [2]. + +Setext-style headers are "underlined" using equal signs (for first-level +headers) and dashes (for second-level headers). For example: + + This is an H1 + ============= + + This is an H2 + ------------- + +Any number of underlining `=`'s or `-`'s will work. + +Atx-style headers use 1-6 hash characters at the start of the line, +corresponding to header levels 1-6. For example: + + # This is an H1 + + ## This is an H2 + + ###### This is an H6 + +Optionally, you may "close" atx-style headers. This is purely +cosmetic -- you can use this if you think it looks better. The +closing hashes don't even need to match the number of hashes +used to open the header. (The number of opening hashes +determines the header level.) : + + # This is an H1 # + + ## This is an H2 ## + + ### This is an H3 ###### + + +

Blockquotes

+ +Markdown uses email-style `>` characters for blockquoting. If you're +familiar with quoting passages of text in an email message, then you +know how to create a blockquote in Markdown. It looks best if you hard +wrap the text and put a `>` before every line: + + > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, + > consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. + > Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. + > + > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse + > id sem consectetuer libero luctus adipiscing. + +Markdown allows you to be lazy and only put the `>` before the first +line of a hard-wrapped paragraph: + + > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, + consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. + Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. + + > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse + id sem consectetuer libero luctus adipiscing. + +Blockquotes can be nested (i.e. a blockquote-in-a-blockquote) by +adding additional levels of `>`: + + > This is the first level of quoting. + > + > > This is nested blockquote. + > + > Back to the first level. + +Blockquotes can contain other Markdown elements, including headers, lists, +and code blocks: + + > ## This is a header. + > + > 1. This is the first list item. + > 2. This is the second list item. + > + > Here's some example code: + > + > return shell_exec("echo input | markdown_script"); + +Any decent text editor should make email-style quoting easy. For +example, with BBEdit, you can make a selection and choose Increase +Quote Level from the Text menu. + + +

Lists

+ +Markdown supports ordered (numbered) and unordered (bulleted) lists. + +Unordered lists use asterisks, pluses, and hyphens -- interchangably +-- as list markers: + + * Red + * Green + * Blue + +is equivalent to: + + + Red + + Green + + Blue + +and: + + - Red + - Green + - Blue + +Ordered lists use numbers followed by periods: + + 1. Bird + 2. McHale + 3. Parish + +It's important to note that the actual numbers you use to mark the +list have no effect on the HTML output Markdown produces. The HTML +Markdown produces from the above list is: + +
    +
  1. Bird
  2. +
  3. McHale
  4. +
  5. Parish
  6. +
+ +If you instead wrote the list in Markdown like this: + + 1. Bird + 1. McHale + 1. Parish + +or even: + + 3. Bird + 1. McHale + 8. Parish + +you'd get the exact same HTML output. The point is, if you want to, +you can use ordinal numbers in your ordered Markdown lists, so that +the numbers in your source match the numbers in your published HTML. +But if you want to be lazy, you don't have to. + +If you do use lazy list numbering, however, you should still start the +list with the number 1. At some point in the future, Markdown may support +starting ordered lists at an arbitrary number. + +List markers typically start at the left margin, but may be indented by +up to three spaces. List markers must be followed by one or more spaces +or a tab. + +To make lists look nice, you can wrap items with hanging indents: + + * Lorem ipsum dolor sit amet, consectetuer adipiscing elit. + Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, + viverra nec, fringilla in, laoreet vitae, risus. + * Donec sit amet nisl. Aliquam semper ipsum sit amet velit. + Suspendisse id sem consectetuer libero luctus adipiscing. + +But if you want to be lazy, you don't have to: + + * Lorem ipsum dolor sit amet, consectetuer adipiscing elit. + Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, + viverra nec, fringilla in, laoreet vitae, risus. + * Donec sit amet nisl. Aliquam semper ipsum sit amet velit. + Suspendisse id sem consectetuer libero luctus adipiscing. + +If list items are separated by blank lines, Markdown will wrap the +items in `

` tags in the HTML output. For example, this input: + + * Bird + * Magic + +will turn into: + +

    +
  • Bird
  • +
  • Magic
  • +
+ +But this: + + * Bird + + * Magic + +will turn into: + +
    +
  • Bird

  • +
  • Magic

  • +
+ +List items may consist of multiple paragraphs. Each subsequent +paragraph in a list item must be intended by either 4 spaces +or one tab: + + 1. This is a list item with two paragraphs. Lorem ipsum dolor + sit amet, consectetuer adipiscing elit. Aliquam hendrerit + mi posuere lectus. + + Vestibulum enim wisi, viverra nec, fringilla in, laoreet + vitae, risus. Donec sit amet nisl. Aliquam semper ipsum + sit amet velit. + + 2. Suspendisse id sem consectetuer libero luctus adipiscing. + +It looks nice if you indent every line of the subsequent +paragraphs, but here again, Markdown will allow you to be +lazy: + + * This is a list item with two paragraphs. + + This is the second paragraph in the list item. You're + only required to indent the first line. Lorem ipsum dolor + sit amet, consectetuer adipiscing elit. + + * Another item in the same list. + +To put a blockquote within a list item, the blockquote's `>` +delimiters need to be indented: + + * A list item with a blockquote: + + > This is a blockquote + > inside a list item. + +To put a code block within a list item, the code block needs +to be indented *twice* -- 8 spaces or two tabs: + + * A list item with a code block: + + + + +It's worth noting that it's possible to trigger an ordered list by +accident, by writing something like this: + + 1986. What a great season. + +In other words, a *number-period-space* sequence at the beginning of a +line. To avoid this, you can backslash-escape the period: + + 1986\. What a great season. + + + +

Code Blocks

+ +Pre-formatted code blocks are used for writing about programming or +markup source code. Rather than forming normal paragraphs, the lines +of a code block are interpreted literally. Markdown wraps a code block +in both `
` and `` tags.
+
+To produce a code block in Markdown, simply indent every line of the
+block by at least 4 spaces or 1 tab. For example, given this input:
+
+    This is a normal paragraph:
+
+        This is a code block.
+
+Markdown will generate:
+
+    

This is a normal paragraph:

+ +
This is a code block.
+    
+ +One level of indentation -- 4 spaces or 1 tab -- is removed from each +line of the code block. For example, this: + + Here is an example of AppleScript: + + tell application "Foo" + beep + end tell + +will turn into: + +

Here is an example of AppleScript:

+ +
tell application "Foo"
+        beep
+    end tell
+    
+ +A code block continues until it reaches a line that is not indented +(or the end of the article). + +Within a code block, ampersands (`&`) and angle brackets (`<` and `>`) +are automatically converted into HTML entities. This makes it very +easy to include example HTML source code using Markdown -- just paste +it and indent it, and Markdown will handle the hassle of encoding the +ampersands and angle brackets. For example, this: + + + +will turn into: + +
<div class="footer">
+        &copy; 2004 Foo Corporation
+    </div>
+    
+ +Regular Markdown syntax is not processed within code blocks. E.g., +asterisks are just literal asterisks within a code block. This means +it's also easy to use Markdown to write about Markdown's own syntax. + + + +

Horizontal Rules

+ +You can produce a horizontal rule tag (`
`) by placing three or +more hyphens, asterisks, or underscores on a line by themselves. If you +wish, you may use spaces between the hyphens or asterisks. Each of the +following lines will produce a horizontal rule: + + * * * + + *** + + ***** + + - - - + + --------------------------------------- + + _ _ _ + + +* * * + +

Span Elements

+ + + +Markdown supports two style of links: *inline* and *reference*. + +In both styles, the link text is delimited by [square brackets]. + +To create an inline link, use a set of regular parentheses immediately +after the link text's closing square bracket. Inside the parentheses, +put the URL where you want the link to point, along with an *optional* +title for the link, surrounded in quotes. For example: + + This is [an example](http://example.com/ "Title") inline link. + + [This link](http://example.net/) has no title attribute. + +Will produce: + +

This is + an example inline link.

+ +

This link has no + title attribute.

+ +If you're referring to a local resource on the same server, you can +use relative paths: + + See my [About](/about/) page for details. + +Reference-style links use a second set of square brackets, inside +which you place a label of your choosing to identify the link: + + This is [an example][id] reference-style link. + +You can optionally use a space to separate the sets of brackets: + + This is [an example] [id] reference-style link. + +Then, anywhere in the document, you define your link label like this, +on a line by itself: + + [id]: http://example.com/ "Optional Title Here" + +That is: + +* Square brackets containing the link identifier (optionally + indented from the left margin using up to three spaces); +* followed by a colon; +* followed by one or more spaces (or tabs); +* followed by the URL for the link; +* optionally followed by a title attribute for the link, enclosed + in double or single quotes. + +The link URL may, optionally, be surrounded by angle brackets: + + [id]: "Optional Title Here" + +You can put the title attribute on the next line and use extra spaces +or tabs for padding, which tends to look better with longer URLs: + + [id]: http://example.com/longish/path/to/resource/here + "Optional Title Here" + +Link definitions are only used for creating links during Markdown +processing, and are stripped from your document in the HTML output. + +Link definition names may constist of letters, numbers, spaces, and punctuation -- but they are *not* case sensitive. E.g. these two links: + + [link text][a] + [link text][A] + +are equivalent. + +The *implicit link name* shortcut allows you to omit the name of the +link, in which case the link text itself is used as the name. +Just use an empty set of square brackets -- e.g., to link the word +"Google" to the google.com web site, you could simply write: + + [Google][] + +And then define the link: + + [Google]: http://google.com/ + +Because link names may contain spaces, this shortcut even works for +multiple words in the link text: + + Visit [Daring Fireball][] for more information. + +And then define the link: + + [Daring Fireball]: http://daringfireball.net/ + +Link definitions can be placed anywhere in your Markdown document. I +tend to put them immediately after each paragraph in which they're +used, but if you want, you can put them all at the end of your +document, sort of like footnotes. + +Here's an example of reference links in action: + + I get 10 times more traffic from [Google] [1] than from + [Yahoo] [2] or [MSN] [3]. + + [1]: http://google.com/ "Google" + [2]: http://search.yahoo.com/ "Yahoo Search" + [3]: http://search.msn.com/ "MSN Search" + +Using the implicit link name shortcut, you could instead write: + + I get 10 times more traffic from [Google][] than from + [Yahoo][] or [MSN][]. + + [google]: http://google.com/ "Google" + [yahoo]: http://search.yahoo.com/ "Yahoo Search" + [msn]: http://search.msn.com/ "MSN Search" + +Both of the above examples will produce the following HTML output: + +

I get 10 times more traffic from Google than from + Yahoo + or MSN.

+ +For comparison, here is the same paragraph written using +Markdown's inline link style: + + I get 10 times more traffic from [Google](http://google.com/ "Google") + than from [Yahoo](http://search.yahoo.com/ "Yahoo Search") or + [MSN](http://search.msn.com/ "MSN Search"). + +The point of reference-style links is not that they're easier to +write. The point is that with reference-style links, your document +source is vastly more readable. Compare the above examples: using +reference-style links, the paragraph itself is only 81 characters +long; with inline-style links, it's 176 characters; and as raw HTML, +it's 234 characters. In the raw HTML, there's more markup than there +is text. + +With Markdown's reference-style links, a source document much more +closely resembles the final output, as rendered in a browser. By +allowing you to move the markup-related metadata out of the paragraph, +you can add links without interrupting the narrative flow of your +prose. + + +

Emphasis

+ +Markdown treats asterisks (`*`) and underscores (`_`) as indicators of +emphasis. Text wrapped with one `*` or `_` will be wrapped with an +HTML `` tag; double `*`'s or `_`'s will be wrapped with an HTML +`` tag. E.g., this input: + + *single asterisks* + + _single underscores_ + + **double asterisks** + + __double underscores__ + +will produce: + + single asterisks + + single underscores + + double asterisks + + double underscores + +You can use whichever style you prefer; the lone restriction is that +the same character must be used to open and close an emphasis span. + +Emphasis can be used in the middle of a word: + + un*fucking*believable + +But if you surround an `*` or `_` with spaces, it'll be treated as a +literal asterisk or underscore. + +To produce a literal asterisk or underscore at a position where it +would otherwise be used as an emphasis delimiter, you can backslash +escape it: + + \*this text is surrounded by literal asterisks\* + + + +

Code

+ +To indicate a span of code, wrap it with backtick quotes (`` ` ``). +Unlike a pre-formatted code block, a code span indicates code within a +normal paragraph. For example: + + Use the `printf()` function. + +will produce: + +

Use the printf() function.

+ +To include a literal backtick character within a code span, you can use +multiple backticks as the opening and closing delimiters: + + ``There is a literal backtick (`) here.`` + +which will produce this: + +

There is a literal backtick (`) here.

+ +The backtick delimiters surrounding a code span may include spaces -- +one after the opening, one before the closing. This allows you to place +literal backtick characters at the beginning or end of a code span: + + A single backtick in a code span: `` ` `` + + A backtick-delimited string in a code span: `` `foo` `` + +will produce: + +

A single backtick in a code span: `

+ +

A backtick-delimited string in a code span: `foo`

+ +With a code span, ampersands and angle brackets are encoded as HTML +entities automatically, which makes it easy to include example HTML +tags. Markdown will turn this: + + Please don't use any `` tags. + +into: + +

Please don't use any <blink> tags.

+ +You can write this: + + `—` is the decimal-encoded equivalent of `—`. + +to produce: + +

&#8212; is the decimal-encoded + equivalent of &mdash;.

+ + + +

Images

+ +Admittedly, it's fairly difficult to devise a "natural" syntax for +placing images into a plain text document format. + +Markdown uses an image syntax that is intended to resemble the syntax +for links, allowing for two styles: *inline* and *reference*. + +Inline image syntax looks like this: + + ![Alt text](/path/to/img.jpg) + + ![Alt text](/path/to/img.jpg "Optional title") + +That is: + +* An exclamation mark: `!`; +* followed by a set of square brackets, containing the `alt` + attribute text for the image; +* followed by a set of parentheses, containing the URL or path to + the image, and an optional `title` attribute enclosed in double + or single quotes. + +Reference-style image syntax looks like this: + + ![Alt text][id] + +Where "id" is the name of a defined image reference. Image references +are defined using syntax identical to link references: + + [id]: url/to/image "Optional title attribute" + +As of this writing, Markdown has no syntax for specifying the +dimensions of an image; if this is important to you, you can simply +use regular HTML `` tags. + + +* * * + + +

Miscellaneous

+ + + +Markdown supports a shortcut style for creating "automatic" links for URLs and email addresses: simply surround the URL or email address with angle brackets. What this means is that if you want to show the actual text of a URL or email address, and also have it be a clickable link, you can do this: + + + +Markdown will turn this into: + + http://example.com/ + +Automatic links for email addresses work similarly, except that +Markdown will also perform a bit of randomized decimal and hex +entity-encoding to help obscure your address from address-harvesting +spambots. For example, Markdown will turn this: + + + +into something like this: + + address@exa + mple.com + +which will render in a browser as a clickable link to "address@example.com". + +(This sort of entity-encoding trick will indeed fool many, if not +most, address-harvesting bots, but it definitely won't fool all of +them. It's better than nothing, but an address published in this way +will probably eventually start receiving spam.) + + + +

Backslash Escapes

+ +Markdown allows you to use backslash escapes to generate literal +characters which would otherwise have special meaning in Markdown's +formatting syntax. For example, if you wanted to surround a word with +literal asterisks (instead of an HTML `` tag), you can backslashes +before the asterisks, like this: + + \*literal asterisks\* + +Markdown provides backslash escapes for the following characters: + + \ backslash + ` backtick + * asterisk + _ underscore + {} curly braces + [] square brackets + () parentheses + # hash mark + + plus sign + - minus sign (hyphen) + . dot + ! exclamation mark + + + + + + + +Foo [bar][]. + +Foo [bar](/url/ "Title with "quotes" inside"). + + + [bar]: /url/ "Title with "quotes" inside" + + + + + + + + +This is the [simple case]. + +[simple case]: /simple + + + +This one has a [line +break]. + +This one has a [line +break] with a line-ending space. + +[line break]: /foo + + +[this] [that] and the [other] + +[this]: /this +[that]: /that +[other]: /other + + + + + +Here's a simple block: + +
+ foo +
+ +This should be a code block, though: + +
+ foo +
+ +As should this: + +
foo
+ +Now, nested: + +
+
+
+ foo +
+
+
+ +This should just be an HTML comment: + + + +Multiline: + + + +Code block: + + + +Just plain comment, with trailing spaces on the line: + + + +Code: + +
+ +Hr's: + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ + + +
+ My Example + Hello world +
+ + +```diff +-oldCode() ++newCode() +``` + +footnotes[^1]. + +[^1]: hello world!. + +

Cryptids of Cornwall:

+ +
+
Beast of Bodmin
+
A large feline inhabiting Bodmin Moor.
+ +
Morgawr
+
A sea serpent.
+ +
Owlman
+
A giant owl-like creature.
+
+ +""" + .trimIndent() diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownEditor.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownEditor.kt new file mode 100644 index 000000000..2ab6579ca --- /dev/null +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownEditor.kt @@ -0,0 +1,133 @@ +package org.jetbrains.jewel.samples.standalone.view.markdown + +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.unit.dp +import com.darkrockstudios.libraries.mpfilepicker.FilePicker +import com.darkrockstudios.libraries.mpfilepicker.JvmFile +import org.jetbrains.jewel.foundation.theme.JewelTheme +import org.jetbrains.jewel.samples.standalone.StandaloneSampleIcons +import org.jetbrains.jewel.ui.Orientation +import org.jetbrains.jewel.ui.component.Divider +import org.jetbrains.jewel.ui.component.Icon +import org.jetbrains.jewel.ui.component.OutlinedButton +import org.jetbrains.jewel.ui.component.PopupMenu +import org.jetbrains.jewel.ui.component.Text +import org.jetbrains.jewel.ui.component.TextArea + +@Composable +internal fun MarkdownEditor( + currentMarkdown: String, + onMarkdownChange: (String) -> Unit, + modifier: Modifier = Modifier, +) { + Column(modifier) { + ControlsRow(onMarkdownChange, Modifier.fillMaxWidth().padding(8.dp)) + Divider(orientation = Orientation.Horizontal) + Editor(currentMarkdown, onMarkdownChange, Modifier.fillMaxSize()) + } +} + +@Composable +private fun ControlsRow(onMarkdownChange: (String) -> Unit, modifier: Modifier = Modifier) { + Row( + modifier.horizontalScroll(rememberScrollState()), + horizontalArrangement = Arrangement.spacedBy(16.dp), + ) { + var showFilePicker by remember { mutableStateOf(false) } + OutlinedButton( + onClick = { showFilePicker = true }, + modifier = Modifier.padding(start = 2.dp), + ) { + Text("Load file...") + } + + FilePicker(show = showFilePicker, fileExtensions = listOf("md")) { platformFile -> + showFilePicker = false + + if (platformFile != null) { + val jvmFile = platformFile as JvmFile + val contents = jvmFile.platformFile.readText() + + onMarkdownChange(contents) + } + } + + OutlinedButton(onClick = { onMarkdownChange("") }) { Text("Clear") } + + Box { + var showPresets by remember { mutableStateOf(false) } + OutlinedButton(onClick = { showPresets = true }) { + Text("Load preset") + Spacer(Modifier.width(8.dp)) + Icon( + resource = "expui/general/chevronDown.svg", + contentDescription = null, + iconClass = StandaloneSampleIcons::class.java, + ) + } + + if (showPresets) { + var selected by remember { mutableStateOf("Jewel readme") } + PopupMenu(onDismissRequest = { + showPresets = false + false + }, horizontalAlignment = Alignment.Start) { + selectableItem( + selected = selected == "Jewel readme", + onClick = { + selected = "Jewel readme" + onMarkdownChange(JewelReadme) + }, + ) { + Text("Jewel readme") + } + + selectableItem( + selected = selected == "Markdown catalog", + onClick = { + selected = "Markdown catalog" + onMarkdownChange(MarkdownCatalog) + }, + ) { + Text("Markdown catalog") + } + } + } + } + } +} + +@Composable +private fun Editor( + currentMarkdown: String, + onMarkdownChange: (String) -> Unit, + modifier: Modifier, +) { + val monospacedTextStyle = JewelTheme.textStyle.copy(fontFamily = FontFamily.Monospace) + + TextArea( + value = currentMarkdown, + onValueChange = onMarkdownChange, + modifier = modifier.padding(16.dp), + undecorated = true, + textStyle = monospacedTextStyle, + ) +} diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownPreview.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownPreview.kt new file mode 100644 index 000000000..fdd053d0a --- /dev/null +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownPreview.kt @@ -0,0 +1,102 @@ +package org.jetbrains.jewel.samples.standalone.view.markdown + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.rememberScrollbarAdapter +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.jetbrains.jewel.foundation.theme.JewelTheme +import org.jetbrains.jewel.markdown.MarkdownBlock +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.alerts.dark +import org.jetbrains.jewel.markdown.extensions.github.alerts.light +import org.jetbrains.jewel.markdown.processing.MarkdownProcessor +import org.jetbrains.jewel.markdown.rendering.InlineMarkdownRenderer +import org.jetbrains.jewel.markdown.rendering.MarkdownBlockRenderer +import org.jetbrains.jewel.markdown.rendering.MarkdownStyling +import org.jetbrains.jewel.markdown.rendering.dark +import org.jetbrains.jewel.markdown.rendering.light +import org.jetbrains.jewel.ui.component.VerticalScrollbar +import java.awt.Desktop +import java.net.URI + +@Composable +internal fun MarkdownPreview(rawMarkdown: String, modifier: Modifier = Modifier) { + val isDark = JewelTheme.isDark + + val markdownStyling = + remember(isDark) { if (isDark) MarkdownStyling.dark() else MarkdownStyling.light() } + + var markdownBlocks by remember { mutableStateOf(emptyList()) } + val extensions = listOf(GitHubAlertProcessorExtension) + val processor = remember { MarkdownProcessor(extensions) } + + LaunchedEffect(rawMarkdown) { + // TODO you may want to debounce or drop on backpressure, in real usages. You should also not do this + // in the UI to begin with. + @Suppress("InjectDispatcher") // This should never go in the composable IRL + markdownBlocks = withContext(Dispatchers.Default) { processor.processMarkdownDocument(rawMarkdown) } + } + + val blockRenderer = + remember(markdownStyling, isDark, extensions) { + if (isDark) { + MarkdownBlockRenderer.dark( + styling = markdownStyling, + rendererExtensions = listOf(GitHubAlertRendererExtension(AlertStyling.dark(), markdownStyling)), + inlineRenderer = InlineMarkdownRenderer.default(extensions), + ) { url -> + Desktop.getDesktop().browse(URI.create(url)) + } + } else { + MarkdownBlockRenderer.light( + styling = markdownStyling, + rendererExtensions = listOf(GitHubAlertRendererExtension(AlertStyling.light(), markdownStyling)), + inlineRenderer = InlineMarkdownRenderer.default(extensions), + ) { url -> + Desktop.getDesktop().browse(URI.create(url)) + } + } + } + + // Using the values from the GitHub rendering to ensure contrast + val background = remember(isDark) { if (isDark) Color(0xff0d1117) else Color.White } + + Box(modifier.background(background)) { + val scrollState = rememberLazyListState() + SelectionContainer { + LazyColumn( + contentPadding = PaddingValues(16.dp), + state = scrollState, + verticalArrangement = Arrangement.spacedBy(markdownStyling.blockVerticalSpacing), + ) { + items(markdownBlocks) { blockRenderer.render(it) } + } + } + + VerticalScrollbar( + rememberScrollbarAdapter(scrollState), + Modifier.align(Alignment.TopEnd).fillMaxHeight().padding(2.dp), + ) + } +} diff --git a/samples/standalone/src/main/resources/icons/markdown.svg b/samples/standalone/src/main/resources/icons/markdown.svg new file mode 100644 index 000000000..1077e9184 --- /dev/null +++ b/samples/standalone/src/main/resources/icons/markdown.svg @@ -0,0 +1,4 @@ + + + + diff --git a/samples/standalone/src/main/resources/icons/markdown@20x20.svg b/samples/standalone/src/main/resources/icons/markdown@20x20.svg new file mode 100644 index 000000000..a5a9b63b0 --- /dev/null +++ b/samples/standalone/src/main/resources/icons/markdown@20x20.svg @@ -0,0 +1,4 @@ + + + + diff --git a/samples/standalone/src/main/resources/icons/markdown@20x20_dark.svg b/samples/standalone/src/main/resources/icons/markdown@20x20_dark.svg new file mode 100644 index 000000000..5aeb9f278 --- /dev/null +++ b/samples/standalone/src/main/resources/icons/markdown@20x20_dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/samples/standalone/src/main/resources/icons/markdown_dark.svg b/samples/standalone/src/main/resources/icons/markdown_dark.svg new file mode 100644 index 000000000..aaeea9d2d --- /dev/null +++ b/samples/standalone/src/main/resources/icons/markdown_dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/settings.gradle.kts b/settings.gradle.kts index 3e3ec8938..e10d3eb4c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -36,6 +36,8 @@ include( ":int-ui:int-ui-decorated-window", ":int-ui:int-ui-standalone", ":ide-laf-bridge", + ":markdown:core", + ":markdown:extension-gfm-alerts", ":samples:standalone", ":samples:ide-plugin", )