diff --git a/java/debugger/frontend/resources/intellij.java.debugger.impl.frontend.xml b/java/debugger/frontend/resources/intellij.java.debugger.impl.frontend.xml
index c5d67e65dd9b..afd43323530c 100644
--- a/java/debugger/frontend/resources/intellij.java.debugger.impl.frontend.xml
+++ b/java/debugger/frontend/resources/intellij.java.debugger.impl.frontend.xml
@@ -5,4 +5,7 @@
+
+
+
\ No newline at end of file
diff --git a/java/debugger/frontend/src/com/intellij/java/debugger/impl/frontend/JavaValueDescriptorStateExtension.kt b/java/debugger/frontend/src/com/intellij/java/debugger/impl/frontend/JavaValueDescriptorStateExtension.kt
new file mode 100644
index 000000000000..641a27a324ac
--- /dev/null
+++ b/java/debugger/frontend/src/com/intellij/java/debugger/impl/frontend/JavaValueDescriptorStateExtension.kt
@@ -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)
+ }
+}
diff --git a/java/debugger/impl/BUILD.bazel b/java/debugger/impl/BUILD.bazel
index f5f067be7b5e..f21859397380 100644
--- a/java/debugger/impl/BUILD.bazel
+++ b/java/debugger/impl/BUILD.bazel
@@ -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"]
)
diff --git a/java/debugger/impl/intellij.java.debugger.impl.iml b/java/debugger/impl/intellij.java.debugger.impl.iml
index cfe0a6a210e8..c68db03b0f45 100644
--- a/java/debugger/impl/intellij.java.debugger.impl.iml
+++ b/java/debugger/impl/intellij.java.debugger.impl.iml
@@ -98,6 +98,7 @@
+
diff --git a/java/debugger/impl/src/com/intellij/debugger/actions/ViewAsGroup.java b/java/debugger/impl/src/com/intellij/debugger/actions/ViewAsGroup.java
index d9574962abc5..d4619a781b78 100644
--- a/java/debugger/impl/src/com/intellij/debugger/actions/ViewAsGroup.java
+++ b/java/debugger/impl/src/com/intellij/debugger/actions/ViewAsGroup.java
@@ -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> getApplicableRenderers(List values, DebugProcessImpl process) {
List>> 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> 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> 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());
diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java b/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java
index 67f8a4103a7c..f704078c2e00 100644
--- a/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java
+++ b/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java
@@ -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 myArguments;
private final List myRenderers = new ArrayList<>();
+ private final MutableSharedFlow myRenderersUpdated = CoroutineUtilsKt.createMutableSharedFlow(1, 1);
// we use null key here
private final Map 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 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()) {
diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/JavaValue.java b/java/debugger/impl/src/com/intellij/debugger/engine/JavaValue.java
index a206456b0aac..24832632b66b 100644
--- a/java/debugger/impl/src/com/intellij/debugger/engine/JavaValue.java
+++ b/java/debugger/impl/src/com/intellij/debugger/engine/JavaValue.java
@@ -523,17 +523,7 @@ public class JavaValue extends XNamedValue implements NodeDescriptorProvider, XV
@Override
public @Nullable CompletableFuture 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
diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/JavaValueUtils.kt b/java/debugger/impl/src/com/intellij/debugger/engine/JavaValueUtils.kt
index fd7fb670ad7b..790f1f5b34c0 100644
--- a/java/debugger/impl/src/com/intellij/debugger/engine/JavaValueUtils.kt
+++ b/java/debugger/impl/src/com/intellij/debugger/engine/JavaValueUtils.kt
@@ -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)
}
}
-}
\ No newline at end of file
+}
+
+internal fun getJavaValueXDescriptor(javaValue: JavaValue): CompletableFuture {
+ 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().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 {
+ 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)
diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/ValueDescriptorImpl.java b/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/ValueDescriptorImpl.java
index 0d6f21d999ca..d4cc62749751 100644
--- a/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/ValueDescriptorImpl.java
+++ b/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/ValueDescriptorImpl.java
@@ -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 myInitFuture;
NodeRenderer myRenderer = null;
-
NodeRenderer myAutoRenderer = null;
+ private final MutableSharedFlow 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 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);
}
diff --git a/java/debugger/shared/src/com/intellij/java/debugger/impl/shared/engine/JavaValueDescriptor.kt b/java/debugger/shared/src/com/intellij/java/debugger/impl/shared/engine/JavaValueDescriptor.kt
index bc211caa6f7b..ed702015339e 100644
--- a/java/debugger/shared/src/com/intellij/java/debugger/impl/shared/engine/JavaValueDescriptor.kt
+++ b/java/debugger/shared/src/com/intellij/java/debugger/impl/shared/engine/JavaValueDescriptor.kt
@@ -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,
+ val applicableRenderersFlow: RpcFlow>,
) : 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())
+
+ 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 get() = _applicableRenderers.value
}
\ No newline at end of file