Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GFM: Use Markdown syntax to render lists #2098

Merged
merged 2 commits into from Oct 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions plugins/base/api/base.api
Expand Up @@ -1411,6 +1411,8 @@ public class org/jetbrains/dokka/base/translators/documentables/PageContentBuild
public final fun list (Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;Ljava/util/Set;Ljava/util/Set;Lkotlin/jvm/functions/Function2;)V
public static synthetic fun list$default (Lorg/jetbrains/dokka/base/translators/documentables/PageContentBuilder$DocumentableContentBuilder;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;Ljava/util/Set;Ljava/util/Set;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V
public final fun operator (Ljava/lang/String;)V
public final fun orderedList (Lorg/jetbrains/dokka/pages/Kind;Ljava/util/Set;Ljava/util/Set;Lorg/jetbrains/dokka/model/properties/PropertyContainer;Lkotlin/jvm/functions/Function1;)V
public static synthetic fun orderedList$default (Lorg/jetbrains/dokka/base/translators/documentables/PageContentBuilder$DocumentableContentBuilder;Lorg/jetbrains/dokka/pages/Kind;Ljava/util/Set;Ljava/util/Set;Lorg/jetbrains/dokka/model/properties/PropertyContainer;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
public final fun punctuation (Ljava/lang/String;)V
public final fun sourceSetDependentHint (Ljava/util/Set;Ljava/util/Set;Lorg/jetbrains/dokka/pages/Kind;Ljava/util/Set;Lorg/jetbrains/dokka/model/properties/PropertyContainer;Lkotlin/jvm/functions/Function1;)V
public final fun sourceSetDependentHint (Lorg/jetbrains/dokka/links/DRI;Ljava/util/Set;Lorg/jetbrains/dokka/pages/Kind;Ljava/util/Set;Lorg/jetbrains/dokka/model/properties/PropertyContainer;Lkotlin/jvm/functions/Function1;)V
Expand All @@ -1425,6 +1427,17 @@ public class org/jetbrains/dokka/base/translators/documentables/PageContentBuild
public static synthetic fun text$default (Lorg/jetbrains/dokka/base/translators/documentables/PageContentBuilder$DocumentableContentBuilder;Ljava/lang/String;Lorg/jetbrains/dokka/pages/Kind;Ljava/util/Set;Ljava/util/Set;Lorg/jetbrains/dokka/model/properties/PropertyContainer;ILjava/lang/Object;)V
public final fun unaryPlus (Ljava/util/Collection;)V
public final fun unaryPlus (Lorg/jetbrains/dokka/pages/ContentNode;)V
public final fun unorderedList (Lorg/jetbrains/dokka/pages/Kind;Ljava/util/Set;Ljava/util/Set;Lorg/jetbrains/dokka/model/properties/PropertyContainer;Lkotlin/jvm/functions/Function1;)V
public static synthetic fun unorderedList$default (Lorg/jetbrains/dokka/base/translators/documentables/PageContentBuilder$DocumentableContentBuilder;Lorg/jetbrains/dokka/pages/Kind;Ljava/util/Set;Ljava/util/Set;Lorg/jetbrains/dokka/model/properties/PropertyContainer;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
}

public class org/jetbrains/dokka/base/translators/documentables/PageContentBuilder$ListBuilder {
public fun <init> (Lorg/jetbrains/dokka/base/translators/documentables/PageContentBuilder;ZLjava/util/Set;Ljava/util/Set;Lorg/jetbrains/dokka/pages/Kind;Ljava/util/Set;Lorg/jetbrains/dokka/model/properties/PropertyContainer;)V
public final fun build (Ljava/util/Set;Lorg/jetbrains/dokka/pages/Kind;Ljava/util/Set;Lorg/jetbrains/dokka/model/properties/PropertyContainer;)Lorg/jetbrains/dokka/pages/ContentList;
public static synthetic fun build$default (Lorg/jetbrains/dokka/base/translators/documentables/PageContentBuilder$ListBuilder;Ljava/util/Set;Lorg/jetbrains/dokka/pages/Kind;Ljava/util/Set;Lorg/jetbrains/dokka/model/properties/PropertyContainer;ILjava/lang/Object;)Lorg/jetbrains/dokka/pages/ContentList;
public final fun getOrdered ()Z
public final fun item (Ljava/util/Set;Ljava/util/Set;Lorg/jetbrains/dokka/pages/Kind;Ljava/util/Set;Lorg/jetbrains/dokka/model/properties/PropertyContainer;Lkotlin/jvm/functions/Function1;)V
public static synthetic fun item$default (Lorg/jetbrains/dokka/base/translators/documentables/PageContentBuilder$ListBuilder;Ljava/util/Set;Ljava/util/Set;Lorg/jetbrains/dokka/pages/Kind;Ljava/util/Set;Lorg/jetbrains/dokka/model/properties/PropertyContainer;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
}

public class org/jetbrains/dokka/base/translators/documentables/PageContentBuilder$TableBuilder {
Expand Down
Expand Up @@ -9,10 +9,10 @@ import org.jetbrains.dokka.model.Documentable
import org.jetbrains.dokka.model.SourceSetDependent
import org.jetbrains.dokka.model.doc.DocTag
import org.jetbrains.dokka.model.properties.PropertyContainer
import org.jetbrains.dokka.model.properties.plus
import org.jetbrains.dokka.model.toDisplaySourceSets
import org.jetbrains.dokka.pages.*
import org.jetbrains.dokka.utilities.DokkaLogger
import org.jetbrains.dokka.model.properties.plus

@DslMarker
annotation class ContentBuilderMarker
Expand Down Expand Up @@ -153,6 +153,26 @@ open class PageContentBuilder(
}.build()
}

fun unorderedList(
kind: Kind = ContentKind.Main,
sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
styles: Set<Style> = mainStyles,
extra: PropertyContainer<ContentNode> = mainExtra,
operation: ListBuilder.() -> Unit = {}
) {
contents += ListBuilder(false, mainDRI, sourceSets, kind, styles, extra).apply(operation).build()
}

fun orderedList(
kind: Kind = ContentKind.Main,
sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
styles: Set<Style> = mainStyles,
extra: PropertyContainer<ContentNode> = mainExtra,
operation: ListBuilder.() -> Unit = {}
) {
contents += ListBuilder(true, mainDRI, sourceSets, kind, styles, extra).apply(operation).build()
}

internal fun headers(vararg label: String) = contentFor(mainDRI, mainSourcesetData) {
label.forEach { text(it) }
}
Expand Down Expand Up @@ -584,4 +604,40 @@ open class PageContentBuilder(
extra
)
}

@ContentBuilderMarker
open inner class ListBuilder(
val ordered: Boolean,
private val mainDRI: Set<DRI>,
private val mainSourceSets: Set<DokkaSourceSet>,
private val mainKind: Kind,
private val mainStyles: Set<Style>,
private val mainExtra: PropertyContainer<ContentNode>
) {
private val contentNodes: MutableList<ContentNode> = mutableListOf()

fun item(
dri: Set<DRI> = mainDRI,
sourceSets: Set<DokkaSourceSet> = mainSourceSets,
kind: Kind = mainKind,
styles: Set<Style> = mainStyles,
extra: PropertyContainer<ContentNode> = mainExtra,
block: DocumentableContentBuilder.() -> Unit
) {
contentNodes += contentFor(dri, sourceSets, kind, styles, extra, block)
}

fun build(
sourceSets: Set<DokkaSourceSet> = mainSourceSets,
kind: Kind = mainKind,
styles: Set<Style> = mainStyles,
extra: PropertyContainer<ContentNode> = mainExtra
) = ContentList(
contentNodes,
ordered,
DCI(mainDRI, kind),
sourceSets.toDisplaySourceSets(),
styles, extra
)
}
}
Expand Up @@ -55,31 +55,42 @@ open class CommonmarkRenderer(
sourceSetRestriction: Set<DisplaySourceSet>?
) {
buildParagraph()
buildListLevel(node, pageContext)
buildList(node, pageContext)
buildParagraph()
}

private fun StringBuilder.buildListItem(items: List<ContentNode>, pageContext: ContentPage) {
items.forEach {
if (it is ContentList) {
buildList(it, pageContext)
private fun StringBuilder.buildList(
node: ContentList,
pageContext: ContentPage
) {
node.children.forEachIndexed { i, it ->
if (node.ordered) {
// number is irrelevant, but a nice touch
// period is more widely compatible
append("${i + 1}. ")
} else {
append("<li>")
append(buildString { it.build(this, pageContext, it.sourceSets) }.trim())
append("</li>")
append("- ")
}
}
}

private fun StringBuilder.buildListLevel(node: ContentList, pageContext: ContentPage) {
if (node.ordered) {
append("<ol>")
buildListItem(node.children, pageContext)
append("</ol>")
} else {
append("<ul>")
buildListItem(node.children, pageContext)
append("</ul>")
/*
Handle case when list item transitions to another complex node with no preceding text.
For example, the equivalent of:
<li>
<ul><li><ul>Item</ul></li></ul>
</li>

Would be:
-
- Item
*/
if (it is ContentGroup && it.children.firstOrNull()?.let { it !is ContentText } == true) {
append("\n ")
}

buildString { it.build(this, pageContext, it.sourceSets) }
.replace("\n", "\n ") // apply indent
.trim().let { append(it) }
buildNewLine()
}
}

Expand Down Expand Up @@ -328,18 +339,18 @@ open class CommonmarkRenderer(
is RenderingStrategy.Write -> outputWriter.write(path, strategy.text, "")
is RenderingStrategy.Callback -> outputWriter.write(path, strategy.instructions(this, page), ".md")
is RenderingStrategy.DriLocationResolvableWrite -> outputWriter.write(
path,
strategy.contentToResolve { dri, sourcesets ->
locationProvider.resolve(dri, sourcesets)
},
""
path,
strategy.contentToResolve { dri, sourcesets ->
locationProvider.resolve(dri, sourcesets)
},
""
)
is RenderingStrategy.PageLocationResolvableWrite -> outputWriter.write(
path,
strategy.contentToResolve { pageToLocate, context ->
locationProvider.resolve(pageToLocate, context)
},
""
path,
strategy.contentToResolve { pageToLocate, context ->
locationProvider.resolve(pageToLocate, context)
},
""
)
RenderingStrategy.DoNothing -> Unit
}
Expand Down
177 changes: 173 additions & 4 deletions plugins/gfm/src/test/kotlin/renderers/gfm/SimpleElementsTest.kt
@@ -1,12 +1,15 @@
package renderers.gfm

import junit.framework.Assert.assertEquals
import org.jetbrains.dokka.gfm.renderer.CommonmarkRenderer
import org.junit.jupiter.api.Test
import renderers.testPage
import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.pages.*
import org.jetbrains.dokka.pages.ContentEmbeddedResource
import org.jetbrains.dokka.pages.ContentKind
import org.jetbrains.dokka.pages.DCI
import org.jetbrains.dokka.pages.TextStyle
import org.junit.Assert.assertEquals
import org.junit.jupiter.api.Test
import renderers.RawTestPage
import renderers.testPage

class SimpleElementsTest : GfmRenderingOnlyTestBase() {

Expand Down Expand Up @@ -217,4 +220,170 @@ class SimpleElementsTest : GfmRenderingOnlyTestBase() {
assertEquals(expect, renderedContent)
}

@Test
fun `unordered list with two items`() {
val page = testPage {
unorderedList {
item { text("Item 1") }
item { text("Item 2") }
}
}

val expect = """|//[testPage](test-page.md)
|
|- Item 1
|- Item 2""".trimMargin()

CommonmarkRenderer(context).render(page)
assertEquals(expect, renderedContent)
}

@Test
fun `unordered list with styled text`() {
val page = testPage {
unorderedList {
item {
text("Nobody", styles = setOf(TextStyle.Italic))
text(" tosses a Dwarf!")
}
}
}

val expect = "//[testPage](test-page.md)\n\n- *Nobody* tosses a Dwarf!"

CommonmarkRenderer(context).render(page)
assertEquals(expect, renderedContent)
}

@Test
fun `ordered list with two items`() {
val page = testPage {
orderedList {
item { text("Item 1") }
item { text("Item 2") }
}
}

val expect = """|//[testPage](test-page.md)
|
|1. Item 1
|2. Item 2""".trimMargin()

CommonmarkRenderer(context).render(page)
assertEquals(expect, renderedContent)
}

@Test
fun `ordered list with nested unordered list`() {
val page = testPage {
orderedList {
item {
text("And another list:")
unorderedList {
item { text("Item 1") }
item { text("Item 2") }
}
}
item { text("Following item") }
}
}

val expect = """|//[testPage](test-page.md)
|
|1. And another list:
|
| - Item 1
| - Item 2
|2. Following item""".trimMargin()

CommonmarkRenderer(context).render(page)
assertEquals(expect, renderedContent)
}

@Test
fun `ordered list with nested table`() {
val page = testPage {
orderedList {
item {
text("The following table is nested in a list:")
table {
header {
text("Col1")
text("Col2")
}
row {
text("Text1")
text("Text2")
}
}
}
}
}

val expect = """|//[testPage](test-page.md)
|
|1. The following table is nested in a list:
| | Col1 | Col2 |
| |---|---|
| | Text1 | Text2 |""".trimMargin()

CommonmarkRenderer(context).render(page)
assertEquals(expect, renderedContent)
}

@Test
fun `three levels of list`() {
val page = testPage {
unorderedList {
item {
text("Level 1")
unorderedList {
item {
text("Level 2")
unorderedList {
item {
text("Level 3")
}
}
}
}
}
}
}

// Extra newlines are not pretty but do not impact formatting
val expect = """|//[testPage](test-page.md)
|
|- Level 1
|
| - Level 2
|
| - Level 3""".trimMargin()

CommonmarkRenderer(context).render(page)
assertEquals(expect, renderedContent)
}

@Test
fun `nested list with no text preceding it`() {
val page = testPage {
unorderedList {
item {
unorderedList {
item {
text("Nested")
}
}
}
}
}

val expect = """|//[testPage](test-page.md)
|
|-
| - Nested""".trimMargin()

CommonmarkRenderer(context).render(page)
assertEquals(expect, renderedContent)
}
}