From f333e425440701e50361f61acc2f9cb2d10fac1a Mon Sep 17 00:00:00 2001 From: Oleg Yukhnevich Date: Thu, 16 Nov 2023 16:58:19 +0200 Subject: Implement custom code block renderers support (#3320) * multiple custom renderers can be installed to support different languages independently * only language and code properties are provided for extension --- .../kotlin/org/jetbrains/dokka/base/DokkaBase.kt | 8 ++++ .../base/renderers/html/HtmlCodeBlockRenderer.kt | 49 ++++++++++++++++++++++ .../dokka/base/renderers/html/HtmlRenderer.kt | 26 ++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/renderers/html/HtmlCodeBlockRenderer.kt (limited to 'dokka-subprojects/plugin-base/src/main') diff --git a/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/DokkaBase.kt b/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/DokkaBase.kt index ca86d4d5..6fa4270b 100644 --- a/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/DokkaBase.kt +++ b/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/DokkaBase.kt @@ -49,6 +49,14 @@ public class DokkaBase : DokkaPlugin() { public val outputWriter: ExtensionPoint by extensionPoint() public val htmlPreprocessors: ExtensionPoint 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 by extensionPoint() + @Deprecated("It is not used anymore") public val tabSortingStrategy: ExtensionPoint by extensionPoint() public val immediateHtmlCommandConsumer: ExtensionPoint by extensionPoint() diff --git a/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/renderers/html/HtmlCodeBlockRenderer.kt b/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/renderers/html/HtmlCodeBlockRenderer.kt new file mode 100644 index 00000000..29af6f98 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/renderers/html/HtmlCodeBlockRenderer.kt @@ -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) +} diff --git a/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/renderers/html/HtmlRenderer.kt b/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/renderers/html/HtmlRenderer.kt index 083876d5..e7b77383 100644 --- a/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/renderers/html/HtmlRenderer.kt +++ b/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/renderers/html/HtmlRenderer.kt @@ -52,6 +52,7 @@ public open class HtmlRenderer( private var shouldRenderSourceSetTabs: Boolean = false override val preprocessors: List = context.plugin().query { htmlPreprocessors } + private val customCodeBlockRenderers = context.plugin().query { htmlCodeBlockRenderers } /** * Tabs themselves are created in HTML plugin since, currently, only HTML format supports them. @@ -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 -- cgit