[ui][IDEA-334229] add "download sources" button into quick doc popup

GitOrigin-RevId: f74a7f19b1663909c438d4ecb3e4a92677f09f11
This commit is contained in:
Alexander.Glukhov
2024-02-02 17:19:05 +01:00
committed by intellij-monorepo-bot
parent db15d18104
commit 2b94dd7636
10 changed files with 86 additions and 16 deletions

View File

@@ -602,3 +602,4 @@ tooltip.computing.classes.for.auto.import=Computing classes to auto-import...
jvm.inspections.dependency.configure.button.text=Configure dependency rules
checking.code.highlightings.in.background=Loading Code Highlighting
title.checking.code.highlightings.in.background.task=Loading code passes
documentation.download.button.label=Download documentation

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2024 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.QuickDocHighlightingHelper;
@@ -65,6 +65,7 @@ public final class DocumentationHtmlUtil {
".separated { padding: 0 0 4px 0; margin: 0; max-width: 100%;" +
" border-bottom: thin solid " + borderColor + "; }",
".bottom { padding: 3px 16px 0 " + leftPadding + "px; }",
".download-documentation { padding: 0px 0px 18px; }",
".separator-container { padding: 18px " + leftPadding + "px 0px; }",
".separator-container .separator { border-top: thin solid " + borderColor + "; }",
".bottom-no-content { padding: 5px 16px 0 " + leftPadding + "px; }",

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2024 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.codeInsight.CodeInsightBundle;
@@ -10,6 +10,7 @@ import com.intellij.codeInsight.lookup.Lookup;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupManager;
import com.intellij.codeWithMe.ClientId;
import com.intellij.icons.AllIcons;
import com.intellij.ide.BrowserUtil;
import com.intellij.ide.IdeEventQueue;
import com.intellij.ide.actions.BaseNavigateToSourceAction;
@@ -1857,7 +1858,7 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
@Nullable DocumentationProvider provider
) {
HtmlChunk locationInfo = getDefaultLocationInfo(element);
return decorate(text, locationInfo, getExternalText(element, externalUrl, provider));
return decorate(text, locationInfo, getExternalText(element, externalUrl, provider), null);
}
@RequiresReadLock
@@ -1897,7 +1898,8 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
@Internal
@Contract(pure = true)
public static @Nls String decorate(@Nls @NotNull String text, @Nullable HtmlChunk location, @Nullable HtmlChunk links) {
public static @Nls String decorate(@Nls @NotNull String text, @Nullable HtmlChunk location, @Nullable HtmlChunk links,
@Nullable String downloadDocumentationActionLink) {
text = StringUtil.replaceIgnoreCase(text, "</html>", "");
text = StringUtil.replaceIgnoreCase(text, "</body>", "");
text = replaceIgnoreQuotesType(text, SECTIONS_START + SECTIONS_END, "");
@@ -1922,10 +1924,21 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
if (!containsIgnoreQuotesType(text, DEFINITION_START)) {
text = replaceIgnoreQuotesType(text, "class='content'", "class='content-only'");
}
if (location != null) {
if (downloadDocumentationActionLink != null || location != null) {
text += HtmlChunk.div().setClass("separator-container")
.child(HtmlChunk.div().setClass("separator"));
text += location;
if (downloadDocumentationActionLink != null) {
text += HtmlChunk.div()
.children(
HtmlChunk.icon("AllIcons.Plugins.Downloads", AllIcons.Plugins.Downloads),
HtmlChunk.nbsp(),
HtmlChunk.link(downloadDocumentationActionLink, CodeInsightBundle.message("documentation.download.button.label"))
)
.setClass("download-documentation");
}
if (location != null) {
text += location;
}
}
if (links != null) {
text += getBottom(location != null).child(links);

View File

@@ -0,0 +1,25 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.documentation.actions
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiElement
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Experimental
interface DocumentationDownloader {
suspend fun canHandle(target: PsiElement): Boolean
suspend fun download(target: VirtualFile)
companion object {
val EP = ExtensionPointName.create<DocumentationDownloader>("com.intellij.documentation.documentationDownloader")
const val HREF_PREFIX = "download_sources:"
fun formatLink(target: PsiElement): String? {
val virtualFileUrl: String = target.containingFile?.virtualFile?.url ?: return null
return HREF_PREFIX + virtualFileUrl
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:Suppress("TestOnlyProblems") // KTIJ-19938
package com.intellij.lang.documentation.ide.impl
@@ -13,6 +13,7 @@ import com.intellij.platform.backend.documentation.DocumentationContentData
import com.intellij.platform.backend.documentation.LinkData
import com.intellij.platform.backend.documentation.impl.DocumentationRequest
import com.intellij.platform.backend.documentation.impl.computeDocumentation
import com.intellij.psi.PsiElement
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
@@ -64,7 +65,7 @@ internal class DocumentationPage(val requests: List<DocumentationRequest>) {
}
private fun prepareContent(content: DocumentationContentData, links: LinkData, uiState: UIState?): DocumentationPageContent.Content {
return DocumentationPageContent.Content(content, links, uiState)
return DocumentationPageContent.Content(content, links, uiState, content.targetElement)
}
/**
@@ -111,5 +112,6 @@ internal sealed class DocumentationPageContent {
val content: DocumentationContentData,
val links: LinkData,
val uiState: UIState?,
val targetElement: PsiElement?
) : DocumentationPageContent()
}

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:Suppress("DEPRECATION")
package com.intellij.lang.documentation.ide.ui
@@ -9,6 +9,7 @@ import com.intellij.codeInsight.documentation.DocumentationLinkHandler
import com.intellij.codeInsight.documentation.DocumentationManager.SELECTED_QUICK_DOC_TEXT
import com.intellij.codeInsight.documentation.DocumentationManager.decorate
import com.intellij.codeInsight.documentation.DocumentationScrollPane
import com.intellij.codeInsight.documentation.actions.DocumentationDownloader
import com.intellij.codeInsight.hint.DefinitionSwitcher
import com.intellij.ide.DataManager
import com.intellij.lang.documentation.DocumentationImageResolver
@@ -24,6 +25,7 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.NlsSafe
import com.intellij.openapi.util.text.HtmlChunk
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.platform.backend.documentation.impl.DocumentationRequest
import com.intellij.platform.backend.presentation.TargetPresentation
import com.intellij.platform.ide.documentation.DOCUMENTATION_BROWSER
@@ -54,6 +56,9 @@ internal class DocumentationUI(
val switcherToolbarComponent: JComponent?
private val icons = mutableMapOf<String, Icon>()
@Volatile
private var documentationDownloader: DocumentationDownloader? = null
private var imageResolver: DocumentationImageResolver? = null
private val linkHandler: DocumentationLinkHandler
private val cs = CoroutineScope(Dispatchers.EDT)
@@ -168,9 +173,17 @@ internal class DocumentationUI(
private suspend fun handleContent(presentation: TargetPresentation, pageContent: DocumentationPageContent.Content) {
val content = pageContent.content
imageResolver = content.imageResolver
val locationChunk = getDefaultLocationChunk(presentation)
val targetElement = pageContent.targetElement
var downloadSourcesLink: String? = null
if (targetElement != null) {
documentationDownloader = DocumentationDownloader.EP.extensionList.find { it.canHandle(targetElement) }
if (documentationDownloader != null) {
downloadSourcesLink = DocumentationDownloader.formatLink(targetElement)
}
}
val linkChunk = linkChunk(presentation.presentableText, pageContent.links)
val decorated = decorate(content.html, locationChunk, linkChunk)
val locationChunk = getDefaultLocationChunk(presentation)
val decorated = decorate(content.html, locationChunk, linkChunk, downloadSourcesLink)
if (!updateContent(decorated)) {
return
}
@@ -277,6 +290,15 @@ internal class DocumentationUI(
if (href.startsWith("#")) {
UIUtil.scrollToReference(editorPane, href.removePrefix("#"))
}
else if (href.startsWith(DocumentationDownloader.HREF_PREFIX) && documentationDownloader != null) {
val filePath = href.replaceFirst(DocumentationDownloader.HREF_PREFIX, "")
val file = VirtualFileManager.getInstance().findFileByUrl(filePath)
if (file != null) {
cs.launch(Dispatchers.Default) {
documentationDownloader?.download(file)
}
}
}
else {
browser.navigateByLink(href)
}

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:Suppress("DEPRECATION", "TestOnlyProblems") // KTIJ-19938
package com.intellij.lang.documentation.psi
@@ -102,6 +102,7 @@ class PsiElementDocumentationTarget private constructor(
html = parts.doc,
definitionDetails = parts.definitionDetails,
imageResolver = pointer.imageResolver,
targetElement = targetElement
),
anchor = pointer.anchor,
)
@@ -158,6 +159,7 @@ class PsiElementDocumentationTarget private constructor(
content = DocumentationContentData(
html = doc,
imageResolver = imageResolver,
targetElement = targetElement
),
anchor = anchor,
links = LinkData(externalUrl = url),

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.platform.backend.documentation;
import org.jetbrains.annotations.ApiStatus.NonExtendable;
@@ -25,6 +25,6 @@ public interface DocumentationContent {
@Nls @NotNull String html,
@NotNull Map<@NotNull String, ? extends @NotNull Image> images
) {
return new DocumentationContentData(html, imageResolver(images), null);
return new DocumentationContentData(html, imageResolver(images), null, null);
}
}

View File

@@ -1,9 +1,10 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.platform.backend.documentation
import com.intellij.lang.documentation.DocumentationImageResolver
import com.intellij.model.Pointer
import com.intellij.openapi.progress.blockingContext
import com.intellij.psi.PsiElement
import com.intellij.util.AsyncSupplier
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.BufferOverflow
@@ -21,7 +22,8 @@ import java.util.function.Supplier
internal data class DocumentationContentData internal constructor(
val html: @Nls String,
val imageResolver: DocumentationImageResolver?,
val definitionDetails: String? = null
val definitionDetails: String? = null,
val targetElement: PsiElement? = null
) : DocumentationContent
@Internal

View File

@@ -568,6 +568,8 @@
<extensionPoint name="elementPreviewProvider" interface="com.intellij.codeInsight.preview.ElementPreviewProvider" dynamic="true"/>
<extensionPoint name="testActionProvider" interface="com.intellij.execution.testframework.ToggleModelActionProvider" dynamic="true"/>
<extensionPoint name="documentation.documentationDownloader"
interface="com.intellij.codeInsight.documentation.actions.DocumentationDownloader" dynamic="true"/>
<extensionPoint name="testDiffProvider" beanClass="com.intellij.lang.LanguageExtensionPoint" dynamic="true">
<with attribute="implementationClass" implements="com.intellij.execution.testframework.actions.TestDiffProvider"/>