package org.jetbrains.dokka.base.renderers.html import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import org.jetbrains.dokka.Platform 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 import java.util.concurrent.ConcurrentHashMap typealias PageId = String typealias Json = String 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 PageWithId(val id: PageId, val page: ContentPage) { val displayableSignature = getSymbolSignature(page)?.let { flattenToText(it) } ?: page.name } private val mapper = jacksonObjectMapper() open fun generatePagesList(pages: Map, locationResolver: PageResolver): Json = pages.entries .filter { it.key.isNotEmpty() } .sortedWith(compareBy({ it.key }, { it.value.displayableSignature })) .groupBy { it.key.substringAfterLast(".") } .entries .flatMap { entry -> entry.value.map { subentry -> createSearchRecord( name = subentry.value.displayableSignature, description = subentry.key, location = resolveLocation(locationResolver, subentry.value.page).orEmpty(), searchKeys = listOf(entry.key, subentry.value.displayableSignature) ) } }.run { mapper.writeValueAsString(this) } open fun createSearchRecord( name: String, description: String?, location: String, searchKeys: List ): SearchRecord = SearchRecord(name, description, location, searchKeys) open fun processPage(page: PageNode): PageWithId? = when (page) { is ContentPage -> page.takeIf { page !is ModulePageNode && page !is PackagePageNode }?.documentable ?.let { documentable -> listOfNotNull( documentable.dri.packageName, documentable.dri.classNames, documentable.dri.callable?.name ).takeIf { it.isNotEmpty() }?.joinToString(".") }?.let { id -> PageWithId(id, page) } else -> null } private fun resolveLocation(locationResolver: PageResolver, page: ContentPage): String? = locationResolver(page, null).also { location -> if (location.isNullOrBlank()) context.logger.warn("Cannot resolve path for ${page.dri}") } override fun invoke(input: RootPageNode): RootPageNode { val page = RendererSpecificResourcePage( name = "scripts/pages.js", children = emptyList(), strategy = RenderingStrategy.PageLocationResolvableWrite { resolver -> input.withDescendants().fold(emptyMap()) { pageList, page -> processPage(page)?.let { pageList + Pair(it.id, it) } ?: pageList }.run { """var pages = ${generatePagesList(this, resolver)}""" } }) return input.modified(children = input.children + page) } } private fun getSymbolSignature(page: ContentPage) = page.content.dfs { it.dci.kind == ContentKind.Symbol } 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 } .orEmpty() else -> emptyList() } val sourceSetRestriction = node.sourceSets.find { it.platform == Platform.common } ?: node.sourceSets.first() return getContentTextNodes(node, sourceSetRestriction).joinToString("") { it.text } }