[rd debugger] IDEA-377734 Move Renderer selection to frontend

GitOrigin-RevId: f05083198ecc50792add12302e51072c3c5ae31a
This commit is contained in:
Maksim Zuev
2025-08-20 10:54:25 +00:00
committed by intellij-monorepo-bot
parent 02dd01217a
commit c87beccab7
19 changed files with 284 additions and 255 deletions
@@ -2,10 +2,7 @@
package com.intellij.java.debugger.impl.backend
import com.intellij.debugger.actions.*
import com.intellij.debugger.engine.AsyncStacksUtils
import com.intellij.debugger.engine.JavaDebugProcess
import com.intellij.debugger.engine.JavaExecutionStack
import com.intellij.debugger.engine.executeOnDMT
import com.intellij.debugger.engine.*
import com.intellij.execution.filters.ExceptionFilters
import com.intellij.ide.ui.icons.rpcId
import com.intellij.java.debugger.impl.shared.rpc.*
@@ -15,6 +12,8 @@ import com.intellij.unscramble.CompoundDumpItem
import com.intellij.unscramble.DumpItem
import com.intellij.xdebugger.impl.rpc.XDebugSessionId
import com.intellij.xdebugger.impl.rpc.XExecutionStackId
import com.intellij.xdebugger.impl.rpc.XValueId
import com.intellij.xdebugger.impl.rpc.models.BackendXValueModel
import com.intellij.xdebugger.impl.rpc.models.findValue
import fleet.util.channels.use
import kotlinx.coroutines.*
@@ -66,6 +65,27 @@ internal class BackendJavaDebuggerSessionApi : JavaDebuggerSessionApi {
}
}
override suspend fun setRenderer(rendererName: String?, xValueIds: List<XValueId>) {
val xValueModels = xValueIds.mapNotNull { BackendXValueModel.findById(it) }
val javaValues = xValueModels.mapNotNull { it.xValue as? JavaValue }
if (javaValues.isEmpty()) return
val renderer = if (rendererName != null) {
javaValues[0].evaluationContext.debugProcess.getRendererByName(rendererName) ?: return
}
else {
null
}
for (javaValue in javaValues) {
withDebugContext(javaValue.evaluationContext.suspendContext) {
javaValue.setRenderer(renderer, null)
}
}
for (xValueModel in xValueModels) {
xValueModel.computeValuePresentation()
}
}
override suspend fun resumeThread(executionStackId: XExecutionStackId) {
invokeThreadCommand(executionStackId, ThreadCommand.RESUME)
}
@@ -3,7 +3,7 @@ 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.platform.debugger.impl.shared.FrontendDescriptorStateManagerExtension
import com.intellij.xdebugger.frame.XDescriptor
import kotlinx.coroutines.CoroutineScope
@@ -250,8 +250,9 @@
<add-to-group group-id="XDebugger.Frames.Tree.Popup.Frontend" anchor="last"/>
</action>
<action id="Debugger.EditFrameSource" class="com.intellij.debugger.actions.EditFrameSourceAction" use-shortcut-of="EditSource"/>
<group id="Debugger.ViewAsGroup" class="com.intellij.debugger.actions.ViewAsGroup">
<group id="Debugger.ViewAsGroup" class="com.intellij.java.debugger.impl.shared.actions.ViewAsGroup">
<add-to-group group-id="XDebugger.ValueGroup" anchor="last"/>
<add-to-group group-id="XDebugger.Inspect.Tree.Popup.Bottom.Section.Frontend" anchor="last"/>
</group>
<action id="Debugger.FilterArray" class="com.intellij.debugger.actions.ArrayAction$FilterArrayAction">
<add-to-group group-id="XDebugger.ValueGroup" anchor="last"/>
@@ -292,7 +293,7 @@
<add-to-group group-id="XDebugger.Watches.Inline.Popup" anchor="last"/>
<override-text place="XDebuggerTreePopup"/>
</action>
<action id="Debugger.AutoRenderer" class="com.intellij.debugger.actions.AutoRendererAction"/>
<action id="Debugger.AutoRenderer" class="com.intellij.java.debugger.impl.shared.actions.AutoRendererAction"/>
<action id="Debugger.MuteRenderers" class="com.intellij.debugger.actions.ForceOnDemandRenderersAction">
<add-to-group group-id="XDebugger.ValueGroup" anchor="last"/>
</action>
@@ -1,42 +0,0 @@
// 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.actions;
import com.intellij.debugger.engine.DebuggerManagerThreadImpl;
import com.intellij.debugger.engine.JavaValue;
import com.intellij.debugger.engine.SuspendContextImpl;
import com.intellij.debugger.engine.events.DebuggerContextCommandImpl;
import com.intellij.debugger.impl.DebuggerContextImpl;
import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class AutoRendererAction extends AnAction {
@Override
public void actionPerformed(final @NotNull AnActionEvent e) {
final DebuggerContextImpl debuggerContext = DebuggerAction.getDebuggerContext(e.getDataContext());
final DebuggerManagerThreadImpl managerThread = debuggerContext.getManagerThread();
if (managerThread != null) {
final List<JavaValue> selectedValues = ViewAsGroup.getSelectedValues(e);
if (!selectedValues.isEmpty()) {
managerThread.schedule(new DebuggerContextCommandImpl(debuggerContext) {
@Override
public void threadAction(@NotNull SuspendContextImpl suspendContext) {
for (JavaValue selectedValue : selectedValues) {
selectedValue.getDescriptor().setRenderer(null);
}
DebuggerAction.refreshViews(e);
}
});
}
}
}
@Override
public @NotNull ActionUpdateThread getActionUpdateThread() {
return ActionUpdateThread.BGT;
}
}
@@ -0,0 +1,21 @@
// 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.actions;
import com.intellij.debugger.engine.JavaValue;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree;
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public final class ViewAsGroup {
public static @NotNull List<JavaValue> getSelectedValues(@NotNull AnActionEvent event) {
List<XValueNodeImpl> selectedNodes = XDebuggerTree.getSelectedNodes(event.getDataContext());
return StreamEx.of(selectedNodes)
.map(XValueNodeImpl::getValueContainer)
.select(JavaValue.class)
.toList();
}
}
@@ -1,181 +0,0 @@
// 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.actions
import com.intellij.debugger.engine.JavaValue
import com.intellij.debugger.engine.SuspendContextImpl
import com.intellij.debugger.engine.events.DebuggerContextCommandImpl
import com.intellij.debugger.impl.DebuggerUtilsAsync
import com.intellij.debugger.ui.tree.render.NodeRenderer
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.progress.util.ProgressIndicatorUtils
import com.intellij.openapi.project.DumbAware
import com.intellij.util.containers.ContainerUtil
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree
import com.intellij.xdebugger.impl.ui.tree.actions.XDebuggerTreeActionBase
import one.util.streamex.StreamEx
import org.jetbrains.annotations.ApiStatus
import java.util.concurrent.CompletableFuture
class ViewAsGroup : ActionGroup(Presentation.NULL_STRING, true), DumbAware {
override fun getActionUpdateThread(): ActionUpdateThread {
return ActionUpdateThread.BGT
}
private class RendererAction(private val nodeRenderer: NodeRenderer) : ToggleAction(nodeRenderer.getName()) {
init {
getTemplatePresentation().setKeepPopupOnPerform(KeepPopupOnPerform.IfRequested)
}
override fun isSelected(e: AnActionEvent): Boolean {
val values = getSelectedValues(e)
if (values.isEmpty()) {
return false
}
for (value in values) {
if (value.descriptor.lastRenderer !== nodeRenderer) {
return false
}
}
return true
}
override fun getActionUpdateThread(): ActionUpdateThread {
return ActionUpdateThread.EDT
}
override fun setSelected(e: AnActionEvent, state: Boolean) {
if (!state) return
val debuggerContext = DebuggerAction.getDebuggerContext(e.dataContext)
val values = getSelectedValues(e)
val selectedNodes = XDebuggerTreeActionBase.getSelectedNodes(e.dataContext)
LOG.assertTrue(!values.isEmpty())
val managerThread = debuggerContext.getManagerThread() ?: return
managerThread.schedule(object : DebuggerContextCommandImpl(debuggerContext) {
override fun threadAction(suspendContext: SuspendContextImpl) {
for (node in selectedNodes) {
val container = node.valueContainer
if (container is JavaValue) {
container.setRenderer(nodeRenderer, node)
}
}
}
})
}
}
override fun getChildren(e: AnActionEvent?): Array<AnAction> {
if (e == null) {
return EMPTY_ARRAY
}
val debuggerContext = DebuggerAction.getDebuggerContext(e.dataContext)
if (debuggerContext.debugProcess == null) {
return EMPTY_ARRAY
}
val values = getSelectedValues(e)
if (!values.isEmpty()) {
val future = CompletableFuture<List<NodeRenderer>>()
val scheduled = debuggerContext.getManagerThread()!!.schedule(object : DebuggerContextCommandImpl(debuggerContext) {
override fun threadAction(suspendContext: SuspendContextImpl) {
getApplicableRenderers(values)
.whenComplete { renderers, throwable ->
DebuggerUtilsAsync.completeFuture(renderers, throwable, future)
}
}
override fun commandCancelled() {
future.cancel(false)
}
})
if (scheduled) {
val rs = ProgressIndicatorUtils.awaitWithCheckCanceled(future)
if (ContainerUtil.isEmpty(rs)) {
return EMPTY_ARRAY
}
val children = mutableListOf<AnAction>()
val actionManager = ActionManager.getInstance()
val viewAsActions = (actionManager.getAction("Debugger.Representation") as DefaultActionGroup)
.getChildren(actionManager)
for (viewAsAction in viewAsActions) {
if (viewAsAction is AutoRendererAction) {
if (rs.size > 1) {
children.add(viewAsAction)
}
}
else {
children.add(viewAsAction)
}
}
if (!children.isEmpty()) {
children.add(Separator.getInstance())
}
children.addAll(rs.map { nodeRenderer -> RendererAction(nodeRenderer) })
return children.toTypedArray()
}
}
return EMPTY_ARRAY
}
override fun update(event: AnActionEvent) {
val debuggerContext = DebuggerAction.getDebuggerContext(event.dataContext)
if (getSelectedValues(event).isEmpty() || debuggerContext.debugProcess == null) {
event.presentation.setEnabledAndVisible(false)
return
}
event.presentation.setEnabledAndVisible(true)
}
companion object {
private val LOG = Logger.getInstance(ViewAsGroup::class.java)
private fun getApplicableRenderers(values: List<JavaValue>): CompletableFuture<List<NodeRenderer>> {
val futures = mutableListOf<CompletableFuture<List<NodeRenderer>>>()
for (value in values) {
val completedFuture = getApplicableNodeRenderers(value)
futures.add(completedFuture)
}
return CompletableFuture.allOf(*futures.toTypedArray()).thenApply {
var res: MutableList<NodeRenderer>? = null
for (future in futures) {
val list = future.join()
if (res == null) {
res = list.toMutableList()
}
else {
res.retainAll(list)
}
}
res ?: emptyList()
}
}
@ApiStatus.Internal
fun getApplicableNodeRenderers(value: JavaValue): CompletableFuture<List<NodeRenderer>> {
if (value is JavaReferringObjectsValue) { // disable for any referrers at all
return CompletableFuture.completedFuture(emptyList())
}
val valueDescriptor = value.descriptor
if (!valueDescriptor.isValueValid) {
return CompletableFuture.completedFuture(emptyList())
}
val process = value.evaluationContext.debugProcess
return process.getApplicableRenderers(valueDescriptor.getType())
}
@JvmStatic
fun getSelectedValues(event: AnActionEvent): List<JavaValue> {
val selectedNodes = XDebuggerTree.getSelectedNodes(event.dataContext)
return StreamEx.of(selectedNodes)
.map { obj -> obj.valueContainer }
.select(JavaValue::class.java)
.toList()
}
}
}
@@ -278,6 +278,11 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb
return DebuggerUtilsImpl.getApplicableRenderers(myRenderers, type);
}
@ApiStatus.Internal
public @Nullable NodeRenderer getRendererByName(String name) {
return ContainerUtil.find(myRenderers, r -> r.getName().equals(name));
}
public @NotNull CompletableFuture<NodeRenderer> getAutoRendererAsync(@Nullable Type type) {
DebuggerManagerThreadImpl.assertIsManagerThread();
// in case evaluation is not possible, force default renderer
@@ -679,14 +679,17 @@ public class JavaValue extends XNamedValue implements NodeDescriptorProvider, XV
};
}
public void setRenderer(NodeRenderer nodeRenderer, final XValueNodeImpl node) {
public void setRenderer(NodeRenderer nodeRenderer, @Nullable final XValueNodeImpl node) {
DebuggerManagerThreadImpl.assertIsManagerThread();
myValueDescriptor.setRenderer(nodeRenderer);
reBuild(node);
}
public void reBuild(final XValueNodeImpl node) {
public void reBuild(final @Nullable XValueNodeImpl node) {
DebuggerManagerThreadImpl.assertIsManagerThread();
if (node == null) {
return;
}
node.invokeNodeUpdate(() -> {
node.clearChildren();
computePresentation(node, XValuePlace.TREE);
@@ -1,7 +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.debugger.engine
import com.intellij.debugger.actions.ViewAsGroup
import com.intellij.debugger.actions.JavaReferringObjectsValue
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.debugger.impl.DebuggerUtilsEx
import com.intellij.debugger.impl.PrioritizedTask
@@ -59,14 +59,14 @@ internal fun getJavaValueXDescriptor(javaValue: JavaValue): CompletableFuture<XD
if (value is ObjectReference) {
objectReferenceInfo = JavaValueObjectReferenceInfo(value.referenceType().name(), value.virtualMachine().canGetInstanceInfo())
}
val cs = valueDescriptor.project.service<JavaValueDescriptorCreationService>().cs
val cs = javaValue.evaluationContext.suspendContext.coroutineScope
return cs.future {
val renderersUpdatedFlow = javaValue.evaluationContext.debugProcess.renderersUpdatedFlow
JavaValueDescriptor(
valueDescriptor.isString(),
objectReferenceInfo,
valueDescriptor.lastRenderer?.toRpc(),
valueDescriptor.lastRendererFlow.map { it.toRpc() }.toRpc(),
valueDescriptor.lastRendererFlow.map { it?.toRpc() }.toRpc(),
renderersUpdatedFlow.map { fetchApplicableNodeRenderers(javaValue).map { it.toRpc() } }.toRpc()
)
}
@@ -74,13 +74,25 @@ internal fun getJavaValueXDescriptor(javaValue: JavaValue): CompletableFuture<XD
private suspend fun fetchApplicableNodeRenderers(javaValue: JavaValue): List<NodeRenderer> {
val renderersFuture = withDebugContext(javaValue.evaluationContext.suspendContext) {
ViewAsGroup.getApplicableNodeRenderers(javaValue)
getApplicableNodeRenderers(javaValue)
}
return renderersFuture.await()
}
private fun getApplicableNodeRenderers(value: JavaValue): CompletableFuture<List<NodeRenderer>> {
if (value is JavaReferringObjectsValue) { // disable for any referrers at all
return CompletableFuture.completedFuture(emptyList())
}
val valueDescriptor = value.descriptor
if (!valueDescriptor.isValueValid) {
return CompletableFuture.completedFuture(emptyList())
}
val process = value.evaluationContext.debugProcess
return process.getApplicableRenderers(valueDescriptor.getType())
}
private fun Renderer.toRpc() = (this as? NodeRenderer)?.toRpc()
private fun NodeRenderer.toRpc() = NodeRendererDto(uniqueId, name)
private fun NodeRenderer.toRpc() = NodeRendererDto(name)
@Service(Service.Level.PROJECT)
private class JavaValueDescriptorCreationService(val cs: CoroutineScope)
@@ -65,7 +65,7 @@ public abstract class ValueDescriptorImpl extends NodeDescriptorImpl implements
NodeRenderer myRenderer = null;
NodeRenderer myAutoRenderer = null;
private final MutableSharedFlow<Unit> myRenderersChangedFlow = CoroutineUtilsKt.createMutableSharedFlow(0, 1);
private final MutableSharedFlow<Unit> myRenderersChangedFlow = CoroutineUtilsKt.createMutableSharedFlow(1, 1);
private Value myValue;
@@ -507,7 +507,7 @@ public abstract class ValueDescriptorImpl extends NodeDescriptorImpl implements
}
@ApiStatus.Internal
public Flow<Renderer> getLastRendererFlow() {
public Flow<@Nullable Renderer> getLastRendererFlow() {
return CoroutineUtilsKt.mapFlow(myRenderersChangedFlow, __ -> getLastRenderer());
}
@@ -0,0 +1,17 @@
// 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.actions
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.remoting.ActionRemoteBehaviorSpecification
internal class AutoRendererAction : AnAction(), ActionRemoteBehaviorSpecification.FrontendOtherwiseBackend {
override fun actionPerformed(e: AnActionEvent) {
setRendererForNodes(e, null)
}
override fun getActionUpdateThread(): ActionUpdateThread {
return ActionUpdateThread.BGT
}
}
@@ -0,0 +1,164 @@
// 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.actions
import com.intellij.java.debugger.impl.shared.engine.JavaValueDescriptor
import com.intellij.java.debugger.impl.shared.engine.JavaValueDescriptorState
import com.intellij.java.debugger.impl.shared.engine.NodeRendererDto
import com.intellij.java.debugger.impl.shared.rpc.JavaDebuggerSessionApi
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.actionSystem.remoting.ActionRemoteBehaviorSpecification
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.Project
import com.intellij.platform.debugger.impl.shared.FrontendDescriptorStateManager
import com.intellij.xdebugger.frame.XValue
import com.intellij.xdebugger.frame.XValuePlace
import com.intellij.xdebugger.impl.frame.XDebugManagerProxy
import com.intellij.xdebugger.impl.frame.XDebugSessionProxy
import com.intellij.xdebugger.impl.rpc.XValueId
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl
import kotlinx.coroutines.launch
internal class ViewAsGroup : ActionGroup(Presentation.NULL_STRING, true), DumbAware {
override fun getActionUpdateThread(): ActionUpdateThread {
return ActionUpdateThread.BGT
}
private class RendererAction(private val nodeRenderer: NodeRendererDto) : ToggleAction(nodeRenderer.name), ActionRemoteBehaviorSpecification.FrontendOtherwiseBackend {
init {
getTemplatePresentation().setKeepPopupOnPerform(KeepPopupOnPerform.IfRequested)
}
override fun isSelected(e: AnActionEvent): Boolean {
val project = e.project ?: return false
val descriptors = getSelectedJavaValueDescriptors(e)
if (descriptors.isEmpty()) {
return false
}
for (descriptor in descriptors) {
val descriptorState = descriptor.getState(project) ?: return false
if (descriptorState.lastRenderer?.name != nodeRenderer.name) {
return false
}
}
return true
}
override fun getActionUpdateThread(): ActionUpdateThread {
return ActionUpdateThread.EDT
}
override fun setSelected(e: AnActionEvent, state: Boolean) {
if (!state) return
setRendererForNodes(e, nodeRenderer)
}
}
override fun getChildren(e: AnActionEvent?): Array<AnAction> {
val project = e?.project ?: return EMPTY_ARRAY
val values = getSelectedJavaValueDescriptors(e)
if (values.isEmpty()) return EMPTY_ARRAY
val rs = getApplicableRenderers(project, values)
if (rs.isEmpty()) return EMPTY_ARRAY
val children = mutableListOf<AnAction>()
val actionManager = ActionManager.getInstance()
val viewAsActions = (actionManager.getAction("Debugger.Representation") as DefaultActionGroup)
.getChildren(actionManager)
for (viewAsAction in viewAsActions) {
if (viewAsAction is AutoRendererAction) {
if (rs.size > 1) {
children.add(viewAsAction)
}
}
else {
children.add(viewAsAction)
}
}
if (!children.isEmpty()) {
children.add(Separator.getInstance())
}
children.addAll(rs.map { nodeRenderer -> RendererAction(nodeRenderer) })
return children.toTypedArray()
}
override fun update(event: AnActionEvent) {
event.presentation.setEnabledAndVisible(getSelectedJavaValuesWithDescriptors(event).isNotEmpty())
}
companion object {
private fun getApplicableRenderers(project: Project, values: List<JavaValueDescriptor>): List<NodeRendererDto> {
var res: MutableList<NodeRendererDto>? = null
for (descriptor in values) {
val state = descriptor.getState(project) ?: return emptyList()
val list = state.applicableRenderers
if (res == null) {
res = list.toMutableList()
}
else {
res.retainAll(list)
}
}
return res ?: emptyList()
}
private fun getSelectedJavaValuesWithDescriptors(event: AnActionEvent): List<Triple<XValueNodeImpl, XValue, JavaValueDescriptor>> {
val selectedNodes = XDebuggerTree.getSelectedNodes(event.dataContext)
return selectedNodes.map { it to it.valueContainer }
.map { it to it.second.xValueDescriptorAsync?.getNow(null) }
.filter { it.second is JavaValueDescriptor }
.map { Triple(it.first.first, it.first.second, it.second as JavaValueDescriptor) }
}
private fun getSelectedJavaValueDescriptors(event: AnActionEvent): List<JavaValueDescriptor> {
return getSelectedJavaValuesWithDescriptors(event).map { it.third }
}
internal fun getSelectedNodesOfJavaValue(event: AnActionEvent): List<Pair<XValueNodeImpl, XValue>> {
return getSelectedJavaValuesWithDescriptors(event).map { it.first to it.second }
}
}
}
internal fun setRendererForNodes(e: AnActionEvent, nodeRenderer: NodeRendererDto?) {
val session = DebuggerUIUtil.getSessionProxy(e) ?: return
val selectedNodesWithJavaValues = ViewAsGroup.getSelectedNodesOfJavaValue(e)
session.coroutineScope.launch {
val xValues = selectedNodesWithJavaValues.map { it.second }
xValues.withId(session) { ids ->
JavaDebuggerSessionApi.getInstance().setRenderer(nodeRenderer?.name, ids)
}
for ((node, value) in selectedNodesWithJavaValues) {
node.invokeNodeUpdate {
node.clearChildren()
value.computePresentation(node, XValuePlace.TREE)
}
}
}
}
private fun JavaValueDescriptor.getState(project: Project): JavaValueDescriptorState? =
FrontendDescriptorStateManager.getInstance(project).getState(this) as? JavaValueDescriptorState
internal suspend fun <T> List<XValue>.withId(session: XDebugSessionProxy, block: suspend (List<XValueId>) -> T): T {
val ids = mutableListOf<XValueId>()
val managerProxy = XDebugManagerProxy.getInstance()
suspend fun rec(i: Int): T {
return if (i < size) {
managerProxy.withId(this[i], session) { id ->
ids.add(id)
rec(i + 1)
}
}
else {
block(ids)
}
}
return rec(0)
}
@@ -11,6 +11,7 @@ import kotlinx.coroutines.launch
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.Nls
@ApiStatus.Internal
const val JAVA_VALUE_KIND: String = "JavaValue"
@@ -31,7 +32,7 @@ data class JavaValueDescriptor(
@ApiStatus.Internal
@Serializable
data class NodeRendererDto(val id: String, val name: String)
data class NodeRendererDto(val name: @Nls String)
@ApiStatus.Internal
@Serializable
@@ -8,6 +8,7 @@ import com.intellij.platform.debugger.impl.rpc.SerializableSimpleTextAttributes
import com.intellij.platform.rpc.RemoteApiProviderService
import com.intellij.xdebugger.impl.rpc.XDebugSessionId
import com.intellij.xdebugger.impl.rpc.XExecutionStackId
import com.intellij.xdebugger.impl.rpc.XValueId
import fleet.rpc.RemoteApi
import fleet.rpc.Rpc
import fleet.rpc.core.ReceiveChannelSerializer
@@ -34,6 +35,8 @@ interface JavaDebuggerSessionApi : RemoteApi<Unit> {
suspend fun stepOutOfCodeBlock(sessionId: XDebugSessionId)
suspend fun setRenderer(rendererName: String?, xValueIds: List<XValueId>)
companion object {
@JvmStatic
suspend fun getInstance(): JavaDebuggerSessionApi {
@@ -10,8 +10,6 @@
<extensionPoints>
<extensionPoint name="xdebugger.customQuickEvaluateActionProvider"
interface="com.intellij.platform.debugger.impl.frontend.actions.CustomQuickEvaluateActionProvider" dynamic="true"/>
<extensionPoint name="xdebugger.descriptorStateManager"
interface="com.intellij.platform.debugger.impl.frontend.FrontendDescriptorStateManagerExtension" dynamic="true"/>
</extensionPoints>
<actions resource-bundle="messages.FrontendDebuggerImplBundle">
<action id="QuickEvaluateExpression" class="com.intellij.platform.debugger.impl.frontend.actions.QuickEvaluateAction"/>
@@ -8,8 +8,8 @@ import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.NlsSafe
import com.intellij.platform.debugger.impl.frontend.FrontendDescriptorStateManager
import com.intellij.platform.debugger.impl.rpc.*
import com.intellij.platform.debugger.impl.shared.FrontendDescriptorStateManager
import com.intellij.platform.util.coroutines.childScope
import com.intellij.util.ConcurrencyUtil
import com.intellij.util.ThreeState
@@ -6,5 +6,8 @@
<extensionPoint qualifiedName="com.intellij.xdebugger.additionalTabComponentConverter"
interface="com.intellij.platform.debugger.impl.shared.XDebugSessionAdditionalTabComponentConverter"
dynamic="true"/>
<extensionPoint name="xdebugger.descriptorStateManager"
interface="com.intellij.platform.debugger.impl.shared.FrontendDescriptorStateManagerExtension"
dynamic="true"/>
</extensionPoints>
</idea-plugin>
@@ -1,5 +1,5 @@
// 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.platform.debugger.impl.frontend
package com.intellij.platform.debugger.impl.shared
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
@@ -33,7 +33,7 @@ interface FrontendDescriptorStateManagerExtension {
class FrontendDescriptorStateManager {
private val states = ConcurrentHashMap<XDescriptor, Any>()
internal fun registerDescriptor(descriptor: XDescriptor, cs: CoroutineScope) {
fun registerDescriptor(descriptor: XDescriptor, cs: CoroutineScope) {
val state = FrontendDescriptorStateManagerExtension.createState(descriptor, cs) ?: return
states[descriptor] = state
cs.coroutineContext.job.invokeOnCompletion { states.remove(descriptor) }
@@ -56,19 +56,23 @@ class BackendXValueModel internal constructor(
init {
if (precomputePresentation) {
xValue.computePresentation(
cs,
XValuePlace.TREE,
presentationHandler = {
_presentation.tryEmit(it)
},
fullValueEvaluatorHandler = {
_fullValueEvaluator.value = it
}
)
computeValuePresentation()
}
}
fun computeValuePresentation() {
xValue.computePresentation(
cs,
XValuePlace.TREE,
presentationHandler = {
_presentation.tryEmit(it)
},
fullValueEvaluatorHandler = {
_fullValueEvaluator.value = it
}
)
}
fun computeTooltipPresentation(): Flow<XValueSerializedPresentation> {
return channelFlow {
val channelCs = this