diff --git a/.idea/modules.xml b/.idea/modules.xml index 5964b4736333..517d7c4a3b0f 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -946,6 +946,7 @@ + diff --git a/build/bazel-generated-file-list.txt b/build/bazel-generated-file-list.txt index 043e83119167..9b8a0eeb0214 100644 --- a/build/bazel-generated-file-list.txt +++ b/build/bazel-generated-file-list.txt @@ -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 diff --git a/images/intellij.platform.images.xdebugger/BUILD.bazel b/images/intellij.platform.images.xdebugger/BUILD.bazel new file mode 100644 index 000000000000..0529c2014d1a --- /dev/null +++ b/images/intellij.platform.images.xdebugger/BUILD.bazel @@ -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 \ No newline at end of file diff --git a/images/intellij.platform.images.xdebugger/intellij.platform.images.xdebugger.iml b/images/intellij.platform.images.xdebugger/intellij.platform.images.xdebugger.iml new file mode 100644 index 000000000000..99d0e0412845 --- /dev/null +++ b/images/intellij.platform.images.xdebugger/intellij.platform.images.xdebugger.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/images/intellij.platform.images.xdebugger/resources/intellij.platform.images.xdebugger.xml b/images/intellij.platform.images.xdebugger/resources/intellij.platform.images.xdebugger.xml new file mode 100644 index 000000000000..736244b2877c --- /dev/null +++ b/images/intellij.platform.images.xdebugger/resources/intellij.platform.images.xdebugger.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/images/intellij.platform.images.xdebugger/src/com/intellij/platform/images/xdebugger/ImageEditorUIProviderImpl.java b/images/intellij.platform.images.xdebugger/src/com/intellij/platform/images/xdebugger/ImageEditorUIProviderImpl.java new file mode 100644 index 000000000000..aec7a2c92f9f --- /dev/null +++ b/images/intellij.platform.images.xdebugger/src/com/intellij/platform/images/xdebugger/ImageEditorUIProviderImpl.java @@ -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); + } +} \ No newline at end of file diff --git a/images/plugin-content.yaml b/images/plugin-content.yaml index 0c278001783c..00e032a6b523 100644 --- a/images/plugin-content.yaml +++ b/images/plugin-content.yaml @@ -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 diff --git a/images/resources/META-INF/plugin.xml b/images/resources/META-INF/plugin.xml index 8100518a6b87..371bb3aa8c14 100644 --- a/images/resources/META-INF/plugin.xml +++ b/images/resources/META-INF/plugin.xml @@ -15,6 +15,7 @@ + diff --git a/java/debugger/impl/BUILD.bazel b/java/debugger/impl/BUILD.bazel index c8652d2d798d..46fe76d5cb9d 100644 --- a/java/debugger/impl/BUILD.bazel +++ b/java/debugger/impl/BUILD.bazel @@ -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 diff --git a/java/debugger/impl/intellij.java.debugger.impl.iml b/java/debugger/impl/intellij.java.debugger.impl.iml index 58b5a23ddcc8..c9dc56076a38 100644 --- a/java/debugger/impl/intellij.java.debugger.impl.iml +++ b/java/debugger/impl/intellij.java.debugger.impl.iml @@ -79,7 +79,6 @@ - @@ -103,6 +102,7 @@ + diff --git a/java/debugger/impl/resources/META-INF/java-debugger.xml b/java/debugger/impl/resources/META-INF/java-debugger.xml index e99cd8a7bd6b..18b80a98e61e 100644 --- a/java/debugger/impl/resources/META-INF/java-debugger.xml +++ b/java/debugger/impl/resources/META-INF/java-debugger.xml @@ -150,6 +150,7 @@ + diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/AbstractImageRenderer.java b/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/AbstractImageRenderer.java new file mode 100644 index 000000000000..013a7b3e71cf --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/AbstractImageRenderer.java @@ -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(""); + } + }; + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/GraphicsObjectRenderer.java b/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/GraphicsObjectRenderer.java index 4546fa9e6262..5aab93b5fbcb 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/GraphicsObjectRenderer.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/GraphicsObjectRenderer.java @@ -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) { } diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/IconObjectRenderer.java b/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/IconObjectRenderer.java index b48d951e067f..4dff88a395f7 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/IconObjectRenderer.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/IconObjectRenderer.java @@ -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; + } } diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/ImageObjectRenderer.java b/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/ImageObjectRenderer.java index 2d04f602992f..864c2db62086 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/ImageObjectRenderer.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/ImageObjectRenderer.java @@ -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 { - 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"); } } diff --git a/java/java-impl/resources/META-INF/JavaPlugin.xml b/java/java-impl/resources/META-INF/JavaPlugin.xml index 1de291b91bce..a550925a6548 100644 --- a/java/java-impl/resources/META-INF/JavaPlugin.xml +++ b/java/java-impl/resources/META-INF/JavaPlugin.xml @@ -2,7 +2,7 @@ - com.intellij.platform.images + com.intellij.platform.images com.intellij.modules.idea.community diff --git a/java/java-impl/resources/META-INF/images-integration.xml b/java/java-impl/resources/META-INF/images-integration.xml deleted file mode 100644 index 93c7155a06bf..000000000000 --- a/java/java-impl/resources/META-INF/images-integration.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/platform/testFramework/monorepo/tests/api/PlatformApi.kt b/platform/testFramework/monorepo/tests/api/PlatformApi.kt index e7cab4cd5e3e..fae564e19726 100644 --- a/platform/testFramework/monorepo/tests/api/PlatformApi.kt +++ b/platform/testFramework/monorepo/tests/api/PlatformApi.kt @@ -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 ) diff --git a/platform/xdebugger-impl/backend/src/com/intellij/platform/debugger/impl/backend/BackendXDebuggerManagerApi.kt b/platform/xdebugger-impl/backend/src/com/intellij/platform/debugger/impl/backend/BackendXDebuggerManagerApi.kt index 0e902fbea266..9cf94f892028 100644 --- a/platform/xdebugger-impl/backend/src/com/intellij/platform/debugger/impl/backend/BackendXDebuggerManagerApi.kt +++ b/platform/xdebugger-impl/backend/src/com/intellij/platform/debugger/impl/backend/BackendXDebuggerManagerApi.kt @@ -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 { val project = projectId.findProject() diff --git a/platform/xdebugger-impl/frontend/resources/intellij.platform.debugger.impl.frontend.xml b/platform/xdebugger-impl/frontend/resources/intellij.platform.debugger.impl.frontend.xml index 99cefc1817fe..6340b708d67b 100644 --- a/platform/xdebugger-impl/frontend/resources/intellij.platform.debugger.impl.frontend.xml +++ b/platform/xdebugger-impl/frontend/resources/intellij.platform.debugger.impl.frontend.xml @@ -10,6 +10,8 @@ + @@ -26,5 +28,6 @@ + diff --git a/platform/xdebugger-impl/frontend/src/com/intellij/platform/debugger/impl/frontend/FrontendXDebuggerManager.kt b/platform/xdebugger-impl/frontend/src/com/intellij/platform/debugger/impl/frontend/FrontendXDebuggerManager.kt index a1910049a6c1..56948f674ca7 100644 --- a/platform/xdebugger-impl/frontend/src/com/intellij/platform/debugger/impl/frontend/FrontendXDebuggerManager.kt +++ b/platform/xdebugger-impl/frontend/src/com/intellij/platform/debugger/impl/frontend/FrontendXDebuggerManager.kt @@ -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 diff --git a/platform/xdebugger-impl/frontend/src/com/intellij/platform/debugger/impl/frontend/frame/ImagePopupUtil.kt b/platform/xdebugger-impl/frontend/src/com/intellij/platform/debugger/impl/frontend/frame/ImagePopupUtil.kt new file mode 100644 index 000000000000..86d818394f45 --- /dev/null +++ b/platform/xdebugger-impl/frontend/src/com/intellij/platform/debugger/impl/frontend/frame/ImagePopupUtil.kt @@ -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("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 { + override val topic: ProjectRemoteTopic = 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))) + } + } +} \ No newline at end of file diff --git a/platform/xdebugger-impl/rpc/src/com/intellij/platform/debugger/impl/rpc/XDebuggerManagerApi.kt b/platform/xdebugger-impl/rpc/src/com/intellij/platform/debugger/impl/rpc/XDebuggerManagerApi.kt index 4ad7190fe289..5060e8d67f59 100644 --- a/platform/xdebugger-impl/rpc/src/com/intellij/platform/debugger/impl/rpc/XDebuggerManagerApi.kt +++ b/platform/xdebugger-impl/rpc/src/com/intellij/platform/debugger/impl/rpc/XDebuggerManagerApi.kt @@ -19,6 +19,8 @@ import org.jetbrains.annotations.ApiStatus @ApiStatus.Internal @Rpc interface XDebuggerManagerApi : RemoteApi { + suspend fun initialize(projectId: ProjectId, capabilities: XFrontendDebuggerCapabilities) + suspend fun currentSession(projectId: ProjectId): Flow suspend fun sessions(projectId: ProjectId): XDebugSessionsList @@ -41,6 +43,12 @@ interface XDebuggerManagerApi : RemoteApi { } } +@ApiStatus.Internal +@Serializable +data class XFrontendDebuggerCapabilities( + val canShowImages: Boolean, +) + @ApiStatus.Internal @Serializable data class XBreakpointsSetDto( diff --git a/platform/xdebugger-impl/shared/BUILD.bazel b/platform/xdebugger-impl/shared/BUILD.bazel index a41bb900ce30..586dc106d8da 100644 --- a/platform/xdebugger-impl/shared/BUILD.bazel +++ b/platform/xdebugger-impl/shared/BUILD.bazel @@ -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 \ No newline at end of file diff --git a/platform/xdebugger-impl/shared/api-dump-experimental.txt b/platform/xdebugger-impl/shared/api-dump-experimental.txt new file mode 100644 index 000000000000..20e303709a14 --- /dev/null +++ b/platform/xdebugger-impl/shared/api-dump-experimental.txt @@ -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 diff --git a/platform/xdebugger-impl/shared/intellij.platform.debugger.impl.shared.iml b/platform/xdebugger-impl/shared/intellij.platform.debugger.impl.shared.iml index 475ffadc9f1c..1cb4841c2214 100644 --- a/platform/xdebugger-impl/shared/intellij.platform.debugger.impl.shared.iml +++ b/platform/xdebugger-impl/shared/intellij.platform.debugger.impl.shared.iml @@ -43,5 +43,6 @@ + \ No newline at end of file diff --git a/platform/xdebugger-impl/shared/src/com/intellij/platform/debugger/impl/shared/ShowImagePopupUtil.kt b/platform/xdebugger-impl/shared/src/com/intellij/platform/debugger/impl/shared/ShowImagePopupUtil.kt new file mode 100644 index 000000000000..c1a8d7f93d0d --- /dev/null +++ b/platform/xdebugger-impl/shared/src/com/intellij/platform/debugger/impl/shared/ShowImagePopupUtil.kt @@ -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 = ProjectRemoteTopic("xdebugger.show.image.popup", Request.serializer()) + + @JvmStatic + fun showOnFrontend(project: Project, imageData: ByteArray?) { + REMOTE_TOPIC.sendToClient(project, Request(imageData)) + } +} diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebuggerManagerImpl.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebuggerManagerImpl.java index e66c36a690a8..0f4a125d31ba 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebuggerManagerImpl.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebuggerManagerImpl.java @@ -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"); }