Skip to content

Commit

Permalink
Address comments
Browse files Browse the repository at this point in the history
  • Loading branch information
Oleg Baskakov committed Mar 30, 2024
1 parent 0c8a1e9 commit e066d06
Show file tree
Hide file tree
Showing 5 changed files with 25 additions and 195 deletions.
20 changes: 10 additions & 10 deletions markdown/core/api/core.api
Original file line number Diff line number Diff line change
Expand Up @@ -283,14 +283,14 @@ public abstract interface class org/jetbrains/jewel/markdown/MarkdownBlock$ListB

public final class org/jetbrains/jewel/markdown/MarkdownBlock$ListBlock$BulletList : org/jetbrains/jewel/markdown/MarkdownBlock$ListBlock {
public static final field $stable I
public fun <init> (Ljava/util/List;ZC)V
public fun <init> (Ljava/util/List;ZLjava/lang/String;)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$BulletList;
public static synthetic fun copy$default (Lorg/jetbrains/jewel/markdown/MarkdownBlock$ListBlock$BulletList;Ljava/util/List;ZCILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$ListBlock$BulletList;
public final fun component3 ()Ljava/lang/String;
public final fun copy (Ljava/util/List;ZLjava/lang/String;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$ListBlock$BulletList;
public static synthetic fun copy$default (Lorg/jetbrains/jewel/markdown/MarkdownBlock$ListBlock$BulletList;Ljava/util/List;ZLjava/lang/String;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$ListBlock$BulletList;
public fun equals (Ljava/lang/Object;)Z
public final fun getBulletMarker ()C
public final fun getBulletMarker ()Ljava/lang/String;
public fun getItems ()Ljava/util/List;
public fun hashCode ()I
public fun isTight ()Z
Expand All @@ -299,15 +299,15 @@ public final class org/jetbrains/jewel/markdown/MarkdownBlock$ListBlock$BulletLi

public final class org/jetbrains/jewel/markdown/MarkdownBlock$ListBlock$OrderedList : org/jetbrains/jewel/markdown/MarkdownBlock$ListBlock {
public static final field $stable I
public fun <init> (Ljava/util/List;ZIC)V
public fun <init> (Ljava/util/List;ZILjava/lang/String;)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 final fun component4 ()Ljava/lang/String;
public final fun copy (Ljava/util/List;ZILjava/lang/String;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$ListBlock$OrderedList;
public static synthetic fun copy$default (Lorg/jetbrains/jewel/markdown/MarkdownBlock$ListBlock$OrderedList;Ljava/util/List;ZILjava/lang/String;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$ListBlock$OrderedList;
public fun equals (Ljava/lang/Object;)Z
public final fun getDelimiter ()C
public final fun getDelimiter ()Ljava/lang/String;
public fun getItems ()Ljava/util/List;
public final fun getStartFrom ()I
public fun hashCode ()I
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ public sealed interface MarkdownBlock {
public data class BulletList(
override val items: List<ListItem>,
override val isTight: Boolean,
val bulletMarker: Char,
val bulletMarker: String,
) : ListBlock

public data class OrderedList(
override val items: List<ListItem>,
override val isTight: Boolean,
val startFrom: Int,
val delimiter: Char,
val delimiter: String,
) : ListBlock
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,19 @@ 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
Expand All @@ -43,11 +33,6 @@ public class MarkdownProcessor(private val extensions: List<MarkdownProcessorExt
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.31.2
* to a list of [MarkdownBlock]. Inline Markdown in leaf nodes
Expand Down Expand Up @@ -100,20 +85,23 @@ public class MarkdownProcessor(private val extensions: List<MarkdownProcessorExt
extensions.find { it.processorExtension.canProcess(this) }
?.processorExtension?.processMarkdownBlock(this, this@MarkdownProcessor)
}
// TODO: add support for LinkReferenceDefinition
else -> null
}

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

private fun Heading.toMarkdownHeadingOrNull(): MarkdownBlock.Heading =
MarkdownBlock.Heading(contentsAsInlineMarkdown(), level)
private fun Heading.toMarkdownHeadingOrNull(): MarkdownBlock.Heading? =
if (level > 6) {
null
} else {
MarkdownBlock.Heading(contentsAsInlineMarkdown(), level)
}

private fun Paragraph.toMarkdownParagraphOrNull(): MarkdownBlock.Paragraph {
private fun Paragraph.toMarkdownParagraphOrNull(): MarkdownBlock.Paragraph? {
val inlineMarkdown = contentsAsInlineMarkdown()

// if (inlineMarkdown.isEmpty()) return null
if (inlineMarkdown.isEmpty()) return null
return MarkdownBlock.Paragraph(inlineMarkdown)
}

Expand All @@ -130,14 +118,14 @@ public class MarkdownProcessor(private val extensions: List<MarkdownProcessorExt
val children = processListItems()
if (children.isEmpty()) return null

return org.jetbrains.jewel.markdown.MarkdownBlock.ListBlock.BulletList(children, isTight, bulletMarker)
return MarkdownBlock.ListBlock.BulletList(children, isTight, marker)
}

private fun OrderedList.toMarkdownListOrNull(): MarkdownBlock.ListBlock.OrderedList? {
val children = processListItems()
if (children.isEmpty()) return null

return MarkdownBlock.ListBlock.OrderedList(children, isTight, startNumber, delimiter)
return MarkdownBlock.ListBlock.OrderedList(children, isTight, markerStartNumber, markerDelimiter)
}

private fun ListBlock.processListItems() = buildList {
Expand Down Expand Up @@ -175,155 +163,4 @@ public class MarkdownProcessor(private val extensions: List<MarkdownProcessorExt
add(it.toInlineNode())
}
}

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("[", "&#91;").replace("]", "&#93;")

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 plainTextContents(node: Node): String = textContentRenderer.render(node)
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import org.jetbrains.jewel.ui.Orientation.Horizontal
import org.jetbrains.jewel.ui.component.Divider
import org.jetbrains.jewel.ui.component.Text

@Suppress("FunctionName")
@ExperimentalJewelApi
public open class DefaultMarkdownBlockRenderer(
private val rootStyling: MarkdownStyling,
Expand All @@ -92,15 +93,7 @@ public open class DefaultMarkdownBlockRenderer(
is BlockQuote -> render(block, rootStyling.blockQuote)
is FencedCodeBlock -> render(block, rootStyling.code.fenced)
is IndentedCodeBlock -> render(block, rootStyling.code.indented)
is Heading -> when (block.level) {
1 -> render(block, rootStyling.heading.h1)
2 -> render(block, rootStyling.heading.h2)
3 -> render(block, rootStyling.heading.h3)
4 -> render(block, rootStyling.heading.h4)
5 -> render(block, rootStyling.heading.h5)
6 -> render(block, rootStyling.heading.h6)
else -> error("$block")
}
is Heading -> render(block, rootStyling.heading)
is HtmlBlock -> render(block, rootStyling.htmlBlock)
is OrderedList -> render(block, rootStyling.list.ordered)
is BulletList -> render(block, rootStyling.list.unordered)
Expand Down Expand Up @@ -129,6 +122,7 @@ public open class DefaultMarkdownBlockRenderer(
4 -> render(block, styling.h4)
5 -> render(block, styling.h5)
6 -> render(block, styling.h6)
else -> error("Heading level ${block.level} not supported:\n$block")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8353,7 +8353,6 @@ class MarkdownProcessorDocumentParsingTest {
parsed.assertEquals(paragraph("\\* a \\*"))
}

@org.junit.Ignore("need to update escapeInlineMarkdownChars implementation")
@Test
fun `should parse spec sample 354 correctly (Emphasis and strong emphasis)`() {
val parsed = processor.processMarkdownDocument(
Expand Down

0 comments on commit e066d06

Please sign in to comment.