1 package com.jetbrains.python.debugger;
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;
13 import java.util.regex.Matcher;
14 import java.util.regex.Pattern;
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;
22 public static final String RETURN_VALUES_PREFIX = "__pydevd_ret_val_dict";
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;
34 private final PyFrameAccessor myFrameAccessor;
36 private PyVariableLocator myVariableLocator;
38 private final boolean myErrorOnEval;
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);
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) {
50 myTypeQualifier = Strings.isNullOrEmpty(typeQualifier) ? null : typeQualifier;
52 myContainer = container;
53 myIsReturnedVal = isReturnedVal;
54 myIsIPythonHidden = isIPythonHidden;
55 myErrorOnEval = errorOnEval;
57 myFrameAccessor = frameAccessor;
60 public String getTempName() {
61 return myTempName != null ? myTempName : myName;
64 public void setTempName(String tempName) {
65 myTempName = tempName;
68 public String getType() {
72 public String getValue() {
76 public boolean isContainer() {
80 public boolean isReturnedVal() {
81 return myIsReturnedVal;
84 public boolean isIPythonHidden() {
85 return myIsIPythonHidden;
88 public boolean isErrorOnEval() {
92 public PyDebugValue setParent(@Nullable PyDebugValue parent) {
93 return new PyDebugValue(myName, myType, myTypeQualifier, myValue, myContainer, myIsReturnedVal, myIsIPythonHidden, myErrorOnEval,
94 parent, myFrameAccessor);
97 public PyDebugValue getParent() {
101 public PyDebugValue getTopParent() {
102 return myParent == null ? this : myParent.getTopParent();
106 public String getEvaluationExpression() {
107 StringBuilder stringBuilder = new StringBuilder();
108 buildExpression(stringBuilder);
109 return stringBuilder.toString();
112 void buildExpression(StringBuilder result) {
113 if (myParent == null) {
114 result.append(getTempName());
117 myParent.buildExpression(result);
118 if (("dict".equals(myParent.getType()) || "list".equals(myParent.getType()) || "tuple".equals(myParent.getType())) &&
120 result.append('[').append(removeLeadingZeros(removeId(myName))).append(']');
122 else if (("set".equals(myParent.getType())) && !isLen(myName)) {
123 //set doesn't support indexing
125 else if (isLen(myName)) {
126 result.append('.').append(myName).append("()");
128 else if (("ndarray".equals(myParent.getType()) || "matrix".equals(myParent.getType())) && myName.startsWith("[")) {
129 result.append(removeLeadingZeros(myName));
132 result.append('.').append(myName);
137 public String getFullName() {
138 return wrapWithPrefix(getName());
141 private static String removeId(@NotNull String name) {
142 if (name.indexOf('(') != -1) {
143 name = name.substring(0, name.indexOf('(')).trim();
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);
157 private static boolean isLen(String name) {
158 return "__len__".equals(name);
161 private static boolean isCollection(@NotNull PyDebugValue parent) {
162 String type = parent.getType();
163 return type.equals("dict") || type.equals("list");
166 private static String getChildNamePresentation(@NotNull PyDebugValue parent, @NotNull String childName) {
167 if (isCollection(parent)) {
168 return "[".concat(removeId(childName)).concat("]");
171 return ".".concat(childName);
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 + "\"]";
185 private String getFullTreeName() {
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();
194 return wrapWithPrefix(curNodeName.concat(result));
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);
204 node.setPresentation(getValueIcon(), myType, value, myContainer);
207 private boolean isDataFrame() {
208 return "DataFrame".equals(myType);
211 private boolean isNdarray() {
212 return "ndarray".equals(myType);
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));
223 String linkText = "...View as " + (isDataFrame() ? "DataFrame" : "Array");
224 node.setFullValueEvaluator(new PyNumericContainerValueEvaluator(linkText, myFrameAccessor, treeName));
228 public void computeChildren(@NotNull final XCompositeNode node) {
229 if (node.isObsolete()) return;
230 ApplicationManager.getApplication().executeOnPooledThread(() -> {
231 if (myFrameAccessor == null) return;
234 final XValueChildrenList values = myFrameAccessor.loadVariable(this);
235 if (!node.isObsolete()) {
236 node.addChildren(values, true);
239 catch (PyDebuggerException e) {
240 if (!node.isObsolete()) {
241 node.setErrorMessage("Unable to display children:" + e.getMessage());
249 public XValueModifier getModifier() {
250 return new PyValueModifier(myFrameAccessor, this);
253 private Icon getValueIcon() {
255 return AllIcons.Debugger.Db_primitive;
257 else if ("list".equals(myType) || "tuple".equals(myType)) {
258 return AllIcons.Debugger.Db_array;
261 return AllIcons.Debugger.Value;
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);
274 public XReferrersProvider getReferrersProvider() {
275 if (myFrameAccessor.getReferrersLoader() != null) {
276 return new XReferrersProvider() {
278 public XValue getReferringObjectsValue() {
279 return new PyReferringObjectsValue(PyDebugValue.this);
287 public PyFrameAccessor getFrameAccessor() {
288 return myFrameAccessor;
291 public PyVariableLocator getVariableLocator() {
292 return myVariableLocator;
295 public void setVariableLocator(PyVariableLocator variableLocator) {
296 myVariableLocator = variableLocator;
299 public String getId() {
303 public void setId(String id) {
308 public boolean canNavigateToSource() {
313 public void computeSourcePosition(@NotNull XNavigatable navigatable) {
314 if (myParent == null) {
315 navigatable.setSourcePosition(myFrameAccessor.getSourcePositionForName(myName, null));
319 navigatable.setSourcePosition(myFrameAccessor.getSourcePositionForName(myName, myParent.getDeclaringType()));
324 public boolean canNavigateToTypeSource() {
328 private static final Pattern IS_TYPE_DECLARATION = Pattern.compile("<(?:class|type)\\s*'(?<TYPE>.*?)'>");
330 public void computeTypeSourcePosition(@NotNull XNavigatable navigatable) {
332 String lookupType = getDeclaringType();
333 navigatable.setSourcePosition(myFrameAccessor.getSourcePositionForType(lookupType));
336 protected final String getDeclaringType() {
337 String lookupType = getQualifiedType();
338 if (!Strings.isNullOrEmpty(myValue))
340 Matcher matcher = IS_TYPE_DECLARATION.matcher(myValue);
341 if (matcher.matches())
343 lookupType = matcher.group("TYPE");
349 public String getQualifiedType() {
350 if (Strings.isNullOrEmpty(myType))
352 return (myTypeQualifier == null) ? myType : (myTypeQualifier + "." + myType);
355 public String getTypeQualifier() {
356 return myTypeQualifier;