Skip to content

Commit

Permalink
Implement custom code block renderers support (#3320)
Browse files Browse the repository at this point in the history
* multiple custom renderers can be installed to support different languages independently
* only language and code properties are provided for extension
  • Loading branch information
whyoleg authored Nov 16, 2023
1 parent 1e12678 commit f333e42
Show file tree
Hide file tree
Showing 5 changed files with 426 additions and 0 deletions.
7 changes: 7 additions & 0 deletions dokka-subprojects/plugin-base/api/plugin-base.api
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public final class org/jetbrains/dokka/base/DokkaBase : org/jetbrains/dokka/plug
public final fun getExternalLocationProviderFactory ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
public final fun getFallbackMerger ()Lorg/jetbrains/dokka/plugability/Extension;
public final fun getFileWriter ()Lorg/jetbrains/dokka/plugability/Extension;
public final fun getHtmlCodeBlockRenderers ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
public final fun getHtmlPreprocessors ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
public final fun getHtmlRenderer ()Lorg/jetbrains/dokka/plugability/Extension;
public final fun getImmediateHtmlCommandConsumer ()Lorg/jetbrains/dokka/plugability/ExtensionPoint;
Expand Down Expand Up @@ -297,6 +298,12 @@ public final class org/jetbrains/dokka/base/renderers/html/CustomResourceInstall
public fun invoke (Lorg/jetbrains/dokka/pages/RootPageNode;)Lorg/jetbrains/dokka/pages/RootPageNode;
}

public abstract interface class org/jetbrains/dokka/base/renderers/html/HtmlCodeBlockRenderer {
public abstract fun buildCodeBlock (Lkotlinx/html/FlowContent;Ljava/lang/String;Ljava/lang/String;)V
public abstract fun isApplicableForDefinedLanguage (Ljava/lang/String;)Z
public abstract fun isApplicableForUndefinedLanguage (Ljava/lang/String;)Z
}

public final class org/jetbrains/dokka/base/renderers/html/HtmlFormatingUtilsKt {
public static final fun buildBreakableDotSeparatedHtml (Lkotlinx/html/FlowContent;Ljava/lang/String;)V
public static final fun buildBreakableText (Lkotlinx/html/FlowContent;Ljava/lang/String;)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ public class DokkaBase : DokkaPlugin() {
public val outputWriter: ExtensionPoint<OutputWriter> by extensionPoint()
public val htmlPreprocessors: ExtensionPoint<PageTransformer> by extensionPoint()

/**
* Extension point for providing custom HTML code block renderers.
*
* This extension point allows overriding the rendering of code blocks in different programming languages.
* Multiple renderers can be installed to support different languages independently.
*/
public val htmlCodeBlockRenderers: ExtensionPoint<HtmlCodeBlockRenderer> by extensionPoint()

@Deprecated("It is not used anymore")
public val tabSortingStrategy: ExtensionPoint<TabSortingStrategy> by extensionPoint()
public val immediateHtmlCommandConsumer: ExtensionPoint<ImmediateHtmlCommandConsumer> by extensionPoint()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package org.jetbrains.dokka.base.renderers.html

import kotlinx.html.FlowContent

/**
* Provides an ability to override code blocks rendering differently dependent on the code language.
*
* Multiple renderers can be installed to support different languages in an independent way.
*/
public interface HtmlCodeBlockRenderer {

/**
* Whether this renderer supports rendering Markdown code blocks
* for the given [language] explicitly specified in the fenced code block definition,
*/
public fun isApplicableForDefinedLanguage(language: String): Boolean

/**
* Whether this renderer supports rendering Markdown code blocks
* for the given [code] when language is not specified in fenced code blocks
* or indented code blocks are used.
*/
public fun isApplicableForUndefinedLanguage(code: String): Boolean

/**
* Defines how to render [code] for specified [language] via HTML tags.
*
* The value of the [language] will be the same as in the input Markdown fenced code block definition.
* In the following example [language] = `kotlin` and [code] = `val a`:
* ~~~markdown
* ```kotlin
* val a
* ```
* ~~~
* The value of the [language] will be `null` if language is not specified in the fenced code block definition
* or indented code blocks are used.
* In the following example [language] = `null` and [code] = `val a`:
* ~~~markdown
* ```
* val a
* ```
* ~~~
*/
public fun FlowContent.buildCodeBlock(language: String?, code: String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public open class HtmlRenderer(
private var shouldRenderSourceSetTabs: Boolean = false

override val preprocessors: List<PageTransformer> = context.plugin<DokkaBase>().query { htmlPreprocessors }
private val customCodeBlockRenderers = context.plugin<DokkaBase>().query { htmlCodeBlockRenderers }

/**
* Tabs themselves are created in HTML plugin since, currently, only HTML format supports them.
Expand Down Expand Up @@ -816,6 +817,31 @@ public open class HtmlRenderer(
code: ContentCodeBlock,
pageContext: ContentPage
) {
if (customCodeBlockRenderers.isNotEmpty()) {
val language = code.language.takeIf(String::isNotBlank)
val codeText = buildString {
code.children.forEach {
when (it) {
is ContentText -> append(it.text)
is ContentBreakLine -> appendLine()
}
}
}

// we use first applicable renderer to override rendering
val applicableRenderer = when (language) {
null -> customCodeBlockRenderers.firstOrNull { it.isApplicableForUndefinedLanguage(codeText) }
else -> customCodeBlockRenderers.firstOrNull { it.isApplicableForDefinedLanguage(language) }
}
if (applicableRenderer != null) {
return with(applicableRenderer) {
buildCodeBlock(language, codeText)
}
}
}

// if there are no applicable custom renderers - fall back to default

div("sample-container") {
val codeLang = "lang-" + code.language.ifEmpty { "kotlin" }
val stylesWithBlock = code.style + TextStyle.Block + codeLang
Expand Down
Loading

0 comments on commit f333e42

Please sign in to comment.