Skip to content

Commit

Permalink
Make Markdown blocks implement equals and hashcode
Browse files Browse the repository at this point in the history
The value classes we used to use would rely on the CommonMark models for
this, but none of the CommonMark models actually implement equals() and
hashCode(), leading to issues in tests and in Compose.
  • Loading branch information
rock3r committed Jul 19, 2024
1 parent ee52934 commit cf0828b
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 56 deletions.
Original file line number Diff line number Diff line change
@@ -1,80 +1,79 @@
package org.jetbrains.jewel.markdown

import org.commonmark.node.Block
import org.commonmark.node.Heading as CMHeading
import org.commonmark.node.Paragraph as CMParagraph
import org.jetbrains.jewel.foundation.GenerateDataFunctions
import org.jetbrains.jewel.foundation.InternalJewelApi

public sealed interface MarkdownBlock {
public data class BlockQuote(val children: List<MarkdownBlock>) : MarkdownBlock

@GenerateDataFunctions
public class BlockQuote(public val children: List<MarkdownBlock>) : MarkdownBlock

public sealed interface CodeBlock : MarkdownBlock {
public val content: String

public data class IndentedCodeBlock(
override val content: String,
) : CodeBlock
@GenerateDataFunctions
public class IndentedCodeBlock(override val content: String) : CodeBlock

public data class FencedCodeBlock(
@GenerateDataFunctions
public class FencedCodeBlock(
override val content: String,
val mimeType: MimeType?,
public val mimeType: MimeType?,
) : CodeBlock
}

public interface CustomBlock : MarkdownBlock

@JvmInline
public value class Heading(
private val nativeBlock: CMHeading,
) : MarkdownBlock, BlockWithInlineMarkdown {
override val inlineContent: Iterable<InlineMarkdown>
get() = nativeBlock.inlineContent()
@GenerateDataFunctions
public class Heading(
override val inlineContent: List<InlineMarkdown>,
public val level: Int,
) : MarkdownBlock, BlockWithInlineMarkdown

public val level: Int
get() = nativeBlock.level
}

public data class HtmlBlock(val content: String) : MarkdownBlock
@GenerateDataFunctions
public class HtmlBlock(public val content: String) : MarkdownBlock

public sealed interface ListBlock : MarkdownBlock {
public val children: List<ListItem>
public val isTight: Boolean

public data class OrderedList(
@GenerateDataFunctions
public class OrderedList(
override val children: List<ListItem>,
override val isTight: Boolean,
val startFrom: Int,
val delimiter: String,
public val startFrom: Int,
public val delimiter: String,
) : ListBlock

public data class UnorderedList(
@GenerateDataFunctions
public class UnorderedList(
override val children: List<ListItem>,
override val isTight: Boolean,
val marker: String,
public val marker: String,
) : ListBlock
}

public data class ListItem(
val children: List<MarkdownBlock>,
) : MarkdownBlock
@GenerateDataFunctions
public class ListItem(public val children: List<MarkdownBlock>) : MarkdownBlock

public object ThematicBreak : MarkdownBlock
public data object ThematicBreak : MarkdownBlock

@JvmInline
public value class Paragraph(private val nativeBlock: CMParagraph) : MarkdownBlock, BlockWithInlineMarkdown {
override val inlineContent: Iterable<InlineMarkdown>
get() = nativeBlock.inlineContent()
}
@GenerateDataFunctions
public class Paragraph(
override val inlineContent: List<InlineMarkdown>,
) : MarkdownBlock, BlockWithInlineMarkdown
}

public interface BlockWithInlineMarkdown {
public val inlineContent: Iterable<InlineMarkdown>
}

private fun Block.inlineContent(): Iterable<InlineMarkdown> =
@InternalJewelApi
public fun Block.readInlineContent(): Iterable<InlineMarkdown> =
object : Iterable<InlineMarkdown> {
override fun iterator(): Iterator<InlineMarkdown> =
object : Iterator<InlineMarkdown> {
var current = this@inlineContent.firstChild
var current = this@readInlineContent.firstChild

override fun hasNext(): Boolean = current != null

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ public class MarkdownProcessor(
private fun Node.tryProcessMarkdownBlock(): MarkdownBlock? =
// Non-Block children are ignored
when (this) {
is Paragraph -> MarkdownBlock.Paragraph(this)
is Paragraph -> toMarkdownParagraph()
is Heading -> toMarkdownHeadingOrNull()
is BulletList -> toMarkdownListOrNull()
is OrderedList -> toMarkdownListOrNull()
Expand All @@ -193,11 +193,18 @@ public class MarkdownProcessor(
else -> null
}

private fun BlockQuote.toMarkdownBlockQuote(): MarkdownBlock.BlockQuote = MarkdownBlock.BlockQuote(processChildren(this))
private fun Paragraph.toMarkdownParagraph(): MarkdownBlock.Paragraph =
MarkdownBlock.Paragraph(readInlineContent().toList())

private fun BlockQuote.toMarkdownBlockQuote(): MarkdownBlock.BlockQuote =
MarkdownBlock.BlockQuote(processChildren(this))

private fun Heading.toMarkdownHeadingOrNull(): MarkdownBlock.Heading? {
if (level < 1 || level > 6) return null
return MarkdownBlock.Heading(this)
return MarkdownBlock.Heading(
inlineContent = readInlineContent().toList(),
level = level
)
}

private fun FencedCodeBlock.toMarkdownCodeBlockOrNull(): CodeBlock.FencedCodeBlock =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.jetbrains.jewel.markdown
import org.commonmark.internal.InlineParserContextImpl
import org.commonmark.internal.InlineParserImpl
import org.commonmark.internal.LinkReferenceDefinitions
import org.commonmark.node.Block
import org.commonmark.node.Node
import org.commonmark.parser.Parser
import org.commonmark.parser.SourceLine
Expand All @@ -22,6 +23,8 @@ 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
import org.commonmark.node.Heading as CMHeading
import org.commonmark.node.Paragraph as CMParagraph

fun List<MarkdownBlock>.assertEquals(vararg expected: MarkdownBlock) {
val differences = findDifferences(expected.toList(), indentSize = 0)
Expand Down Expand Up @@ -238,7 +241,7 @@ private fun Node.children() =
/** skip root Document and Paragraph nodes */
private fun inlineMarkdowns(content: String): List<InlineMarkdown> {
val document = parser.parse(content).firstChild ?: return emptyList()
return if (document.firstChild is org.commonmark.node.Paragraph) {
return if (document.firstChild is CMParagraph) {
document.firstChild
} else {
document
Expand All @@ -249,46 +252,44 @@ private val inlineParser = InlineParserImpl(InlineParserContextImpl(emptyList(),

fun paragraph(
@Language("Markdown") content: String,
): Paragraph =
Paragraph(
org.commonmark.node.Paragraph().let { block ->
inlineParser.parse(SourceLines.of(content.lines().map { SourceLine.of(it, null) }), block)
block
},
)
) =
Paragraph(CMParagraph().parseInline(content))

fun heading(
level: Int,
@Language("Markdown") content: String,
) = Heading(
org.commonmark.node.Heading().let { block ->
inlineParser.parse(SourceLines.of(SourceLine.of(content, null)), block)
block.level = level
block
},
)
) =
Heading(inlineContent = CMHeading().parseInline(content), level = level)

private fun Block.parseInline(content: String): List<InlineMarkdown> {
inlineParser.parse(SourceLines.of(SourceLine.of(content, null)), this)
return readInlineContent().toList()
}

fun indentedCodeBlock(content: String) = IndentedCodeBlock(content)

fun fencedCodeBlock(
content: String,
mimeType: MimeType? = null,
) = FencedCodeBlock(content, mimeType)
) =
FencedCodeBlock(content, mimeType)

fun blockQuote(vararg contents: MarkdownBlock) = BlockQuote(contents.toList())

fun unorderedList(
vararg items: ListItem,
isTight: Boolean = true,
marker: String = "-",
) = UnorderedList(items.toList(), isTight, marker)
) =
UnorderedList(items.toList(), isTight, marker)

fun orderedList(
vararg items: ListItem,
isTight: Boolean = true,
startFrom: Int = 1,
delimiter: String = ".",
) = OrderedList(items.toList(), isTight, startFrom, delimiter)
) =
OrderedList(items.toList(), isTight, startFrom, delimiter)

fun listItem(vararg items: MarkdownBlock) = ListItem(items.toList())

Expand Down

0 comments on commit cf0828b

Please sign in to comment.