[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
This commit is contained in:
Alexander Kuznetsov
2024-10-14 17:27:11 +00:00
committed by intellij-monorepo-bot
parent 4dc61efa3b
commit 7d22b2f36b
7 changed files with 120 additions and 3 deletions
@@ -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<CollectionVisualizer>("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
}
}
}
@@ -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() ?: "<unknown 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()
}
})
}
}
@@ -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<Value> 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) {
@@ -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<Element> {
createExpressionArrayChildrenRenderer("toArray()", "!isEmpty()", arrayRenderer));
setClassName(CommonClassNames.JAVA_UTIL_LIST);
setIsApplicableChecker(type -> DebuggerUtilsAsync.instanceOf(type, getClassName()));
setFullValueEvaluator(new CollectionVisualizerEvaluator(getClassName()));
}
@Override
@@ -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;
}
@@ -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
@@ -314,6 +314,8 @@
<extensionPoint qualifiedName="com.intellij.debugger.additionalContextProvider"
interface="com.intellij.debugger.engine.evaluation.AdditionalContextProvider" dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.debugger.collectionVisualizer"
interface="com.intellij.debugger.collections.visualizer.CollectionVisualizer" dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.compiler.inspectionValidator"
interface="com.intellij.openapi.compiler.util.InspectionValidator"