enhanced object markup in debugger, initial version

This commit is contained in:
Eugene Zhuravlev
2009-09-25 17:40:43 +04:00
parent f0f1979293
commit 74d9f661cc
8 changed files with 276 additions and 54 deletions
@@ -1,5 +1,7 @@
package com.intellij.debugger.actions;
import com.intellij.debugger.engine.DebugProcessImpl;
import com.intellij.debugger.engine.events.DebuggerContextCommandImpl;
import com.intellij.debugger.impl.DebuggerContextImpl;
import com.intellij.debugger.impl.DebuggerSession;
import com.intellij.debugger.ui.impl.watch.DebuggerTree;
@@ -11,12 +13,25 @@ import com.intellij.debugger.ui.tree.ValueMarkup;
import com.intellij.idea.ActionsBundle;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.util.containers.HashMap;
import com.sun.jdi.*;
import javax.swing.*;
import java.awt.*;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/*
* Class SetValueAction
* @author Jeka
*/
public class MarkObjectAction extends DebuggerAction {
private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.actions.MarkObjectAction");
private final String MARK_TEXT = ActionsBundle.message("action.Debugger.MarkObject.text");
private final String UNMARK_TEXT = ActionsBundle.message("action.Debugger.MarkObject.unmark.text");
@@ -35,32 +50,160 @@ public class MarkObjectAction extends DebuggerAction {
final ValueDescriptorImpl valueDescriptor = ((ValueDescriptorImpl)descriptor);
final DebuggerContextImpl debuggerContext = tree.getDebuggerContext();
final ValueMarkup markup = valueDescriptor.getMarkup(debuggerContext.getDebugProcess());
try {
if (markup != null) {
valueDescriptor.setMarkup(debuggerContext.getDebugProcess(), null);
final DebugProcessImpl debugProcess = debuggerContext.getDebugProcess();
final ValueMarkup markup = valueDescriptor.getMarkup(debugProcess);
debugProcess.getManagerThread().invoke(new DebuggerContextCommandImpl(debuggerContext) {
public Priority getPriority() {
return Priority.HIGH;
}
else {
final ValueMarkup valueMarkup = ObjectMarkupPropertiesDialog.chooseMarkup(valueDescriptor.getName());
if (valueMarkup != null) {
valueDescriptor.setMarkup(debuggerContext.getDebugProcess(), valueMarkup);
public void threadAction() {
boolean sessionRefreshNeeded = true;
try {
if (markup != null) {
valueDescriptor.setMarkup(debugProcess, null);
}
else {
final Value value = valueDescriptor.getValue();
final Map<Long, ValueMarkup> additionalMarkup = suggestMarkup(debugProcess, value);
ValueMarkup valueMarkup = null;
if (value instanceof ObjectReference) {
valueMarkup = additionalMarkup.get(((ObjectReference)value).uniqueID());
}
if (valueMarkup == null) {
valueMarkup = new ValueMarkup(valueDescriptor.getName(), Color.RED);
}
final Ref<Pair<ValueMarkup,Boolean>> result = new Ref<Pair<ValueMarkup, Boolean>>(null);
try {
final ValueMarkup _suggestion = valueMarkup;
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
result.set(ObjectMarkupPropertiesDialog.chooseMarkup(_suggestion, additionalMarkup));
}
});
}
catch (InterruptedException ignored) {
}
catch (InvocationTargetException e) {
LOG.error(e);
}
final Pair<ValueMarkup, Boolean> pair = result.get();
if (pair != null) {
valueDescriptor.setMarkup(debugProcess, pair.first);
if (pair.second) {
final Map<Long, ValueMarkup> map = NodeDescriptorImpl.getMarkupMap(debugProcess);
if (map != null) {
for (Map.Entry<Long,ValueMarkup> entry : additionalMarkup.entrySet()) {
final Long key = entry.getKey();
if (!map.containsKey(key)) {
map.put(key, entry.getValue());
}
}
}
}
}
else {
sessionRefreshNeeded = false;
}
}
}
else {
return;
finally {
final boolean _sessionRefreshNeeded = sessionRefreshNeeded;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
tree.restoreState(node);
node.labelChanged();
if (_sessionRefreshNeeded) {
final DebuggerSession session = debuggerContext.getDebuggerSession();
if (session != null) {
session.refresh(true);
}
}
}
});
}
}
}
finally {
tree.restoreState(node);
node.labelChanged();
}
final DebuggerSession session = debuggerContext.getDebuggerSession();
if (session != null) {
session.refresh(true);
}
});
}
private static Map<Long, ValueMarkup> suggestMarkup(DebugProcessImpl debugProcess, Value value) {
if (!(value instanceof ObjectReference)) {
return Collections.emptyMap();
}
final ObjectReference objRef = (ObjectReference)value;
if (objRef instanceof ArrayReference || objRef instanceof ClassObjectReference || objRef instanceof ThreadReference || objRef instanceof ThreadGroupReference || objRef instanceof ClassLoaderReference) {
return Collections.emptyMap();
}
final Map<Long, ValueMarkup> result = new HashMap<Long, ValueMarkup>();
if (debugProcess.getVirtualMachineProxy().canGetInstanceInfo()) {
for (ObjectReference ref : getReferringObjects(objRef)) {
if (!(ref instanceof ClassObjectReference)) {
// consider references from statisc fields only
continue;
}
final ReferenceType refType = ((ClassObjectReference)ref).reflectedType();
if (!refType.isAbstract()) {
continue;
}
for (Field field : refType.fields()) {
if (!(field.isStatic() && field.isFinal())) {
continue;
}
final Value fieldValue = refType.getValue(field);
if (!(fieldValue instanceof ObjectReference)) {
continue;
}
final long id = ((ObjectReference)fieldValue).uniqueID();
final ValueMarkup markup = result.get(id);
final String fieldName = field.name();
final Color autoMarkupColor = ValueMarkup.getAutoMarkupColor();
if (markup == null) {
result.put(id, new ValueMarkup(fieldName, autoMarkupColor, createMarkupTooltipText(null, refType, fieldName)));
}
else {
final String currentText = markup.getText();
if (!currentText.contains(fieldName)) {
final String currentTooltip = markup.getToolTipText();
final String tooltip = createMarkupTooltipText(currentTooltip, refType, fieldName);
result.put(id, new ValueMarkup(currentText + ", " + fieldName, autoMarkupColor, tooltip));
}
}
}
}
}
return result;
}
private static List<ObjectReference> getReferringObjects(ObjectReference value) {
// invoke the following method using Reflection in order to remain compilable on jdk 1.5
// java.util.List<com.sun.jdi.ObjectReference> referringObjects(long l);
try {
final java.lang.reflect.Method apiMethod = ObjectReference.class.getMethod("referringObjects", long.class);
return (List<ObjectReference>)apiMethod.invoke(value, ValueMarkup.AUTO_MARKUP_REFERRING_OBJECTS_LIMIT);
}
catch (IllegalAccessException e) {
LOG.error(e); // should not happen
}
catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
catch (NoSuchMethodException ignored) {
}
return Collections.emptyList();
}
private static String createMarkupTooltipText(String prefix, ReferenceType refType, String fieldName) {
final StringBuilder builder = new StringBuilder();
if (prefix == null) {
builder.append("Value referenced from:");
}
else {
builder.append(prefix);
}
return builder.append("<br><b>").append(refType.name()).append(".").append(fieldName).append("</b>").toString();
}
public void update(AnActionEvent e) {
boolean enable = false;
String text = MARK_TEXT;
@@ -79,4 +222,4 @@ public class MarkObjectAction extends DebuggerAction {
presentation.setVisible(enable);
presentation.setText(text);
}
}
}
@@ -7,11 +7,14 @@ package com.intellij.debugger.actions;
import com.intellij.debugger.ui.tree.ValueMarkup;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.FixedSizeButton;
import com.intellij.openapi.ui.ex.MultiLineLabel;
import com.intellij.openapi.util.Pair;
import com.intellij.ui.ColorChooser;
import com.intellij.ui.DocumentAdapter;
import com.intellij.ui.SimpleColoredComponent;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.util.Alarm;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
@@ -19,6 +22,9 @@ import javax.swing.event.DocumentEvent;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.Map;
/**
* @author Eugene Zhuravlev
@@ -26,27 +32,35 @@ import java.awt.event.ActionListener;
*/
public class ObjectMarkupPropertiesDialog extends DialogWrapper {
private final JTextField myTextMarkupField;
private final JCheckBox myCbMarkAdditionalFields;
private final SimpleColoredComponent myColorSample;
private SimpleTextAttributes myAttributes;
private final Alarm myUpdateAlarm;
private static final int UPDATE_DELAY = 200;
private final Map<Long, ValueMarkup> myAdditional;
private static Boolean ourMarkCbSavedState;
public ObjectMarkupPropertiesDialog(final String suggestedName) {
public ObjectMarkupPropertiesDialog(@NotNull final ValueMarkup suggestion, @NotNull Map<Long, ValueMarkup> additional) {
super(true);
myAdditional = additional;
setTitle("Select object label");
setModal(true);
myTextMarkupField = new JTextField(30);
myCbMarkAdditionalFields = new JCheckBox("Mark values referenced from constant fields", ourMarkCbSavedState == null? additional.size() > 0 : ourMarkCbSavedState);
myCbMarkAdditionalFields.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
ourMarkCbSavedState = myCbMarkAdditionalFields.isSelected();
}
});
myColorSample = new SimpleColoredComponent();
myAttributes = createAttributes(Color.RED);
myUpdateAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
if (suggestedName != null) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
myTextMarkupField.setText(suggestedName.trim());
updateLabelSample(0);
}
});
}
myAttributes = createAttributes(suggestion.getColor());
SwingUtilities.invokeLater(new Runnable() {
public void run() {
myTextMarkupField.setText(suggestion.getText().trim());
updateLabelSample(0);
}
});
init();
}
@@ -62,20 +76,30 @@ public class ObjectMarkupPropertiesDialog extends DialogWrapper {
@Nullable
protected JComponent createCenterPanel() {
final JPanel mainPanel = new JPanel(new GridBagLayout());
mainPanel.add(new JLabel("Label:"), new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
mainPanel.add(new JLabel("Label:"), new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
mainPanel.add(myTextMarkupField, new GridBagConstraints(1, GridBagConstraints.RELATIVE, 2, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
final JPanel samplePanel = new JPanel(new BorderLayout());
samplePanel.add(myColorSample, BorderLayout.CENTER);
samplePanel.setBorder(BorderFactory.createEtchedBorder());
final FixedSizeButton chooseColorButton = new FixedSizeButton(samplePanel);
mainPanel.add(new JLabel("Preview: "), new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 0.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 0, 0, 0), 0, 0));
mainPanel.add(samplePanel, new GridBagConstraints(1, GridBagConstraints.RELATIVE, 1, 1, 1.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(5, 0, 0, 0), 0, 0));
mainPanel.add(chooseColorButton, new GridBagConstraints(2, GridBagConstraints.RELATIVE, 1, 1, 0.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 0, 0, 0), 0, 0));
final boolean shouldShowCheckbox = myAdditional.size() > 0;
double weighty = shouldShowCheckbox? 0.0 : 1.0;
mainPanel.add(new JLabel("Preview: "), new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 0.0, weighty, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 0, 0, 0), 0, 0));
mainPanel.add(samplePanel, new GridBagConstraints(1, GridBagConstraints.RELATIVE, 1, 1, 1.0, weighty, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(5, 0, 0, 0), 0, 0));
mainPanel.add(chooseColorButton, new GridBagConstraints(2, GridBagConstraints.RELATIVE, 1, 1, 0.0, weighty, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 0, 0, 0), 0, 0));
if (shouldShowCheckbox) {
final JPanel panel = new JPanel(new BorderLayout());
panel.add(new MultiLineLabel(
"The value is referenced by a constant field of an abstract class.\nIDEA could mark values referenced from this class with the names of referencing fields."
), BorderLayout.CENTER);
panel.add(myCbMarkAdditionalFields, BorderLayout.SOUTH);
mainPanel.add(panel, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 3, 1, 0.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(10, 0, 0, 0), 0, 0));
}
myTextMarkupField.getDocument().addDocumentListener(new DocumentAdapter() {
protected void textChanged(final DocumentEvent e) {
@@ -103,13 +127,13 @@ public class ObjectMarkupPropertiesDialog extends DialogWrapper {
}, updateDelay);
}
public static ValueMarkup chooseMarkup(final String suggestedName) {
final ObjectMarkupPropertiesDialog dialog = new ObjectMarkupPropertiesDialog(suggestedName);
public static Pair<ValueMarkup,Boolean> chooseMarkup(ValueMarkup suggestion, Map<Long, ValueMarkup> additionalMarkup) {
final ObjectMarkupPropertiesDialog dialog = new ObjectMarkupPropertiesDialog(suggestion, additionalMarkup);
dialog.show();
if (dialog.isOK()) {
final String text = dialog.myTextMarkupField.getText().trim();
final Color color = dialog.myAttributes.getFgColor();
return text.length() > 0? new ValueMarkup(text, color) : null;
return text.length() > 0? new Pair<ValueMarkup, Boolean>(new ValueMarkup(text, color, suggestion.getToolTipText()), dialog.myCbMarkAdditionalFields.isSelected()) : null;
}
return null;
}
@@ -83,7 +83,7 @@ public class DebuggerTreeBase extends DnDAwareTree implements Disposable {
return minChar;
}
private JComponent createTipContent(String tipText) {
private JComponent createTipContent(String tipText, DebuggerTreeNodeImpl node) {
final JToolTip tooltip = new JToolTip();
if (tipText == null) {
@@ -98,12 +98,21 @@ public class DebuggerTreeBase extends DnDAwareTree implements Disposable {
//noinspection HardCodedStringLiteral
final StringBuffer tipBuilder = new StringBuffer();
try {
final StringTokenizer tokenizer = new StringTokenizer(tipText, "\n");
final String markupText = node.getMarkupTooltipText();
if (markupText != null) {
tipBuilder.append(markupText);
}
while (tokenizer.hasMoreElements()) {
final String each = tokenizer.nextElement();
tipBuilder.append(JDOMUtil.legalizeText(each));
tipBuilder.append("<br>");
if (tipText.length() > 0) {
tipBuilder.append("<br><br>").append("Value=");
final StringTokenizer tokenizer = new StringTokenizer(tipText, "\n");
while (tokenizer.hasMoreElements()) {
final String each = tokenizer.nextElement();
tipBuilder.append(JDOMUtil.legalizeText(each));
tipBuilder.append("<br>");
}
}
tooltip.setTipText(UIUtil.toHtml(tipBuilder.toString(), 0));
@@ -162,7 +171,7 @@ public class DebuggerTreeBase extends DnDAwareTree implements Disposable {
return null;
}
final JComponent tipContent = createTipContent(toolTipText);
final JComponent tipContent = createTipContent(toolTipText, node);
final JScrollPane scrollPane = new JScrollPane(tipContent);
scrollPane.setBorder(null);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
@@ -260,7 +269,7 @@ public class DebuggerTreeBase extends DnDAwareTree implements Disposable {
}
}
}
return null;
return node.getMarkupTooltipText() != null? "" : null;
}
@Nullable
@@ -12,8 +12,8 @@ import com.intellij.ui.ColoredTreeCellRenderer;
import com.intellij.ui.SimpleColoredText;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.util.Icons;
import com.intellij.xdebugger.ui.DebuggerIcons;
import com.intellij.xdebugger.impl.ui.XDebuggerUIConstants;
import com.intellij.xdebugger.ui.DebuggerIcons;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
@@ -125,7 +125,7 @@ public class DebuggerTreeRenderer extends ColoredTreeCellRenderer {
if (descriptor instanceof ValueDescriptor) {
final ValueMarkup markup = ((ValueDescriptor)descriptor).getMarkup(debuggerContext.getDebugProcess());
if (markup != null) {
descriptorText.append(markup.getText(), new SimpleTextAttributes(SimpleTextAttributes.STYLE_BOLD, markup.getColor()));
descriptorText.append("[" + markup.getText() + "] ", new SimpleTextAttributes(SimpleTextAttributes.STYLE_BOLD, markup.getColor()));
}
}
@@ -12,6 +12,8 @@ import com.intellij.debugger.ui.impl.tree.TreeBuilder;
import com.intellij.debugger.ui.impl.tree.TreeBuilderNode;
import com.intellij.debugger.ui.tree.DebuggerTreeNode;
import com.intellij.debugger.ui.tree.NodeDescriptor;
import com.intellij.debugger.ui.tree.ValueDescriptor;
import com.intellij.debugger.ui.tree.ValueMarkup;
import com.intellij.debugger.ui.tree.render.DescriptorLabelListener;
import com.intellij.debugger.ui.tree.render.NodeRenderer;
import com.intellij.openapi.application.ApplicationManager;
@@ -19,6 +21,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.ui.SimpleColoredText;
import com.intellij.util.containers.HashMap;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.Map;
@@ -26,6 +29,7 @@ import java.util.Map;
public class DebuggerTreeNodeImpl extends TreeBuilderNode implements DebuggerTreeNode{
private Icon myIcon;
private SimpleColoredText myText;
private String myMarkupTooltipText;
private final DebuggerTree myTree;
private final Map myProperties = new HashMap();
@@ -66,7 +70,15 @@ public class DebuggerTreeNodeImpl extends TreeBuilderNode implements DebuggerTre
private void updateCaches() {
final NodeDescriptorImpl descriptor = getDescriptor();
myIcon = DebuggerTreeRenderer.getDescriptorIcon(descriptor);
myText = DebuggerTreeRenderer.getDescriptorText(getTree().getDebuggerContext(), descriptor, false);
final DebuggerContextImpl context = getTree().getDebuggerContext();
myText = DebuggerTreeRenderer.getDescriptorText(context, descriptor, false);
if (descriptor instanceof ValueDescriptor) {
final ValueMarkup markup = ((ValueDescriptor)descriptor).getMarkup(context.getDebugProcess());
myMarkupTooltipText = markup != null? markup.getToolTipText() : null;
}
else {
myMarkupTooltipText = null;
}
}
public Icon getIcon() {
@@ -77,6 +89,11 @@ public class DebuggerTreeNodeImpl extends TreeBuilderNode implements DebuggerTre
return myText;
}
@Nullable
public String getMarkupTooltipText() {
return myMarkupTooltipText;
}
public void clear() {
removeAllChildren();
myIcon = null;
@@ -139,7 +139,7 @@ public abstract class NodeDescriptorImpl implements NodeDescriptor {
}
@Nullable
protected static Map<Long, ValueMarkup> getMarkupMap(final DebugProcess process) {
public static Map<Long, ValueMarkup> getMarkupMap(final DebugProcess process) {
if (process == null) {
return null;
}
@@ -407,7 +407,12 @@ public abstract class ValueDescriptorImpl extends NodeDescriptorImpl implements
final Map<Long, ValueMarkup> map = getMarkupMap(debugProcess);
if (map != null) {
final long id = ((ObjectReference)value).uniqueID();
map.put(id, markup);
if (markup != null) {
map.put(id, markup);
}
else {
map.remove(id);
}
}
}
}
@@ -4,7 +4,11 @@
*/
package com.intellij.debugger.ui.tree;
import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.markup.TextAttributes;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
@@ -13,12 +17,21 @@ import java.awt.*;
* Date: Jan 27, 2007
*/
public class ValueMarkup {
public static final long AUTO_MARKUP_REFERRING_OBJECTS_LIMIT = 100L; // todo: some reasonable limit
private final String myText;
private final Color myColor;
@Nullable
private final String myToolTipText;
public ValueMarkup(final String text, final Color color) {
myText = "[" + text + "] ";
this(text, color, null);
}
public ValueMarkup(final String text, final Color color, String toolTipText) {
myText = text;
myColor = color;
myToolTipText = toolTipText;
}
@NotNull
@@ -29,4 +42,15 @@ public class ValueMarkup {
public Color getColor() {
return myColor;
}
@Nullable
public String getToolTipText() {
return myToolTipText;
}
public static Color getAutoMarkupColor() {
final EditorColorsManager manager = EditorColorsManager.getInstance();
final TextAttributes textAttributes = manager.getGlobalScheme().getAttributes(HighlightInfoType.STATIC_FIELD.getAttributesKey());
return textAttributes.getForegroundColor();
}
}