evaluate array view for numpy arrays throw callbacks and listeners
authorAlexander Marchuk <Alexander.Marchuk@jetbrains.com>
Fri, 5 Sep 2014 17:24:11 +0000 (21:24 +0400)
committerAlexander Marchuk <Alexander.Marchuk@jetbrains.com>
Fri, 5 Sep 2014 17:24:11 +0000 (21:24 +0400)
improve logic

python/src/com/jetbrains/python/actions/view/array/ArrayTableComponent.java
python/src/com/jetbrains/python/actions/view/array/ArrayValueProvider.java
python/src/com/jetbrains/python/actions/view/array/NumpyArrayValueProvider.java
python/src/com/jetbrains/python/actions/view/array/PyViewArrayAction.java

index b8d739f44f9519bc9edb18bce23263afb1a57819..f37d9deffefe8d3dc8de34f61a0ba35cf86259d0 100644 (file)
@@ -17,6 +17,7 @@ package com.jetbrains.python.actions.view.array;
 
 import com.intellij.ui.components.JBScrollPane;
 import com.intellij.ui.table.JBTable;
+import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl;
 
 import javax.swing.*;
 import javax.swing.table.DefaultTableModel;
@@ -35,6 +36,8 @@ class ArrayTableComponent extends JPanel {
 
   private static final String DATA_LOADING_IN_PROCESS = "Please wait, load array data.";
 
+  private static final String NOT_APPLICABLE = "View not applicable for ";
+
   public ArrayTableComponent() {
     super(new GridBagLayout());
 
@@ -98,9 +101,25 @@ class ArrayTableComponent extends JPanel {
     return myCheckBox;
   }
 
-  public void setDefaultSpinnerText() {
+  private void setSpinnerText(String text) {
     DefaultTableModel model = new DefaultTableModel(1, 1);
     myTable.setModel(model);
-    myTable.setValueAt(DATA_LOADING_IN_PROCESS, 0, 0);
+    myTable.setValueAt(text, 0, 0);
+  }
+
+  public void setDefaultSpinnerText() {
+    setSpinnerText(DATA_LOADING_IN_PROCESS);
+  }
+
+  public void setErrorSpinnerText(Exception e) {
+    setSpinnerText(e.getMessage());
+  }
+
+  public void setErrorSpinnerText(String message) {
+    setSpinnerText(message);
+  }
+
+  public void setNotApplicableSpinner(XValueNodeImpl node){
+    setSpinnerText(NOT_APPLICABLE + node.getName());
   }
 }
index 895c6e4e3bd109a08da430dc9aea369230f06756..f131f562f9af960966ecade2356830de06b7a1e2 100644 (file)
  */
 package com.jetbrains.python.actions.view.array;
 
+import com.intellij.xdebugger.frame.XValueNode;
+
 /**
 * @author amarch
 */
 abstract class ArrayValueProvider {
+  XValueNode myBaseNode;
+
+  public ArrayValueProvider(XValueNode node){
+    myBaseNode = node;
+  }
 
-  public abstract Object[][] parseValues(String rawValue);
+  public abstract boolean isNumeric();
 }
index aa21e11787e5814bfec3d7ff7297fabb1d31f471..f6476e71e18c1f9b8dfb1767b9ad60862131cb30 100644 (file)
  */
 package com.jetbrains.python.actions.view.array;
 
+import com.intellij.openapi.project.Project;
+import com.intellij.xdebugger.evaluation.XDebuggerEvaluator;
+import com.intellij.xdebugger.frame.XValue;
+import com.intellij.xdebugger.frame.XValueNode;
+import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreeListener;
+import com.intellij.xdebugger.impl.ui.tree.nodes.RestorableStateNode;
+import com.intellij.xdebugger.impl.ui.tree.nodes.XDebuggerTreeNode;
+import com.intellij.xdebugger.impl.ui.tree.nodes.XValueContainerNode;
+import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl;
+import com.jetbrains.python.debugger.PyDebugValue;
+import com.jetbrains.python.debugger.PyDebuggerEvaluator;
+import org.jetbrains.annotations.NotNull;
+
+import javax.naming.directory.InvalidAttributeValueException;
+import javax.swing.*;
+import javax.swing.table.DefaultTableModel;
+import java.util.List;
+
 /**
-* @author amarch
-*/
+ * @author amarch
+ */
 class NumpyArrayValueProvider extends ArrayValueProvider {
 
+  private ArrayTableComponent myComponent;
+  private JTable myTable;
+  private Project myProject;
+  private PyDebuggerEvaluator myEvaluator;
+  private NumpyArrayPresentation myLastPresentation;
+
+  public NumpyArrayValueProvider(XValueNode node, ArrayTableComponent component, Project project) {
+    super(node);
+    myComponent = component;
+    myProject = project;
+    myTable = component.getTable();
+    myEvaluator = new PyDebuggerEvaluator(project, ((PyDebugValue)((XValueNodeImpl)node).getValueContainer()).getFrameAccessor());
+    myLastPresentation = new NumpyArrayPresentation(((XValueNodeImpl)node).getName());
+  }
+
+  private class NumpyArrayPresentation {
+    private Object[][] myData;
+    private String mySlice;
+    private String myArrayName;
+    private int[] myShape;
+    private int myRows = 0;
+    private int myFilledRows = 0;
+    private int nextRow = 0;
+    private String myDtype;
+
+    public NumpyArrayPresentation(String slice, String name) {
+      mySlice = slice;
+      myArrayName = name;
+    }
+
+    public NumpyArrayPresentation(String name) {
+      myArrayName = name;
+    }
+
+    public NumpyArrayPresentation getInstance() {
+      return this;
+    }
+
+    public int[] getShape() {
+      return myShape;
+    }
+
+    public void setShape(int[] shape) {
+      myShape = shape;
+    }
+
+    public String getDtype() {
+      return myDtype;
+    }
+
+    public void setDtype(String dtype) {
+      myDtype = dtype;
+    }
+
+    public Object[][] getData() {
+      return myData;
+    }
+
+    public void fillShape(final boolean stop) {
+      XDebuggerEvaluator.XEvaluationCallback callback = new XDebuggerEvaluator.XEvaluationCallback() {
+        @Override
+        public void evaluated(@NotNull XValue result) {
+          try {
+            myShape = parseShape(((PyDebugValue)result).getValue());
+
+            if (myShape.length == 1) {
+              myShape = new int[]{1, myShape[0]};
+            }
+
+            myData = new Object[myShape[myShape.length - 2]][myShape[myShape.length - 1]];
+            myRows = myShape[myShape.length - 2];
+            if (!stop) {
+              startFillTable(getInstance());
+            }
+          }
+          catch (InvalidAttributeValueException e) {
+            errorOccurred(e.getMessage());
+          }
+        }
+
+        @Override
+        public void errorOccurred(@NotNull String errorMessage) {
+          myComponent.setErrorSpinnerText(errorMessage);
+        }
+      };
+      fillShape(stop, callback);
+    }
+
+    public void fillShape(final boolean stop, XDebuggerEvaluator.XEvaluationCallback callback) {
+      String evalShapeCommand = myArrayName + ".shape";
+      myEvaluator.evaluate(evalShapeCommand, callback, null);
+    }
+
+    public void fillSliceShape(final XDebuggerEvaluator.XEvaluationCallback callback) {
+      if (mySlice == null) {
+        callback.errorOccurred("Null slice");
+        return;
+      }
+      XDebuggerEvaluator.XEvaluationCallback innerCallback = new XDebuggerEvaluator.XEvaluationCallback() {
+        @Override
+        public void evaluated(@NotNull XValue result) {
+          try {
+            myShape = parseShape(((PyDebugValue)result).getValue());
+
+            if (myShape.length > 2) {
+              errorOccurred("Slice not present valid 2d array.");
+              return;
+            }
+
+            if (myShape.length == 1) {
+              myShape = new int[]{1, myShape[0]};
+            }
+            myData = new Object[myShape[myShape.length - 2]][myShape[myShape.length - 1]];
+            myRows = myShape[myShape.length - 2];
+            callback.evaluated(result);
+          }
+          catch (InvalidAttributeValueException e) {
+            errorOccurred(e.getMessage());
+          }
+        }
+
+        @Override
+        public void errorOccurred(@NotNull String errorMessage) {
+          callback.errorOccurred(errorMessage);
+        }
+      };
+
+      String evalShapeCommand = mySlice + ".shape";
+      myEvaluator.evaluate(evalShapeCommand, innerCallback, null);
+    }
+
+    private int[] parseShape(String shape) throws InvalidAttributeValueException {
+      String[] dimensions = shape.substring(1, shape.length() - 1).trim().split(",");
+      if (dimensions.length > 0) {
+        int[] result = new int[dimensions.length];
+        for (int i = 0; i < dimensions.length; i++) {
+          result[i] = Integer.parseInt(dimensions[i].trim());
+        }
+        return result;
+      }
+      else {
+        throw new InvalidAttributeValueException("Invalid shape string for " + ((XValueNodeImpl)myBaseNode).getName());
+      }
+    }
+
+    public void fillType(final boolean stop) {
+      XDebuggerEvaluator.XEvaluationCallback callback = new XDebuggerEvaluator.XEvaluationCallback() {
+        @Override
+        public void evaluated(@NotNull XValue result) {
+          myDtype = ((PyDebugValue)result).getValue();
+          if (!stop) {
+            startFillTable(getInstance());
+          }
+        }
+
+        @Override
+        public void errorOccurred(@NotNull String errorMessage) {
+
+        }
+      };
+      String evalTypeCommand = myArrayName + ".dtype.kind";
+      myEvaluator.evaluate(evalTypeCommand, callback, null);
+    }
+
+    public boolean dataFilled() {
+      return myRows > 0 && myFilledRows == myRows;
+    }
+
+    public void fillData(final boolean stop) {
+      final XDebuggerEvaluator.XEvaluationCallback callback = new XDebuggerEvaluator.XEvaluationCallback() {
+        @Override
+        public void evaluated(@NotNull XValue result) {
+          String name = ((PyDebugValue)result).getName();
+          XValueNodeImpl node = new XValueNodeImpl(((XValueNodeImpl)myBaseNode).getTree(), null, name, result);
+          node.startComputingChildren();
+        }
+
+        @Override
+        public void errorOccurred(@NotNull String errorMessage) {
+        }
+      };
+
+      XDebuggerTreeListener treeListener = new XDebuggerTreeListener() {
+        @Override
+        public void nodeLoaded(@NotNull RestorableStateNode node, String name) {
+          return;
+        }
+
+        @Override
+        public void childrenLoaded(@NotNull XDebuggerTreeNode node, @NotNull List<XValueContainerNode<?>> children, boolean last) {
+          String fullName = ((XValueNodeImpl)node).getName();
+          int row = 0;
+          if (fullName.contains("[")) {
+            row = Integer.parseInt(fullName.substring(fullName.lastIndexOf('[') + 1, fullName.length() - 2));
+          }
+          if (myData[row][0] == null) {
+            for (int i = 0; i < node.getChildCount() - 1; i++) {
+              myData[row][i] = ((XValueNodeImpl)node.getChildAt(i + 1)).getRawValue();
+            }
+            myFilledRows += 1;
+          }
+          if (myFilledRows == myRows) {
+            node.getTree().removeTreeListener(this);
+            if (!stop) {
+              startFillTable(getInstance());
+            }
+          }
+          else {
+            nextRow += 1;
+            startEvalNextRow(callback);
+          }
+        }
+      };
+
+      ((XValueNodeImpl)myBaseNode).getTree().addTreeListener(treeListener);
+      nextRow = 0;
+      startEvalNextRow(callback);
+    }
+
+    private void startEvalNextRow(XDebuggerEvaluator.XEvaluationCallback callback) {
+      String evalRowCommand = "list(" + myArrayName;
+      if (myShape.length > 2) {
+        evalRowCommand += new String(new char[myShape.length - 2]).replace("\0", "[0]");
+      }
+
+      if (myShape[0] > 1) {
+        evalRowCommand += "[" + nextRow + "])";
+      }
+      else {
+        evalRowCommand += ")";
+      }
+      myEvaluator.evaluate(evalRowCommand, callback, null);
+    }
+
+    public String getSlice() {
+      return mySlice;
+    }
+
+    public void setSlice(String slice) {
+      mySlice = slice;
+    }
+
+    public void computeSlice() {
+      String presentation = "";
+
+      if (myBaseNode != null) {
+        presentation += ((XValueNodeImpl)myBaseNode).getName();
+
+        if (myShape != null) {
+          presentation += new String(new char[myShape.length - 2]).replace("\0", "[0]");
+          if (myShape[0] == 1) {
+            presentation += "[0:" + myShape[1] + "]";
+          }
+          else {
+            presentation += "[0:" + myShape[myShape.length - 2] + "][0:" + myShape[myShape.length - 1] + "]";
+          }
+        }
+      }
+      setSlice(presentation);
+    }
+  }
+
   @Override
-  public Object[][] parseValues(String rawValues) {
-    return null;
+  public boolean isNumeric() {
+    if (myLastPresentation.getDtype() != null) {
+      return "biufc".contains(myLastPresentation.getDtype().substring(0, 1));
+    }
+    return false;
+  }
+
+  public void startFillTable(NumpyArrayPresentation presentation) {
+
+    if (presentation == null) {
+      presentation = new NumpyArrayPresentation(((XValueNodeImpl)myBaseNode).getName());
+    }
+
+    if (presentation.getShape() == null) {
+      presentation.fillShape(false);
+      return;
+    }
+
+    if (presentation.getDtype() == null) {
+      presentation.fillType(false);
+      return;
+    }
+
+    if (!presentation.dataFilled()) {
+      presentation.fillData(false);
+      return;
+    }
+
+    if (presentation.getSlice() == null) {
+      presentation.computeSlice();
+    }
+
+    Object[][] data = presentation.getData();
+
+    if (myLastPresentation == null || !presentation.getSlice().equals(myLastPresentation.getSlice())) {
+      myLastPresentation = presentation;
+    }
+
+    DefaultTableModel model = new DefaultTableModel(data, range(0, data[0].length - 1));
+    myTable.setModel(model);
+    myTable.setDefaultEditor(myTable.getColumnClass(0), new ArrayTableCellEditor(myProject));
+    enableColor(data);
+    myComponent.getTextField().setText(myLastPresentation.getSlice());
   }
 
-  private boolean isNumeric(String value) {
-    return true;
+  private String[] range(int min, int max) {
+    String[] array = new String[max - min + 1];
+    for (int i = min; i <= max; i++) {
+      array[i] = Integer.toString(i);
+    }
+    return array;
+  }
+
+  private void enableColor(Object[][] data) {
+    if (isNumeric()) {
+      double min = Double.MAX_VALUE;
+      double max = Double.MIN_VALUE;
+      if (data.length > 0) {
+        try {
+          for (int i = 0; i < data.length; i++) {
+            for (int j = 0; j < data[0].length; j++) {
+              double d = Double.parseDouble(data[i][j].toString());
+              min = min > d ? d : min;
+              max = max < d ? d : max;
+            }
+          }
+        }
+        catch (NumberFormatException e) {
+          min = 0;
+          max = 0;
+        }
+      }
+      else {
+        min = 0;
+        max = 0;
+      }
+
+      myTable.setDefaultRenderer(myTable.getColumnClass(0), new ArrayTableCellRenderer(min, max));
+    }
+    else {
+      myComponent.getColored().setSelected(false);
+      myComponent.getColored().setVisible(false);
+    }
+  }
+
+  public void refreshTable(final NumpyArrayPresentation presentation) {
+
+    if (presentation.getSlice() != null) {
+      presentation.fillSliceShape(new XDebuggerEvaluator.XEvaluationCallback() {
+        @Override
+        public void evaluated(@NotNull XValue result) {
+          refreshTable(presentation);
+        }
+
+        @Override
+        public void errorOccurred(@NotNull String errorMessage) {
+          myComponent.setErrorSpinnerText(errorMessage);
+        }
+      });
+      return;
+    }
+
+    presentation.fillData(false);
   }
 }
index 247f508b94855c3d19997c9f723d622ba5c48acf..0299ab09caa34cb220d6a6fd6683eaef00d3b3b9 100644 (file)
@@ -21,6 +21,7 @@ import com.intellij.openapi.ui.DialogWrapper;
 import com.intellij.xdebugger.frame.XFullValueEvaluator;
 import com.intellij.xdebugger.impl.ui.tree.actions.XDebuggerTreeActionBase;
 import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl;
+import com.jetbrains.python.debugger.PyDebugValue;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -67,20 +68,25 @@ public class PyViewArrayAction extends XDebuggerTreeActionBase {
 
 
     public void setValue(XValueNodeImpl node) {
-      final ArrayValueProvider valueProvider;
 
-      if (node.getValuePresentation() != null &&
-          node.getValuePresentation().getType() != null &&
-          node.getValuePresentation().getType().equals("ndarray")) {
-        valueProvider = new NumpyArrayValueProvider();
-
-        //myComponent.setDefaultSpinnerText();
-
-        XDebuggerTreeTableListener tableUpdater = new XDebuggerTreeTableListener(node, myTable, myComponent, myProject);
-
-        node.getTree().addTreeListener(tableUpdater);
-
-        node.startComputingChildren();
+      if (node.getValueContainer() instanceof PyDebugValue) {
+        PyDebugValue debugValue = (PyDebugValue)node.getValueContainer();
+        if ("ndarray".equals(debugValue.getType())) {
+          myComponent.setDefaultSpinnerText();
+
+          final NumpyArrayValueProvider valueProvider = new NumpyArrayValueProvider(node, myComponent, myProject);
+          try {
+            valueProvider.startFillTable(null);
+          }
+          catch (Exception e) {
+            myComponent.setErrorSpinnerText(e);
+          }
+        }
+        else {
+          //show hint about 'not applicable'
+          myComponent.setNotApplicableSpinner(node);
+          //this.close(CLOSE_EXIT_CODE);
+        }
       }
     }