From 035e4bd3aa2fa2f9baac6cf9eef125461c3dc782 Mon Sep 17 00:00:00 2001 From: Denis Zhdanov Date: Tue, 19 Jul 2011 11:47:48 +0400 Subject: [PATCH] IDEA-71882 Debugger: Improve usability for checking data sub-range 1. 'Adjust range' action operates on lists as well now; 2. Adjusted range is preserved for local variables delivered at another stack frame; 3. Node representation is updated on target range change now (cached values were used before); --- .../actions/AdjustArrayRangeAction.java | 239 ++++++++++++++++-- .../ui/impl/watch/ValueDescriptorImpl.java | 25 +- .../ui/tree/render/ChildrenRenderer.java | 9 +- .../ui/tree/render/DebuggerRendererUtil.java | 91 +++++++ .../render/DelegatingChildrenRenderer.java | 57 +++++ .../render/ExpressionChildrenRenderer.java | 46 +++- 6 files changed, 428 insertions(+), 39 deletions(-) create mode 100644 java/debugger/impl/src/com/intellij/debugger/ui/tree/render/DebuggerRendererUtil.java create mode 100644 java/debugger/impl/src/com/intellij/debugger/ui/tree/render/DelegatingChildrenRenderer.java diff --git a/java/debugger/impl/src/com/intellij/debugger/actions/AdjustArrayRangeAction.java b/java/debugger/impl/src/com/intellij/debugger/actions/AdjustArrayRangeAction.java index be9e3af4d4c2..762282dd22d1 100644 --- a/java/debugger/impl/src/com/intellij/debugger/actions/AdjustArrayRangeAction.java +++ b/java/debugger/impl/src/com/intellij/debugger/actions/AdjustArrayRangeAction.java @@ -16,46 +16,79 @@ package com.intellij.debugger.actions; import com.intellij.debugger.engine.DebugProcessImpl; +import com.intellij.debugger.engine.DebuggerUtils; +import com.intellij.debugger.engine.events.DebuggerCommandImpl; import com.intellij.debugger.engine.events.SuspendContextCommandImpl; import com.intellij.debugger.impl.DebuggerContextImpl; import com.intellij.debugger.settings.ArrayRendererConfigurable; import com.intellij.debugger.ui.impl.watch.DebuggerTreeNodeImpl; import com.intellij.debugger.ui.impl.watch.NodeDescriptorImpl; import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl; -import com.intellij.debugger.ui.tree.render.ArrayRenderer; +import com.intellij.debugger.ui.tree.DebuggerTreeNode; +import com.intellij.debugger.ui.tree.NodeDescriptor; +import com.intellij.debugger.ui.tree.render.*; +import com.intellij.debugger.ui.tree.render.Renderer; +import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.options.ex.SingleConfigurableEditor; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.util.ui.UIUtil; +import com.sun.jdi.Type; +import com.sun.jdi.Value; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; +import java.util.List; public class AdjustArrayRangeAction extends DebuggerAction { + + private static final AdjustedRendererManager[] MANAGERS = { new ArrayRendererManager(), new ListRendererManager() }; + public void actionPerformed(AnActionEvent e) { - DebuggerContextImpl debuggerContext = DebuggerAction.getDebuggerContext(e.getDataContext()); + final DebuggerContextImpl debuggerContext = DebuggerAction.getDebuggerContext(e.getDataContext()); if(debuggerContext == null) { return; } - DebugProcessImpl debugProcess = debuggerContext.getDebugProcess(); + final DebugProcessImpl debugProcess = debuggerContext.getDebugProcess(); if(debugProcess == null) { return; } - Project project = debuggerContext.getProject(); - final DebuggerTreeNodeImpl selectedNode = getSelectedNode(e.getDataContext()); if (selectedNode == null) { return; } - NodeDescriptorImpl descriptor = selectedNode.getDescriptor(); - if(!(descriptor instanceof ValueDescriptorImpl && ((ValueDescriptorImpl)descriptor).isArray())) { + + debugProcess.getManagerThread().invoke(new DebuggerCommandImpl() { + @Override + protected void action() throws Exception { + adjustRange(debuggerContext, selectedNode, debugProcess); + } + }); + } + + private static void adjustRange(@NotNull final DebuggerContextImpl debuggerContext, @NotNull final DebuggerTreeNodeImpl selectedNode, + @NotNull final DebugProcessImpl debugProcess) + { + final Project project = debuggerContext.getProject(); + final ValueDescriptorImpl valueDescriptor = (ValueDescriptorImpl)selectedNode.getDescriptor(); + ArrayRenderer renderer = null; + AdjustedRendererManager manager = null; + for (AdjustedRendererManager m : MANAGERS) { + renderer = m.getRenderer(selectedNode, debuggerContext); + if (renderer != null) { + manager = m; + break; + } + } + if (renderer == null) { return; } - ArrayRenderer renderer = (ArrayRenderer)((ValueDescriptorImpl)selectedNode.getDescriptor()).getLastRenderer(); - String title = createNodeTitle("", selectedNode); String label = selectedNode.toString(); int index = label.indexOf('='); @@ -63,31 +96,49 @@ public class AdjustArrayRangeAction extends DebuggerAction { title = title + " " + label.substring(index); } final ArrayRenderer cloneRenderer = renderer.clone(); - SingleConfigurableEditor editor = new SingleConfigurableEditor(project, new NamedArrayConfigurable(title, cloneRenderer)) { - protected Action[] createActions() { - final String helpTopic = getConfigurable().getHelpTopic(); - return (helpTopic != null)? - new Action[]{getOKAction(), getCancelAction(), getHelpAction()} : - new Action[]{getOKAction(), getCancelAction()}; - } - }; - editor.show(); + final AdjustedRendererManager m = manager; - if(editor.getExitCode() == DialogWrapper.OK_EXIT_CODE) { - debugProcess.getManagerThread().schedule(new SuspendContextCommandImpl(debuggerContext.getSuspendContext()) { - public void contextAction() throws Exception { - selectedNode.setRenderer(cloneRenderer); + final String finalTitle = title; + UIUtil.invokeLaterIfNeeded(new Runnable() { + @Override + public void run() { + SingleConfigurableEditor editor = new SingleConfigurableEditor(project, new NamedArrayConfigurable(finalTitle, cloneRenderer)) { + protected Action[] createActions() { + final String helpTopic = getConfigurable().getHelpTopic(); + return (helpTopic != null) ? + new Action[]{getOKAction(), getCancelAction(), getHelpAction()} : + new Action[]{getOKAction(), getCancelAction()}; } - }); - } + }; + editor.show(); + if(editor.getExitCode() == DialogWrapper.OK_EXIT_CODE) { + debugProcess.getManagerThread().schedule(new SuspendContextCommandImpl(debuggerContext.getSuspendContext()) { + public void contextAction() throws Exception { + m.apply(cloneRenderer, valueDescriptor, debuggerContext); + selectedNode.clear(); + selectedNode.calcRepresentation(); + } + }); + } + } + }); + } public void update(AnActionEvent e) { boolean enable = false; DebuggerTreeNodeImpl selectedNode = getSelectedNode(e.getDataContext()); if(selectedNode != null) { - NodeDescriptorImpl descriptor = selectedNode.getDescriptor(); - enable = descriptor instanceof ValueDescriptorImpl && ((ValueDescriptorImpl)descriptor).isArray() && ((ValueDescriptorImpl) descriptor).getLastRenderer() instanceof ArrayRenderer; + final NodeDescriptorImpl d = selectedNode.getDescriptor(); + if (d instanceof ValueDescriptorImpl) { + ValueDescriptorImpl descriptor = (ValueDescriptorImpl)d; + for (AdjustedRendererManager manager : MANAGERS) { + if (manager.canBeApplied(descriptor)) { + enable = true; + break; + } + } + } } e.getPresentation().setVisible(enable); } @@ -126,4 +177,140 @@ public class AdjustArrayRangeAction extends DebuggerAction { return null; } } + + /** + * Encapsulates details of array renderer retrieval and appliance (e.g. different logic is used for array references and list references). + *

+ * Implementations of this interface are assumed to be thread-safe. + */ + private interface AdjustedRendererManager { + + /** + * Allows to answer if current action can be applied to the value referenced by the given descriptor. + *

+ * Note: we can't just use checking like '{@link #getRenderer(DebuggerTreeNode, DebuggerContextImpl)} == null' + * because this method is called outside of debug manager thread context, i.e. it's assumed to be called on + * {@link AnAction#update(AnActionEvent)} and perform light-weight check from EDT. + * + * @param descriptor target value descriptor + * @return true if current action can be applied to the value referenced by the given descriptor; + * false otherwise + */ + boolean canBeApplied(@NotNull ValueDescriptorImpl descriptor); + + /** + * @param node target node + * @param context current debug context process + * @return array-like data renderer used at the moment (if any) + */ + @Nullable + ArrayRenderer getRenderer(@NotNull DebuggerTreeNode node, @NotNull DebuggerContextImpl context); + + /** + * Asks to use given renderer for array-like data. + * + * @param renderer renderer to use + * @param descriptor target node descriptor + * @param context target debugging context + */ + void apply(@NotNull ArrayRenderer renderer, @NotNull ValueDescriptorImpl descriptor, @NotNull DebuggerContextImpl context); + } + + private static abstract class AbstractAdjustedRendererManager implements AdjustedRendererManager { + + @Override + public ArrayRenderer getRenderer(@NotNull final DebuggerTreeNode node, @NotNull final DebuggerContextImpl context) { + final NodeDescriptor descriptor = node.getDescriptor(); + if (!(descriptor instanceof ValueDescriptorImpl)) { + return null; + } + + return getRenderer(node, (ValueDescriptorImpl)descriptor, context); + } + + @Nullable + protected abstract ArrayRenderer getRenderer(@NotNull DebuggerTreeNode node, @NotNull ValueDescriptorImpl descriptor, + @NotNull DebuggerContextImpl context); + } + + private static class ArrayRendererManager extends AbstractAdjustedRendererManager { + + @Override + public boolean canBeApplied(@NotNull ValueDescriptorImpl descriptor) { + return descriptor.getLastRenderer() instanceof ArrayRenderer; + } + + @Override + public ArrayRenderer getRenderer(@NotNull DebuggerTreeNode node, @NotNull ValueDescriptorImpl descriptor, + @NotNull DebuggerContextImpl context) + { + final Renderer result = descriptor.getLastRenderer(); + if (result instanceof ArrayRenderer) { + return (ArrayRenderer)result; + } + return null; + } + + @Override + public void apply(@NotNull ArrayRenderer renderer, @NotNull ValueDescriptorImpl valueDescriptor, @NotNull DebuggerContextImpl context) { + valueDescriptor.setRenderer(renderer, context.getDebugProcess(), true); + } + } + + private static class ListRendererManager extends AbstractAdjustedRendererManager { + + @Override + public boolean canBeApplied(@NotNull ValueDescriptorImpl descriptor) { + final Value value = descriptor.getValue(); + if (value == null) { + return false; + } + final Type type = value.type(); + if (type == null) { + return false; + } + + return DebuggerUtils.instanceOf(type, List.class.getName()); + } + + @Override + public ArrayRenderer getRenderer(@NotNull DebuggerTreeNode node, @NotNull final ValueDescriptorImpl descriptor, + @NotNull final DebuggerContextImpl context) + { + final DelegatingChildrenRenderer delegatingRenderer = getDelegateRenderer(descriptor, context.getDebugProcess()); + if (delegatingRenderer == null) { + return null; + } + final ChildrenRenderer result = delegatingRenderer.getDelegate(context.createEvaluationContext(), descriptor, descriptor.getValue()); + if (result instanceof ArrayRenderer) { + return (ArrayRenderer)result; + } + return null; + } + + @Override + public void apply(@NotNull ArrayRenderer renderer, @NotNull ValueDescriptorImpl descriptor, @NotNull DebuggerContextImpl context) { + final DelegatingChildrenRenderer delegateRenderer = getDelegateRenderer(descriptor, context.getDebugProcess()); + if (delegateRenderer != null) { + delegateRenderer.setDelegate(context.createEvaluationContext(), descriptor, descriptor.getValue(), renderer); + } + } + + @Nullable + private static DelegatingChildrenRenderer getDelegateRenderer(ValueDescriptorImpl descriptor, DebugProcessImpl process) { + final NodeRenderer renderer = descriptor.getRenderer(process); + if (!(renderer instanceof CompoundReferenceRenderer)) { + return null; + } + + CompoundReferenceRenderer compoundReferenceRenderer = (CompoundReferenceRenderer)renderer; + final ChildrenRenderer result = compoundReferenceRenderer.getChildrenRenderer(); + if (result instanceof DelegatingChildrenRenderer) { + return (DelegatingChildrenRenderer)result; + } + else { + return null; + } + } + } } 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 ab731f3e0daf..dabec03d00c0 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 @@ -29,9 +29,7 @@ import com.intellij.debugger.jdi.VirtualMachineProxyImpl; import com.intellij.debugger.settings.NodeRendererSettings; import com.intellij.debugger.ui.tree.NodeDescriptor; import com.intellij.debugger.ui.tree.ValueDescriptor; -import com.intellij.debugger.ui.tree.render.ClassRenderer; -import com.intellij.debugger.ui.tree.render.DescriptorLabelListener; -import com.intellij.debugger.ui.tree.render.NodeRenderer; +import com.intellij.debugger.ui.tree.render.*; import com.intellij.debugger.ui.tree.render.Renderer; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; @@ -111,7 +109,7 @@ public abstract class ValueDescriptorImpl extends NodeDescriptorImpl implements } public Value getValue() { - // the following code makes sence only if we do not use ObjectReference.enableCollection() / disableCollection() + // the following code makes sense only if we do not use ObjectReference.enableCollection() / disableCollection() // to keep temporary objects if (Patches.IBM_JDK_DISABLE_COLLECTION_BUG && myStoredEvaluationContext != null && !myStoredEvaluationContext.getSuspendContext().isResumed() && myValue instanceof ObjectReference && VirtualMachineProxyImpl.isCollected((ObjectReference)myValue)) { @@ -168,6 +166,9 @@ public abstract class ValueDescriptorImpl extends NodeDescriptorImpl implements } myValue = value; myValueException = null; + if (myRenderer == null) { + myRenderer = DebuggerRendererUtil.getCustomRenderer(evaluationContext.getDebugProcess(), myValue, NodeRenderer.class); + } } catch (EvaluateException e) { myValueException = e; @@ -342,6 +343,22 @@ public abstract class ValueDescriptorImpl extends NodeDescriptorImpl implements myAutoRenderer = null; } + /** + * Allows to apply given renderer to the value referenced by the current descriptor. + * + * @param renderer custom renderer to use + * @param process current debug process + * @param remember flag that indicates if given renderer should be used at stack frames over than the current + * one (e.g. there is a possible case that custom renderer is applied to the local variable. If given flag + * is 'true', the renderer is applied down at stack as well) + */ + public void setRenderer(NodeRenderer renderer, @Nullable DebugProcess process, boolean remember) { + setRenderer(renderer); + if (remember) { + DebuggerRendererUtil.setCustomRenderer(myValue, process, renderer); + } + } + //returns expression that evaluates tree to this descriptor public PsiExpression getTreeEvaluation(DebuggerTreeNodeImpl debuggerTreeNode, DebuggerContextImpl context) throws EvaluateException { if(debuggerTreeNode.getParent() != null && debuggerTreeNode.getParent().getDescriptor() instanceof ValueDescriptor) { diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/ChildrenRenderer.java b/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/ChildrenRenderer.java index 838749e8f401..969635a20446 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/ChildrenRenderer.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/ChildrenRenderer.java @@ -15,16 +15,13 @@ */ package com.intellij.debugger.ui.tree.render; -import com.intellij.psi.PsiExpression; -import com.intellij.util.IncorrectOperationException; -import com.intellij.debugger.engine.evaluation.EvaluationContext; +import com.intellij.debugger.DebuggerContext; import com.intellij.debugger.engine.evaluation.EvaluateException; -import com.intellij.debugger.engine.StackFrameContext; +import com.intellij.debugger.engine.evaluation.EvaluationContext; import com.intellij.debugger.ui.tree.DebuggerTreeNode; import com.intellij.debugger.ui.tree.NodeDescriptor; -import com.intellij.debugger.DebuggerContext; +import com.intellij.psi.PsiExpression; import com.sun.jdi.Value; -import com.sun.tools.corba.se.idl.constExpr.EvaluationException; public interface ChildrenRenderer extends Renderer { void buildChildren(Value value, ChildrenBuilder builder, EvaluationContext evaluationContext); diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/DebuggerRendererUtil.java b/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/DebuggerRendererUtil.java new file mode 100644 index 000000000000..4e4ac43f060e --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/DebuggerRendererUtil.java @@ -0,0 +1,91 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.debugger.ui.tree.render; + +import com.intellij.debugger.engine.DebugProcess; +import com.intellij.openapi.util.Key; +import com.intellij.util.containers.WeakHashMap; +import com.sun.jdi.Value; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +/** + * @author Denis Zhdanov + * @since 7/18/11 2:43 PM + */ +public class DebuggerRendererUtil { + + /** + * Holds mappings between particular values and their custom renderers. + */ + private static final Key> RENDERERS_MAP_KEY = new Key>("ValueRendererMap"); + + private DebuggerRendererUtil() { + } + + /** + * Asks for custom renderer registered for the given value at the given debug process (if any). + * + * @param process target debug process + * @param value target value which custom renderer is being retrieved + * @param clazz class that specifies IS-A constraint for the desired renderer + * @return 'value - custom renderer' registered for the given value at the given debug process (if any); + * null otherwise + */ + @SuppressWarnings("unchecked") + @Nullable + public static T getCustomRenderer(@Nullable final DebugProcess process, @NotNull Value value, + @NotNull Class clazz) + { + if (process == null) { + return null; + } + Map map = process.getUserData(RENDERERS_MAP_KEY); + if (map == null) { + process.putUserData(RENDERERS_MAP_KEY, map = new WeakHashMap()); + } + final Renderer renderer = map.get(value); + if (clazz.isInstance(renderer)) { + return (T)renderer; + } + else { + return null; + } + } + + /** + * Tries to store given custom renderer for the given value within the given debug process. + * + * @param value target value + * @param process target debug process + * @param renderer target custom renderer to store + * @return true if the mapping is successfully stored; false otherwise + */ + public static boolean setCustomRenderer(@NotNull Value value, @Nullable DebugProcess process, @NotNull Renderer renderer) { + if (process == null) { + return false; + } + + Map map = process.getUserData(RENDERERS_MAP_KEY); + if (map == null) { + process.putUserData(RENDERERS_MAP_KEY, map = new WeakHashMap()); + } + map.put(value, renderer); + return true; + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/DelegatingChildrenRenderer.java b/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/DelegatingChildrenRenderer.java new file mode 100644 index 000000000000..f2edff7c3c53 --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/DelegatingChildrenRenderer.java @@ -0,0 +1,57 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.debugger.ui.tree.render; + +import com.intellij.debugger.engine.evaluation.EvaluationContext; +import com.intellij.debugger.ui.tree.NodeDescriptor; +import com.sun.jdi.Value; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; + +/** + * Defines contract for renderer that performs the job (rendering) by delegating to another object. + *

+ * Example: we can render {@link ArrayList} by delegating to its backed array. The array is a 'delegate object' here + * and its renderer is exposed via the current interface. + * + * @author Denis Zhdanov + * @since 7/18/11 12:14 PM + */ +public interface DelegatingChildrenRenderer extends ChildrenRenderer { + + /** + * @param evaluationContext evaluation context to use + * @param descriptor target node descriptor + * @param value target value for which 'delegate object' can be determined + * @return renderer fo the 'delegate object' if the one exists; null otherwise + */ + @Nullable + ChildrenRenderer getDelegate(@NotNull EvaluationContext evaluationContext, @NotNull NodeDescriptor descriptor, @NotNull Value value); + + /** + * Instructs to use given renderer when 'delegate object' has given type. + * + * @param evaluationContext evaluation context to use + * @param descriptor target node descriptor + * @param value target value for which 'delegate object' given custom renderer should be used + * @param renderer delegating renderer to use + * @return true if given custom renderer is applied; false otherwise + */ + boolean setDelegate(@NotNull EvaluationContext evaluationContext, @NotNull NodeDescriptor descriptor, @NotNull Value value, + @NotNull ChildrenRenderer renderer); +} diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/ExpressionChildrenRenderer.java b/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/ExpressionChildrenRenderer.java index 25857c5ec8bb..dee92a19d1fc 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/ExpressionChildrenRenderer.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/ExpressionChildrenRenderer.java @@ -34,9 +34,11 @@ import com.intellij.openapi.util.Key; import com.intellij.openapi.util.WriteExternalException; import com.intellij.psi.PsiExpression; import com.sun.jdi.BooleanValue; +import com.sun.jdi.Type; import com.sun.jdi.Value; import org.jdom.Element; import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; @@ -46,7 +48,8 @@ import java.util.List; * Date: Sep 17, 2003 * Time: 2:04:00 PM */ -public class ExpressionChildrenRenderer extends ReferenceRenderer implements ChildrenRenderer { +public class ExpressionChildrenRenderer extends ReferenceRenderer implements ChildrenRenderer, DelegatingChildrenRenderer { + public static final @NonNls String UNIQUE_ID = "ExpressionChildrenRenderer"; private static final Key EXPRESSION_VALUE = new Key("EXPRESSION_VALUE"); @@ -78,8 +81,8 @@ public class ExpressionChildrenRenderer extends ReferenceRenderer implements Chi evaluationContext.createEvaluationContext(value), builder.getParentDescriptor() ); - final ChildrenRenderer defaultRenderer = ((DebugProcessImpl)evaluationContext.getDebugProcess()).getDefaultRenderer(childrenValue != null? childrenValue.type() : null); - defaultRenderer.buildChildren(childrenValue, builder, evaluationContext); + final ChildrenRenderer delegateRenderer = doGetDelegate(evaluationContext, builder.getParentDescriptor(), value); + delegateRenderer.buildChildren(childrenValue, builder, evaluationContext); } catch (final EvaluateException e) { List errorChildren = new ArrayList(); @@ -88,6 +91,43 @@ public class ExpressionChildrenRenderer extends ReferenceRenderer implements Chi } } + @Override + public ChildrenRenderer getDelegate(@NotNull EvaluationContext evaluationContext, @NotNull NodeDescriptor descriptor, + @NotNull Value value) + { + try { + return doGetDelegate(evaluationContext, descriptor, value); + } + catch (EvaluateException e) { + return null; + } + } + + private ChildrenRenderer doGetDelegate(@NotNull EvaluationContext evaluationContext, @NotNull NodeDescriptor descriptor, + @NotNull Value value) throws EvaluateException + { + final Value childrenValue = evaluateChildren(evaluationContext.createEvaluationContext(value), descriptor); + if (childrenValue == null) { + return ((DebugProcessImpl)evaluationContext.getDebugProcess()).getDefaultRenderer((Type)null); + } + ChildrenRenderer result = DebuggerRendererUtil.getCustomRenderer(evaluationContext.getDebugProcess(), value, + ChildrenRenderer.class); + if (result != null) { + return result; + } + + return ((DebugProcessImpl)evaluationContext.getDebugProcess()).getDefaultRenderer(childrenValue.type()); + } + + @Override + public boolean setDelegate(@NotNull EvaluationContext evaluationContext, + @NotNull NodeDescriptor descriptor, + @NotNull Value value, + @NotNull ChildrenRenderer renderer) + { + return DebuggerRendererUtil.setCustomRenderer(value, evaluationContext.getDebugProcess(), renderer); + } + private Value evaluateChildren(EvaluationContext context, NodeDescriptor descriptor) throws EvaluateException { final ExpressionEvaluator evaluator = myChildrenExpression.getEvaluator(context.getProject());