mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 15:19:59 +07:00
[command-completion] IDEA-374206 Use action preview instead of javadoc for command completion or customized javadoc
GitOrigin-RevId: a0175f4bb548de35bea02ceb320fe552fbf43fd8
This commit is contained in:
committed by
intellij-monorepo-bot
parent
cfb2e2fe61
commit
92f157bdfd
@@ -776,9 +776,9 @@ class JavaCommandsCompletionTest : LightFixtureCompletionTestCase() {
|
||||
assertNotNull(documentation)
|
||||
val resultDocumentation = documentation?.supplier?.invoke() as? DocumentationData
|
||||
assertNotNull(resultDocumentation)
|
||||
val expected = "<div style=\"min-width: 150px; max-width: 250px; padding: 0; margin: 0;\"> \n" +
|
||||
"<div style=\"width: 95%; background-color:#ffffff; line-height: 1.3200000524520874\"><div style=\"background-color:#ffffff;color:#000000\"><pre style=\"font-family:'JetBrains Mono',monospace;\"><span style=\"font-size: 90%; color:#999999;\"> 2 </span><span style=\"color:#000080;font-weight:bold;\">int </span>y = <span style=\"color:#0000ff;background-color:#cad9fa;\">10</span>;                        </pre></div><br/>\n" +
|
||||
"</div></div>"
|
||||
val expected = """<ideaFloatingCodePreview background-color="#ffffff" font-size="13">
|
||||
<div style="#ffffff; line-height:1.3200000524520874;"><div style="background-color:#ffffff;color:#000000"><pre style="font-family:'JetBrains Mono',monospace;"><span style="font-size: 90%; color:#999999;"> 3 </span><span style="color:#000080;font-weight:bold;">int </span>y = <span style="color:#0000ff;background-color:#cad9fa;">10</span>;</pre></div>
|
||||
</div></ideaFloatingCodePreview>"""
|
||||
assertEquals(expected, resultDocumentation?.html ?: "")
|
||||
selectItem(item)
|
||||
myFixture.checkResult("""
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.intellij.codeInsight.completion.command
|
||||
|
||||
import com.intellij.codeInsight.CodeInsightSettings
|
||||
import com.intellij.codeInsight.completion.command.configuration.ApplicationCommandCompletionService
|
||||
import com.intellij.codeInsight.documentation.DocumentationHtmlUtil
|
||||
import com.intellij.codeInsight.documentation.actions.ShowQuickDocInfoAction
|
||||
import com.intellij.codeInsight.intention.impl.preview.IntentionPreviewDiffResult
|
||||
import com.intellij.codeInsight.intention.impl.preview.IntentionPreviewDiffResult.HighlightingType
|
||||
@@ -163,18 +164,14 @@ private class CommandCompletionDocumentationTarget(
|
||||
@NlsSafe
|
||||
private fun renderHtml(diffs: List<IntentionPreviewDiffResult.DiffInfo>): String {
|
||||
val builder = StringBuilder()
|
||||
val maxLine = (diffs.maxOfOrNull { it.startLine + it.length } ?: 1000).toString().length
|
||||
val maxLine = (diffs.maxOfOrNull { it.startLine + it.length + 1 } ?: 1000).toString().length
|
||||
for (i in 0..diffs.size - 1) {
|
||||
ProgressManager.checkCanceled()
|
||||
var codeSnippet = diffs[i].fileText
|
||||
val length = codeSnippet.split("\n").lastOrNull()?.length ?: -1
|
||||
if (length > 0 && length < 35) {
|
||||
codeSnippet += " ".repeat(35 - length)
|
||||
}
|
||||
val codeSnippet = diffs[i].fileText
|
||||
val lineNumberIndexes = codeSnippet.indexesOf("\n").map { it + 1 }.toMutableList()
|
||||
lineNumberIndexes.add(0, 0)
|
||||
val additionalHighlighting = additionalHighlighting(diffs[i].fragments, lineNumberIndexes)
|
||||
val textHandler = if (diffs[i].startLine != -1) createLineNumberTextHandler(lineNumberIndexes, diffs[i].startLine, maxLine) else null
|
||||
val textHandler = if (diffs[i].startLine != -1) createLineNumberTextHandler(lineNumberIndexes, diffs[i].startLine + 1, maxLine) else null
|
||||
var properties = HtmlSyntaxInfoUtil.HtmlGeneratorProperties.createDefault()
|
||||
.generateWrappedTags()
|
||||
.generateBackground()
|
||||
@@ -196,10 +193,10 @@ private class CommandCompletionDocumentationTarget(
|
||||
val defaultBackground = scheme.defaultBackground
|
||||
val lineSpacing = scheme.lineSpacing
|
||||
val backgroundColor = ColorUtil.toHtmlColor(defaultBackground)
|
||||
return """
|
||||
<div style="min-width: 150px; max-width: 250px; padding: 0; margin: 0;">
|
||||
<div style="width: 95%; background-color:$backgroundColor; line-height: ${lineSpacing * 1.1}">$builder<br/>
|
||||
</div></div>""".trimIndent()
|
||||
val editorFontSize = scheme.editorFontSize
|
||||
return """<${DocumentationHtmlUtil.codePreviewFloatingKey} background-color="$backgroundColor" font-size="$editorFontSize">""" + """
|
||||
<div style="$backgroundColor; line-height:${lineSpacing * 1.1};">$builder
|
||||
</div>""" + "</${DocumentationHtmlUtil.codePreviewFloatingKey}>".trimIndent()
|
||||
}
|
||||
|
||||
private fun createLineNumberTextHandler(
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.intellij.ui.scale.JBUIScale;
|
||||
import com.intellij.util.ui.ExtendableHTMLViewFactory;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import com.intellij.util.ui.accessibility.ScreenReader;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.ApiStatus.Internal;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -65,7 +66,14 @@ public abstract class DocumentationEditorPane extends JBHtmlPane implements Disp
|
||||
.keyboardActions(keyboardActions)
|
||||
.imageResolverFactory(component -> new JBHtmlPaneImageResolver(component, it -> imageResolver.resolveImage(it)))
|
||||
.iconResolver(name -> iconResolver.apply(name))
|
||||
.customStyleSheetProvider(pane -> getDocumentationPaneAdditionalCssRules(num -> (int)(pane.getContentsScaleFactor() * num)))
|
||||
.customStyleSheetProvider(pane -> {
|
||||
return getDocumentationPaneAdditionalCssRules(num -> {
|
||||
if (pane instanceof DocumentationHintEditorPane hintEditorPane && hintEditorPane.isCustomSettingsEnabled()) {
|
||||
return 0;
|
||||
}
|
||||
return (int)(pane.getContentsScaleFactor() * num);
|
||||
});
|
||||
})
|
||||
.extensions(ExtendableHTMLViewFactory.Extensions.FIT_TO_WIDTH_IMAGES)
|
||||
.build()
|
||||
);
|
||||
@@ -106,10 +114,18 @@ public abstract class DocumentationEditorPane extends JBHtmlPane implements Disp
|
||||
}
|
||||
setSize(width, Short.MAX_VALUE);
|
||||
Dimension result = getPreferredSize();
|
||||
myCachedPreferredSize = new Dimension(width, result.height);
|
||||
// Add extra height to prevent bottom clipping
|
||||
int extraHeight = getExtraHeight(result.height, contentsPreferredWidth(), width);
|
||||
myCachedPreferredSize = new Dimension(width, result.height + extraHeight);
|
||||
return myCachedPreferredSize.height;
|
||||
}
|
||||
|
||||
@Internal
|
||||
@ApiStatus.Experimental
|
||||
protected int getExtraHeight(int height, int contentPreferredWidth, int expectedWidth) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int getPreferredWidth() {
|
||||
int definitionPreferredWidth = contentsPreferredWidth();
|
||||
return definitionPreferredWidth < 0 ? getPreferredSize().width
|
||||
@@ -149,14 +165,19 @@ public abstract class DocumentationEditorPane extends JBHtmlPane implements Disp
|
||||
if (!(document instanceof StyledDocument)) {
|
||||
return;
|
||||
}
|
||||
String fontName = Registry.is("documentation.component.editor.font")
|
||||
? EditorColorsManager.getInstance().getGlobalScheme().getEditorFontName()
|
||||
: getFont().getFontName();
|
||||
|
||||
myFontSize = size;
|
||||
|
||||
// changing font will change the doc's CSS as myEditorPane has JEditorPane.HONOR_DISPLAY_PROPERTIES via UIUtil.getHTMLEditorKit
|
||||
setFont(UIUtil.getFontWithFallback(fontName, Font.PLAIN, JBUIScale.scale(size.getSize())));
|
||||
setFont(UIUtil.getFontWithFallback(getFontName(), Font.PLAIN, JBUIScale.scale(size.getSize())));
|
||||
}
|
||||
|
||||
@Internal
|
||||
public String getFontName() {
|
||||
String fontName = Registry.is("documentation.component.editor.font")
|
||||
? EditorColorsManager.getInstance().getGlobalScheme().getEditorFontName()
|
||||
: getFont().getFontName();
|
||||
return fontName;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.popup.JBPopup;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.wm.ex.WindowManagerEx;
|
||||
import com.intellij.ui.scale.JBUIScale;
|
||||
import org.jetbrains.annotations.ApiStatus.Internal;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -17,6 +18,8 @@ import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.intellij.codeInsight.documentation.DocumentationHtmlUtil.getDocPopupPreferredMinWidth;
|
||||
|
||||
@Internal
|
||||
public final class DocumentationHintEditorPane extends DocumentationEditorPane {
|
||||
|
||||
@@ -33,6 +36,18 @@ public final class DocumentationHintEditorPane extends DocumentationEditorPane {
|
||||
myProject = project;
|
||||
}
|
||||
|
||||
private boolean customSettingsEnabled = false;
|
||||
|
||||
@Internal
|
||||
public boolean isCustomSettingsEnabled() {
|
||||
return customSettingsEnabled;
|
||||
}
|
||||
|
||||
@Internal
|
||||
public void setCustomSettingsEnabled(boolean customSettingsEnabled) {
|
||||
this.customSettingsEnabled = customSettingsEnabled;
|
||||
}
|
||||
|
||||
public void setHint(@NotNull JBPopup hint) {
|
||||
myHint = hint;
|
||||
FocusListener focusAdapter = new FocusAdapter() {
|
||||
@@ -93,4 +108,15 @@ public final class DocumentationHintEditorPane extends DocumentationEditorPane {
|
||||
}
|
||||
super.processMouseMotionEvent(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getExtraHeight(int height, int contentPreferredWidth, int expectedWidth) {
|
||||
if (!customSettingsEnabled) return 0;
|
||||
if (contentPreferredWidth <= getDocPopupPreferredMinWidth()) return 0;
|
||||
int lines = (int)Math.ceil(contentPreferredWidth * 1.0 / expectedWidth);
|
||||
if (lines <= 1) return 0;
|
||||
FontMetrics fontMetrics = this.getFontMetrics(getFont());
|
||||
int lineHeight = fontMetrics.getHeight();
|
||||
return JBUIScale.scale((lines - 1) * lineHeight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,14 @@ object DocumentationHtmlUtil {
|
||||
@JvmStatic
|
||||
val lookupDocPopupMinHeight: Int get() = 300
|
||||
|
||||
/**
|
||||
* Represents a key used internally to identify the configuration or behavior of the code preview for floating documentation popup.
|
||||
* It enables customization of this popup and some documentation panes
|
||||
*/
|
||||
@JvmStatic
|
||||
@get:ApiStatus.Internal
|
||||
val codePreviewFloatingKey: String get() = "ideaFloatingCodePreview"
|
||||
|
||||
@JvmStatic
|
||||
fun getModuleIconResolver(baseIconResolver: Function<in String?, out Icon?>): (String) -> Icon? = { key: String ->
|
||||
baseIconResolver.apply(key)
|
||||
|
||||
@@ -14,7 +14,10 @@ class AdjustFontSizeAction : AnAction(CodeInsightBundle.message("javadoc.adjust.
|
||||
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
e.presentation.isEnabledAndVisible = documentationBrowser(e.dataContext) != null
|
||||
val documentationBrowser = documentationBrowser(e.dataContext)
|
||||
val ui = documentationBrowser?.ui
|
||||
e.presentation.isEnabledAndVisible = documentationBrowser != null &&
|
||||
ui?.editorPane?.isCustomSettingsEnabled == false
|
||||
}
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
|
||||
@@ -102,7 +102,7 @@ private class DocumentationTargetHoverInfo(
|
||||
|
||||
override fun createQuickDocComponent(editor: Editor, jointPopup: Boolean, bridge: PopupBridge): JComponent {
|
||||
val project = editor.project!!
|
||||
val documentationUI = DocumentationUI(project, browser)
|
||||
val documentationUI = DocumentationUI(project, browser, isPopup = true)
|
||||
val popupUI = DocumentationPopupUI(project, documentationUI)
|
||||
if (jointPopup) {
|
||||
popupUI.jointHover()
|
||||
|
||||
@@ -40,7 +40,7 @@ internal suspend fun showDocumentationPopup(
|
||||
Disposer.dispose(browser)
|
||||
throw ce
|
||||
}
|
||||
val popupUI = DocumentationPopupUI(project, DocumentationUI(project, browser))
|
||||
val popupUI = DocumentationPopupUI(project, DocumentationUI(project, browser, isPopup = true))
|
||||
val popup = createDocumentationPopup(project, popupUI, popupContext)
|
||||
try {
|
||||
writeIntentReadAction {
|
||||
|
||||
@@ -170,6 +170,7 @@ internal class DocumentationPopupUI(
|
||||
EDT.assertIsEdt()
|
||||
browser.clearCloseTrigger()
|
||||
val ui = ui
|
||||
ui.reloadAsDetached()
|
||||
_ui = null
|
||||
return ui
|
||||
}
|
||||
|
||||
@@ -24,14 +24,17 @@ import com.intellij.lang.documentation.ide.ui.PopupUpdateEvent.ContentUpdateKind
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.actionSystem.DataProvider
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.options.FontSize
|
||||
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.platform.backend.documentation.impl.DocumentationRequest
|
||||
import com.intellij.platform.backend.presentation.TargetPresentation
|
||||
import com.intellij.platform.ide.documentation.DOCUMENTATION_BROWSER
|
||||
import com.intellij.platform.util.coroutines.flow.collectLatestUndispatched
|
||||
import com.intellij.ui.PopupHandler
|
||||
import com.intellij.ui.scale.JBUIScale
|
||||
import com.intellij.util.ui.EDT
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.SwingTextTrimmer
|
||||
@@ -44,7 +47,10 @@ import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import org.jetbrains.annotations.Nls
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import java.awt.Color
|
||||
import java.awt.Font
|
||||
import java.awt.Rectangle
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JLabel
|
||||
@@ -53,6 +59,7 @@ import javax.swing.JScrollPane
|
||||
internal class DocumentationUI(
|
||||
val project: Project,
|
||||
val browser: DocumentationBrowser,
|
||||
private var isPopup: Boolean = false,
|
||||
) : DataProvider, Disposable {
|
||||
|
||||
val scrollPane: JScrollPane
|
||||
@@ -183,6 +190,16 @@ internal class DocumentationUI(
|
||||
imageResolver = null
|
||||
}
|
||||
|
||||
fun reloadAsDetached() {
|
||||
val localInitialDecoratedData = initialDecoratedData
|
||||
if (localInitialDecoratedData != null && editorPane.isCustomSettingsEnabled) {
|
||||
editorPane.isCustomSettingsEnabled = false
|
||||
editorPane.background = localInitialDecoratedData.backgroundColor
|
||||
editorPane.applyFontProps(localInitialDecoratedData.fontSize)
|
||||
}
|
||||
isPopup = false
|
||||
}
|
||||
|
||||
private suspend fun handlePage(page: DocumentationPage) {
|
||||
val presentation = page.request.presentation
|
||||
val i = switcher.elements.indexOf(page.request)
|
||||
@@ -193,16 +210,20 @@ internal class DocumentationUI(
|
||||
else {
|
||||
switcher.index = i
|
||||
}
|
||||
updateSwitcherVisibility()
|
||||
if (!editorPane.isCustomSettingsEnabled) {
|
||||
updateSwitcherVisibility()
|
||||
}
|
||||
page.contentFlow.collectLatest {
|
||||
handleContent(presentation, it)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateSwitcherVisibility() {
|
||||
fun updateSwitcherVisibility(forceTopMarginDisabled: Boolean = false) {
|
||||
val visible = switcher.elements.count() > 1
|
||||
switcherToolbarComponent.isVisible = visible
|
||||
editorPane.border = JBUI.Borders.emptyTop(
|
||||
editorPane.border =
|
||||
if (forceTopMarginDisabled) JBUI.Borders.empty(0, 0, 2, JBUI.scale(20))
|
||||
else JBUI.Borders.emptyTop(
|
||||
if (visible) 0 else DocumentationHtmlUtil.contentOuterPadding - DocumentationHtmlUtil.spaceBeforeParagraph
|
||||
)
|
||||
}
|
||||
@@ -232,8 +253,9 @@ internal class DocumentationUI(
|
||||
val content = pageContent.content
|
||||
imageResolver = content.imageResolver
|
||||
val linkChunk = linkChunk(presentation.presentableText, pageContent.links)
|
||||
val decorated = decorate(content.html, null, linkChunk, pageContent.downloadSourcesLink)
|
||||
if (!updateContent(decorated, presentation, ContentKind.DocumentationPage)) {
|
||||
val decoratedData = extractAdditionalData(content.html)
|
||||
val decorated = decorate(decoratedData?.html ?: content.html, null, linkChunk, pageContent.downloadSourcesLink)
|
||||
if (!updateContent(decorated, presentation, ContentKind.DocumentationPage, decoratedData?.decoratedStyle)) {
|
||||
return
|
||||
}
|
||||
val uiState = pageContent.uiState
|
||||
@@ -243,6 +265,25 @@ internal class DocumentationUI(
|
||||
}
|
||||
}
|
||||
|
||||
private data class DecoratedData(@NlsSafe val html: String, val decoratedStyle: DecoratedStyle?)
|
||||
private data class DecoratedStyle(val fontSize: Float, val backgroundColor: Color)
|
||||
private data class PreviousDecoratedStyle(val fontSize: FontSize, val backgroundColor: Color)
|
||||
private fun extractAdditionalData(@NlsSafe html: String): DecoratedData? {
|
||||
if (!html.startsWith("<" + DocumentationHtmlUtil.codePreviewFloatingKey)) return null
|
||||
val document: Document = Jsoup.parse(html)
|
||||
val children = document.getElementsByTag(DocumentationHtmlUtil.codePreviewFloatingKey)
|
||||
if (children.size != 1) return null
|
||||
val element = children[0]
|
||||
if (!isPopup) {
|
||||
return DecoratedData("<div style=\"min-width: 150px; max-width: 300px; padding: 0; margin: 0;\"> " +
|
||||
element.children()[0].html() + "</div></div>", null)
|
||||
}
|
||||
val backgroundColor = element.attribute("background-color")?.value ?: return null
|
||||
val fontSize = element.attribute("font-size")?.value?.toFloat() ?: return null
|
||||
if(element.children().size!=1) return null
|
||||
return DecoratedData(element.children()[0].html(), DecoratedStyle(fontSize, Color.decode(backgroundColor)))
|
||||
}
|
||||
|
||||
private fun fetchingMessage() {
|
||||
updateContent(message(CodeInsightBundle.message("javadoc.fetching.progress")), null, ContentKind.InfoMessage)
|
||||
}
|
||||
@@ -260,9 +301,13 @@ internal class DocumentationUI(
|
||||
.toString()
|
||||
}
|
||||
|
||||
private fun updateContent(text: @Nls String,
|
||||
presentation: TargetPresentation?,
|
||||
newContentKind: ContentKind): Boolean {
|
||||
private var initialDecoratedData: PreviousDecoratedStyle? = null
|
||||
private fun updateContent(
|
||||
text: @Nls String,
|
||||
presentation: TargetPresentation?,
|
||||
newContentKind: ContentKind,
|
||||
decoratedStyle: DecoratedStyle? = null,
|
||||
): Boolean {
|
||||
EDT.assertIsEdt()
|
||||
if (editorPane.text == text &&
|
||||
locationLabel.text == presentation?.locationText &&
|
||||
@@ -273,6 +318,7 @@ internal class DocumentationUI(
|
||||
val oldContentKind = contentKind
|
||||
contentKind = newContentKind
|
||||
editorPane.text = text
|
||||
customizePane(decoratedStyle)
|
||||
if (presentation?.locationText != null) {
|
||||
locationLabel.text = presentation.locationText
|
||||
locationLabel.toolTipText = presentation.locationText
|
||||
@@ -294,6 +340,35 @@ internal class DocumentationUI(
|
||||
return true
|
||||
}
|
||||
|
||||
private fun customizePane(decoratedStyle: DecoratedStyle?) {
|
||||
if (decoratedStyle != null) {
|
||||
updateSwitcherVisibility(true)
|
||||
}
|
||||
else {
|
||||
updateSwitcherVisibility()
|
||||
}
|
||||
if (isPopup && (decoratedStyle != null && decoratedStyle.backgroundColor != editorPane.background ||
|
||||
decoratedStyle == null && initialDecoratedData != null &&
|
||||
initialDecoratedData?.backgroundColor != editorPane.background)) {
|
||||
if (initialDecoratedData == null) {
|
||||
initialDecoratedData = PreviousDecoratedStyle(fontSize.value, editorPane.background)
|
||||
}
|
||||
if (decoratedStyle != null) {
|
||||
editorPane.isCustomSettingsEnabled = true
|
||||
editorPane.setFont(UIUtil.getFontWithFallback(editorPane.getFontName(), Font.PLAIN, JBUIScale.scale(decoratedStyle.fontSize).toInt()))
|
||||
editorPane.background = decoratedStyle.backgroundColor
|
||||
}
|
||||
else {
|
||||
val localInitialDecoratedData = initialDecoratedData
|
||||
if (localInitialDecoratedData != null) {
|
||||
editorPane.isCustomSettingsEnabled = false
|
||||
editorPane.background = localInitialDecoratedData.backgroundColor
|
||||
editorPane.applyFontProps(localInitialDecoratedData.fontSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyUIState(uiState: UIState) {
|
||||
when (uiState) {
|
||||
UIState.Reset -> {
|
||||
|
||||
Reference in New Issue
Block a user