[rd debugger] show image popup on frontend for image values, IDEA-378972

^IDEA-378972 fixed

GitOrigin-RevId: 4d039ac4cb9ab2be5ce2019abf995d202e6679d8
This commit is contained in:
Vladimir Parfinenko
2025-10-13 14:46:25 +00:00
committed by intellij-monorepo-bot
parent 5b7c197709
commit e2ee08973d
28 changed files with 322 additions and 121 deletions
+1
View File
@@ -946,6 +946,7 @@
<module fileurl="file://$PROJECT_DIR$/images/intellij.platform.images.iml" filepath="$PROJECT_DIR$/images/intellij.platform.images.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/build-scripts/icons/intellij.platform.images.build.iml" filepath="$PROJECT_DIR$/platform/build-scripts/icons/intellij.platform.images.build.iml" />
<module fileurl="file://$PROJECT_DIR$/images/intellij.platform.images.copyright/intellij.platform.images.copyright.iml" filepath="$PROJECT_DIR$/images/intellij.platform.images.copyright/intellij.platform.images.copyright.iml" />
<module fileurl="file://$PROJECT_DIR$/images/intellij.platform.images.xdebugger/intellij.platform.images.xdebugger.iml" filepath="$PROJECT_DIR$/images/intellij.platform.images.xdebugger/intellij.platform.images.xdebugger.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/indexing-api/intellij.platform.indexing.iml" filepath="$PROJECT_DIR$/platform/indexing-api/intellij.platform.indexing.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/indexing-impl/intellij.platform.indexing.impl.iml" filepath="$PROJECT_DIR$/platform/indexing-impl/intellij.platform.indexing.impl.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/indexing-tests/intellij.platform.indexing.tests.iml" filepath="$PROJECT_DIR$/platform/indexing-tests/intellij.platform.indexing.tests.iml" />
+1
View File
@@ -264,6 +264,7 @@ idea/customization/base
idea/customization/min
images
images/intellij.platform.images.copyright
images/intellij.platform.images.xdebugger
java/codeserver/core
java/codeserver/highlighting
java/compiler
@@ -0,0 +1,25 @@
### auto-generated section `build intellij.platform.images.xdebugger` start
load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup")
resourcegroup(
name = "xdebugger_resources",
srcs = glob(["resources/**/*"]),
strip_prefix = "resources"
)
jvm_library(
name = "xdebugger",
module_name = "intellij.platform.images.xdebugger",
visibility = ["//visibility:public"],
srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True),
resources = [":xdebugger_resources"],
deps = [
"//images",
"//platform/projectModel-api:projectModel",
"//platform/util",
"//platform/core-api:core",
"//platform/xdebugger-impl/frontend",
"//platform/ide-core",
]
)
### auto-generated section `build intellij.platform.images.xdebugger` end
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="intellij.platform.images" />
<orderEntry type="module" module-name="intellij.platform.projectModel" />
<orderEntry type="module" module-name="intellij.platform.util" />
<orderEntry type="module" module-name="intellij.platform.core" />
<orderEntry type="module" module-name="intellij.platform.debugger.impl.frontend" />
<orderEntry type="module" module-name="intellij.platform.ide.core" />
</component>
</module>
@@ -0,0 +1,10 @@
<idea-plugin>
<dependencies>
<plugin id="com.intellij.platform.images"/>
<module name="intellij.platform.debugger.impl.frontend"/>
</dependencies>
<extensions defaultExtensionNs="com.intellij">
<xdebugger.imageEditorUIProvider implementation="com.intellij.platform.images.xdebugger.ImageEditorUIProviderImpl"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,29 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.platform.images.xdebugger;
import com.intellij.platform.debugger.impl.frontend.frame.ImageEditorUIProvider;
import org.intellij.images.editor.impl.ImageEditorManagerImpl;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
class ImageEditorUIProviderImpl implements ImageEditorUIProvider {
@Override
public @NotNull JComponent createImageEditorUI(byte @NotNull [] imageData) {
var icon = new ImageIcon(imageData);
var w = icon.getIconWidth();
var h = icon.getIconHeight();
var image = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration()
.createCompatibleImage(w, h, Transparency.TRANSLUCENT);
var g = image.createGraphics();
try {
icon.paintIcon(null, g, 0, 0);
}
finally {
g.dispose();
}
return ImageEditorManagerImpl.createImageEditorUI(image);
}
}
+3
View File
@@ -1,3 +1,6 @@
- name: lib/modules/intellij.platform.images.xdebugger.jar
contentModules:
- name: intellij.platform.images.xdebugger
- name: lib/platform-images.jar
modules:
- name: intellij.platform.images
+1
View File
@@ -15,6 +15,7 @@
<content>
<module name="intellij.platform.images.copyright"/>
<module name="intellij.platform.images.xdebugger"/>
</content>
<extensions defaultExtensionNs="com.intellij">
+2 -3
View File
@@ -51,7 +51,6 @@ jvm_library(
"@lib//:jb-jdi",
"//libraries/stream",
"//platform/statistics",
"//images",
"//platform/core-ui",
"//libraries/fastutil",
"//platform/platform-util-io-impl:ide-util-io-impl",
@@ -74,6 +73,7 @@ jvm_library(
"//platform/xdebugger-impl/rpc",
"//fleet/rpc",
"//platform/kernel/rpc",
"//platform/remote-topics/shared:rpc-topics",
]
)
@@ -118,8 +118,6 @@ jvm_library(
"//libraries/stream",
"//platform/statistics",
"//platform/statistics:statistics_test_lib",
"//images",
"//images:images_test_lib",
"//platform/core-ui",
"//libraries/fastutil",
"//platform/platform-util-io-impl:ide-util-io-impl",
@@ -144,6 +142,7 @@ jvm_library(
"//fleet/rpc",
"//platform/kernel/rpc",
"//platform/testFramework/common",
"//platform/remote-topics/shared:rpc-topics",
]
)
### auto-generated section `build intellij.java.debugger.impl` end
@@ -79,7 +79,6 @@
<orderEntry type="library" name="jb-jdi" level="project" />
<orderEntry type="module" module-name="intellij.libraries.stream" />
<orderEntry type="module" module-name="intellij.platform.statistics" />
<orderEntry type="module" module-name="intellij.platform.images" />
<orderEntry type="module" module-name="intellij.platform.core.ui" />
<orderEntry type="module" module-name="intellij.libraries.fastutil" />
<orderEntry type="module" module-name="intellij.platform.ide.util.io.impl" />
@@ -103,6 +102,7 @@
<orderEntry type="module" module-name="fleet.rpc" />
<orderEntry type="module" module-name="intellij.platform.rpc" />
<orderEntry type="module" module-name="intellij.platform.testFramework.common" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.rpc.topics" />
</component>
<component name="copyright">
<Base>
@@ -150,6 +150,7 @@
<debugger.compoundRendererProvider implementation="com.intellij.debugger.ui.tree.render.UnboxableTypeRenderer$FloatRenderer"/>
<debugger.compoundRendererProvider implementation="com.intellij.debugger.ui.tree.render.UnboxableTypeRenderer$DoubleRenderer"/>
<debugger.compoundRendererProvider implementation="com.intellij.debugger.ui.tree.render.ColorObjectRenderer"/>
<debugger.compoundRendererProvider implementation="com.intellij.debugger.ui.tree.render.ImageObjectRenderer"/>
<debugger.compoundRendererProvider implementation="com.intellij.debugger.ui.tree.render.IconObjectRenderer"/>
<debugger.compoundRendererProvider implementation="com.intellij.debugger.ui.tree.render.GraphicsObjectRenderer"/>
<debugger.compoundRendererProvider implementation="com.intellij.debugger.ui.tree.render.BinaryRenderer"/>
@@ -0,0 +1,74 @@
// 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.debugger.ui.tree.render;
import com.intellij.debugger.engine.JavaValue;
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
import com.intellij.debugger.impl.DebuggerUtilsImpl;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.platform.debugger.impl.shared.ShowImagePopupUtil;
import com.intellij.rt.debugger.ImageSerializer;
import com.intellij.xdebugger.XDebuggerManager;
import com.intellij.xdebugger.frame.XFullValueEvaluator;
import com.intellij.xdebugger.impl.XDebuggerManagerImpl;
import com.sun.jdi.StringReference;
import com.sun.jdi.Value;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
abstract class AbstractImageRenderer extends CompoundRendererProvider {
private static final Logger LOG = Logger.getInstance(AbstractImageRenderer.class);
@Override
public boolean isApplicable(Project project) {
if (!super.isApplicable(project)) return false;
var manager = (XDebuggerManagerImpl)XDebuggerManager.getInstance(project);
return manager.getFrontendCapabilities().getCanShowImages();
}
@Override
protected boolean isEnabled() {
return true;
}
static byte @Nullable [] getImageBytes(EvaluationContextImpl evaluationContext, Value obj, String methodName) {
try {
EvaluationContextImpl copyContext = evaluationContext.createEvaluationContext(obj);
StringReference bytes =
(StringReference)DebuggerUtilsImpl.invokeHelperMethod(copyContext, ImageSerializer.class, methodName, Collections.singletonList(obj));
if (bytes != null) {
return bytes.value().getBytes(StandardCharsets.ISO_8859_1);
}
}
catch (Exception e) {
DebuggerUtilsImpl.logError("Exception while getting image data", e);
}
return null;
}
protected XFullValueEvaluator createImagePopupEvaluator(@NotNull @Nls String message,
@NotNull EvaluationContextImpl evaluationContext,
Value imageObj,
String getBytesMethodName) {
return new JavaValue.JavaFullValueEvaluator(message, evaluationContext) {
{
setShowValuePopup(false);
}
@Override
public void evaluate(@NotNull XFullValueEvaluationCallback callback) {
if (callback.isObsolete()) return;
var imageData = getImageBytes(getEvaluationContext(), imageObj, getBytesMethodName);
ShowImagePopupUtil.showOnFrontend(getEvaluationContext().getProject(), imageData);
callback.evaluated("");
}
};
}
}
@@ -6,9 +6,7 @@ import com.intellij.debugger.engine.DebuggerUtils;
import com.intellij.debugger.engine.FullValueEvaluatorProvider;
import com.sun.jdi.*;
import javax.swing.*;
public final class GraphicsObjectRenderer extends CompoundRendererProvider {
public final class GraphicsObjectRenderer extends AbstractImageRenderer {
@Override
protected String getName() {
return "Graphics";
@@ -19,11 +17,6 @@ public final class GraphicsObjectRenderer extends CompoundRendererProvider {
return "sun.java2d.SunGraphics2D";
}
@Override
protected boolean isEnabled() {
return true;
}
@Override
protected FullValueEvaluatorProvider getFullValueEvaluatorProvider() {
return (evaluationContext, valueDescriptor) -> {
@@ -45,12 +38,8 @@ public final class GraphicsObjectRenderer extends CompoundRendererProvider {
if (!(type instanceof ReferenceType) || !DebuggerUtils.instanceOf(type, "java.awt.Image")) {
return null;
}
return new ImageObjectRenderer.IconPopupEvaluator(JavaDebuggerBundle.message("message.node.show.image"), evaluationContext) {
@Override
protected Icon getData() {
return ImageObjectRenderer.getIcon(getEvaluationContext(), bufImgValue, "imageToBytes");
}
};
return createImagePopupEvaluator(JavaDebuggerBundle.message("message.node.show.image"), evaluationContext,
bufImgValue, "imageToBytes");
}
catch (Exception ignored) {
}
@@ -10,11 +10,13 @@ import com.intellij.debugger.engine.events.SuspendContextCommandImpl;
import com.intellij.debugger.impl.DebuggerUtilsImpl;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.util.registry.Registry;
import com.sun.jdi.Value;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
final class IconObjectRenderer extends CompoundRendererProvider {
final class IconObjectRenderer extends AbstractImageRenderer {
@Override
protected String getName() {
return "Icon";
@@ -25,11 +27,6 @@ final class IconObjectRenderer extends CompoundRendererProvider {
return "javax.swing.Icon";
}
@Override
protected boolean isEnabled() {
return true;
}
@Override
protected ValueIconRenderer getIconRenderer() {
return (descriptor, evaluationContext, listener) -> {
@@ -42,7 +39,7 @@ final class IconObjectRenderer extends CompoundRendererProvider {
@Override
public void contextAction(@NotNull SuspendContextImpl suspendContext) {
String getterName = AllIcons.Debugger.Value.getIconHeight() <= 16 ? "iconToBytesPreviewNormal" : "iconToBytesPreviewRetina";
descriptor.setValueIcon(ImageObjectRenderer.getIcon(evalContext, descriptor.getValue(), getterName));
descriptor.setValueIcon(getImageIcon(evalContext, descriptor.getValue(), getterName));
listener.labelChanged();
}
});
@@ -53,12 +50,13 @@ final class IconObjectRenderer extends CompoundRendererProvider {
@Override
protected FullValueEvaluatorProvider getFullValueEvaluatorProvider() {
return (evaluationContext, valueDescriptor) -> {
return new ImageObjectRenderer.IconPopupEvaluator(JavaDebuggerBundle.message("message.node.show.icon"), evaluationContext) {
@Override
protected Icon getData() {
return ImageObjectRenderer.getIcon(getEvaluationContext(), valueDescriptor.getValue(), "iconToBytes");
}
};
return createImagePopupEvaluator(JavaDebuggerBundle.message("message.node.show.icon"), evaluationContext,
valueDescriptor.getValue(), "iconToBytes");
};
}
static @Nullable ImageIcon getImageIcon(EvaluationContextImpl evaluationContext, Value obj, String methodName) {
byte[] data = getImageBytes(evaluationContext, obj, methodName);
return data != null ? new ImageIcon(data) : null;
}
}
@@ -1,30 +1,10 @@
// 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.debugger.ui.tree.render;
import com.intellij.CommonBundle;
import com.intellij.debugger.JavaDebuggerBundle;
import com.intellij.debugger.engine.FullValueEvaluatorProvider;
import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
import com.intellij.debugger.impl.DebuggerUtilsImpl;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.rt.debugger.ImageSerializer;
import com.sun.jdi.StringReference;
import com.sun.jdi.Value;
import org.intellij.images.editor.impl.ImageEditorManagerImpl;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
final class ImageObjectRenderer extends CompoundRendererProvider {
private static final Logger LOG = Logger.getInstance(ImageObjectRenderer.class);
final class ImageObjectRenderer extends AbstractImageRenderer {
@Override
protected String getName() {
return "Image";
@@ -35,67 +15,10 @@ final class ImageObjectRenderer extends CompoundRendererProvider {
return "java.awt.Image";
}
@Override
protected boolean isEnabled() {
return true;
}
@Override
protected FullValueEvaluatorProvider getFullValueEvaluatorProvider() {
return (evaluationContext, valueDescriptor) ->
new IconPopupEvaluator(JavaDebuggerBundle.message("message.node.show.image"), evaluationContext) {
@Override
protected Icon getData() {
return getIcon(getEvaluationContext(), valueDescriptor.getValue(), "imageToBytes");
}
};
}
static JComponent createIconViewer(@Nullable Icon icon) {
if (icon == null) return new JLabel(CommonBundle.message("label.no.data"), SwingConstants.CENTER);
final int w = icon.getIconWidth();
final int h = icon.getIconHeight();
final BufferedImage image = GraphicsEnvironment.getLocalGraphicsEnvironment()
.getDefaultScreenDevice().getDefaultConfiguration().createCompatibleImage(w, h, Transparency.TRANSLUCENT);
final Graphics2D g = image.createGraphics();
icon.paintIcon(null, g, 0, 0);
g.dispose();
return ImageEditorManagerImpl.createImageEditorUI(image);
}
static @Nullable ImageIcon getIcon(EvaluationContextImpl evaluationContext, Value obj, String methodName) {
try {
byte[] data = getImageBytes(evaluationContext, obj, methodName);
if (data != null) {
return new ImageIcon(data);
}
}
catch (Exception e) {
LOG.info("Exception while getting image data", e);
}
return null;
}
private static byte @Nullable [] getImageBytes(EvaluationContextImpl evaluationContext, Value obj, String methodName)
throws EvaluateException {
EvaluationContextImpl copyContext = evaluationContext.createEvaluationContext(obj);
StringReference bytes =
(StringReference)DebuggerUtilsImpl.invokeHelperMethod(copyContext, ImageSerializer.class, methodName, Collections.singletonList(obj));
if (bytes != null) {
return bytes.value().getBytes(StandardCharsets.ISO_8859_1);
}
return null;
}
abstract static class IconPopupEvaluator extends CustomPopupFullValueEvaluator<Icon> {
IconPopupEvaluator(@NotNull @Nls String linkText, @NotNull EvaluationContextImpl evaluationContext) {
super(linkText, evaluationContext);
}
@Override
protected JComponent createComponent(Icon data) {
return createIconViewer(data);
}
createImagePopupEvaluator(JavaDebuggerBundle.message("message.node.show.image"), evaluationContext,
valueDescriptor.getValue(), "imageToBytes");
}
}
@@ -2,7 +2,7 @@
<idea-plugin xmlns:xi="http://www.w3.org/2001/XInclude">
<module value="com.intellij.modules.java"/>
<depends optional="true" config-file="images-integration.xml">com.intellij.platform.images</depends>
<depends optional="true">com.intellij.platform.images</depends>
<depends optional="true" config-file="community-integration.xml">com.intellij.modules.idea.community</depends>
<xi:include href="/idea/JavaActions.xml"/>
@@ -1,5 +0,0 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<debugger.compoundRendererProvider implementation="com.intellij.debugger.ui.tree.render.ImageObjectRenderer"/>
</extensions>
</idea-plugin>
@@ -14,6 +14,7 @@ internal object PlatformApi {
"intellij.platform.uast.ide", // IDEA
"intellij.platform.images", // plugin
"intellij.platform.images.copyright", // plugin
"intellij.platform.images.xdebugger", // plugin
"intellij.platform.images.build", // build scripts
)
@@ -7,6 +7,7 @@ import com.intellij.openapi.application.EDT
import com.intellij.openapi.editor.impl.EditorId
import com.intellij.openapi.editor.impl.findEditorOrNull
import com.intellij.platform.debugger.impl.rpc.*
import com.intellij.platform.debugger.impl.rpc.XFrontendDebuggerCapabilities
import com.intellij.platform.execution.impl.backend.createProcessHandlerDto
import com.intellij.platform.project.ProjectId
import com.intellij.platform.project.findProject
@@ -31,6 +32,17 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.*
internal class BackendXDebuggerManagerApi : XDebuggerManagerApi {
override suspend fun initialize(projectId: ProjectId, capabilities: XFrontendDebuggerCapabilities) {
val project = projectId.findProject()
val manager = XDebuggerManager.getInstance(project) as XDebuggerManagerImpl
val old = manager.frontendCapabilities
val new = XFrontendDebuggerCapabilities(
// _any_ of clients can show images
canShowImages = old.canShowImages || capabilities.canShowImages,
)
manager.frontendCapabilities = new
}
@OptIn(ExperimentalCoroutinesApi::class)
override suspend fun currentSession(projectId: ProjectId): Flow<XDebugSessionId?> {
val project = projectId.findProject()
@@ -10,6 +10,8 @@
<extensionPoints>
<extensionPoint name="xdebugger.customQuickEvaluateActionProvider"
interface="com.intellij.platform.debugger.impl.frontend.actions.CustomQuickEvaluateActionProvider" dynamic="true"/>
<extensionPoint name="xdebugger.imageEditorUIProvider"
interface="com.intellij.platform.debugger.impl.frontend.frame.ImageEditorUIProvider" dynamic="true"/>
</extensionPoints>
<actions resource-bundle="messages.FrontendDebuggerImplBundle">
<action id="QuickEvaluateExpression" class="com.intellij.platform.debugger.impl.frontend.actions.QuickEvaluateAction"/>
@@ -26,5 +28,6 @@
<postStartupActivity implementation="com.intellij.platform.debugger.impl.frontend.FrontendXDebuggerInitializationProjectActivity"/>
<xdebugger.managerProxy implementation="com.intellij.platform.debugger.impl.frontend.FrontendXDebugManagerProxy" order="first"/>
<platform.rpc.projectRemoteTopicListener implementation="com.intellij.platform.debugger.impl.frontend.ShowBreakpointsDialogRemoteTopicListener"/>
<platform.rpc.projectRemoteTopicListener implementation="com.intellij.platform.debugger.impl.frontend.frame.ShowImagePopupRemoteTopicListener"/>
</extensions>
</idea-plugin>
@@ -13,10 +13,8 @@ import com.intellij.openapi.wm.ToolWindowManager
import com.intellij.openapi.wm.ex.ToolWindowManagerListener
import com.intellij.platform.debugger.impl.frontend.editor.BreakpointPromoterEditorListener
import com.intellij.platform.debugger.impl.frontend.evaluate.quick.common.ValueLookupManager
import com.intellij.platform.debugger.impl.rpc.XDebugSessionDto
import com.intellij.platform.debugger.impl.rpc.XDebuggerManagerApi
import com.intellij.platform.debugger.impl.rpc.XDebuggerManagerSessionEvent
import com.intellij.platform.debugger.impl.rpc.XDebuggerValueLookupHintsRemoteApi
import com.intellij.platform.debugger.impl.frontend.frame.ImageEditorUIUtil
import com.intellij.platform.debugger.impl.rpc.*
import com.intellij.platform.project.projectId
import com.intellij.ui.content.ContentManagerEvent
import com.intellij.ui.content.ContentManagerListener
@@ -64,6 +62,9 @@ class FrontendXDebuggerManager(private val project: Project, private val cs: Cor
internal val sessions get() = sessionsFlow.value
init {
initCapabilities()
// TODO: make sure that capabilities are send before anything else
cs.launch {
for (event in synchronousExecutor) {
event()
@@ -81,6 +82,12 @@ class FrontendXDebuggerManager(private val project: Project, private val cs: Cor
}
}
private fun initCapabilities() = cs.launch {
XDebuggerManagerApi.getInstance().initialize(project.projectId(), XFrontendDebuggerCapabilities(
canShowImages = ImageEditorUIUtil.canCreateImageEditor(),
))
}
private fun initSessions() = cs.launch {
// When the registry flag is not set, we would prefer to have XDebugSessionProxy.Monolith in a listener
// see com.intellij.xdebugger.impl.MonolithListenerAdapter
@@ -0,0 +1,60 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.platform.debugger.impl.frontend.frame
import com.intellij.CommonBundle
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.wm.WindowManager
import com.intellij.platform.debugger.impl.shared.ShowImagePopupUtil
import com.intellij.platform.rpc.topics.ProjectRemoteTopic
import com.intellij.platform.rpc.topics.ProjectRemoteTopicListener
import com.intellij.ui.awt.RelativePoint
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
import org.jetbrains.annotations.ApiStatus
import java.awt.Dimension
import java.awt.Point
import javax.swing.JComponent
import javax.swing.JLabel
import javax.swing.SwingConstants
@ApiStatus.Internal
interface ImageEditorUIProvider {
fun createImageEditorUI(imageData: ByteArray): JComponent
}
@ApiStatus.Internal // might be public in theory
object ImageEditorUIUtil {
private val EP_NAME = ExtensionPointName<ImageEditorUIProvider>("com.intellij.xdebugger.imageEditorUIProvider")
fun canCreateImageEditor(): Boolean = EP_NAME.hasAnyExtensions()
fun createImageEditorUI(imageData: ByteArray): JComponent? = EP_NAME.extensionList.firstOrNull()?.createImageEditorUI(imageData)
}
private class ShowImagePopupRemoteTopicListener : ProjectRemoteTopicListener<ShowImagePopupUtil.Request> {
override val topic: ProjectRemoteTopic<ShowImagePopupUtil.Request> = ShowImagePopupUtil.REMOTE_TOPIC
override fun handleEvent(project: Project, event: ShowImagePopupUtil.Request) {
runInEdt {
val frame = WindowManager.getInstance().getFrame(project)
if (frame == null) return@runInEdt
val popupSize = Dimension(frame.size.width / 2, frame.size.height / 2)
val imageData = event.imageData
val content =
if (imageData != null) ImageEditorUIUtil.createImageEditorUI(imageData) ?: return@runInEdt
else JLabel(CommonBundle.message("label.no.data"), SwingConstants.CENTER)
val popup = DebuggerUIUtil.createValuePopup(project, content, null)
if (content is Disposable) {
Disposer.register(popup, content)
}
popup.setSize(popupSize)
popup.show(RelativePoint(frame, Point(popupSize.width / 2, popupSize.height / 2)))
}
}
}
@@ -19,6 +19,8 @@ import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
@Rpc
interface XDebuggerManagerApi : RemoteApi<Unit> {
suspend fun initialize(projectId: ProjectId, capabilities: XFrontendDebuggerCapabilities)
suspend fun currentSession(projectId: ProjectId): Flow<XDebugSessionId?>
suspend fun sessions(projectId: ProjectId): XDebugSessionsList
@@ -41,6 +43,12 @@ interface XDebuggerManagerApi : RemoteApi<Unit> {
}
}
@ApiStatus.Internal
@Serializable
data class XFrontendDebuggerCapabilities(
val canShowImages: Boolean,
)
@ApiStatus.Internal
@Serializable
data class XBreakpointsSetDto(
@@ -24,6 +24,7 @@ jvm_library(
"//platform/platform-impl/rpc",
"//platform/project/shared:project",
"//platform/editor-ui-api:editor-ui",
"//platform/remote-topics/shared:rpc-topics",
]
)
### auto-generated section `build intellij.platform.debugger.impl.shared` end
@@ -0,0 +1,3 @@
*f:com.intellij.platform.debugger.impl.shared.ShowImagePopupUtil
- sf:INSTANCE:com.intellij.platform.debugger.impl.shared.ShowImagePopupUtil
- sf:showOnFrontend(com.intellij.openapi.project.Project,B[]):V
@@ -43,5 +43,6 @@
<orderEntry type="module" module-name="intellij.platform.ide.rpc" />
<orderEntry type="module" module-name="intellij.platform.project" />
<orderEntry type="module" module-name="intellij.platform.editor.ui" />
<orderEntry type="module" module-name="intellij.platform.rpc.topics" />
</component>
</module>
@@ -0,0 +1,23 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.platform.debugger.impl.shared
import com.intellij.openapi.project.Project
import com.intellij.platform.rpc.topics.ProjectRemoteTopic
import com.intellij.platform.rpc.topics.sendToClient
import kotlinx.serialization.Serializable
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Experimental
object ShowImagePopupUtil {
@ApiStatus.Internal
@Serializable
class Request(val imageData: ByteArray?)
@ApiStatus.Internal
val REMOTE_TOPIC: ProjectRemoteTopic<Request> = ProjectRemoteTopic("xdebugger.show.image.popup", Request.serializer())
@JvmStatic
fun showOnFrontend(project: Project, imageData: ByteArray?) {
REMOTE_TOPIC.sendToClient(project, Request(imageData))
}
}
@@ -39,6 +39,7 @@ import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.wm.IdeGlassPaneUtil;
import com.intellij.openapi.wm.ToolWindowId;
import com.intellij.platform.debugger.impl.rpc.XFrontendDebuggerCapabilities;
import com.intellij.ui.ExperimentalUI;
import com.intellij.ui.HintHint;
import com.intellij.ui.awt.RelativePoint;
@@ -95,6 +96,11 @@ public final class XDebuggerManagerImpl extends XDebuggerManager implements Pers
private InlayRunToCursorEditorListener myNewRunToCursorListener = null;
private @NotNull XFrontendDebuggerCapabilities myFrontendCapabilities =
new XFrontendDebuggerCapabilities(
false
);
XDebuggerManagerImpl(@NotNull Project project, @NotNull CoroutineScope coroutineScope) {
myProject = project;
myCoroutineScope = coroutineScope;
@@ -368,6 +374,15 @@ public final class XDebuggerManagerImpl extends XDebuggerManager implements Pers
private static final TooltipGroup RUN_TO_CURSOR_TOOLTIP_GROUP = new TooltipGroup("RUN_TO_CURSOR_TOOLTIP_GROUP", 0);
@ApiStatus.Internal
public void setFrontendCapabilities(@NotNull XFrontendDebuggerCapabilities capabilities) {
myFrontendCapabilities = capabilities;
}
public @NotNull XFrontendDebuggerCapabilities getFrontendCapabilities() {
return myFrontendCapabilities;
}
public static @NotNull NotificationGroup getNotificationGroup() {
return NotificationGroupManager.getInstance().getNotificationGroup("Debugger messages");
}