mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
WH-4315-offline-search
Support new format for search results; fix the display of json-based pages. Merge-request: IJ-MR-101302 Merged-by: Egor Malyshev <egor.malyshev@jetbrains.com> GitOrigin-RevId: b7cf90e0c5569f130b8c505e9936f0d0880095c4
This commit is contained in:
committed by
intellij-monorepo-bot
parent
91750d36b6
commit
45b31fae2a
@@ -13,6 +13,7 @@ import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
|
||||
private const val MODULE_NAME = "intellij.platform.builtInHelp"
|
||||
private val LUCENE_LIBRARIES = setOf("lucene-queryparser", "lucene-highlighter", "lucene-memory")
|
||||
|
||||
internal fun buildHelpPlugin(pluginVersion: String, context: BuildContext): PluginLayout? {
|
||||
val productName = context.applicationInfo.productName
|
||||
@@ -46,6 +47,7 @@ internal fun buildHelpPlugin(pluginVersion: String, context: BuildContext): Plug
|
||||
content = pluginXml(buildContext, pluginVersion),
|
||||
overwrite = true)
|
||||
}
|
||||
LUCENE_LIBRARIES.forEach { spec.withProjectLibrary(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +84,7 @@ private suspend fun buildResourcesForHelpPlugin(resourceRoot: Path, classPath: L
|
||||
spanBuilder("index help topics").useWithScope {
|
||||
runIdea(context = context, mainClass = "com.jetbrains.builtInHelp.indexer.HelpIndexer",
|
||||
args = listOf(resourceRoot.resolve("search").toString(),
|
||||
resourceRoot.resolve("topics").toString()),
|
||||
resourceRoot.resolve("topics").toString()),
|
||||
jvmArgs = emptyList(),
|
||||
classPath = classPath)
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
<orderEntry type="library" name="lucene-highlighter" level="project" />
|
||||
<orderEntry type="library" name="lucene-queryparser" level="project" />
|
||||
<orderEntry type="library" name="lucene-suggest" level="project" />
|
||||
<orderEntry type="library" name="gson" level="project" />
|
||||
<orderEntry type="library" name="commons-compress" level="project" />
|
||||
<orderEntry type="library" name="jsoup" level="project" />
|
||||
<orderEntry type="library" name="jaxb-api" level="project" />
|
||||
@@ -31,5 +30,9 @@
|
||||
<orderEntry type="library" name="netty-buffer" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ide.util.io" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ide.util.netty" />
|
||||
<orderEntry type="library" name="jackson" level="project" />
|
||||
<orderEntry type="library" name="jackson-databind" level="project" />
|
||||
<orderEntry type="library" name="jackson-module-kotlin" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.maven.server.indexer" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,58 +1,104 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.builtInHelp
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.util.ResourceUtil
|
||||
import com.jetbrains.builtInHelp.mapping.HelpMap
|
||||
import com.jetbrains.builtInHelp.mapping.HelpMapId
|
||||
import io.netty.channel.ChannelFutureListener
|
||||
import io.netty.channel.ChannelHandlerContext
|
||||
import io.netty.handler.codec.http.*
|
||||
import org.jetbrains.builtInWebServer.BuiltInServerOptions
|
||||
import org.jetbrains.io.addCommonHeaders
|
||||
import org.jetbrains.io.send
|
||||
import javax.xml.bind.JAXBContext
|
||||
|
||||
@Suppress("unused")
|
||||
class HelpContentRequestHandler : HelpRequestHandlerBase() {
|
||||
override fun process(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext): Boolean {
|
||||
|
||||
if (request.uri().contains("config.json")) {
|
||||
private val propsToRemove = listOf(
|
||||
"searchAlgoliaApiKey", "searchAlgoliaId",
|
||||
"searchAlgoliaIndexName", "versionsService"
|
||||
)
|
||||
|
||||
val config = HelpRequestHandlerBase::class.java.getResource("/topics/config.json")
|
||||
.openStream().bufferedReader().use { it.readText() }.replace("https://data.services.jetbrains.com",
|
||||
"http://127.0.0.1:${BuiltInServerOptions.getInstance().effectiveBuiltInServerPort}").replace(
|
||||
"true", "false")
|
||||
override fun process(
|
||||
urlDecoder: QueryStringDecoder,
|
||||
request: FullHttpRequest,
|
||||
context: ChannelHandlerContext
|
||||
): Boolean {
|
||||
for (name: String in urlDecoder.parameters().keys) {
|
||||
val param = urlDecoder.parameters()[name]
|
||||
if (param != null && (param.isEmpty() || StringUtil.isEmpty(param[0]))) {
|
||||
val mapStream = ResourceUtil.getResourceAsStream(
|
||||
HelpContentRequestHandler::class.java.classLoader,
|
||||
"topics", "Map.jhm"
|
||||
)
|
||||
val map: HelpMap = JAXBContext.newInstance(
|
||||
HelpMap::class.java, HelpMapId::class.java
|
||||
).createUnmarshaller().unmarshal(
|
||||
mapStream
|
||||
) as HelpMap
|
||||
|
||||
sendData(config.toByteArray(Charsets.UTF_8), "config.json", request, context.channel(), request.headers())
|
||||
return true
|
||||
val response = DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.TEMPORARY_REDIRECT)
|
||||
response.headers().add(
|
||||
"Location",
|
||||
"http://127.0.0.1:${BuiltInServerOptions.getInstance().effectiveBuiltInServerPort}/help/${
|
||||
map.getUrlForId(
|
||||
name
|
||||
)
|
||||
}"
|
||||
)
|
||||
context.channel().writeAndFlush(response).addListener(ChannelFutureListener.CLOSE)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if (urlDecoder.parameters().isNotEmpty()) {
|
||||
for (name: String in urlDecoder.parameters().keys) {
|
||||
val param = urlDecoder.parameters()[name]
|
||||
if (param != null && (param.isEmpty() || StringUtil.isEmpty(param[0]))) {
|
||||
val map: HelpMap = JAXBContext.newInstance(
|
||||
HelpMap::class.java, HelpMapId::class.java).createUnmarshaller().unmarshal(
|
||||
HelpRequestHandlerBase::class.java.getResource("/topics/Map.jhm")) as HelpMap
|
||||
val response = DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.FOUND)
|
||||
response.addCommonHeaders()
|
||||
response.headers().add("Location", prefix + map.getUrlForId(name))
|
||||
context.channel().writeAndFlush(response).addListener(ChannelFutureListener.CLOSE)
|
||||
return true
|
||||
when (val resourceName: String = urlDecoder.path().substringAfterLast("/")) {
|
||||
"config.json" -> {
|
||||
val configStream = ResourceUtil.getResourceAsStream(
|
||||
HelpContentRequestHandler::class.java.classLoader,
|
||||
"topics", "config.json"
|
||||
)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val configJson: LinkedHashMap<String, Any> = jacksonObjectMapper().readValue(
|
||||
configStream,
|
||||
LinkedHashMap::class.java
|
||||
) as LinkedHashMap<String, Any>
|
||||
|
||||
configJson.keys.removeAll { propsToRemove.contains(it) }
|
||||
|
||||
configJson.keys.forEach {
|
||||
if (configJson[it] is String) {
|
||||
val current: String = configJson[it] as String
|
||||
configJson[it] = current.replace(
|
||||
"https://data.services.jetbrains.com",
|
||||
"http://127.0.0.1:${BuiltInServerOptions.getInstance().effectiveBuiltInServerPort}"
|
||||
).replace(
|
||||
"true", "false"
|
||||
)
|
||||
}
|
||||
}
|
||||
sendData(
|
||||
jacksonObjectMapper().writeValueAsBytes(configJson),
|
||||
"config.json",
|
||||
request,
|
||||
context.channel(),
|
||||
request.headers()
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
try {
|
||||
sendResource(
|
||||
resourceName, urlDecoder.path().substringBeforeLast("/"),
|
||||
request, context.channel(), request.headers()
|
||||
)
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
HttpResponseStatus.NOT_FOUND.send(context.channel(), request, e.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
val resName: String = urlDecoder.path().substring(request.uri().lastIndexOf("/") + 1)
|
||||
|
||||
if (StringUtil.isNotEmpty(resName)) {
|
||||
try {
|
||||
sendResource(resName, request, context.channel(), request.headers())
|
||||
return true
|
||||
}
|
||||
catch (e: Exception) {
|
||||
HttpResponseStatus.NOT_FOUND.send(context.channel(), request)
|
||||
}
|
||||
}
|
||||
return false
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.builtInHelp
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext
|
||||
@@ -6,8 +6,6 @@ import io.netty.handler.codec.http.FullHttpRequest
|
||||
import io.netty.handler.codec.http.QueryStringDecoder
|
||||
|
||||
abstract class HelpProcessingRequestBase : HelpRequestHandlerBase() {
|
||||
abstract override val prefix: String
|
||||
|
||||
override fun process(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext): Boolean {
|
||||
|
||||
val num: String? = urlDecoder.parameters()["num"]?.get(0)
|
||||
@@ -15,7 +13,6 @@ abstract class HelpProcessingRequestBase : HelpRequestHandlerBase() {
|
||||
val dataToSend = getProcessedData(urlDecoder.parameters()["q"]?.get(0)!!,
|
||||
if (num != null) Integer.valueOf(num) else 100).toByteArray(Charsets.UTF_8)
|
||||
|
||||
sendData(dataToSend, "data.json", request, context.channel(), request.headers())
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.builtInHelp
|
||||
|
||||
import com.intellij.util.ResourceUtil
|
||||
import io.netty.channel.Channel
|
||||
import io.netty.handler.codec.http.FullHttpRequest
|
||||
import io.netty.handler.codec.http.HttpHeaders
|
||||
import io.netty.handler.codec.http.HttpRequest
|
||||
import org.apache.commons.compress.utils.IOUtils
|
||||
import org.jetbrains.ide.HttpRequestHandler
|
||||
import org.apache.commons.compress.utils.FileNameUtils
|
||||
import org.jetbrains.annotations.NonNls
|
||||
import org.jetbrains.ide.HttpRequestHandler
|
||||
|
||||
/**
|
||||
* Created by Egor.Malyshev on 7/13/2017.
|
||||
@@ -17,12 +18,33 @@ abstract class HelpRequestHandlerBase : HttpRequestHandler() {
|
||||
open val prefix: String = "/help/"
|
||||
|
||||
override fun isAccessible(request: HttpRequest): Boolean {
|
||||
return super.isAccessible(request) && request.uri().startsWith(prefix)
|
||||
return super.isAccessible(request) && request.uri().contains(prefix)
|
||||
}
|
||||
|
||||
protected fun sendResource(resourceName: String, request: FullHttpRequest, channel: Channel, extraHeaders: HttpHeaders): Boolean {
|
||||
return sendData(IOUtils.toByteArray(HelpRequestHandlerBase::class.java.getResourceAsStream(
|
||||
(if (request.uri().contains("/img/")) "/images/" else "/topics/") + resourceName) ?: throw Exception("$resourceName not found")),
|
||||
resourceName, request, channel, extraHeaders)
|
||||
protected fun sendResource(
|
||||
resourceName: String,
|
||||
resourceLocation: String,
|
||||
request: FullHttpRequest,
|
||||
channel: Channel,
|
||||
extraHeaders: HttpHeaders
|
||||
): Boolean {
|
||||
|
||||
val isImage = resourceLocation.contains("/img/")
|
||||
val retrieveName = when (isImage) {
|
||||
true -> {
|
||||
val baseName = FileNameUtils.getBaseName(resourceName)
|
||||
"""${baseName.substringBeforeLast("_dark")}.${FileNameUtils.getExtension(resourceName)}"""
|
||||
}
|
||||
else -> resourceName
|
||||
}
|
||||
val resStream = ResourceUtil.getResourceAsStream(
|
||||
HelpRequestHandlerBase::class.java.classLoader,
|
||||
if (isImage) "images" else "topics", retrieveName
|
||||
)
|
||||
return sendData(
|
||||
resStream?.readAllBytes() ?: throw Exception("$resourceName not found in $resourceLocation via ${request.uri()}"), resourceName,
|
||||
request, channel,
|
||||
extraHeaders
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,26 @@
|
||||
package com.jetbrains.builtInHelp
|
||||
|
||||
import com.jetbrains.builtInHelp.search.HelpSearch
|
||||
import io.netty.channel.ChannelHandlerContext
|
||||
import io.netty.handler.codec.http.FullHttpRequest
|
||||
import io.netty.handler.codec.http.QueryStringDecoder
|
||||
import org.jetbrains.annotations.NonNls
|
||||
|
||||
@Suppress("unused")
|
||||
class HelpSearchRequestHandler : HelpProcessingRequestBase() {
|
||||
class HelpSearchRequestHandler : HelpRequestHandlerBase() {
|
||||
@NonNls
|
||||
override val prefix: String = "/search/"
|
||||
override fun process(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext): Boolean {
|
||||
|
||||
override fun getProcessedData(query: String, maxHits: Int): String {
|
||||
return HelpSearch.search(query, maxHits)
|
||||
val query = urlDecoder.parameters()["query"]?.get(0)
|
||||
val maxHitsParam = urlDecoder.parameters()["maxHits"]?.get(0)
|
||||
|
||||
sendData(HelpSearch.search(query, if (maxHitsParam != null) Integer.parseInt(maxHitsParam) else 100).encodeToByteArray(),
|
||||
"data.json",
|
||||
request,
|
||||
context.channel(),
|
||||
request.headers())
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.builtInHelp.search
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import org.apache.commons.compress.utils.IOUtils
|
||||
import org.apache.lucene.analysis.standard.StandardAnalyzer
|
||||
import org.apache.lucene.index.DirectoryReader
|
||||
import org.apache.lucene.search.spell.LuceneDictionary
|
||||
import org.apache.lucene.search.suggest.analyzing.BlendedInfixSuggester
|
||||
import org.apache.lucene.store.FSDirectory
|
||||
import org.jetbrains.annotations.NotNull
|
||||
import java.io.FileOutputStream
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import org.jetbrains.annotations.NonNls
|
||||
|
||||
|
||||
class HelpComplete {
|
||||
|
||||
companion object {
|
||||
private val resources = arrayOf("_0.cfe", "_0.cfs", "_0.si", "segments_1")
|
||||
@NonNls
|
||||
val PREFIX = "/search/"
|
||||
@NonNls
|
||||
val NOT_FOUND = "[]"
|
||||
|
||||
private val analyzer: StandardAnalyzer = StandardAnalyzer()
|
||||
|
||||
@NotNull
|
||||
fun complete(query: String, maxHits: Int): String {
|
||||
|
||||
val indexDir: Path? = Files.createTempDirectory("search-index")
|
||||
var indexDirectory: FSDirectory? = null
|
||||
var reader: DirectoryReader? = null
|
||||
var suggester: BlendedInfixSuggester? = null
|
||||
if (indexDir != null)
|
||||
try {
|
||||
for (resourceName in resources) {
|
||||
val input = HelpSearch::class.java.getResourceAsStream(
|
||||
PREFIX + resourceName)
|
||||
val fos: FileOutputStream = FileOutputStream(Paths.get(indexDir.toAbsolutePath().toString(), resourceName).toFile())
|
||||
IOUtils.copy(input, fos)
|
||||
fos.flush()
|
||||
fos.close()
|
||||
input.close()
|
||||
}
|
||||
indexDirectory = FSDirectory.open(indexDir)
|
||||
reader = DirectoryReader.open(indexDirectory)
|
||||
|
||||
suggester = BlendedInfixSuggester(indexDirectory, analyzer)
|
||||
|
||||
suggester.build(LuceneDictionary(reader, "contents"))
|
||||
|
||||
val completionResults = suggester.lookup(query, maxHits, false, true)
|
||||
|
||||
return Gson().toJson(completionResults)
|
||||
}
|
||||
catch (e: Exception) {
|
||||
Logger.getInstance(HelpComplete::class.java).error("Error searching help for $query", e)
|
||||
}
|
||||
finally {
|
||||
suggester?.close()
|
||||
indexDirectory?.close()
|
||||
reader?.close()
|
||||
for (f in indexDir.toFile().listFiles()) f.delete()
|
||||
Files.delete(indexDir)
|
||||
}
|
||||
return NOT_FOUND
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.builtInHelp.search
|
||||
|
||||
import org.apache.commons.compress.utils.IOUtils
|
||||
import org.apache.lucene.analysis.standard.StandardAnalyzer
|
||||
import org.apache.lucene.store.FSDirectory
|
||||
import org.jetbrains.annotations.NotNull
|
||||
import java.io.FileOutputStream
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import org.jetbrains.annotations.NonNls
|
||||
|
||||
|
||||
abstract class HelpIndexProcessor {
|
||||
|
||||
companion object {
|
||||
val resources = arrayOf("_0.cfe", "_0.cfs", "_0.si", "segments_1")
|
||||
@NonNls
|
||||
val STORAGE_PREFIX = "/search/"
|
||||
@NonNls
|
||||
val EMPTY_RESULT = "[]"
|
||||
}
|
||||
|
||||
val analyzer: StandardAnalyzer = StandardAnalyzer()
|
||||
|
||||
fun deployIndex(): FSDirectory? {
|
||||
val indexDir: Path? = Files.createTempDirectory("search-index")
|
||||
var result: FSDirectory? = null
|
||||
if (indexDir != null) {
|
||||
for (resourceName in resources) {
|
||||
val input = HelpSearch::class.java.getResourceAsStream(
|
||||
STORAGE_PREFIX + resourceName)
|
||||
val fos: FileOutputStream = FileOutputStream(Paths.get(indexDir.toAbsolutePath().toString(), resourceName).toFile())
|
||||
IOUtils.copy(input, fos)
|
||||
fos.flush()
|
||||
fos.close()
|
||||
input.close()
|
||||
}
|
||||
result = FSDirectory.open(indexDir)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun releaseIndex(indexDirectory: FSDirectory) {
|
||||
val path = indexDirectory.directory
|
||||
for (f in path.toFile().listFiles()) f.delete()
|
||||
Files.delete(path)
|
||||
}
|
||||
|
||||
@NotNull
|
||||
abstract fun process(query: String, maxResults: Int): String
|
||||
}
|
||||
@@ -1,95 +1,153 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// Copyright 2000-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.builtInHelp.search
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.intellij.openapi.application.ApplicationInfo
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.util.ResourceUtil
|
||||
import org.apache.commons.compress.utils.IOUtils
|
||||
import org.apache.lucene.analysis.standard.StandardAnalyzer
|
||||
import org.apache.lucene.index.DirectoryReader
|
||||
import org.apache.lucene.queryparser.classic.QueryParser
|
||||
import org.apache.lucene.queryparser.simple.SimpleQueryParser
|
||||
import org.apache.lucene.search.IndexSearcher
|
||||
import org.apache.lucene.search.Query
|
||||
import org.apache.lucene.search.TopScoreDocCollector
|
||||
import org.apache.lucene.search.highlight.Highlighter
|
||||
import org.apache.lucene.search.highlight.QueryScorer
|
||||
import org.apache.lucene.search.highlight.Scorer
|
||||
import org.apache.lucene.store.FSDirectory
|
||||
import org.jetbrains.annotations.NotNull
|
||||
import org.jetbrains.annotations.NonNls
|
||||
import org.jetbrains.annotations.NotNull
|
||||
import java.io.FileOutputStream
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.util.*
|
||||
|
||||
|
||||
class HelpSearch {
|
||||
|
||||
companion object {
|
||||
private val resources = arrayOf("_0.cfe", "_0.cfs", "_0.si", "segments_1")
|
||||
|
||||
@NonNls
|
||||
val PREFIX = "/search/"
|
||||
private val NOT_FOUND = "[]"
|
||||
private const val NOT_FOUND = "[]"
|
||||
|
||||
private val analyzer: StandardAnalyzer = StandardAnalyzer()
|
||||
|
||||
@NotNull
|
||||
fun search(query: String, maxHits: Int): String {
|
||||
fun search(query: String?, maxHits: Int): String {
|
||||
|
||||
val indexDir: Path? = Files.createTempDirectory("search-index")
|
||||
var indexDirectory: FSDirectory? = null
|
||||
var reader: DirectoryReader? = null
|
||||
if (indexDir != null)
|
||||
try {
|
||||
if (query != null) {
|
||||
val indexDir: Path? = Files.createTempDirectory("search-index")
|
||||
var indexDirectory: FSDirectory? = null
|
||||
var reader: DirectoryReader? = null
|
||||
if (indexDir != null)
|
||||
try {
|
||||
|
||||
for (resourceName in resources) {
|
||||
val input = HelpSearch::class.java.getResourceAsStream(
|
||||
PREFIX + resourceName)
|
||||
val fos = FileOutputStream(Paths.get(indexDir.toAbsolutePath().toString(), resourceName).toFile())
|
||||
IOUtils.copy(input, fos)
|
||||
fos.flush()
|
||||
fos.close()
|
||||
input.close()
|
||||
for (resourceName in resources) {
|
||||
val input = ResourceUtil.getResourceAsStream(HelpSearch::class.java.classLoader,
|
||||
"search",
|
||||
resourceName)
|
||||
val fos =
|
||||
FileOutputStream(Paths.get(indexDir.toAbsolutePath().toString(), resourceName).toFile())
|
||||
IOUtils.copy(input, fos)
|
||||
fos.flush()
|
||||
fos.close()
|
||||
input?.close()
|
||||
}
|
||||
|
||||
indexDirectory = FSDirectory.open(indexDir)
|
||||
reader = DirectoryReader.open(indexDirectory)
|
||||
|
||||
val searcher = IndexSearcher(reader)
|
||||
val collector: TopScoreDocCollector = TopScoreDocCollector.create(maxHits, maxHits)
|
||||
val q = SimpleQueryParser(analyzer, mapOf(Pair("contents", 1.0F), Pair("title", 1.5F))).parse(query)
|
||||
searcher.search(q, collector)
|
||||
val hits = collector.topDocs().scoreDocs
|
||||
|
||||
val scorer: Scorer = QueryScorer(q)
|
||||
val highlighter = Highlighter(scorer)
|
||||
|
||||
val results = ArrayList<HelpSearchResult>()
|
||||
for (i in hits.indices) {
|
||||
val doc = searcher.doc(hits[i].doc)
|
||||
val contentValue = buildString {
|
||||
append(highlighter.getBestFragment(
|
||||
analyzer, "contents", doc.get("contents")
|
||||
))
|
||||
append("...")
|
||||
}
|
||||
results.add(
|
||||
HelpSearchResult(
|
||||
doc.get("filename"),
|
||||
doc.get("title"),
|
||||
"",
|
||||
doc.get("title"),
|
||||
i.toString(),
|
||||
HelpSearchResult.SnippetResult(
|
||||
HelpSearchResult.SnippetResult.Content(
|
||||
contentValue, "full"
|
||||
)
|
||||
),
|
||||
HelpSearchResult.HighlightedResult(
|
||||
HelpSearchResult.HelpSearchResultDetails(doc.get("filename")),
|
||||
HelpSearchResult.HelpSearchResultDetails(doc.get("title")),
|
||||
HelpSearchResult.HelpSearchResultDetails(contentValue),
|
||||
HelpSearchResult.HelpSearchResultDetails(doc.get("title")),
|
||||
HelpSearchResult.HelpSearchResultDetails(doc.get("title"))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
val searchResults = HelpSearchResults(results)
|
||||
if (searchResults.hits.isNotEmpty()) return jacksonObjectMapper().writeValueAsString(searchResults)
|
||||
}
|
||||
|
||||
indexDirectory = FSDirectory.open(indexDir)
|
||||
reader = DirectoryReader.open(indexDirectory)
|
||||
ApplicationInfo.getInstance()
|
||||
|
||||
val searcher = IndexSearcher(reader)
|
||||
val collector: TopScoreDocCollector = TopScoreDocCollector.create(maxHits, maxHits)
|
||||
|
||||
val q: Query = QueryParser("contents", analyzer).parse(query)
|
||||
searcher.search(q, collector)
|
||||
val hits = collector.topDocs().scoreDocs
|
||||
|
||||
|
||||
val scorer: Scorer = QueryScorer(q)
|
||||
val highlighter = Highlighter(scorer)
|
||||
|
||||
val results = ArrayList<HelpSearchResult>()
|
||||
for (i in hits.indices) {
|
||||
val doc = searcher.doc(hits[i].doc)
|
||||
results.add(
|
||||
HelpSearchResult(i, doc.get("filename"), highlighter.getBestFragment(
|
||||
analyzer, "contents", doc.get("contents")) + "...",
|
||||
doc.get("title"), listOf("webhelp")))
|
||||
catch (e: Throwable) {
|
||||
Logger.getInstance(HelpSearch::class.java).info("Error searching help for $query", e)
|
||||
}
|
||||
|
||||
val searchResults = HelpSearchResults(results)
|
||||
return if (searchResults.results.isEmpty()) NOT_FOUND else Gson().toJson(searchResults)
|
||||
}
|
||||
catch (e: Exception) {
|
||||
Logger.getInstance(HelpSearch::class.java).error("Error searching help for $query", e)
|
||||
}
|
||||
finally {
|
||||
indexDirectory?.close()
|
||||
reader?.close()
|
||||
val tempFiles = indexDir.toFile().listFiles()
|
||||
tempFiles?.forEach { it.delete() }
|
||||
Files.delete(indexDir)
|
||||
}
|
||||
finally {
|
||||
indexDirectory?.close()
|
||||
reader?.close()
|
||||
val tempFiles = indexDir.toFile().listFiles()
|
||||
tempFiles?.forEach { it.delete() }
|
||||
Files.delete(indexDir)
|
||||
}
|
||||
}
|
||||
return NOT_FOUND
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class HelpSearchResult(
|
||||
val url: String,
|
||||
val pageTitle: String,
|
||||
val breadcrumbs: String,
|
||||
val mainTitle: String,
|
||||
val objectID: String,
|
||||
val _snippetResult: SnippetResult,
|
||||
val _highlightResult: HighlightedResult
|
||||
) {
|
||||
data class SnippetResult(val content: Content) {
|
||||
data class Content(
|
||||
val value: String,
|
||||
val matchLevel: String
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
data class HelpSearchResultDetails(
|
||||
val value: String,
|
||||
val matchLevel: String = "full",
|
||||
val fullyHighlighted: Boolean = true,
|
||||
val matchedWords: List<String> = Collections.emptyList()
|
||||
)
|
||||
|
||||
data class HighlightedResult(
|
||||
val url: HelpSearchResultDetails,
|
||||
val pageTitle: HelpSearchResultDetails,
|
||||
val metaDescription: HelpSearchResultDetails,
|
||||
val mainTitle: HelpSearchResultDetails,
|
||||
val headings: HelpSearchResultDetails
|
||||
)
|
||||
}
|
||||
|
||||
data class HelpSearchResults(var hits: List<HelpSearchResult>)
|
||||
@@ -1,5 +0,0 @@
|
||||
package com.jetbrains.builtInHelp.search
|
||||
|
||||
data class HelpSearchResult(var number: Int, var url: String, var snippet: String, var title: String, var categories: List<String>)
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.builtInHelp.search
|
||||
|
||||
data class HelpSearchResults(var results: List<HelpSearchResult>)
|
||||
Reference in New Issue
Block a user