mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[rd debugger] IDEA-377734 Move Renderer selection to frontend
GitOrigin-RevId: f05083198ecc50792add12302e51072c3c5ae31a
This commit is contained in:
committed by
intellij-monorepo-bot
parent
02dd01217a
commit
c87beccab7
+24
-4
@@ -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)
|
||||
}
|
||||
|
||||
+1
-1
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
+17
@@ -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
|
||||
}
|
||||
}
|
||||
+164
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
+3
@@ -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 {
|
||||
|
||||
-2
@@ -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"/>
|
||||
|
||||
+1
-1
@@ -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>
|
||||
+2
-2
@@ -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) }
|
||||
+14
-10
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user