5737592904ede6b85a579638299a0c6d276b77ec
[idea/community.git] / python / pydevSrc / com / jetbrains / python / debugger / PyDebugValue.java
1 package com.jetbrains.python.debugger;
2
3 import com.google.common.base.Strings;
4 import com.intellij.icons.AllIcons;
5 import com.intellij.openapi.application.ApplicationManager;
6 import com.intellij.openapi.diagnostic.Logger;
7 import com.intellij.xdebugger.frame.*;
8 import com.jetbrains.python.debugger.pydev.PyVariableLocator;
9 import org.jetbrains.annotations.NotNull;
10 import org.jetbrains.annotations.Nullable;
11
12 import javax.swing.*;
13 import java.util.regex.Matcher;
14 import java.util.regex.Pattern;
15
16 // todo: load long lists by parts
17 // todo: null modifier for modify modules, class objects etc.
18 public class PyDebugValue extends XNamedValue {
19   private static final Logger LOG = Logger.getInstance("#com.jetbrains.python.pydev.PyDebugValue");
20   public static final int MAX_VALUE = 256;
21
22   public static final String RETURN_VALUES_PREFIX = "__pydevd_ret_val_dict";
23
24   private String myTempName = null;
25   private final String myType;
26   private final String myTypeQualifier;
27   private final String myValue;
28   private final boolean myContainer;
29   private final boolean myIsReturnedVal;
30   private final boolean myIsIPythonHidden;
31   private final PyDebugValue myParent;
32   private String myId = null;
33
34   private final PyFrameAccessor myFrameAccessor;
35
36   private PyVariableLocator myVariableLocator;
37
38   private final boolean myErrorOnEval;
39
40   public PyDebugValue(@NotNull final String name, final String type, String typeQualifier, final String value, final boolean container,
41                       boolean isReturnedVal, boolean isIPythonHidden, boolean errorOnEval, final PyFrameAccessor frameAccessor) {
42     this(name, type, typeQualifier, value, container, isReturnedVal, isIPythonHidden, errorOnEval, null, frameAccessor);
43   }
44
45   public PyDebugValue(@NotNull final String name, final String type, String typeQualifier, final String value, final boolean container,
46                       boolean isReturnedVal, boolean isIPythonHidden, boolean errorOnEval, final PyDebugValue parent,
47                       final PyFrameAccessor frameAccessor) {
48     super(name);
49     myType = type;
50     myTypeQualifier = Strings.isNullOrEmpty(typeQualifier) ? null : typeQualifier;
51     myValue = value;
52     myContainer = container;
53     myIsReturnedVal = isReturnedVal;
54     myIsIPythonHidden = isIPythonHidden;
55     myErrorOnEval = errorOnEval;
56     myParent = parent;
57     myFrameAccessor = frameAccessor;
58   }
59
60   public String getTempName() {
61     return myTempName != null ? myTempName : myName;
62   }
63
64   public void setTempName(String tempName) {
65     myTempName = tempName;
66   }
67
68   public String getType() {
69     return myType;
70   }
71
72   public String getValue() {
73     return myValue;
74   }
75
76   public boolean isContainer() {
77     return myContainer;
78   }
79
80   public boolean isReturnedVal() {
81     return myIsReturnedVal;
82   }
83
84   public boolean isIPythonHidden() {
85     return myIsIPythonHidden;
86   }
87
88   public boolean isErrorOnEval() {
89     return myErrorOnEval;
90   }
91   
92   public PyDebugValue setParent(@Nullable PyDebugValue parent) {
93     return new PyDebugValue(myName, myType, myTypeQualifier, myValue, myContainer, myIsReturnedVal, myIsIPythonHidden, myErrorOnEval,
94                             parent, myFrameAccessor);
95   }
96
97   public PyDebugValue getParent() {
98     return myParent;
99   }
100
101   public PyDebugValue getTopParent() {
102     return myParent == null ? this : myParent.getTopParent();
103   }
104
105   @Override
106   public String getEvaluationExpression() {
107     StringBuilder stringBuilder = new StringBuilder();
108     buildExpression(stringBuilder);
109     return stringBuilder.toString();
110   }
111
112   void buildExpression(StringBuilder result) {
113     if (myParent == null) {
114       result.append(getTempName());
115     }
116     else {
117       myParent.buildExpression(result);
118       if (("dict".equals(myParent.getType()) || "list".equals(myParent.getType()) || "tuple".equals(myParent.getType())) &&
119           !isLen(myName)) {
120         result.append('[').append(removeLeadingZeros(removeId(myName))).append(']');
121       }
122       else if (("set".equals(myParent.getType())) && !isLen(myName)) {
123         //set doesn't support indexing
124       }
125       else if (isLen(myName)) {
126         result.append('.').append(myName).append("()");
127       }
128       else if (("ndarray".equals(myParent.getType()) || "matrix".equals(myParent.getType())) && myName.startsWith("[")) {
129         result.append(removeLeadingZeros(myName));
130       }
131       else {
132         result.append('.').append(myName);
133       }
134     }
135   }
136
137   public String getFullName() {
138     return wrapWithPrefix(getName());
139   }
140
141   private static String removeId(@NotNull String name) {
142     if (name.indexOf('(') != -1) {
143       name = name.substring(0, name.indexOf('(')).trim();
144     }
145
146     return name;
147   }
148
149   private static String removeLeadingZeros(@NotNull String name) {
150     //bugs.python.org/issue15254: "0" prefix for octal
151     while (name.length() > 1 && name.startsWith("0")) {
152       name = name.substring(1);
153     }
154     return name;
155   }
156
157   private static boolean isLen(String name) {
158     return "__len__".equals(name);
159   }
160
161   private static boolean isCollection(@NotNull PyDebugValue parent) {
162     String type = parent.getType();
163     return type.equals("dict") || type.equals("list");
164   }
165
166   private static String getChildNamePresentation(@NotNull PyDebugValue parent, @NotNull String childName) {
167     if (isCollection(parent)) {
168       return "[".concat(removeId(childName)).concat("]");
169     }
170     else {
171       return ".".concat(childName);
172     }
173   }
174
175   private String wrapWithPrefix(String name) {
176     if (isReturnedVal()) {
177       // return values are saved in dictionary on Python side, so the variable's name should be transformed
178       return RETURN_VALUES_PREFIX + "[\"" + name + "\"]";
179     }
180     else {
181       return name;
182     }
183   }
184
185   private String getFullTreeName() {
186     String result = "";
187     String curNodeName = myName;
188     PyDebugValue parent = myParent;
189     while (parent != null) {
190       result = getChildNamePresentation(parent, curNodeName).concat(result);
191       curNodeName = parent.getName();
192       parent = parent.getParent();
193     }
194     return wrapWithPrefix(curNodeName.concat(result));
195   }
196
197   @Override
198   public void computePresentation(@NotNull XValueNode node, @NotNull XValuePlace place) {
199     String value = PyTypeHandler.format(this);
200
201     if (value.length() >= MAX_VALUE) {
202       node.setFullValueEvaluator(new PyFullValueEvaluator(myFrameAccessor, getFullTreeName()));
203       value = value.substring(0, MAX_VALUE);
204     }
205
206     node.setPresentation(getValueIcon(), myType, value, myContainer);
207   }
208
209   @Override
210   public void computeChildren(@NotNull final XCompositeNode node) {
211     if (node.isObsolete()) return;
212     ApplicationManager.getApplication().executeOnPooledThread(() -> {
213       if (myFrameAccessor == null) return;
214
215       try {
216         final XValueChildrenList values = myFrameAccessor.loadVariable(this);
217         if (!node.isObsolete()) {
218           node.addChildren(values, true);
219         }
220       }
221       catch (PyDebuggerException e) {
222         if (!node.isObsolete()) {
223           node.setErrorMessage("Unable to display children:" + e.getMessage());
224         }
225         LOG.warn(e);
226       }
227     });
228   }
229
230   @Override
231   public XValueModifier getModifier() {
232     return new PyValueModifier(myFrameAccessor, this);
233   }
234
235   private Icon getValueIcon() {
236     if (!myContainer) {
237       return AllIcons.Debugger.Db_primitive;
238     }
239     else if ("list".equals(myType) || "tuple".equals(myType)) {
240       return AllIcons.Debugger.Db_array;
241     }
242     else {
243       return AllIcons.Debugger.Value;
244     }
245   }
246   
247   public PyDebugValue setName(String newName) {
248     PyDebugValue value = new PyDebugValue(newName, myType, myTypeQualifier, myValue, myContainer, myIsReturnedVal, myIsIPythonHidden,
249                                           myErrorOnEval, myParent, myFrameAccessor);
250     value.setTempName(myTempName);
251     return value;
252   }
253
254   @Nullable
255   @Override
256   public XReferrersProvider getReferrersProvider() {
257     if (myFrameAccessor.getReferrersLoader() != null) {
258       return new XReferrersProvider() {
259         @Override
260         public XValue getReferringObjectsValue() {
261           return new PyReferringObjectsValue(PyDebugValue.this);
262         }
263       };
264     } else {
265       return null;
266     }
267   }
268
269   public PyFrameAccessor getFrameAccessor() {
270     return myFrameAccessor;
271   }
272
273   public PyVariableLocator getVariableLocator() {
274     return myVariableLocator;
275   }
276
277   public void setVariableLocator(PyVariableLocator variableLocator) {
278     myVariableLocator = variableLocator;
279   }
280
281   public String getId() {
282     return myId;
283   }
284
285   public void setId(String id) {
286     myId = id;
287   }
288
289   @Override
290   public boolean canNavigateToSource() {
291     return true;
292   }
293
294   @Override
295   public void computeSourcePosition(@NotNull XNavigatable navigatable) {
296     if (myParent == null) {
297       navigatable.setSourcePosition(myFrameAccessor.getSourcePositionForName(myName, null));
298     }
299     else
300     {
301       navigatable.setSourcePosition(myFrameAccessor.getSourcePositionForName(myName, myParent.getDeclaringType()));
302     }
303   }
304
305   @Override
306   public boolean canNavigateToTypeSource() {
307     return true;
308   }
309
310   private static final  Pattern IS_TYPE_DECLARATION = Pattern.compile("<(?:class|type)\\s*'(?<TYPE>.*?)'>");
311   @Override
312   public void computeTypeSourcePosition(@NotNull XNavigatable navigatable) {
313
314     String lookupType = getDeclaringType();
315     navigatable.setSourcePosition(myFrameAccessor.getSourcePositionForType(lookupType));
316   }
317
318   protected final String getDeclaringType() {
319     String lookupType = getQualifiedType();
320     if (!Strings.isNullOrEmpty(myValue))
321     {
322       Matcher matcher = IS_TYPE_DECLARATION.matcher(myValue);
323       if (matcher.matches())
324       {
325         lookupType = matcher.group("TYPE");
326       }
327     }
328     return lookupType;
329   }
330
331   public String getQualifiedType() {
332     if (Strings.isNullOrEmpty(myType))
333       return null;
334     return (myTypeQualifier == null) ? myType : (myTypeQualifier + "." + myType);
335   }
336
337   public String getTypeQualifier() {
338     return myTypeQualifier;
339   }
340 }