Skip to content

Commit

Permalink
Add strikethrough markdown support
Browse files Browse the repository at this point in the history
  • Loading branch information
obask committed Sep 6, 2024
1 parent 5efee37 commit 5e24c92
Show file tree
Hide file tree
Showing 15 changed files with 213 additions and 10 deletions.
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ poko = "0.15.3"
[libraries]
commonmark-core = { module = "org.commonmark:commonmark", version.ref = "commonmark" }
commonmark-ext-autolink = { module = "org.commonmark:commonmark-ext-autolink", version.ref = "commonmark" }
commonmark-ext-strikethrough = { module = "org.commonmark:commonmark-ext-gfm-strikethrough", version.ref = "commonmark" }

filePicker = { module = "com.darkrockstudios:mpfilepicker", version = "3.1.0" }

Expand Down
2 changes: 2 additions & 0 deletions markdown/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ Additional supported Markdown, via extensions:

* Alerts ([GitHub Flavored Markdown][alerts-specs]) — see [`extension-gfm-alerts`](extension/gfm-alerts)
* Autolink (standard CommonMark, but provided as extension) — see [`extension-autolink`](extension/autolink)
* Strikethrough ([GitHub Flavored Markdown][strikethrough-specs]) — see [`extension-strikethrough`](extension/strikethrough)

[alerts-specs]: https://github.com/orgs/community/discussions/16925
[strikethrough-specs]: https://github.github.com/gfm/#strikethrough-extension-

On the roadmap, but not currently supported — in no particular order:

Expand Down
6 changes: 5 additions & 1 deletion markdown/core/api/core.api
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ public abstract interface class org/jetbrains/jewel/markdown/extensions/Markdown

public abstract interface class org/jetbrains/jewel/markdown/extensions/MarkdownInlineRendererExtension {
public abstract fun canRender (Lorg/jetbrains/jewel/markdown/InlineMarkdown$CustomNode;)Z
public abstract fun render (Lorg/jetbrains/jewel/markdown/InlineMarkdown$CustomNode;Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer;Z)V
public abstract fun render (Landroidx/compose/ui/text/AnnotatedString$Builder;Lorg/jetbrains/jewel/markdown/InlineMarkdown$CustomNode;Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer;Z)V
}

public final class org/jetbrains/jewel/markdown/extensions/MarkdownKt {
Expand Down Expand Up @@ -374,6 +374,10 @@ public final class org/jetbrains/jewel/markdown/processing/MarkdownProcessor {
public final fun processMarkdownDocument (Ljava/lang/String;)Ljava/util/List;
}

public final class org/jetbrains/jewel/markdown/processing/ProcessingUtilKt {
public static final fun toInlineMarkdownOrNull (Lorg/commonmark/node/Node;Lorg/jetbrains/jewel/markdown/processing/MarkdownProcessor;Ljava/util/List;)Lorg/jetbrains/jewel/markdown/InlineMarkdown;
}

public class org/jetbrains/jewel/markdown/rendering/DefaultInlineMarkdownRenderer : org/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer {
public static final field $stable I
public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/DefaultInlineMarkdownRenderer$Companion;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.jetbrains.jewel.markdown.extensions

import androidx.compose.ui.text.AnnotatedString
import org.jetbrains.jewel.markdown.InlineMarkdown
import org.jetbrains.jewel.markdown.InlineMarkdown.CustomNode
import org.jetbrains.jewel.markdown.rendering.InlineMarkdownRenderer
Expand All @@ -13,5 +14,10 @@ public interface MarkdownInlineRendererExtension {
* Render a [CustomNode] as an annotated string. Note that if [canRender] returns `false` for [inline], the
* implementation might throw.
*/
public fun render(inline: CustomNode, inlineRenderer: InlineMarkdownRenderer, enabled: Boolean)
public fun render(
builder: AnnotatedString.Builder,
inline: CustomNode,
inlineRenderer: InlineMarkdownRenderer,
enabled: Boolean,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ internal fun Node.readInlineContent(
}

@VisibleForTesting
internal fun Node.toInlineMarkdownOrNull(
public fun Node.toInlineMarkdownOrNull(
markdownProcessor: MarkdownProcessor,
extensions: List<MarkdownProcessorExtension>,
) =
): InlineMarkdown? =
when (this) {
is CMText -> InlineMarkdown.Text(literal)
is CMLink ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,12 @@ public open class DefaultInlineMarkdownRenderer(private val rendererExtensions:
)
}

is InlineMarkdown.CustomNode ->
is InlineMarkdown.CustomNode -> {
rendererExtensions
.find { it.inlineRenderer?.canRender(child) == true }
?.inlineRenderer
?.render(child, inlineRenderer = this@DefaultInlineMarkdownRenderer, enabled)
?.render(builder = this, child, inlineRenderer = this@DefaultInlineMarkdownRenderer, enabled)
}
}
}
}
Expand Down
29 changes: 29 additions & 0 deletions markdown/extension/strikethrough/api/strikethrough.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
public final class org/jetbrains/jewel/markdown/extension/strikethrough/CustomStrikethroughNode : org/jetbrains/jewel/markdown/InlineMarkdown$CustomNode, org/jetbrains/jewel/markdown/WithInlineMarkdown {
public static final field $stable I
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V
public fun <init> (Lorg/commonmark/ext/gfm/strikethrough/Strikethrough;Lorg/jetbrains/jewel/markdown/processing/MarkdownProcessor;)V
public fun contentOrNull ()Ljava/lang/String;
public fun equals (Ljava/lang/Object;)Z
public final fun getClosingDelimiter ()Ljava/lang/String;
public fun getInlineContent ()Ljava/util/List;
public final fun getOpeningDelimiter ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class org/jetbrains/jewel/markdown/extension/strikethrough/StrikethroughProcessorExtension : org/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension {
public static final field $stable I
public static final field INSTANCE Lorg/jetbrains/jewel/markdown/extension/strikethrough/StrikethroughProcessorExtension;
public fun getBlockProcessorExtension ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownBlockProcessorExtension;
public fun getInlineProcessorExtension ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownInlineProcessorExtension;
public fun getParserExtension ()Lorg/commonmark/parser/Parser$ParserExtension;
public fun getTextRendererExtension ()Lorg/commonmark/renderer/text/TextContentRenderer$TextContentRendererExtension;
}

public final class org/jetbrains/jewel/markdown/extension/strikethrough/StrikethroughRendererExtension : org/jetbrains/jewel/markdown/extensions/MarkdownRendererExtension {
public static final field $stable I
public fun <init> (Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;)V
public fun getBlockRenderer ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownBlockRendererExtension;
public fun getInlineRenderer ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownInlineRendererExtension;
}

17 changes: 17 additions & 0 deletions markdown/extension/strikethrough/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
plugins {
jewel
`jewel-publish`
`jewel-check-public-api`
alias(libs.plugins.composeDesktop)
}

dependencies {
implementation(projects.markdown.core)
implementation(libs.commonmark.ext.strikethrough)
testImplementation(compose.desktop.uiTestJUnit4)
}

publishing.publications.named<MavenPublication>("main") {
val ijpTarget = project.property("ijp.target") as String
artifactId = "jewel-markdown-extension-${project.name}-$ijpTarget"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.jetbrains.jewel.markdown.extension.strikethrough

import org.commonmark.ext.gfm.strikethrough.Strikethrough
import org.commonmark.node.Node
import org.jetbrains.jewel.foundation.GenerateDataFunctions
import org.jetbrains.jewel.markdown.InlineMarkdown
import org.jetbrains.jewel.markdown.WithInlineMarkdown
import org.jetbrains.jewel.markdown.processing.MarkdownProcessor
import org.jetbrains.jewel.markdown.processing.toInlineMarkdownOrNull

@GenerateDataFunctions
public class CustomStrikethroughNode(
public val openingDelimiter: String,
public val closingDelimiter: String,
public override val inlineContent: List<InlineMarkdown>,
) : InlineMarkdown.CustomNode, WithInlineMarkdown {
public constructor(
node: Strikethrough,
processor: MarkdownProcessor,
) : this(
node.openingDelimiter,
node.closingDelimiter,
node.children().mapNotNull { it.toInlineMarkdownOrNull(processor, emptyList()) },
)
}

private fun Node.children() =
buildList<Node> {
var current = this@children.firstChild
while (current != null) {
this.add(current)
current = current.next
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.jetbrains.jewel.markdown.extension.strikethrough

import org.commonmark.ext.gfm.strikethrough.Strikethrough
import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension
import org.commonmark.node.CustomNode
import org.commonmark.parser.Parser.ParserExtension
import org.jetbrains.jewel.markdown.InlineMarkdown
import org.jetbrains.jewel.markdown.extensions.MarkdownInlineProcessorExtension
import org.jetbrains.jewel.markdown.extensions.MarkdownProcessorExtension
import org.jetbrains.jewel.markdown.processing.MarkdownProcessor

public object StrikethroughProcessorExtension : MarkdownProcessorExtension {
override val parserExtension: ParserExtension = StrikethroughExtension.create() as ParserExtension

override val inlineProcessorExtension: MarkdownInlineProcessorExtension
get() =
object : MarkdownInlineProcessorExtension {
override fun canProcess(node: CustomNode): Boolean = node is Strikethrough

override fun processInlineMarkdown(
node: CustomNode,
processor: MarkdownProcessor,
): InlineMarkdown.CustomNode = CustomStrikethroughNode(node as Strikethrough, processor)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.jetbrains.jewel.markdown.extension.strikethrough

import androidx.compose.ui.text.AnnotatedString.Builder
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.style.TextDecoration
import org.jetbrains.jewel.markdown.InlineMarkdown
import org.jetbrains.jewel.markdown.WithInlineMarkdown
import org.jetbrains.jewel.markdown.extensions.MarkdownInlineRendererExtension
import org.jetbrains.jewel.markdown.extensions.MarkdownRendererExtension
import org.jetbrains.jewel.markdown.rendering.InlineMarkdownRenderer
import org.jetbrains.jewel.markdown.rendering.InlinesStyling

public class StrikethroughRendererExtension(private val inlinesStyling: InlinesStyling) : MarkdownRendererExtension {
override val inlineRenderer: MarkdownInlineRendererExtension
get() =
object : MarkdownInlineRendererExtension {
override fun canRender(inline: InlineMarkdown.CustomNode): Boolean = inline is CustomStrikethroughNode

override fun render(
builder: Builder,
inline: InlineMarkdown.CustomNode,
inlineRenderer: InlineMarkdownRenderer,
enabled: Boolean,
) {
with(builder) {
val popTo =
pushStyle(
inlinesStyling.textStyle
.copy(textDecoration = TextDecoration.LineThrough)
.toSpanStyle()
.copy()
)
val inlineMarkdowns = (inline as? WithInlineMarkdown)?.inlineContent ?: emptyList()
for (markdown in inlineMarkdowns) {
append(
inlineRenderer.renderAsAnnotatedString(listOf(markdown), inlinesStyling, enabled = true)
)
}
pop(popTo)
}
}
}
}

private inline fun Builder.withStyles(spanStyle: SpanStyle, action: Builder.() -> Unit) {
val popTo = pushStyle(spanStyle)
action()
pop(popTo)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.jetbrains.jewel.markdown.extension.autolink

import org.jetbrains.jewel.markdown.InlineMarkdown
import org.jetbrains.jewel.markdown.MarkdownBlock
import org.jetbrains.jewel.markdown.extension.strikethrough.StrikethroughProcessorExtension
import org.jetbrains.jewel.markdown.processing.MarkdownProcessor
import org.junit.Assert.assertTrue
import org.junit.Test

class StrikethroughProcessorExtensionTest {
// testing a simple case to assure wiring up our AutolinkProcessorExtension works correctly
@Test
fun `https text gets processed into a link`() {
val processor = MarkdownProcessor(listOf(StrikethroughProcessorExtension))
val rawMarkDown = "~~fdsa~~"
val processed = processor.processMarkdownDocument(rawMarkDown)
val paragraph = processed.first() as MarkdownBlock.Paragraph

assertTrue(paragraph.inlineContent.first() is InlineMarkdown.Link)
}
}
3 changes: 2 additions & 1 deletion samples/standalone/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ dependencies {
implementation(projects.intUi.intUiStandalone)
implementation(projects.intUi.intUiDecoratedWindow)
implementation(projects.markdown.intUiStandaloneStyling)
implementation(projects.markdown.extension.gfmAlerts)
implementation(projects.markdown.extension.autolink)
implementation(projects.markdown.extension.gfmAlerts)
implementation(projects.markdown.extension.strikethrough)
implementation(compose.desktop.currentOs) { exclude(group = "org.jetbrains.compose.material") }
implementation(libs.intellijPlatform.icons)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import org.jetbrains.jewel.intui.markdown.standalone.styling.light
import org.jetbrains.jewel.markdown.LazyMarkdown
import org.jetbrains.jewel.markdown.MarkdownBlock
import org.jetbrains.jewel.markdown.extension.autolink.AutolinkProcessorExtension
import org.jetbrains.jewel.markdown.extension.strikethrough.StrikethroughProcessorExtension
import org.jetbrains.jewel.markdown.extension.strikethrough.StrikethroughRendererExtension
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
Expand All @@ -43,7 +45,9 @@ internal fun MarkdownPreview(modifier: Modifier = Modifier, rawMarkdown: CharSeq
val markdownStyling = remember(isDark) { if (isDark) MarkdownStyling.dark() else MarkdownStyling.light() }

var markdownBlocks by remember { mutableStateOf(emptyList<MarkdownBlock>()) }
val extensions = remember { listOf(GitHubAlertProcessorExtension, AutolinkProcessorExtension) }
val extensions = remember {
listOf(GitHubAlertProcessorExtension, AutolinkProcessorExtension, StrikethroughProcessorExtension)
}

// We are doing this here for the sake of simplicity.
// In a real-world scenario you would be doing this outside your Composables,
Expand All @@ -63,12 +67,20 @@ internal fun MarkdownPreview(modifier: Modifier = Modifier, rawMarkdown: CharSeq
if (isDark) {
MarkdownBlockRenderer.dark(
styling = markdownStyling,
rendererExtensions = listOf(GitHubAlertRendererExtension(AlertStyling.dark(), markdownStyling)),
rendererExtensions =
listOf(
GitHubAlertRendererExtension(AlertStyling.dark(), markdownStyling),
StrikethroughRendererExtension(markdownStyling.paragraph.inlinesStyling),
),
)
} else {
MarkdownBlockRenderer.light(
styling = markdownStyling,
rendererExtensions = listOf(GitHubAlertRendererExtension(AlertStyling.light(), markdownStyling)),
rendererExtensions =
listOf(
GitHubAlertRendererExtension(AlertStyling.light(), markdownStyling),
StrikethroughRendererExtension(markdownStyling.paragraph.inlinesStyling),
),
)
}
}
Expand Down
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ include(
":markdown:core",
":markdown:extension:autolink",
":markdown:extension:gfm-alerts",
":markdown:extension:strikethrough",
":markdown:int-ui-standalone-styling",
":markdown:ide-laf-bridge-styling",
":samples:ide-plugin",
Expand Down

0 comments on commit 5e24c92

Please sign in to comment.