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);
This commit is contained in:
Denis Zhdanov
2011-07-19 11:48:51 +04:00
parent d1f3af7db8
commit 035e4bd3aa
6 changed files with 428 additions and 39 deletions
@@ -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).
* <p/>
* 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.
* <p/>
* <b>Note:</b> we can't just use checking like <code>'{@link #getRenderer(DebuggerTreeNode, DebuggerContextImpl)} == null'</code>
* 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 <code>true</code> if current action can be applied to the value referenced by the given descriptor;
* <code>false</code> 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;
}
}
}
}
@@ -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 <code>'true'</code>, 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) {
@@ -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);
@@ -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<Map<Value, Renderer>> RENDERERS_MAP_KEY = new Key<Map<Value, Renderer>>("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 <code>'value - custom renderer'</code> registered for the given value at the given debug process (if any);
* <code>null</code> otherwise
*/
@SuppressWarnings("unchecked")
@Nullable
public static <T extends Renderer> T getCustomRenderer(@Nullable final DebugProcess process, @NotNull Value value,
@NotNull Class<T> clazz)
{
if (process == null) {
return null;
}
Map<Value, Renderer> map = process.getUserData(RENDERERS_MAP_KEY);
if (map == null) {
process.putUserData(RENDERERS_MAP_KEY, map = new WeakHashMap<Value, Renderer>());
}
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 <code>true</code> if the mapping is successfully stored; <code>false</code> otherwise
*/
public static boolean setCustomRenderer(@NotNull Value value, @Nullable DebugProcess process, @NotNull Renderer renderer) {
if (process == null) {
return false;
}
Map<Value, Renderer> map = process.getUserData(RENDERERS_MAP_KEY);
if (map == null) {
process.putUserData(RENDERERS_MAP_KEY, map = new WeakHashMap<Value, Renderer>());
}
map.put(value, renderer);
return true;
}
}
@@ -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.
* <p/>
* Example: we can render {@link ArrayList} by delegating to its backed array. The array is a <code>'delegate object'</code> 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 <code>'delegate object'</code> can be determined
* @return renderer fo the <code>'delegate object'</code> if the one exists; <code>null</code> otherwise
*/
@Nullable
ChildrenRenderer getDelegate(@NotNull EvaluationContext evaluationContext, @NotNull NodeDescriptor descriptor, @NotNull Value value);
/**
* Instructs to use given renderer when <code>'delegate object'</code> has given type.
*
* @param evaluationContext evaluation context to use
* @param descriptor target node descriptor
* @param value target value for which <code>'delegate object'</code> given custom renderer should be used
* @param renderer delegating renderer to use
* @return <code>true</code> if given custom renderer is applied; <code>false</code> otherwise
*/
boolean setDelegate(@NotNull EvaluationContext evaluationContext, @NotNull NodeDescriptor descriptor, @NotNull Value value,
@NotNull ChildrenRenderer renderer);
}
@@ -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<Value> EXPRESSION_VALUE = new Key<Value>("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<DebuggerTreeNode> errorChildren = new ArrayList<DebuggerTreeNode>();
@@ -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());