From 7d22b2f36b47b6338d6af8ea802f084fb0ce02ac Mon Sep 17 00:00:00 2001 From: Alexander Kuznetsov Date: Wed, 2 Oct 2024 14:23:59 +0200 Subject: [PATCH] [debugger] IDEA-349254 Initial support for collections Done: - datagrid view (pagination, sorting, filtering) - support for all inheritors of java.util.List - the first element of the collection specifies the columns (if other elements don't have a field, the cell is empty) To be done: - tests - support more types - variables view on click (may result in producing nested sub-views) - maybe include a variable name in the title GitOrigin-RevId: 41ddf8cc16a9aae6aafc671ce1897584d198a6aa --- .../visualizer/CollectionVisualizer.kt | 37 +++++++++++ .../CollectionVisualizerEvaluator.kt | 65 +++++++++++++++++++ .../debugger/impl/DebuggerUtilsImpl.java | 12 +++- .../settings/NodeRendererSettings.java | 2 + .../render/CompoundReferenceRenderer.java | 4 +- .../messages/JavaDebuggerBundle.properties | 1 + java/java-impl/src/META-INF/JavaPlugin.xml | 2 + 7 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 java/debugger/impl/src/com/intellij/debugger/collections/visualizer/CollectionVisualizer.kt create mode 100644 java/debugger/impl/src/com/intellij/debugger/collections/visualizer/CollectionVisualizerEvaluator.kt diff --git a/java/debugger/impl/src/com/intellij/debugger/collections/visualizer/CollectionVisualizer.kt b/java/debugger/impl/src/com/intellij/debugger/collections/visualizer/CollectionVisualizer.kt new file mode 100644 index 000000000000..5cabe5f85469 --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/collections/visualizer/CollectionVisualizer.kt @@ -0,0 +1,37 @@ +// 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.collections.visualizer + +import com.intellij.debugger.engine.evaluation.EvaluationContextImpl +import com.intellij.debugger.ui.tree.ValueDescriptor +import com.intellij.openapi.extensions.ExtensionPointName +import com.intellij.openapi.project.Project +import kotlinx.coroutines.CoroutineScope +import javax.swing.JComponent + +interface CollectionVisualizer { + fun createComponent( + project: Project, + baseCollectionClass: String, + descriptor: ValueDescriptor, + evaluationContext: EvaluationContextImpl, + scope: CoroutineScope, + ): JComponent? + + companion object { + private val EP_NAME = ExtensionPointName.create("com.intellij.debugger.collectionVisualizer") + + fun createComponent( + project: Project, + baseCollectionClass: String, + valueDescriptor: ValueDescriptor, + evaluationContext: EvaluationContextImpl, + scope: CoroutineScope + ): JComponent? { + for (visualizer in EP_NAME.extensionList) { + val component = visualizer.createComponent(project, baseCollectionClass, valueDescriptor, evaluationContext, scope) ?: continue + return component + } + return null + } + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/collections/visualizer/CollectionVisualizerEvaluator.kt b/java/debugger/impl/src/com/intellij/debugger/collections/visualizer/CollectionVisualizerEvaluator.kt new file mode 100644 index 000000000000..77c988a2157c --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/collections/visualizer/CollectionVisualizerEvaluator.kt @@ -0,0 +1,65 @@ +// 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.collections.visualizer + +import com.intellij.debugger.JavaDebuggerBundle +import com.intellij.debugger.engine.DebugProcessListener +import com.intellij.debugger.engine.FullValueEvaluatorProvider +import com.intellij.debugger.engine.SuspendContext +import com.intellij.debugger.engine.evaluation.EvaluationContextImpl +import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.FrameWrapper +import com.intellij.platform.util.coroutines.childScope +import com.intellij.xdebugger.frame.XFullValueEvaluator +import com.intellij.xdebugger.impl.ui.CustomComponentEvaluator +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.cancel +import java.awt.event.MouseEvent +import javax.swing.JComponent + +class CollectionVisualizerEvaluator(private val baseCollectionClass: String) : FullValueEvaluatorProvider { + override fun getFullValueEvaluator(evaluationContext: EvaluationContextImpl, valueDescriptor: ValueDescriptorImpl): XFullValueEvaluator? { + val presenterName = valueDescriptor.type?.name() ?: "" + val scope = evaluationContext.managerThread.coroutineScope.childScope("Collection presentation for $presenterName") + + return object : CustomComponentEvaluator("CoVi") { + override fun startEvaluation(callback: XFullValueEvaluationCallback) { + callback.evaluated("") + } + + override fun createComponent(fullValue: String?): JComponent? { + return CollectionVisualizer.createComponent(evaluationContext.project, baseCollectionClass, valueDescriptor, evaluationContext, scope) + } + + override fun showValuePopup(event: MouseEvent, project: Project, editor: Editor?, component: JComponent, cancelCallback: Runnable?) { + val frame = FrameWrapper( + project, + dimensionKey = "debugger-collection-visualizer", + isDialog = false, + title = JavaDebuggerBundle.message("debugger.collection.visualizer.title"), + component, + // don't cancel the whole scope when the window is closed -- otherwise it wouldn't be possible to reopen + scope.childScope("Collection presentation for $presenterName (limited to a window)"), + ) + frame.apply { + disposeOnResume(evaluationContext, scope) + closeOnEsc() + show() + } + } + } + } + + private fun FrameWrapper.disposeOnResume( + evaluationContext: EvaluationContextImpl, + scope: CoroutineScope, + ) { + evaluationContext.debugProcess.addDebugProcessListener(object : DebugProcessListener { + override fun resumed(suspendContext: SuspendContext?) { + close() + scope.cancel() + } + }) + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsImpl.java b/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsImpl.java index efbe9c5da904..6230a99f287a 100644 --- a/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsImpl.java +++ b/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsImpl.java @@ -556,14 +556,22 @@ public final class DebuggerUtilsImpl extends DebuggerUtilsEx { return evaluationContext.getDebugProcess().invokeMethod(evaluationContext, type, method, Collections.emptyList()); } - public static @Nullable Value invokeObjectMethod(@NotNull EvaluationContext evaluationContext, + public static @Nullable Value invokeObjectMethod(@NotNull EvaluationContextImpl evaluationContext, @NotNull ObjectReference value, @NotNull String methodName, @Nullable String signature) throws EvaluateException { + return invokeObjectMethod(evaluationContext, value, methodName, signature, Collections.emptyList()); + } + + public static @Nullable Value invokeObjectMethod(@NotNull EvaluationContextImpl evaluationContext, + @NotNull ObjectReference value, + @NotNull String methodName, + @Nullable String signature, + @NotNull List arguments) throws EvaluateException { ReferenceType type = value.referenceType(); Method method = findMethodOrLogError(type, methodName, signature); if (method == null) return null; - return evaluationContext.getDebugProcess().invokeMethod(evaluationContext, value, method, Collections.emptyList()); + return evaluationContext.getDebugProcess().invokeMethod(evaluationContext, value, method, arguments); } private static @Nullable Method findMethodOrLogError(ReferenceType type, @NotNull String methodName, @Nullable String signature) { diff --git a/java/debugger/impl/src/com/intellij/debugger/settings/NodeRendererSettings.java b/java/debugger/impl/src/com/intellij/debugger/settings/NodeRendererSettings.java index 1eb97731858f..a7b7ee0c7ac2 100644 --- a/java/debugger/impl/src/com/intellij/debugger/settings/NodeRendererSettings.java +++ b/java/debugger/impl/src/com/intellij/debugger/settings/NodeRendererSettings.java @@ -4,6 +4,7 @@ package com.intellij.debugger.settings; import com.intellij.codeInsight.AnnotationUtil; import com.intellij.debugger.DebuggerContext; import com.intellij.debugger.JavaDebuggerBundle; +import com.intellij.debugger.collections.visualizer.CollectionVisualizerEvaluator; import com.intellij.debugger.engine.DebugProcess; import com.intellij.debugger.engine.JavaValuePresentation; import com.intellij.debugger.engine.evaluation.*; @@ -574,6 +575,7 @@ public class NodeRendererSettings implements PersistentStateComponent { createExpressionArrayChildrenRenderer("toArray()", "!isEmpty()", arrayRenderer)); setClassName(CommonClassNames.JAVA_UTIL_LIST); setIsApplicableChecker(type -> DebuggerUtilsAsync.instanceOf(type, getClassName())); + setFullValueEvaluator(new CollectionVisualizerEvaluator(getClassName())); } @Override diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/CompoundReferenceRenderer.java b/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/CompoundReferenceRenderer.java index 37c48766a331..0dd78a3a371d 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/CompoundReferenceRenderer.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/CompoundReferenceRenderer.java @@ -29,6 +29,7 @@ import com.sun.jdi.ReferenceType; import com.sun.jdi.Type; import com.sun.jdi.Value; import org.jdom.Element; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -112,7 +113,8 @@ public class CompoundReferenceRenderer extends NodeRendererImpl implements FullV return null; } - void setFullValueEvaluator(FullValueEvaluatorProvider fullValueEvaluatorProvider) { + @ApiStatus.Internal + public void setFullValueEvaluator(FullValueEvaluatorProvider fullValueEvaluatorProvider) { myFullValueEvaluatorProvider = fullValueEvaluatorProvider; } diff --git a/java/debugger/openapi/resources/messages/JavaDebuggerBundle.properties b/java/debugger/openapi/resources/messages/JavaDebuggerBundle.properties index 5902cc6dc29f..686023d6343d 100644 --- a/java/debugger/openapi/resources/messages/JavaDebuggerBundle.properties +++ b/java/debugger/openapi/resources/messages/JavaDebuggerBundle.properties @@ -267,6 +267,7 @@ user.renderers.configurable.display.name=Java Type Renderers async.stacktraces.configurable.display.name=Async Stack Traces label.user.renderers.configurable.renderer.name=Renderer name: text.user.renderers.configurable.no.renderers=No renderers +debugger.collection.visualizer.title=Collection Presentation button.copy=Copy user.renderers.configurable.button.description.copy=Copy selected renderer threads.export.attribute.label.daemon=daemon diff --git a/java/java-impl/src/META-INF/JavaPlugin.xml b/java/java-impl/src/META-INF/JavaPlugin.xml index a8679a8b36d0..0970a369a9d1 100644 --- a/java/java-impl/src/META-INF/JavaPlugin.xml +++ b/java/java-impl/src/META-INF/JavaPlugin.xml @@ -314,6 +314,8 @@ +