[java-rd] IJPL-176967: Introduce Frontend's Show Instances action

GitOrigin-RevId: 7bdacf3c8a31a853fbc30336b3976c7d158c32d1
This commit is contained in:
Nikolay Rykunov
2025-02-20 18:52:25 +00:00
committed by intellij-monorepo-bot
parent 50996fd44e
commit ab83df801d
17 changed files with 203 additions and 70 deletions
@@ -43,5 +43,6 @@
<orderEntry type="library" name="kotlinx-serialization-json" level="project" />
<orderEntry type="module" module-name="intellij.java.debugger.impl.shared" />
<orderEntry type="module" module-name="intellij.platform.rpc.backend" />
<orderEntry type="module" module-name="intellij.java.debugger.impl" />
</component>
</module>
@@ -2,4 +2,8 @@
<dependencies>
<module name="intellij.platform.kernel.backend"/>
</dependencies>
<extensions defaultExtensionNs="com.intellij">
<platform.rpc.backend.remoteApiProvider
implementation="com.intellij.java.debugger.impl.backend.BackendJavaDebuggerRemoteApiProvider"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,27 @@
// 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.java.debugger.impl.backend
import com.intellij.debugger.memory.action.DebuggerTreeAction
import com.intellij.debugger.memory.filtering.ClassInstancesProvider
import com.intellij.debugger.memory.ui.InstancesWindow
import com.intellij.java.debugger.impl.shared.rpc.JavaDebuggerLuxActionsApi
import com.intellij.openapi.application.EDT
import com.intellij.xdebugger.impl.rhizome.XValueEntity
import com.intellij.xdebugger.impl.rpc.XValueId
import com.jetbrains.rhizomedb.entity
import com.sun.jdi.ReferenceType
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
internal class BackendJavaDebuggerLuxActionsApi : JavaDebuggerLuxActionsApi {
override suspend fun showInstancesDialog(xValueId: XValueId) {
val xValueEntity = entity(XValueEntity.XValueId, xValueId) ?: return
val xValue = xValueEntity.xValue
val session = xValueEntity.sessionEntity.session
val objectRef = DebuggerTreeAction.getObjectReference(xValue) ?: return
val referenceType: ReferenceType = objectRef.referenceType()
withContext(Dispatchers.EDT) {
InstancesWindow(session, ClassInstancesProvider(referenceType), referenceType).show()
}
}
}
@@ -0,0 +1,14 @@
// 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.java.debugger.impl.backend
import com.intellij.java.debugger.impl.shared.rpc.JavaDebuggerLuxActionsApi
import com.intellij.platform.rpc.backend.RemoteApiProvider
import fleet.rpc.remoteApiDescriptor
private class BackendJavaDebuggerRemoteApiProvider : RemoteApiProvider {
override fun RemoteApiProvider.Sink.remoteApis() {
remoteApi(remoteApiDescriptor<JavaDebuggerLuxActionsApi>()) {
BackendJavaDebuggerLuxActionsApi()
}
}
}
@@ -7,5 +7,9 @@
<action id="Debugger.ViewText.Frontend" class="com.intellij.java.debugger.impl.frontend.actions.FrontendViewTextAction">
<add-to-group group-id="XDebugger.Inspect.Tree.Popup.Bottom.Section.Frontend" anchor="last"/>
</action>
<action class="com.intellij.java.debugger.impl.frontend.actions.FrontendShowInstancesByClassAction"
id="MemoryView.ShowInstancesFromDebuggerTree.Frontend">
<add-to-group group-id="XDebugger.Inspect.Tree.Popup.Middle.Section.Frontend" anchor="last"/>
</action>
</actions>
</idea-plugin>
@@ -1,2 +1,4 @@
action.Debugger.ViewText.Frontend.text=View Text
action.Debugger.ViewText.Frontend.description=View text value of selected node in a separate pane
action.Debugger.ViewText.Frontend.description=View text value of selected node in a separate pane
action.MemoryView.ShowInstancesFromDebuggerTree.Frontend.text=Show Instances
action.show.objects.text=Show {0} Objects\u2026
@@ -0,0 +1,57 @@
// 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.java.debugger.impl.frontend.actions
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.util.text.StringUtil
import com.intellij.platform.debugger.impl.frontend.evaluate.quick.FrontendXValue
import com.intellij.xdebugger.impl.actions.areFrontendDebuggerActionsEnabled
import com.intellij.xdebugger.impl.ui.tree.actions.XDebuggerTreeActionBase
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl
import com.intellij.java.debugger.impl.frontend.messages.JavaDebuggerImplFrontendBundle
import com.intellij.java.debugger.impl.shared.engine.JavaValueDescriptor
import com.intellij.java.debugger.impl.shared.rpc.JavaDebuggerLuxActionsApi
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
/**
* Frontend version of [com.intellij.debugger.memory.action.ShowInstancesByClassAction]
*/
private class FrontendShowInstancesByClassAction : XDebuggerTreeActionBase() {
override fun getActionUpdateThread(): ActionUpdateThread {
return ActionUpdateThread.BGT
}
override fun update(e: AnActionEvent) {
if (!areFrontendDebuggerActionsEnabled()) {
e.presentation.isEnabledAndVisible = false
return
}
super.update(e)
}
override fun isEnabled(node: XValueNodeImpl, e: AnActionEvent): Boolean {
val xValue = node.valueContainer as? FrontendXValue ?: return false
val descriptor = (xValue.descriptor as? JavaValueDescriptor) ?: return false
val objectReferenceInfo = descriptor.objectReferenceInfo ?: return false
val visibleTypeName = StringUtil.getShortName(objectReferenceInfo.typeName)
e.presentation.setText(JavaDebuggerImplFrontendBundle.message("action.show.objects.text", visibleTypeName))
return true
}
override fun perform(node: XValueNodeImpl, nodeName: String, e: AnActionEvent) {
val xValue = node.valueContainer as? FrontendXValue ?: return
service<FrontendShowInstancesByClassActionCoroutineScope>().cs.launch {
JavaDebuggerLuxActionsApi.getInstance().showInstancesDialog(xValue.xValueDto.id)
}
}
}
@Service(Service.Level.APP)
private class FrontendShowInstancesByClassActionCoroutineScope(val cs: CoroutineScope)
@@ -6,7 +6,8 @@ import org.jetbrains.annotations.*;
import java.util.function.Supplier;
final class JavaDebuggerImplFrontendBundle {
@ApiStatus.Internal
public final class JavaDebuggerImplFrontendBundle {
private static final @NonNls String BUNDLE_FQN = "messages.JavaDebuggerImplFrontendBundle";
private static final DynamicBundle BUNDLE = new DynamicBundle(JavaDebuggerImplFrontendBundle.class, BUNDLE_FQN);
@@ -23,6 +23,7 @@ import com.intellij.debugger.ui.tree.*;
import com.intellij.debugger.ui.tree.render.*;
import com.intellij.debugger.ui.tree.render.Renderer;
import com.intellij.java.debugger.impl.shared.engine.JavaValueDescriptor;
import com.intellij.java.debugger.impl.shared.engine.JavaValueObjectReferenceInfo;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.diagnostic.Logger;
@@ -59,8 +60,6 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import static com.intellij.java.debugger.impl.shared.engine.JavaValueDescriptorKt.JAVA_VALUE_KIND;
public class JavaValue extends XNamedValue implements NodeDescriptorProvider, XValueTextProvider,
PinToTopParentValue, PinToTopMemberValue {
private static final Logger LOG = Logger.getInstance(JavaValue.class);
@@ -514,8 +513,14 @@ public class JavaValue extends XNamedValue implements NodeDescriptorProvider, XV
@Override
public @Nullable CompletableFuture<XValueDescriptor> getXValueDescriptorAsync() {
return myValueDescriptor.getInitFuture().thenApply(ignored -> {
Value value = myValueDescriptor.getValue();
JavaValueObjectReferenceInfo objectReferenceInfo = null;
if (value instanceof ObjectReference ref) {
objectReferenceInfo = new JavaValueObjectReferenceInfo(ref.referenceType().name(), ref.virtualMachine().canGetInstanceInfo());
}
return new JavaValueDescriptor(
myValueDescriptor.isString()
myValueDescriptor.isString(),
objectReferenceInfo
);
});
}
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// 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.debugger.memory.action;
import com.intellij.debugger.ui.impl.watch.NodeDescriptorProvider;
@@ -9,12 +9,18 @@ import com.intellij.xdebugger.impl.ui.tree.actions.XDebuggerTreeActionBase;
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.Value;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public abstract class DebuggerTreeAction extends XDebuggerTreeActionBase {
public static @Nullable ObjectReference getObjectReference(@NotNull XValueNodeImpl node) {
XValue valueContainer = node.getValueContainer();
return getObjectReference(valueContainer);
}
@ApiStatus.Internal
public static @Nullable ObjectReference getObjectReference(@NotNull XValue valueContainer) {
if (valueContainer instanceof NodeDescriptorProvider) {
NodeDescriptor descriptor = ((NodeDescriptorProvider)valueContainer).getDescriptor();
if (descriptor instanceof ValueDescriptorImpl) {
@@ -1,4 +1,4 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// 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.debugger.memory.action;
import com.intellij.debugger.JavaDebuggerBundle;
@@ -15,6 +15,9 @@ import com.sun.jdi.ObjectReference;
import com.sun.jdi.ReferenceType;
import org.jetbrains.annotations.NotNull;
/**
* See the frontend version of the action: com.intellij.java.debugger.impl.frontend.actions.FrontendShowInstancesByClassAction
*/
public class ShowInstancesByClassAction extends DebuggerTreeAction {
@Override
public @NotNull ActionUpdateThread getActionUpdateThread() {
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// 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.debugger.memory.filtering
import com.intellij.debugger.memory.ui.JavaReferenceInfo
@@ -6,8 +6,10 @@ import com.intellij.xdebugger.memory.ui.ReferenceInfo
import com.intellij.xdebugger.memory.utils.InstancesProvider
import com.sun.jdi.ObjectReference
import com.sun.jdi.ReferenceType
import org.jetbrains.annotations.ApiStatus
internal interface InstanceProviderEx : InstancesProvider {
@ApiStatus.Internal
interface InstanceProviderEx : InstancesProvider {
fun returnAllInstancesOfAClass(): Boolean
/**
@@ -23,9 +25,10 @@ internal class FixedListProvider(private val references: List<ObjectReference>)
override fun estimateInstancesCount(): Int = references.size
}
internal class ClassInstancesProvider(private val referenceType: ReferenceType) : InstanceProviderEx {
@ApiStatus.Internal
class ClassInstancesProvider(private val referenceType: ReferenceType) : InstanceProviderEx {
override fun getInstances(limit: Int): MutableList<ReferenceInfo> = referenceType.instances(limit.toLong()).mapToInfoList()
override fun returnAllInstancesOfAClass() = true
override fun returnAllInstancesOfAClass(): Boolean = true
override fun estimateInstancesCount(): Int = referenceType.virtualMachine().instanceCounts(listOf(referenceType))[0]
.let { if (it < 0 || it > Int.MAX_VALUE) Int.MAX_VALUE else it.toInt() }
}
@@ -3,24 +3,36 @@ package com.intellij.java.debugger.impl.shared.engine
import com.intellij.xdebugger.frame.XValueCustomDescriptorSerializerProvider
import com.intellij.xdebugger.frame.XValueDescriptor
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.modules.PolymorphicModuleBuilder
import kotlinx.serialization.modules.subclass
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
const val JAVA_VALUE_KIND: String = "JavaValue"
// TODO: this class can be refactored,
// for now its fields are just added adhoc for actions
@ApiStatus.Internal
@Serializable
class JavaValueDescriptor(
data class JavaValueDescriptor(
val isString: Boolean,
val objectReferenceInfo: JavaValueObjectReferenceInfo?,
) : XValueDescriptor {
override val kind: String = JAVA_VALUE_KIND
}
@ApiStatus.Internal
@Serializable
data class JavaValueObjectReferenceInfo(
val typeName: String,
val canGetInstanceInfo: Boolean,
)
private class JavaValueDescriptorSerializerProvider : XValueCustomDescriptorSerializerProvider {
override fun registerSerializer(builder: PolymorphicModuleBuilder<XValueDescriptor>) {
builder.subclass(JavaValueDescriptor::class)
override fun getSerializer(kind: String): KSerializer<out XValueDescriptor>? {
if (kind == JAVA_VALUE_KIND) {
return JavaValueDescriptor.serializer()
}
return null
}
}
@@ -0,0 +1,20 @@
// 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.java.debugger.impl.shared.rpc
import com.intellij.platform.rpc.RemoteApiProviderService
import com.intellij.xdebugger.impl.rpc.XValueId
import fleet.rpc.RemoteApi
import fleet.rpc.Rpc
import fleet.rpc.remoteApiDescriptor
@Rpc
interface JavaDebuggerLuxActionsApi : RemoteApi<Unit> {
suspend fun showInstancesDialog(xValueId: XValueId)
companion object {
@JvmStatic
suspend fun getInstance(): JavaDebuggerLuxActionsApi {
return RemoteApiProviderService.resolve(remoteApiDescriptor<JavaDebuggerLuxActionsApi>())
}
}
}
@@ -0,0 +1,5 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@Internal
package com.intellij.java.debugger.impl.shared.rpc;
import org.jetbrains.annotations.ApiStatus.Internal;
+1 -3
View File
@@ -76,6 +76,7 @@
- name: intellij.java.performancePlugin
- name: intellij.java.compiler.charts
- name: intellij.java.terminal
- name: intellij.java.debugger.impl.backend
- name: lib/javac2.jar
modules:
- name: intellij.java.compiler.antTasks
@@ -128,9 +129,6 @@
- name: lib/maven-resolver-connector-basic.jar
- name: lib/maven-resolver-transport-file.jar
- name: lib/maven-resolver-transport-http.jar
- name: lib/modules/intellij.java.debugger.impl.backend.jar
contentModules:
- name: intellij.java.debugger.impl.backend
- name: lib/modules/intellij.java.debugger.impl.frontend.jar
contentModules:
- name: intellij.java.debugger.impl.frontend
@@ -1,11 +1,7 @@
// 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.xdebugger.frame
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.extensions.ExtensionPointAdapter
import com.intellij.openapi.extensions.ExtensionPointName
import kotlinx.coroutines.CoroutineScope
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.SerialDescriptor
@@ -13,11 +9,8 @@ import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.encodeToJsonElement
import kotlinx.serialization.modules.PolymorphicModuleBuilder
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.polymorphic
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import org.jetbrains.annotations.ApiStatus
/**
@@ -58,60 +51,38 @@ interface XValueCustomDescriptorSerializerProvider {
internal val EP_NAME = ExtensionPointName<XValueCustomDescriptorSerializerProvider>("com.intellij.xdebugger.xValueCustomDescriptorSerializerProvider")
}
fun registerSerializer(builder: PolymorphicModuleBuilder<XValueDescriptor>)
fun getSerializer(kind: String): KSerializer<out XValueDescriptor>?
}
@Serializable
private data class XValueDescriptorImpl(override val kind: String) : XValueDescriptor
private object XValueDescriptorSerializer : KSerializer<XValueDescriptor> {
override val descriptor: SerialDescriptor = JsonElement.serializer().descriptor
override fun serialize(encoder: Encoder, value: XValueDescriptor) {
val json = service<XValueDescriptorJsonProvider>().json
val element = json.encodeToJsonElement(value)
encoder.encodeSerializableValue(JsonElement.serializer(), element)
}
override fun deserialize(decoder: Decoder): XValueDescriptor {
val json = service<XValueDescriptorJsonProvider>().json
val element = decoder.decodeSerializableValue(JsonElement.serializer())
return json.decodeFromJsonElement(element)
}
}
@Service
private class XValueDescriptorJsonProvider(cs: CoroutineScope) {
@Volatile
var json = Json {
private val json = Json {
ignoreUnknownKeys = true
encodeDefaults = true
}
init {
updateJson()
XValueCustomDescriptorSerializerProvider.EP_NAME.addExtensionPointListener(cs, object : ExtensionPointAdapter<XValueCustomDescriptorSerializerProvider>() {
override fun extensionListChanged() {
updateJson()
}
})
override val descriptor: SerialDescriptor = JsonElement.serializer().descriptor
override fun serialize(encoder: Encoder, value: XValueDescriptor) {
val serializer = getSerializerByKind(value.kind)
val element = json.encodeToJsonElement(serializer as KSerializer<XValueDescriptor>, value)
encoder.encodeSerializableValue(JsonElement.serializer(), element)
}
private fun updateJson() {
json = Json {
ignoreUnknownKeys = true
encodeDefaults = true
serializersModule = SerializersModule {
polymorphic(XValueDescriptor::class) {
for (provider in XValueCustomDescriptorSerializerProvider.EP_NAME.extensionList) {
provider.registerSerializer(this)
}
defaultDeserializer {
XValueDescriptorImpl.serializer()
}
}
}
override fun deserialize(decoder: Decoder): XValueDescriptor {
val element = decoder.decodeSerializableValue(JsonElement.serializer())
if (!element.jsonObject.containsKey("kind")) {
throw IllegalArgumentException("Missing required 'kind' property")
}
val kind = element.jsonObject["kind"]!!.jsonPrimitive.content
val serializer = getSerializerByKind(kind)
return json.decodeFromJsonElement(serializer, element)
}
private fun getSerializerByKind(kind: String): KSerializer<out XValueDescriptor> {
return XValueCustomDescriptorSerializerProvider.EP_NAME.extensionList.firstNotNullOfOrNull { it.getSerializer(kind) }
?: XValueDescriptorImpl.serializer()
}
}