always use watch icon when watches in variables enabled
[idea/community.git] / java / debugger / impl / src / com / intellij / debugger / ui / impl / DebuggerTreeRenderer.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.debugger.ui.impl;
17
18 import com.intellij.debugger.engine.evaluation.EvaluateException;
19 import com.intellij.debugger.impl.DebuggerContextImpl;
20 import com.intellij.debugger.impl.DebuggerUtilsEx;
21 import com.intellij.debugger.ui.impl.watch.*;
22 import com.intellij.debugger.ui.tree.ValueDescriptor;
23 import com.intellij.icons.AllIcons;
24 import com.intellij.ide.highlighter.JavaHighlightingColors;
25 import com.intellij.openapi.editor.colors.EditorColorsScheme;
26 import com.intellij.openapi.editor.markup.TextAttributes;
27 import com.intellij.openapi.util.text.StringUtil;
28 import com.intellij.ui.*;
29 import com.intellij.util.PlatformIcons;
30 import com.intellij.xdebugger.XDebugSession;
31 import com.intellij.xdebugger.XDebuggerManager;
32 import com.intellij.xdebugger.impl.XDebugSessionImpl;
33 import com.intellij.xdebugger.impl.ui.DebuggerUIUtil;
34 import com.intellij.xdebugger.impl.ui.XDebugSessionTab;
35 import com.intellij.xdebugger.impl.ui.XDebuggerUIConstants;
36 import com.intellij.xdebugger.impl.ui.tree.ValueMarkup;
37 import org.jetbrains.annotations.NotNull;
38 import org.jetbrains.annotations.Nullable;
39
40 import javax.swing.*;
41 import java.awt.*;
42
43 public class DebuggerTreeRenderer extends ColoredTreeCellRenderer {
44
45   private static final SimpleTextAttributes DEFAULT_ATTRIBUTES = new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, null);
46   private static final SimpleTextAttributes SPECIAL_NODE_ATTRIBUTES = new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, new JBColor(Color.lightGray, Gray._130));
47   private static final SimpleTextAttributes OBJECT_ID_HIGHLIGHT_ATTRIBUTES = new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, new JBColor(Color.lightGray, Gray._130));
48
49   public void customizeCellRenderer(@NotNull JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
50     final DebuggerTreeNodeImpl node = (DebuggerTreeNodeImpl) value;
51
52     if (node != null) {
53       final SimpleColoredText text = node.getText();
54       if (text != null) {
55         text.appendToComponent(this);
56       }
57       setIcon(node.getIcon());
58     }
59   }
60
61   @Nullable
62   public static Icon getDescriptorIcon(NodeDescriptorImpl descriptor) {
63     Icon nodeIcon = null;
64     if (descriptor instanceof ThreadGroupDescriptorImpl) {
65       nodeIcon = (((ThreadGroupDescriptorImpl)descriptor).isCurrent() ? AllIcons.Debugger.ThreadGroupCurrent : AllIcons.Debugger.ThreadGroup);
66     }
67     else if (descriptor instanceof ThreadDescriptorImpl) {
68       ThreadDescriptorImpl threadDescriptor = (ThreadDescriptorImpl)descriptor;
69       nodeIcon = threadDescriptor.getIcon();
70     }
71     else if (descriptor instanceof StackFrameDescriptorImpl) {
72       StackFrameDescriptorImpl stackDescriptor = (StackFrameDescriptorImpl)descriptor;
73       nodeIcon = stackDescriptor.getIcon();
74     }
75     else if (descriptor instanceof ValueDescriptorImpl) {
76       nodeIcon = getValueIcon((ValueDescriptorImpl)descriptor);
77     }
78     else if (descriptor instanceof MessageDescriptor) {
79       MessageDescriptor messageDescriptor = (MessageDescriptor)descriptor;
80       if (messageDescriptor.getKind() == MessageDescriptor.ERROR) {
81         nodeIcon = XDebuggerUIConstants.ERROR_MESSAGE_ICON;
82       }
83       else if (messageDescriptor.getKind() == MessageDescriptor.INFORMATION) {
84         nodeIcon = XDebuggerUIConstants.INFORMATION_MESSAGE_ICON;
85       }
86       else if (messageDescriptor.getKind() == MessageDescriptor.SPECIAL) {
87         nodeIcon = null;
88       }
89     }
90     else if (descriptor instanceof StaticDescriptorImpl) {
91       nodeIcon = AllIcons.Nodes.Static;
92     }
93
94     return nodeIcon;
95   }
96
97   public static Icon getValueIcon(ValueDescriptorImpl valueDescriptor) {
98     Icon nodeIcon;
99     if (valueDescriptor instanceof FieldDescriptorImpl) {
100       FieldDescriptorImpl fieldDescriptor = (FieldDescriptorImpl)valueDescriptor;
101       nodeIcon = PlatformIcons.FIELD_ICON;
102       if (fieldDescriptor.getField().isFinal()) {
103         nodeIcon = new LayeredIcon(nodeIcon, AllIcons.Nodes.FinalMark);
104       }
105       if (fieldDescriptor.isStatic()) {
106         nodeIcon = new LayeredIcon(nodeIcon, AllIcons.Nodes.StaticMark);
107       }
108     }
109     else if (valueDescriptor instanceof ThrownExceptionValueDescriptorImpl) {
110       nodeIcon = AllIcons.Nodes.ExceptionClass;
111     }
112     else if (valueDescriptor instanceof MethodReturnValueDescriptorImpl) {
113       nodeIcon = AllIcons.Debugger.WatchLastReturnValue;
114     }
115     else if (isParameter(valueDescriptor)) {
116       nodeIcon = PlatformIcons.PARAMETER_ICON;
117     }
118     else if (valueDescriptor.isEnumConstant()) {
119       nodeIcon = PlatformIcons.ENUM_ICON;
120     }
121     else if (valueDescriptor.isArray()) {
122       nodeIcon = AllIcons.Debugger.Db_array;
123     }
124     else if (valueDescriptor.isPrimitive()) {
125       nodeIcon = AllIcons.Debugger.Db_primitive;
126     }
127     else {
128       if (valueDescriptor instanceof WatchItemDescriptor) {
129         nodeIcon = AllIcons.Debugger.Watch;
130       }
131       else {
132         nodeIcon = AllIcons.Debugger.Value;
133       }
134     }
135
136     // if watches in variables enabled, always use watch icon
137     if (valueDescriptor instanceof WatchItemDescriptor && nodeIcon != AllIcons.Debugger.Watch) {
138       XDebugSession session = XDebuggerManager.getInstance(valueDescriptor.getProject()).getCurrentSession();
139       if (session != null) {
140         XDebugSessionTab tab = ((XDebugSessionImpl)session).getSessionTab();
141         if (tab != null && tab.isWatchesInVariables()) {
142           nodeIcon = AllIcons.Debugger.Watch;
143         }
144       }
145     }
146
147     final Icon valueIcon = valueDescriptor.getValueIcon();
148     if (nodeIcon != null && valueIcon != null) {
149       nodeIcon = new RowIcon(nodeIcon, valueIcon);
150     }
151     return nodeIcon;
152   }
153
154   private static boolean isParameter(ValueDescriptorImpl valueDescriptor) {
155     if (valueDescriptor instanceof LocalVariableDescriptorImpl) {
156       try {
157         return ((LocalVariableDescriptorImpl)valueDescriptor).getLocalVariable().getVariable().isArgument();
158       }
159       catch (EvaluateException ignored) {
160       }
161     }
162     else if (valueDescriptor instanceof ArgumentValueDescriptorImpl) {
163       return ((ArgumentValueDescriptorImpl)valueDescriptor).isParameter();
164     }
165     return false;
166   }
167
168   public static SimpleColoredText getDescriptorText(DebuggerContextImpl debuggerContext,
169                                                     NodeDescriptorImpl descriptor,
170                                                     EditorColorsScheme colorsScheme,
171                                                     boolean multiline) {
172     return getDescriptorText(debuggerContext, descriptor, colorsScheme, multiline, true);
173   }
174
175   public static SimpleColoredText getDescriptorText(final DebuggerContextImpl debuggerContext, NodeDescriptorImpl descriptor, boolean multiline) {
176     return getDescriptorText(debuggerContext, descriptor, DebuggerUIUtil.getColorScheme(null), multiline, true);
177   }
178
179   public static SimpleColoredText getDescriptorTitle(final DebuggerContextImpl debuggerContext, NodeDescriptorImpl descriptor) {
180     return getDescriptorText(debuggerContext, descriptor, DebuggerUIUtil.getColorScheme(null), false, false);
181   }
182
183   private static SimpleColoredText getDescriptorText(DebuggerContextImpl debuggerContext,
184                                                      NodeDescriptorImpl descriptor,
185                                                      EditorColorsScheme colorScheme,
186                                                      boolean multiline,
187                                                      boolean appendValue) {
188     SimpleColoredText descriptorText = new SimpleColoredText();
189
190     String text;
191     String nodeName;
192
193     if (descriptor == null) {
194       text = "";
195       nodeName = null;
196     }
197     else {
198       text = descriptor.getLabel();
199       nodeName = descriptor.getName();
200     }
201
202     if(text.equals(XDebuggerUIConstants.COLLECTING_DATA_MESSAGE)) {
203       descriptorText.append(XDebuggerUIConstants.COLLECTING_DATA_MESSAGE, XDebuggerUIConstants.COLLECTING_DATA_HIGHLIGHT_ATTRIBUTES);
204       return descriptorText;
205     }
206
207     if (descriptor instanceof ValueDescriptor) {
208       final ValueMarkup markup = ((ValueDescriptor)descriptor).getMarkup(debuggerContext.getDebugProcess());
209       if (markup != null) {
210         descriptorText.append("[" + markup.getText() + "] ", new SimpleTextAttributes(SimpleTextAttributes.STYLE_BOLD, markup.getColor()));
211       }
212     }
213
214     String[] strings = breakString(text, nodeName);
215
216     if (strings[0] != null) {
217       if (descriptor instanceof MessageDescriptor && ((MessageDescriptor)descriptor).getKind() == MessageDescriptor.SPECIAL) {
218         descriptorText.append(strings[0], SPECIAL_NODE_ATTRIBUTES);
219       }
220       else {
221         descriptorText.append(strings[0], DEFAULT_ATTRIBUTES);
222       }
223     }
224     if (strings[1] != null) {
225       descriptorText.append(strings[1], XDebuggerUIConstants.VALUE_NAME_ATTRIBUTES);
226     }
227     if (strings[2] != null) {
228       if (descriptor instanceof ValueDescriptorImpl) {
229         if(multiline && strings[2].indexOf('\n') >=0) {
230           strings = breakString(strings[2], "=");
231           if(strings[2] != null) {
232             strings[2] = strings[0] + strings[1] + "\n" + strings[2];
233           }
234         }
235
236
237         ValueDescriptorImpl valueDescriptor = (ValueDescriptorImpl)descriptor;
238         String valueLabel = valueDescriptor.getValueLabel();
239
240         strings = breakString(strings[2], valueLabel);
241         if (strings[0] != null) {
242           descriptorText.append(strings[0], DEFAULT_ATTRIBUTES);
243         }
244         if (appendValue && strings[1] != null) {
245           if(valueLabel != null && StringUtil.startsWithChar(valueLabel, '{') && valueLabel.indexOf('}') > 0 && !StringUtil.endsWithChar(valueLabel, '}')) {
246             int idx = valueLabel.indexOf('}');
247             String objectId = valueLabel.substring(0, idx + 1);
248             valueLabel = valueLabel.substring(idx + 1);
249             descriptorText.append(objectId, OBJECT_ID_HIGHLIGHT_ATTRIBUTES);
250           }
251
252           valueLabel =  DebuggerUtilsEx.truncateString(valueLabel);
253
254           final SimpleTextAttributes valueLabelAttribs;
255           if (valueDescriptor.isDirty()) {
256             valueLabelAttribs = XDebuggerUIConstants.CHANGED_VALUE_ATTRIBUTES;
257           }
258           else {
259             TextAttributes attributes = null;
260             if (valueDescriptor.isNull()){
261               attributes = colorScheme.getAttributes(JavaHighlightingColors.KEYWORD);
262             }
263             else if (valueDescriptor.isString()) {
264               attributes = colorScheme.getAttributes(JavaHighlightingColors.STRING);
265             }
266             valueLabelAttribs = attributes != null? SimpleTextAttributes.fromTextAttributes(attributes) : DEFAULT_ATTRIBUTES;
267           }
268
269           final EvaluateException exception = descriptor.getEvaluateException();
270           if(exception != null) {
271             final String errorMessage = exception.getMessage();
272             if(valueLabel.endsWith(errorMessage)) {
273               appendValueTextWithEscapesRendering(descriptorText, valueLabel.substring(0, valueLabel.length() - errorMessage.length()), valueLabelAttribs, colorScheme);
274               descriptorText.append(errorMessage, XDebuggerUIConstants.EXCEPTION_ATTRIBUTES);
275             }
276             else {
277               appendValueTextWithEscapesRendering(descriptorText, valueLabel, valueLabelAttribs, colorScheme);
278               descriptorText.append(errorMessage, XDebuggerUIConstants.EXCEPTION_ATTRIBUTES);
279             }
280           }
281           else {
282             if(valueLabel.equals(XDebuggerUIConstants.COLLECTING_DATA_MESSAGE)) {
283               descriptorText.append(XDebuggerUIConstants.COLLECTING_DATA_MESSAGE, XDebuggerUIConstants.COLLECTING_DATA_HIGHLIGHT_ATTRIBUTES);
284             }
285             else {
286               appendValueTextWithEscapesRendering(descriptorText, valueLabel, valueLabelAttribs, colorScheme);
287             }
288           }
289         }
290       }
291       else {
292         descriptorText.append(strings[2], DEFAULT_ATTRIBUTES);
293       }
294     }
295
296     return descriptorText;
297   }
298
299   private static void appendValueTextWithEscapesRendering(SimpleColoredText descriptorText,
300                                                           String valueText,
301                                                           SimpleTextAttributes attribs,
302                                                           EditorColorsScheme colorScheme) {
303     SimpleTextAttributes escapeAttribs = null;
304     final StringBuilder buf = new StringBuilder();
305     boolean slashFound = false;
306     for (int idx= 0; idx < valueText.length(); idx++) {
307       final char ch = valueText.charAt(idx);
308       if (slashFound) {
309         slashFound = false;
310         if (ch == '\\' || ch == '\"' || ch == 'b'|| ch == 't'|| ch == 'n'|| ch == 'f'|| ch == 'r' ) {
311           if (buf.length() > 0) {
312             descriptorText.append(buf.toString(), attribs);
313             buf.setLength(0);
314           }
315
316           if (escapeAttribs == null) { // lazy init
317             TextAttributes fromHighlighter = colorScheme.getAttributes(JavaHighlightingColors.VALID_STRING_ESCAPE);
318             if (fromHighlighter != null) {
319               escapeAttribs = SimpleTextAttributes.fromTextAttributes(fromHighlighter);
320             }
321             else {
322               escapeAttribs = DEFAULT_ATTRIBUTES.derive(SimpleTextAttributes.STYLE_BOLD, JBColor.GRAY, null, null);
323             }
324           }
325
326           if (ch != '\\' && ch != '\"') {
327             descriptorText.append("\\", escapeAttribs);
328           }
329           descriptorText.append(String.valueOf(ch), escapeAttribs);
330         }
331         else {
332           buf.append('\\').append(ch);
333         }
334       }
335       else {
336         if (ch == '\\') {
337           slashFound = true;
338         }
339         else {
340           buf.append(ch);
341         }
342       }
343     }
344     if (buf.length() > 0) {
345       descriptorText.append(buf.toString(), attribs);
346     }
347   }
348
349   private static String[] breakString(String source, String substr) {
350     if (substr != null && substr.length() > 0) {
351       int index = Math.max(source.indexOf(substr), 0);
352       String prefix = (index > 0) ? source.substring(0, index) : null;
353       index += substr.length();
354       String suffix = (index < source.length() - 1) ? source.substring(index) : null;
355       return new String[]{prefix, substr, suffix};
356     }
357     return new String[]{source, null, null};
358   }
359 }