PY-21230 Open numeric container viewers from "View" hyperlink
[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     setFullValueEvaluator(node, value);
201     if (value.length() >= MAX_VALUE) {
202       value = value.substring(0, MAX_VALUE);
203     }
204     node.setPresentation(getValueIcon(), myType, value, myContainer);
205   }
206
207   private boolean isDataFrame() {
208     return "DataFrame".equals(myType);
209   }
210
211   private boolean isNdarray() {
212     return "ndarray".equals(myType);
213   }
214
215   private void setFullValueEvaluator(XValueNode node, String value) {
216     String treeName = getFullTreeName();
217     if (!isDataFrame() && !isNdarray()) {
218       if (value.length() >= MAX_VALUE) {
219         node.setFullValueEvaluator(new PyFullValueEvaluator(myFrameAccessor, treeName));
220       }
221       return;
222     }
223     String linkText = "...View as " + (isDataFrame() ? "DataFrame" : "Array");
224     node.setFullValueEvaluator(new PyNumericContainerValueEvaluator(linkText, myFrameAccessor, treeName));
225   }
226
227   @Override
228   public void computeChildren(@NotNull final XCompositeNode node) {
229     if (node.isObsolete()) return;
230     ApplicationManager.getApplication().executeOnPooledThread(() -> {
231       if (myFrameAccessor == null) return;
232
233       try {
234         final XValueChildrenList values = myFrameAccessor.loadVariable(this);
235         if (!node.isObsolete()) {
236           node.addChildren(values, true);
237         }
238       }
239       catch (PyDebuggerException e) {
240         if (!node.isObsolete()) {
241           node.setErrorMessage("Unable to display children:" + e.getMessage());
242         }
243         LOG.warn(e);
244       }
245     });
246   }
247
248   @Override
249   public XValueModifier getModifier() {
250     return new PyValueModifier(myFrameAccessor, this);
251   }
252
253   private Icon getValueIcon() {
254     if (!myContainer) {
255       return AllIcons.Debugger.Db_primitive;
256     }
257     else if ("list".equals(myType) || "tuple".equals(myType)) {
258       return AllIcons.Debugger.Db_array;
259     }
260     else {
261       return AllIcons.Debugger.Value;
262     }
263   }
264
265   public PyDebugValue setName(String newName) {
266     PyDebugValue value = new PyDebugValue(newName, myType, myTypeQualifier, myValue, myContainer, myIsReturnedVal, myIsIPythonHidden,
267                                           myErrorOnEval, myParent, myFrameAccessor);
268     value.setTempName(myTempName);
269     return value;
270   }
271
272   @Nullable
273   @Override
274   public XReferrersProvider getReferrersProvider() {
275     if (myFrameAccessor.getReferrersLoader() != null) {
276       return new XReferrersProvider() {
277         @Override
278         public XValue getReferringObjectsValue() {
279           return new PyReferringObjectsValue(PyDebugValue.this);
280         }
281       };
282     } else {
283       return null;
284     }
285   }
286
287   public PyFrameAccessor getFrameAccessor() {
288     return myFrameAccessor;
289   }
290
291   public PyVariableLocator getVariableLocator() {
292     return myVariableLocator;
293   }
294
295   public void setVariableLocator(PyVariableLocator variableLocator) {
296     myVariableLocator = variableLocator;
297   }
298
299   public String getId() {
300     return myId;
301   }
302
303   public void setId(String id) {
304     myId = id;
305   }
306
307   @Override
308   public boolean canNavigateToSource() {
309     return true;
310   }
311
312   @Override
313   public void computeSourcePosition(@NotNull XNavigatable navigatable) {
314     if (myParent == null) {
315       navigatable.setSourcePosition(myFrameAccessor.getSourcePositionForName(myName, null));
316     }
317     else
318     {
319       navigatable.setSourcePosition(myFrameAccessor.getSourcePositionForName(myName, myParent.getDeclaringType()));
320     }
321   }
322
323   @Override
324   public boolean canNavigateToTypeSource() {
325     return true;
326   }
327
328   private static final  Pattern IS_TYPE_DECLARATION = Pattern.compile("<(?:class|type)\\s*'(?<TYPE>.*?)'>");
329   @Override
330   public void computeTypeSourcePosition(@NotNull XNavigatable navigatable) {
331
332     String lookupType = getDeclaringType();
333     navigatable.setSourcePosition(myFrameAccessor.getSourcePositionForType(lookupType));
334   }
335
336   protected final String getDeclaringType() {
337     String lookupType = getQualifiedType();
338     if (!Strings.isNullOrEmpty(myValue))
339     {
340       Matcher matcher = IS_TYPE_DECLARATION.matcher(myValue);
341       if (matcher.matches())
342       {
343         lookupType = matcher.group("TYPE");
344       }
345     }
346     return lookupType;
347   }
348
349   public String getQualifiedType() {
350     if (Strings.isNullOrEmpty(myType))
351       return null;
352     return (myTypeQualifier == null) ? myType : (myTypeQualifier + "." + myType);
353   }
354
355   public String getTypeQualifier() {
356     return myTypeQualifier;
357   }
358 }