package org.jetbrains.dokka.base.renderers.html
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.jetbrains.dokka.Platform
import org.jetbrains.dokka.base.renderers.sourceSets
import org.jetbrains.dokka.base.templating.AddToSearch
import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.model.DisplaySourceSet
import org.jetbrains.dokka.model.dfs
import org.jetbrains.dokka.model.withDescendants
import org.jetbrains.dokka.pages.*
import org.jetbrains.dokka.plugability.DokkaContext
import org.jetbrains.dokka.transformers.pages.PageTransformer
data class SearchRecord(
val name: String,
val description: String? = null,
val location: String,
val searchKeys: List = listOf(name)
) {
companion object
}
open class SearchbarDataInstaller(val context: DokkaContext) : PageTransformer {
data class DRIWithSourceSets(val dri: DRI, val sourceSet: Set)
data class SignatureWithId(val driWithSourceSets: DRIWithSourceSets, val displayableSignature: String) {
constructor(dri: DRI, page: ContentPage) : this( DRIWithSourceSets(dri, page.sourceSets()),
getSymbolSignature(page, dri)?.let { flattenToText(it) } ?: page.name)
val id: String
get() = with(driWithSourceSets.dri) {
listOfNotNull(
packageName?.takeIf { it.isNotBlank() },
classNames,
callable?.name
).joinToString(".")
}
}
private val mapper = jacksonObjectMapper()
open fun generatePagesList(pages: List, locationResolver: DriResolver): List =
pages.map { pageWithId ->
createSearchRecord(
name = pageWithId.displayableSignature,
description = pageWithId.id,
location = resolveLocation(locationResolver, pageWithId.driWithSourceSets).orEmpty(),
searchKeys = listOf(
pageWithId.id.substringAfterLast("."),
pageWithId.displayableSignature,
pageWithId.id,
)
)
}.sortedWith(compareBy({ it.name }, { it.description }))
open fun createSearchRecord(
name: String,
description: String?,
location: String,
searchKeys: List
): SearchRecord =
SearchRecord(name, description, location, searchKeys)
open fun processPage(page: PageNode): List =
when (page) {
is ContentPage -> page.takeIf { page !is ModulePageNode && page !is PackagePageNode }?.dri
?.map { dri -> SignatureWithId(dri, page) }.orEmpty()
else -> emptyList()
}
private fun resolveLocation(locationResolver: DriResolver, driWithSourceSets: DRIWithSourceSets): String? =
locationResolver(driWithSourceSets.dri, driWithSourceSets.sourceSet).also { location ->
if (location.isNullOrBlank()) context.logger.warn("Cannot resolve path for ${driWithSourceSets.dri}")
}
override fun invoke(input: RootPageNode): RootPageNode {
val signatureWithIds = input.withDescendants().fold(emptyList()) { pageList, page ->
pageList + processPage(page)
}
val page = RendererSpecificResourcePage(
name = "scripts/pages.json",
children = emptyList(),
strategy = RenderingStrategy.DriLocationResolvableWrite { resolver ->
val content = signatureWithIds.run {
generatePagesList(this, resolver)
}
if (context.configuration.delayTemplateSubstitution) {
mapper.writeValueAsString(AddToSearch(context.configuration.moduleName, content))
} else {
mapper.writeValueAsString(content)
}
})
return input.modified(children = input.children + page)
}
}
private fun getSymbolSignature(page: ContentPage, dri: DRI) =
page.content.dfs { it.dci.kind == ContentKind.Symbol && it.dci.dri.contains(dri) }
private fun flattenToText(node: ContentNode): String {
fun getContentTextNodes(node: ContentNode, sourceSetRestriction: DisplaySourceSet): List =
when (node) {
is ContentText -> listOf(node)
is ContentComposite -> node.children
.filter { sourceSetRestriction in it.sourceSets }
.flatMap { getContentTextNodes(it, sourceSetRestriction) }
.takeIf { node.dci.kind != ContentKind.Annotations && node.dci.kind != ContentKind.Source }
.orEmpty()
else -> emptyList()
}
val sourceSetRestriction =
node.sourceSets.find { it.platform == Platform.common } ?: node.sourceSets.first()
return getContentTextNodes(node, sourceSetRestriction).joinToString("") { it.text }
}