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

Implement custom code block renderers support #3320

Merged
merged 4 commits into from
Nov 16, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading