[rd debugger] IDEA-377734 Implement JavaValueDescriptor state

GitOrigin-RevId: fd6aa8ba21aae57121cda5f2fea3e22994ab42b9
This commit is contained in:
Maksim Zuev
2025-08-20 10:54:25 +00:00
committed by intellij-monorepo-bot
parent d05e2ad41e
commit b5f40fabac
10 changed files with 151 additions and 24 deletions
@@ -5,4 +5,7 @@
</dependencies>
<actions resource-bundle="messages.JavaDebuggerImplFrontendBundle">
</actions>
<extensions defaultExtensionNs="com.intellij">
<xdebugger.descriptorStateManager implementation="com.intellij.java.debugger.impl.frontend.JavaValueDescriptorStateExtension"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,15 @@
// 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
import com.intellij.java.debugger.impl.shared.engine.JavaValueDescriptor
import com.intellij.java.debugger.impl.shared.engine.JavaValueDescriptorState
import com.intellij.platform.debugger.impl.frontend.FrontendDescriptorStateManagerExtension
import com.intellij.xdebugger.frame.XDescriptor
import kotlinx.coroutines.CoroutineScope
private class JavaValueDescriptorStateExtension : FrontendDescriptorStateManagerExtension {
override fun createState(descriptor: XDescriptor, cs: CoroutineScope): Any? {
if (descriptor !is JavaValueDescriptor) return null
return JavaValueDescriptorState(descriptor, cs)
}
}
+1
View File
@@ -65,6 +65,7 @@ jvm_library(
"//xml/xml-psi-api:psi",
"//platform/xdebugger-impl/shared",
"//platform/xdebugger-impl/rpc",
"//fleet/rpc",
],
runtime_deps = [":impl_resources"]
)
@@ -98,6 +98,7 @@
<orderEntry type="module" module-name="intellij.xml.psi" />
<orderEntry type="module" module-name="intellij.platform.debugger.impl.shared" />
<orderEntry type="module" module-name="intellij.platform.debugger.impl.rpc" />
<orderEntry type="module" module-name="fleet.rpc" />
</component>
<component name="copyright">
<Base>
@@ -20,6 +20,7 @@ import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree;
import com.intellij.xdebugger.impl.ui.tree.actions.XDebuggerTreeActionBase;
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -89,8 +90,8 @@ public class ViewAsGroup extends ActionGroup implements DumbAware {
public void threadAction(@NotNull SuspendContextImpl suspendContext) {
for (XValueNodeImpl node : selectedNodes) {
XValue container = node.getValueContainer();
if (container instanceof JavaValue) {
((JavaValue)container).setRenderer(myNodeRenderer, node);
if (container instanceof JavaValue javaValue) {
javaValue.setRenderer(myNodeRenderer, node);
}
}
}
@@ -158,14 +159,8 @@ public class ViewAsGroup extends ActionGroup implements DumbAware {
private static @NotNull CompletableFuture<List<NodeRenderer>> getApplicableRenderers(List<JavaValue> values, DebugProcessImpl process) {
List<CompletableFuture<List<NodeRenderer>>> futures = new ArrayList<>(values.size());
for (JavaValue value : values) {
if (value instanceof JavaReferringObjectsValue) { // disable for any referrers at all
return CompletableFuture.completedFuture(Collections.emptyList());
}
ValueDescriptorImpl valueDescriptor = value.getDescriptor();
if (!valueDescriptor.isValueValid()) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
futures.add(process.getApplicableRenderers(valueDescriptor.getType()));
CompletableFuture<List<NodeRenderer>> completedFuture = getApplicableNodeRenderers(value);
futures.add(completedFuture);
}
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenApply(__ -> {
@@ -183,6 +178,19 @@ public class ViewAsGroup extends ActionGroup implements DumbAware {
});
}
@ApiStatus.Internal
public static CompletableFuture<List<NodeRenderer>> getApplicableNodeRenderers(JavaValue value) {
if (value instanceof JavaReferringObjectsValue) { // disable for any referrers at all
return CompletableFuture.completedFuture(Collections.emptyList());
}
ValueDescriptorImpl valueDescriptor = value.getDescriptor();
if (!valueDescriptor.isValueValid()) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
DebugProcessImpl process = value.getEvaluationContext().getDebugProcess();
return process.getApplicableRenderers(valueDescriptor.getType());
}
@Override
public void update(final @NotNull AnActionEvent event) {
DebuggerContextImpl debuggerContext = DebuggerAction.getDebuggerContext(event.getDataContext());
@@ -81,6 +81,7 @@ import com.intellij.xdebugger.XDebuggerBundle;
import com.intellij.xdebugger.XDebuggerManager;
import com.intellij.xdebugger.XSourcePosition;
import com.intellij.xdebugger.frame.XExecutionStack;
import com.intellij.xdebugger.impl.CoroutineUtilsKt;
import com.intellij.xdebugger.impl.XDebugSessionImpl;
import com.intellij.xdebugger.impl.XDebuggerManagerImpl;
import com.intellij.xdebugger.impl.actions.XDebuggerActions;
@@ -94,10 +95,13 @@ import com.sun.jdi.event.LocatableEvent;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.StepRequest;
import kotlin.Unit;
import kotlin.coroutines.EmptyCoroutineContext;
import kotlinx.coroutines.CoroutineScope;
import kotlinx.coroutines.CoroutineScopeKt;
import kotlinx.coroutines.Job;
import kotlinx.coroutines.flow.Flow;
import kotlinx.coroutines.flow.MutableSharedFlow;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.*;
@@ -146,6 +150,7 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb
private volatile Map<String, Connector.Argument> myArguments;
private final List<NodeRenderer> myRenderers = new ArrayList<>();
private final MutableSharedFlow<Unit> myRenderersUpdated = CoroutineUtilsKt.createMutableSharedFlow(1, 1);
// we use null key here
private final Map<Type, Object> myNodeRenderersMap = Collections.synchronizedMap(new HashMap<>());
@@ -220,6 +225,11 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb
return new DebuggerManagerThreadImpl(disposable, projectScope, this);
}
@ApiStatus.Internal
public Flow<Unit> getRenderersUpdatedFlow() {
return myRenderersUpdated;
}
private void reloadRenderers() {
getManagerThread().schedule(new DebuggerCommandImpl(PrioritizedTask.Priority.HIGH) {
@Override
@@ -230,6 +240,7 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb
myRenderers.addAll(NodeRendererSettings.getInstance().getAllRenderers(project));
}
finally {
myRenderersUpdated.tryEmit(Unit.INSTANCE);
DebuggerInvocationUtil.invokeLaterAnyModality(project, () -> {
final DebuggerSession session = mySession;
if (session != null && session.isAttached()) {
@@ -523,17 +523,7 @@ public class JavaValue extends XNamedValue implements NodeDescriptorProvider, XV
@Override
public @Nullable CompletableFuture<XDescriptor> 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(),
objectReferenceInfo
);
});
return myValueDescriptor.getInitFuture().thenCompose(ignored -> JavaValueUtilsKt.getJavaValueXDescriptor(this));
}
@Override
@@ -1,13 +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.debugger.engine
import com.intellij.debugger.actions.ViewAsGroup
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.debugger.impl.DebuggerUtilsEx
import com.intellij.debugger.impl.PrioritizedTask
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl
import com.intellij.debugger.ui.tree.render.NodeRenderer
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.java.debugger.impl.shared.engine.NodeRendererDto
import com.intellij.openapi.application.readAction
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.xdebugger.XSourcePosition
import com.intellij.xdebugger.frame.XDescriptor
import com.sun.jdi.ObjectReference
import fleet.rpc.core.toRpc
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.future.await
import kotlinx.coroutines.future.future
import java.util.concurrent.CompletableFuture
import kotlin.coroutines.cancellation.CancellationException
@OptIn(InternalCoroutinesApi::class)
@@ -34,4 +50,37 @@ internal fun scheduleSourcePositionCompute(
positionCallback(null)
}
}
}
}
internal fun getJavaValueXDescriptor(javaValue: JavaValue): CompletableFuture<XDescriptor> {
val valueDescriptor = javaValue.descriptor
val value = valueDescriptor.getValue()
var objectReferenceInfo: JavaValueObjectReferenceInfo? = null
if (value is ObjectReference) {
objectReferenceInfo = JavaValueObjectReferenceInfo(value.referenceType().name(), value.virtualMachine().canGetInstanceInfo())
}
val cs = valueDescriptor.project.service<JavaValueDescriptorCreationService>().cs
return cs.future {
val renderersUpdatedFlow = javaValue.evaluationContext.debugProcess.renderersUpdatedFlow
JavaValueDescriptor(
valueDescriptor.isString(),
objectReferenceInfo,
valueDescriptor.lastRenderer?.toRpc(),
valueDescriptor.lastRendererFlow.map { it.toRpc() }.toRpc(),
renderersUpdatedFlow.map { fetchApplicableNodeRenderers(javaValue).map { it.toRpc() } }.toRpc()
)
}
}
private suspend fun fetchApplicableNodeRenderers(javaValue: JavaValue): List<NodeRenderer> {
val renderersFuture = withDebugContext(javaValue.evaluationContext.suspendContext) {
ViewAsGroup.getApplicableNodeRenderers(javaValue)
}
return renderersFuture.await()
}
private fun Renderer.toRpc() = (this as? NodeRenderer)?.toRpc()
private fun NodeRenderer.toRpc() = NodeRendererDto(uniqueId, name)
@Service(Service.Level.PROJECT)
private class JavaValueDescriptorCreationService(val cs: CoroutineScope)
@@ -39,9 +39,13 @@ import com.intellij.util.concurrency.Semaphore;
import com.intellij.xdebugger.frame.XValueModifier;
import com.intellij.xdebugger.frame.XValueNode;
import com.intellij.xdebugger.frame.presentation.XRegularValuePresentation;
import com.intellij.xdebugger.impl.CoroutineUtilsKt;
import com.intellij.xdebugger.impl.frame.XValueMarkers;
import com.intellij.xdebugger.impl.ui.tree.ValueMarkup;
import com.sun.jdi.*;
import kotlin.Unit;
import kotlinx.coroutines.flow.Flow;
import kotlinx.coroutines.flow.MutableSharedFlow;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -60,8 +64,8 @@ public abstract class ValueDescriptorImpl extends NodeDescriptorImpl implements
private final CompletableFuture<Void> myInitFuture;
NodeRenderer myRenderer = null;
NodeRenderer myAutoRenderer = null;
private final MutableSharedFlow<Unit> myRenderersChangedFlow = CoroutineUtilsKt.createMutableSharedFlow(0, 1);
private Value myValue;
@@ -497,10 +501,16 @@ public abstract class ValueDescriptorImpl extends NodeDescriptorImpl implements
public void displayAs(NodeDescriptor descriptor) {
if (descriptor instanceof ValueDescriptorImpl valueDescriptor) {
myRenderer = valueDescriptor.myRenderer;
myRenderersChangedFlow.tryEmit(Unit.INSTANCE);
}
super.displayAs(descriptor);
}
@ApiStatus.Internal
public Flow<Renderer> getLastRendererFlow() {
return CoroutineUtilsKt.mapFlow(myRenderersChangedFlow, __ -> getLastRenderer());
}
public Renderer getLastRenderer() {
return myRenderer != null ? myRenderer : myAutoRenderer;
}
@@ -539,7 +549,11 @@ public abstract class ValueDescriptorImpl extends NodeDescriptorImpl implements
}
else {
return DebuggerUtilsAsync.reschedule(debugProcess.getAutoRendererAsync(type))
.thenApply(r -> myAutoRenderer = r);
.thenApply(r -> {
myAutoRenderer = r;
myRenderersChangedFlow.tryEmit(Unit.INSTANCE);
return r;
});
}
});
}
@@ -548,6 +562,7 @@ public abstract class ValueDescriptorImpl extends NodeDescriptorImpl implements
DebuggerManagerThreadImpl.assertIsManagerThread();
myRenderer = renderer;
myAutoRenderer = null;
myRenderersChangedFlow.tryEmit(Unit.INSTANCE);
}
@@ -3,6 +3,11 @@ package com.intellij.java.debugger.impl.shared.engine
import com.intellij.xdebugger.frame.CustomXDescriptorSerializerProvider
import com.intellij.xdebugger.frame.XDescriptor
import fleet.rpc.core.RpcFlow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import org.jetbrains.annotations.ApiStatus
@@ -17,10 +22,17 @@ const val JAVA_VALUE_KIND: String = "JavaValue"
data class JavaValueDescriptor(
val isString: Boolean,
val objectReferenceInfo: JavaValueObjectReferenceInfo?,
val initialRenderer: NodeRendererDto?,
val rendererFlow: RpcFlow<NodeRendererDto?>,
val applicableRenderersFlow: RpcFlow<List<NodeRendererDto>>,
) : XDescriptor {
override val kind: String = JAVA_VALUE_KIND
}
@ApiStatus.Internal
@Serializable
data class NodeRendererDto(val id: String, val name: String)
@ApiStatus.Internal
@Serializable
data class JavaValueObjectReferenceInfo(
@@ -35,4 +47,26 @@ private class JavaValueDescriptorSerializerProvider : CustomXDescriptorSerialize
}
return null
}
}
@ApiStatus.Internal
class JavaValueDescriptorState(val descriptor: JavaValueDescriptor, cs: CoroutineScope) {
private val _lastRenderer = MutableStateFlow(descriptor.initialRenderer)
private val _applicableRenderers = MutableStateFlow(emptyList<NodeRendererDto>())
init {
cs.launch {
descriptor.rendererFlow.toFlow().collectLatest {
_lastRenderer.value = it
}
}
cs.launch {
descriptor.applicableRenderersFlow.toFlow().collectLatest {
_applicableRenderers.value = it
}
}
}
val lastRenderer: NodeRendererDto? get() = _lastRenderer.value
val applicableRenderers: List<NodeRendererDto> get() = _applicableRenderers.value
}