mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-08 15:09:39 +07:00
expandable definition support
IJPL-156 Implement improvements for Quick documentation popup GitOrigin-RevId: c4036fa30ccfe96c7742e95d1f383ff666432930
This commit is contained in:
committed by
intellij-monorepo-bot
parent
043e0e2665
commit
bf6f9730f4
@@ -9,6 +9,7 @@ import com.intellij.openapi.util.text.HtmlChunk;
|
||||
*/
|
||||
public interface DocumentationMarkup {
|
||||
@NlsSafe String DEFINITION_START = "<div class='definition'><pre>";
|
||||
@NlsSafe String EXPANDABLE_DEFINITION_START = "<div class='definition expandable'><pre>";
|
||||
@NlsSafe String DEFINITION_END = "</pre></div>";
|
||||
@NlsSafe String CONTENT_START = "<div class='content'>";
|
||||
@NlsSafe String CONTENT_END = "</div>";
|
||||
|
||||
@@ -594,3 +594,5 @@ command.title.finishing.template=Finishing Template
|
||||
notification.group.batch.quick.fix=Batch quick fix
|
||||
command.check.availability.for=Check Availability for {0}
|
||||
dialog.title.searching.for.usages=Searching for Usages
|
||||
show.more=Show more
|
||||
show.less=Show less
|
||||
|
||||
@@ -110,7 +110,7 @@ internal class DocumentationBrowser private constructor(
|
||||
private suspend fun handleLink(url: String) {
|
||||
val targetPointer = this.targetPointer
|
||||
val internalResult = try {
|
||||
handleLink(project, targetPointer, url)
|
||||
handleLink(project, targetPointer, url, page)
|
||||
}
|
||||
catch (e: IndexNotReadyException) {
|
||||
return // normal situation, nothing to do
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
|
||||
package com.intellij.lang.documentation.ide.impl
|
||||
|
||||
import com.intellij.lang.documentation.ide.ui.ExpandableDefinition
|
||||
import com.intellij.lang.documentation.ide.ui.UISnapshot
|
||||
import com.intellij.lang.documentation.ide.ui.UIState
|
||||
import com.intellij.lang.documentation.ide.ui.parseExpandableDefinition
|
||||
import com.intellij.openapi.project.IndexNotReadyException
|
||||
import com.intellij.platform.backend.documentation.ContentUpdater
|
||||
import com.intellij.platform.backend.documentation.DocumentationContentData
|
||||
@@ -19,6 +21,7 @@ internal class DocumentationPage(val request: DocumentationRequest) {
|
||||
private val myContentFlow = MutableStateFlow<DocumentationPageContent?>(null)
|
||||
val contentFlow: SharedFlow<DocumentationPageContent?> = myContentFlow.asSharedFlow()
|
||||
val currentContent: DocumentationPageContent.Content? get() = myContentFlow.value as? DocumentationPageContent.Content
|
||||
var expandableDefinition: ExpandableDefinition? = null
|
||||
|
||||
/**
|
||||
* @return `true` if some content was loaded, `false` if content is empty
|
||||
@@ -39,7 +42,9 @@ internal class DocumentationPage(val request: DocumentationRequest) {
|
||||
return
|
||||
}
|
||||
val uiState = data.anchor?.let(UIState::ScrollToAnchor) ?: UIState.Reset
|
||||
myContentFlow.value = prepareContent(data.content, data.links, uiState)
|
||||
val original = data.content.html
|
||||
expandableDefinition = parseExpandableDefinition(original, 4)
|
||||
myContentFlow.value = prepareContent(data.content.copy(html = expandableDefinition?.getDecorated() ?: original), data.links, uiState)
|
||||
update(data.updates, data.links)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,12 +8,14 @@ import com.intellij.codeInsight.documentation.DocumentationManagerProtocol
|
||||
import com.intellij.ide.BrowserUtil
|
||||
import com.intellij.lang.documentation.CompositeDocumentationProvider
|
||||
import com.intellij.lang.documentation.ExternalDocumentationHandler
|
||||
import com.intellij.lang.documentation.ide.ui.TOGGLE_EXPANDABLE_DEFINITION
|
||||
import com.intellij.lang.documentation.psi.PsiElementDocumentationTarget
|
||||
import com.intellij.model.Pointer
|
||||
import com.intellij.openapi.application.readAction
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.OrderEntry
|
||||
import com.intellij.platform.backend.documentation.DocumentationTarget
|
||||
import com.intellij.platform.backend.documentation.impl.InternalLinkResult
|
||||
import com.intellij.platform.backend.documentation.impl.handleLink
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.util.SlowOperations
|
||||
@@ -25,11 +27,19 @@ internal suspend fun handleLink(
|
||||
project: Project,
|
||||
targetPointer: Pointer<out DocumentationTarget>,
|
||||
url: String,
|
||||
page: DocumentationPage
|
||||
): Any? {
|
||||
if (url.startsWith("open")) {
|
||||
return libraryEntry(project, targetPointer)
|
||||
when {
|
||||
url.startsWith("open") -> {
|
||||
return libraryEntry(project, targetPointer)
|
||||
}
|
||||
url == TOGGLE_EXPANDABLE_DEFINITION -> {
|
||||
val expandableDefinition = page.expandableDefinition!!
|
||||
expandableDefinition.toggleExpanded()
|
||||
return InternalLinkResult.Updater(expandableDefinition)
|
||||
}
|
||||
else -> return handleLink(targetPointer, url)
|
||||
}
|
||||
return handleLink(targetPointer, url)
|
||||
}
|
||||
|
||||
private suspend fun libraryEntry(project: Project, targetPointer: Pointer<out DocumentationTarget>): OrderEntry? {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.lang.documentation.ide.ui
|
||||
|
||||
import com.intellij.lang.LangBundle
|
||||
import com.intellij.lang.documentation.DocumentationMarkup.*
|
||||
import com.intellij.openapi.util.text.HtmlChunk
|
||||
import com.intellij.platform.backend.documentation.ContentUpdater
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
fun parseExpandableDefinition(doc: String, maxLines: Int): ExpandableDefinition? {
|
||||
val start = doc.indexOf(EXPANDABLE_DEFINITION_START)
|
||||
if (start < 0) return null
|
||||
val end = doc.indexOf(DEFINITION_END)
|
||||
if (end == -1) return null
|
||||
return ExpandableDefinition(doc.substring(0, start),
|
||||
doc.substring(start + EXPANDABLE_DEFINITION_START.length, end),
|
||||
doc.substring(end + DEFINITION_END.length), maxLines)
|
||||
}
|
||||
|
||||
const val TOGGLE_EXPANDABLE_DEFINITION = "toggle.expandable.definition"
|
||||
|
||||
class ExpandableDefinition(private val prefix: String, definition: String, private val content: String, maxLines: Int): ContentUpdater {
|
||||
private val variants: Pair<String, String> = createVariants(definition, maxLines)
|
||||
private var expanded = false
|
||||
fun getDecorated(): String = prefix + DEFINITION_START + (if (expanded) variants.second else variants.first) + DEFINITION_END + content
|
||||
|
||||
fun toggleExpanded() {
|
||||
expanded = !expanded
|
||||
}
|
||||
|
||||
private fun createVariants(definition: String, maxLines: Int): Pair<String, String> {
|
||||
var offset = 0
|
||||
for (i in 0..<maxLines) {
|
||||
offset = definition.indexOf("<br>", offset)
|
||||
if (offset == -1) {
|
||||
return Pair(definition, definition)
|
||||
}
|
||||
offset += "<br>".length
|
||||
}
|
||||
val collapsed = definition.substring(0, offset) + HtmlChunk.link(TOGGLE_EXPANDABLE_DEFINITION, LangBundle.message("show.more"))
|
||||
val full = definition + HtmlChunk.br() + HtmlChunk.link(TOGGLE_EXPANDABLE_DEFINITION, LangBundle.message("show.less"))
|
||||
return Pair(collapsed, full)
|
||||
}
|
||||
|
||||
override fun prepareContentUpdates(currentContent: String) = flowOf(getDecorated())
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.documentation
|
||||
|
||||
import com.intellij.lang.documentation.ide.ui.parseExpandableDefinition
|
||||
import junit.framework.TestCase
|
||||
import kotlin.test.assertNotEquals
|
||||
|
||||
class ExpandableDefinitionTest: TestCase() {
|
||||
fun testShort() {
|
||||
val short = "<div class='content-only'><div class='definition expandable'><pre><span style=\"color:#cf8e6d;\">interface </span><span style=\"\">Box</span> <span style=\"\">{<br></span><span style=\"\">    scale: </span><span style=\"color:#cf8e6d;\">number</span><span style=\"\">,<br></span><span style=\"\">    x: </span><span style=\"color:#cf8e6d;\">number<br></span><span style=\"\">}</span></pre></div><table class='sections'><tr><td valign='top'><icon src='JavaScriptPsiIcons.FileTypes.TypeScriptFile'/> src/foo/test.ts</td></table></div><div class=\"bottom\"><icon src=\"0\"/> TestProject</div>"
|
||||
val definition = parseExpandableDefinition(short, 4)!!
|
||||
assertEquals(
|
||||
"<div class='content-only'><div class='definition'><pre><span style=\"color:#cf8e6d;\">interface </span><span style=\"\">Box</span> <span style=\"\">{<br></span><span style=\"\">    scale: </span><span style=\"color:#cf8e6d;\">number</span><span style=\"\">,<br></span><span style=\"\">    x: </span><span style=\"color:#cf8e6d;\">number<br></span><span style=\"\">}</span></pre></div><table class='sections'><tr><td valign='top'><icon src='JavaScriptPsiIcons.FileTypes.TypeScriptFile'/> src/foo/test.ts</td></table></div><div class=\"bottom\"><icon src=\"0\"/> TestProject</div>",
|
||||
definition.getDecorated())
|
||||
}
|
||||
|
||||
fun testCollapsedExpanded() {
|
||||
val definition = parseExpandableDefinition(
|
||||
"<div class='content-only'><div class='definition expandable'><pre><span style=\"color:#cf8e6d;\">interface </span><span style=\"\">Box</span> <span style=\"\">{<br></span><span style=\"\">    scale: </span><span style=\"color:#cf8e6d;\">number</span><span style=\"\">,<br></span><span style=\"\">    x: </span><span style=\"color:#cf8e6d;\">number</span><span style=\"\">,<br></span><span style=\"\">    y: </span><span style=\"color:#cf8e6d;\">number</span><span style=\"\">,<br></span><span style=\"\">    z: </span><span style=\"color:#cf8e6d;\">number<br></span><span style=\"\">}</span></pre></div><table class='sections'><tr><td valign='top'><icon src='JavaScriptPsiIcons.FileTypes.TypeScriptFile'/> src/foo/test.ts</td></table></div><div class=\"bottom\"><icon src=\"0\"/> TestProject</div>",
|
||||
4)!!
|
||||
assertNotNull(definition)
|
||||
val collapsed = "<div class='content-only'><div class='definition'><pre><span style=\"color:#cf8e6d;\">interface </span><span style=\"\">Box</span> <span style=\"\">{<br></span><span style=\"\">    scale: </span><span style=\"color:#cf8e6d;\">number</span><span style=\"\">,<br></span><span style=\"\">    x: </span><span style=\"color:#cf8e6d;\">number</span><span style=\"\">,<br></span><span style=\"\">    y: </span><span style=\"color:#cf8e6d;\">number</span><span style=\"\">,<br><a href=\"toggle.expandable.definition\">Show more</a></pre></div><table class='sections'><tr><td valign='top'><icon src='JavaScriptPsiIcons.FileTypes.TypeScriptFile'/> src/foo/test.ts</td></table></div><div class=\"bottom\"><icon src=\"0\"/> TestProject</div>"
|
||||
assertEquals(collapsed, definition.getDecorated())
|
||||
definition.toggleExpanded()
|
||||
assertNotEquals(collapsed, definition.getDecorated())
|
||||
val expanded = "<div class='content-only'><div class='definition'><pre><span style=\"color:#cf8e6d;\">interface </span><span style=\"\">Box</span> <span style=\"\">{<br></span><span style=\"\">    scale: </span><span style=\"color:#cf8e6d;\">number</span><span style=\"\">,<br></span><span style=\"\">    x: </span><span style=\"color:#cf8e6d;\">number</span><span style=\"\">,<br></span><span style=\"\">    y: </span><span style=\"color:#cf8e6d;\">number</span><span style=\"\">,<br></span><span style=\"\">    z: </span><span style=\"color:#cf8e6d;\">number<br></span><span style=\"\">}</span><br/><a href=\"toggle.expandable.definition\">Show less</a></pre></div><table class='sections'><tr><td valign='top'><icon src='JavaScriptPsiIcons.FileTypes.TypeScriptFile'/> src/foo/test.ts</td></table></div><div class=\"bottom\"><icon src=\"0\"/> TestProject</div>"
|
||||
assertEquals(expanded, definition.getDecorated())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user