Merge branch 'liana/simplified_debugger'
authorEkaterina Tuzova <Ekaterina.Tuzova@jetbrains.com>
Mon, 24 Aug 2015 08:33:01 +0000 (11:33 +0300)
committerEkaterina Tuzova <Ekaterina.Tuzova@jetbrains.com>
Mon, 24 Aug 2015 08:33:01 +0000 (11:33 +0300)
Conflicts:
python/edu/src/META-INF/PyCharmEduPlugin.xml
python/edu/src/com/jetbrains/python/edu/PyCharmEduInitialConfigurator.java

21 files changed:
python/edu/gen/icons/PythonEducationalIcons.java [new file with mode: 0644]
python/edu/resources/icon-robots.txt [new file with mode: 0644]
python/edu/resources/icons/com/jetbrains/edu/Field.png [new file with mode: 0644]
python/edu/resources/icons/com/jetbrains/edu/Field@2x.png [new file with mode: 0644]
python/edu/resources/icons/com/jetbrains/edu/specialVar.png [new file with mode: 0644]
python/edu/resources/icons/com/jetbrains/edu/specialVar@2x.png [new file with mode: 0644]
python/edu/src/META-INF/PyCharmEduPlugin.xml
python/edu/src/com/jetbrains/python/edu/PyCharmEduInitialConfigurator.java
python/edu/src/com/jetbrains/python/edu/PyDebugCurrentFile.java [new file with mode: 0644]
python/edu/src/com/jetbrains/python/edu/PyExecuteFileExtensionPoint.java
python/edu/src/com/jetbrains/python/edu/PyExecuteFileLineMarkerProvider.java
python/edu/src/com/jetbrains/python/edu/PyRunCurrentFileAction.java [deleted file]
python/edu/src/com/jetbrains/python/edu/debugger/PyEduConsoleInputFilterProvider.java [new file with mode: 0644]
python/edu/src/com/jetbrains/python/edu/debugger/PyEduDebugExecutor.java [new file with mode: 0644]
python/edu/src/com/jetbrains/python/edu/debugger/PyEduDebugProcess.java [new file with mode: 0644]
python/edu/src/com/jetbrains/python/edu/debugger/PyEduDebugRunner.java [new file with mode: 0644]
python/edu/src/com/jetbrains/python/edu/debugger/PyEduStackFrame.java [new file with mode: 0644]
python/educational/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCRunTestsAction.java
python/src/com/jetbrains/python/debugger/PyDebugProcess.java
python/src/com/jetbrains/python/debugger/PyDebugRunner.java
python/src/com/jetbrains/python/debugger/PyStackFrame.java

diff --git a/python/edu/gen/icons/PythonEducationalIcons.java b/python/edu/gen/icons/PythonEducationalIcons.java
new file mode 100644 (file)
index 0000000..fb04abc
--- /dev/null
@@ -0,0 +1,18 @@
+package icons;
+
+import com.intellij.openapi.util.IconLoader;
+
+import javax.swing.*;
+
+/**
+ * NOTE THIS FILE IS AUTO-GENERATED
+ * DO NOT EDIT IT BY HAND, run build/scripts/icons.gant instead
+ */
+public class PythonEducationalIcons {
+  private static Icon load(String path) {
+    return IconLoader.getIcon(path, PythonEducationalIcons.class);
+  }
+
+  public static final Icon Field = load("/icons/com/jetbrains/edu/Field.png"); // 16x16
+  public static final Icon SpecialVar = load("/icons/com/jetbrains/edu/specialVar.png"); // 16x16
+}
diff --git a/python/edu/resources/icon-robots.txt b/python/edu/resources/icon-robots.txt
new file mode 100644 (file)
index 0000000..c6ebcdb
--- /dev/null
@@ -0,0 +1 @@
+skip: tips
diff --git a/python/edu/resources/icons/com/jetbrains/edu/Field.png b/python/edu/resources/icons/com/jetbrains/edu/Field.png
new file mode 100644 (file)
index 0000000..f4a6122
Binary files /dev/null and b/python/edu/resources/icons/com/jetbrains/edu/Field.png differ
diff --git a/python/edu/resources/icons/com/jetbrains/edu/Field@2x.png b/python/edu/resources/icons/com/jetbrains/edu/Field@2x.png
new file mode 100644 (file)
index 0000000..8ad521f
Binary files /dev/null and b/python/edu/resources/icons/com/jetbrains/edu/Field@2x.png differ
diff --git a/python/edu/resources/icons/com/jetbrains/edu/specialVar.png b/python/edu/resources/icons/com/jetbrains/edu/specialVar.png
new file mode 100644 (file)
index 0000000..25612ae
Binary files /dev/null and b/python/edu/resources/icons/com/jetbrains/edu/specialVar.png differ
diff --git a/python/edu/resources/icons/com/jetbrains/edu/specialVar@2x.png b/python/edu/resources/icons/com/jetbrains/edu/specialVar@2x.png
new file mode 100644 (file)
index 0000000..343e0b7
Binary files /dev/null and b/python/edu/resources/icons/com/jetbrains/edu/specialVar@2x.png differ
index b2ee67e705e097d1aaadb3ede4fe92181e683c68..65eb9ca1bed03a92b922961a71fc512723de1ee9 100644 (file)
 
   <extensions defaultExtensionNs="com.intellij">
       <codeInsight.lineMarkerProvider language="Python" implementationClass="com.jetbrains.python.edu.PyExecuteFileLineMarkerProvider"/>
+      <programRunner implementation="com.jetbrains.python.edu.debugger.PyEduDebugRunner"/>
+      <executor implementation="com.jetbrains.python.edu.debugger.PyEduDebugExecutor" order="first,after run"/>
+      <consoleInputFilterProvider implementation="com.jetbrains.python.edu.debugger.PyEduConsoleInputFilterProvider"/>
   </extensions>
   <extensions defaultExtensionNs="Edu">
     <executeFile implementation="com.jetbrains.python.edu.PyEduAddParametersAction"/>
   </extensions>
 
-  <actions>
-    <group id="PyRunMenu">
-      <action id="runCurrentFile" class="com.jetbrains.python.edu.PyRunCurrentFileAction"/>
-      <add-to-group group-id="RunMenu" anchor="first"/>
-    </group>
-
-
-  </actions>
 </idea-plugin>
index 18197fe169d7ef0b6a446ba21ea6d4d30cb5b810..d8c78bb1dedaee5f1c5f648f597a08cee9b29a93 100644 (file)
@@ -19,6 +19,9 @@ import com.google.common.collect.Sets;
 import com.intellij.codeInsight.CodeInsightSettings;
 import com.intellij.codeInsight.intention.IntentionActionBean;
 import com.intellij.codeInsight.intention.IntentionManager;
+import com.intellij.execution.Executor;
+import com.intellij.execution.ExecutorRegistryImpl;
+import com.intellij.execution.executors.DefaultDebugExecutor;
 import com.intellij.ide.AppLifecycleListener;
 import com.intellij.ide.GeneralSettings;
 import com.intellij.ide.SelectInTarget;
@@ -31,6 +34,9 @@ import com.intellij.ide.ui.customization.CustomizationUtil;
 import com.intellij.ide.util.PropertiesComponent;
 import com.intellij.ide.util.TipAndTrickBean;
 import com.intellij.notification.EventLog;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.DefaultActionGroup;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.editor.colors.EditorColorsManager;
 import com.intellij.openapi.editor.colors.EditorColorsScheme;
@@ -313,6 +319,10 @@ public class PyCharmEduInitialConfigurator {
   }
 
   private static void patchProjectAreaExtensions(@NotNull final Project project) {
+    Executor debugExecutor = DefaultDebugExecutor.getDebugExecutorInstance();
+    unregisterAction(debugExecutor.getId(), ExecutorRegistryImpl.RUNNERS_GROUP);
+    unregisterAction(debugExecutor.getContextActionId(), ExecutorRegistryImpl.RUN_CONTEXT_GROUP);
+
     ExtensionsArea projectArea = Extensions.getArea(project);
 
     for (SelectInTarget target : Extensions.getExtensions(SelectInTarget.EP_NAME, project)) {
@@ -330,6 +340,18 @@ public class PyCharmEduInitialConfigurator {
     }
   }
 
+  private static void unregisterAction(String actionId, String groupId) {
+    ActionManager actionManager = ActionManager.getInstance();
+    AnAction action = actionManager.getAction(actionId);
+    if (action != null) {
+      AnAction actionGroup = actionManager.getAction(groupId);
+      if (actionGroup != null && actionGroup instanceof DefaultActionGroup) {
+        ((DefaultActionGroup)actionGroup).remove(action);
+        actionManager.unregisterAction(actionId);
+      }
+    }
+  }
+
   private static void patchKeymap() {
     Set<String> droppedActions = ContainerUtil.newHashSet(
       "AddToFavoritesPopup",
diff --git a/python/edu/src/com/jetbrains/python/edu/PyDebugCurrentFile.java b/python/edu/src/com/jetbrains/python/edu/PyDebugCurrentFile.java
new file mode 100644 (file)
index 0000000..f9f9a68
--- /dev/null
@@ -0,0 +1,16 @@
+package com.jetbrains.python.edu;
+
+import com.intellij.execution.actions.RunContextAction;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.jetbrains.python.edu.debugger.PyEduDebugExecutor;
+import org.jetbrains.annotations.Nullable;
+
+public class PyDebugCurrentFile implements PyExecuteFileExtensionPoint {
+
+  @Nullable
+  @Override
+  public AnAction getRunAction() {
+    return new RunContextAction(PyEduDebugExecutor.getInstance());
+  }
+}
+
index 7f5a077c2650d557cf778b778cfc3258b049b844..d0c54fe86b3b8d8eaf3757e2f3a61f0530ea2286 100644 (file)
@@ -17,13 +17,13 @@ package com.jetbrains.python.edu;
 
 import com.intellij.openapi.actionSystem.AnAction;
 import com.intellij.openapi.extensions.ExtensionPointName;
-import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 public interface PyExecuteFileExtensionPoint {
 
   ExtensionPointName<PyExecuteFileExtensionPoint> EP_NAME = ExtensionPointName.create("Edu.executeFile");
 
-  @NotNull
+  @Nullable
   AnAction getRunAction();
 
 }
index 0c0208b1f144edb5763f788450d4e172b8c8158e..e0c76446ce4e396fe542b2211302d4b5ed993568 100644 (file)
@@ -3,22 +3,16 @@ package com.jetbrains.python.edu;
 import com.intellij.codeHighlighting.Pass;
 import com.intellij.codeInsight.daemon.LineMarkerInfo;
 import com.intellij.codeInsight.daemon.LineMarkerProvider;
+import com.intellij.execution.actions.RunContextAction;
+import com.intellij.execution.executors.DefaultRunExecutor;
 import com.intellij.icons.AllIcons;
 import com.intellij.openapi.actionSystem.ActionGroup;
 import com.intellij.openapi.actionSystem.AnAction;
-import com.intellij.openapi.actionSystem.AnActionEvent;
 import com.intellij.openapi.actionSystem.DefaultActionGroup;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.editor.markup.GutterIconRenderer;
-import com.intellij.openapi.ui.popup.ListPopup;
-import com.intellij.psi.PsiComment;
 import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiWhiteSpace;
-import com.intellij.ui.popup.PopupFactoryImpl;
 import com.intellij.util.Function;
-import com.jetbrains.python.psi.PyFile;
-import com.jetbrains.python.psi.PyImportStatement;
-import com.jetbrains.python.psi.PyStatement;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -37,90 +31,48 @@ public class PyExecuteFileLineMarkerProvider implements LineMarkerProvider {
 
   @Override
   public void collectSlowLineMarkers(@NotNull List<PsiElement> elements, @NotNull Collection<LineMarkerInfo> result) {
-    for (PsiElement element : elements) {
-      if (isFirstCodeLine(element)) {
-        final LineMarkerInfo<PsiElement> markerInfo = new LineMarkerInfo<PsiElement>(
-          element, element.getTextRange(), AllIcons.Actions.Execute, Pass.UPDATE_OVERRIDEN_MARKERS,
-          new Function<PsiElement, String>() {
-            @Override
-            public String fun(PsiElement e) {
-              return "Execute '" + e.getContainingFile().getName() + "'";
-            }
-          }, null,
-          GutterIconRenderer.Alignment.RIGHT) {
-          @Nullable
+    if (elements.isEmpty()) {
+      return;
+    }
+    PsiElement element = elements.get(0).getContainingFile();
+    final RunContextAction runAction = new RunContextAction(DefaultRunExecutor.getRunExecutorInstance());
+    final LineMarkerInfo<PsiElement> markerInfo = new LineMarkerInfo<PsiElement>(
+      element, 0, AllIcons.Actions.Execute, Pass.UPDATE_OVERRIDEN_MARKERS,
+      new Function<PsiElement, String>() {
+        @Override
+        public String fun(PsiElement e) {
+          return "Execute '" + e.getContainingFile().getName() + "'";
+        }
+      }, null,
+      GutterIconRenderer.Alignment.RIGHT) {
+      @Nullable
+      @Override
+      public GutterIconRenderer createGutterRenderer() {
+        return new LineMarkerGutterIconRenderer<PsiElement>(this) {
           @Override
-          public GutterIconRenderer createGutterRenderer() {
-            return new LineMarkerGutterIconRenderer<PsiElement>(this){
-              @Override
-              public AnAction getClickAction() {
-
-                return new AnAction() {
-                  @Override
-                  public void actionPerformed(@NotNull AnActionEvent e) {
-                    final DefaultActionGroup group = new DefaultActionGroup();
-                    group.add(new PyRunCurrentFileAction());
-                    final PyExecuteFileExtensionPoint[] extensions =
-                      ApplicationManager.getApplication().getExtensions(PyExecuteFileExtensionPoint.EP_NAME);
-                    for (PyExecuteFileExtensionPoint extension : extensions) {
-                      final AnAction action = extension.getRunAction();
-                      action.update(e);
-                      if (e.getPresentation().isEnabled())
-                        group.add(action);
-                    }
-                    if (group.getChildrenCount() == 1) {
-                      new PyRunCurrentFileAction().actionPerformed(e);
-                    }
-                    else {
-                      final ListPopup popup =
-                        new PopupFactoryImpl().createActionGroupPopup(null, group, e.getDataContext(), false, false, false, null, 5);
-                      popup.showInBestPositionFor(e.getDataContext());
-                    }
-                  }
-                };
-              }
+          public AnAction getClickAction() {
+            return runAction;
+          }
 
-              @Nullable
-              @Override
-              public ActionGroup getPopupMenuActions() {
-                final DefaultActionGroup group = new DefaultActionGroup();
-                group.add(new PyRunCurrentFileAction());
-                final PyExecuteFileExtensionPoint[] extensions =
-                  ApplicationManager.getApplication().getExtensions(PyExecuteFileExtensionPoint.EP_NAME);
-                for (PyExecuteFileExtensionPoint extension : extensions) {
-                  final AnAction action = extension.getRunAction();
-                  group.add(action);
-                }
-                return group;
+          @Nullable
+          @Override
+          public ActionGroup getPopupMenuActions() {
+            final DefaultActionGroup group = new DefaultActionGroup();
+            group.add(runAction);
+            final PyExecuteFileExtensionPoint[] extensions =
+              ApplicationManager.getApplication().getExtensions(PyExecuteFileExtensionPoint.EP_NAME);
+            for (PyExecuteFileExtensionPoint extension : extensions) {
+              final AnAction action = extension.getRunAction();
+              if (action == null) {
+                continue;
               }
-            };
+              group.add(action);
+            }
+            return group;
           }
         };
-        result.add(markerInfo);
       }
-    }
-  }
-
-  private static boolean isFirstCodeLine(PsiElement element) {
-    return element instanceof PyStatement &&
-           element.getParent() instanceof PyFile &&
-           !isNothing(element) &&
-           nothingBefore(element);
-  }
-
-  private static boolean nothingBefore(PsiElement element) {
-    element = element.getPrevSibling();
-    while (element != null) {
-      if (!isNothing(element)) {
-        return false;
-      }
-      element = element.getPrevSibling();
-    }
-
-    return true;
-  }
-
-  private static boolean isNothing(PsiElement element) {
-    return (element instanceof PsiComment) || (element instanceof PyImportStatement) || (element instanceof PsiWhiteSpace);
+    };
+    result.add(markerInfo);
   }
 }
diff --git a/python/edu/src/com/jetbrains/python/edu/PyRunCurrentFileAction.java b/python/edu/src/com/jetbrains/python/edu/PyRunCurrentFileAction.java
deleted file mode 100644 (file)
index 4d30fa2..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-package com.jetbrains.python.edu;
-
-import com.intellij.execution.Location;
-import com.intellij.execution.RunManagerEx;
-import com.intellij.execution.RunnerAndConfigurationSettings;
-import com.intellij.execution.actions.ConfigurationContext;
-import com.intellij.execution.executors.DefaultRunExecutor;
-import com.intellij.execution.runners.ExecutionUtil;
-import com.intellij.icons.AllIcons;
-import com.intellij.openapi.actionSystem.AnAction;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.actionSystem.Presentation;
-import com.jetbrains.python.PythonFileType;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * @author traff
- */
-public class PyRunCurrentFileAction extends AnAction {
-  public PyRunCurrentFileAction() {
-    getTemplatePresentation().setIcon(AllIcons.Actions.Execute);
-  }
-
-  @Override
-  public void update(AnActionEvent e) {
-    Presentation presentation = e.getPresentation();
-    final ConfigurationContext context = ConfigurationContext.getFromContext(e.getDataContext());
-    Location location = context.getLocation();
-    if (location != null && location.getPsiElement().getContainingFile() != null && location.getPsiElement().getContainingFile().getFileType() == PythonFileType.INSTANCE) {
-      presentation.setEnabled(true);
-      presentation.setText("Run '" + location.getPsiElement().getContainingFile().getName() + "'");
-    }
-  }
-
-  @Override
-  public void actionPerformed(AnActionEvent e) {
-    final ConfigurationContext context = ConfigurationContext.getFromContext(e.getDataContext());
-
-    run(context);
-  }
-
-  public static void run(@NotNull ConfigurationContext context) {
-    RunnerAndConfigurationSettings configuration = context.findExisting();
-    final RunManagerEx runManager = (RunManagerEx)context.getRunManager();
-    if (configuration == null) {
-      configuration = context.getConfiguration();
-      if (configuration == null) {
-        return;
-      }
-      runManager.setTemporaryConfiguration(configuration);
-    }
-    runManager.setSelectedConfiguration(configuration);
-
-    ExecutionUtil.runConfiguration(configuration, DefaultRunExecutor.getRunExecutorInstance());
-  }
-}
diff --git a/python/edu/src/com/jetbrains/python/edu/debugger/PyEduConsoleInputFilterProvider.java b/python/edu/src/com/jetbrains/python/edu/debugger/PyEduConsoleInputFilterProvider.java
new file mode 100644 (file)
index 0000000..0f74c02
--- /dev/null
@@ -0,0 +1,32 @@
+package com.jetbrains.python.edu.debugger;
+
+import com.intellij.execution.filters.ConsoleInputFilterProvider;
+import com.intellij.execution.filters.InputFilter;
+import com.intellij.execution.ui.ConsoleViewContentType;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Pair;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collections;
+import java.util.List;
+
+public class PyEduConsoleInputFilterProvider implements ConsoleInputFilterProvider {
+  @NotNull
+  @Override
+  public InputFilter[] getDefaultFilters(@NotNull Project project) {
+    return new InputFilter[]{new InputFilter() {
+      @Nullable
+      @Override
+      public List<Pair<String, ConsoleViewContentType>> applyFilter(String text, ConsoleViewContentType outputType) {
+        if (outputType.equals(ConsoleViewContentType.SYSTEM_OUTPUT) && !text.contains("exit code")) {
+          return Collections.emptyList();
+        }
+        if (text.startsWith("pydev debugger")) {
+          return Collections.emptyList();
+        }
+        return Collections.singletonList(Pair.create(text, outputType));
+      }
+    }};
+  }
+}
diff --git a/python/edu/src/com/jetbrains/python/edu/debugger/PyEduDebugExecutor.java b/python/edu/src/com/jetbrains/python/edu/debugger/PyEduDebugExecutor.java
new file mode 100644 (file)
index 0000000..4c05b0d
--- /dev/null
@@ -0,0 +1,31 @@
+package com.jetbrains.python.edu.debugger;
+
+import com.intellij.execution.Executor;
+import com.intellij.execution.ExecutorRegistry;
+import com.intellij.execution.executors.DefaultDebugExecutor;
+import org.jetbrains.annotations.NotNull;
+
+public class PyEduDebugExecutor extends DefaultDebugExecutor {
+  public static final String ID = "EduExecutor";
+
+  @NotNull
+  @Override
+  public String getId() {
+    return ID;
+  }
+
+  public static Executor getInstance() {
+    return ExecutorRegistry.getInstance().getExecutorById(ID);
+  }
+
+  @Override
+  public String getContextActionId() {
+    return "EduDebug";
+  }
+
+  @NotNull
+  @Override
+  public String getStartActionText() {
+    return "Step Through";
+  }
+}
diff --git a/python/edu/src/com/jetbrains/python/edu/debugger/PyEduDebugProcess.java b/python/edu/src/com/jetbrains/python/edu/debugger/PyEduDebugProcess.java
new file mode 100644 (file)
index 0000000..0904ec7
--- /dev/null
@@ -0,0 +1,67 @@
+package com.jetbrains.python.edu.debugger;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.intellij.execution.process.ProcessHandler;
+import com.intellij.execution.ui.ExecutionConsole;
+import com.intellij.xdebugger.XDebugSession;
+import com.jetbrains.python.PythonHelpersLocator;
+import com.jetbrains.python.debugger.*;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.net.ServerSocket;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+class PyEduDebugProcess extends PyDebugProcess {
+
+  private final String myScriptName;
+  private final int myLine;
+
+  public PyEduDebugProcess(@NotNull XDebugSession session,
+                           @NotNull ServerSocket serverSocket,
+                           @NotNull ExecutionConsole executionConsole,
+                           @Nullable ProcessHandler processHandler, boolean multiProcess,
+                           String scriptName,
+                           int line) {
+    super(session, serverSocket, executionConsole, processHandler, multiProcess);
+    myScriptName = scriptName;
+    myLine = line;
+  }
+
+  @Override
+  public PyStackFrame createStackFrame(PyStackFrameInfo frameInfo) {
+    return new PyEduStackFrame(getSession().getProject(), this, frameInfo,
+                               getPositionConverter().convertFromPython(frameInfo.getPosition()));
+  }
+
+  @Override
+  public void init() {
+    super.init();
+    addTemporaryBreakpoint(PyLineBreakpointType.ID, myScriptName, myLine);
+  }
+
+  @NotNull
+  @Override
+  protected PySuspendContext createSuspendContext(PyThreadInfo threadInfo) {
+    threadInfo.updateState(threadInfo.getState(), new ArrayList<PyStackFrameInfo>(filterFrames(threadInfo.getFrames())));
+    return new PySuspendContext(this, threadInfo);
+  }
+
+  public Collection<PyStackFrameInfo> filterFrames(@Nullable List<PyStackFrameInfo> frames) {
+    if (frames == null) {
+      return Collections.emptyList();
+    }
+    final String debugger = PythonHelpersLocator.getHelperPath(PyDebugRunner.DEBUGGER_MAIN);
+    return Collections2.filter(frames, new Predicate<PyStackFrameInfo>() {
+      @Override
+      public boolean apply(PyStackFrameInfo frame) {
+        String file = frame.getPosition().getFile();
+        return !debugger.equals(file);
+      }
+    });
+  }
+}
diff --git a/python/edu/src/com/jetbrains/python/edu/debugger/PyEduDebugRunner.java b/python/edu/src/com/jetbrains/python/edu/debugger/PyEduDebugRunner.java
new file mode 100644 (file)
index 0000000..c8cb537
--- /dev/null
@@ -0,0 +1,191 @@
+package com.jetbrains.python.edu.debugger;
+
+import com.intellij.execution.ExecutionResult;
+import com.intellij.execution.Executor;
+import com.intellij.execution.configurations.RunProfile;
+import com.intellij.execution.configurations.RunProfileState;
+import com.intellij.execution.filters.UrlFilter;
+import com.intellij.execution.process.ProcessHandler;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.intellij.execution.ui.ExecutionConsole;
+import com.intellij.execution.ui.RunnerLayoutUi;
+import com.intellij.execution.ui.actions.CloseAction;
+import com.intellij.execution.ui.layout.PlaceInGrid;
+import com.intellij.icons.AllIcons;
+import com.intellij.ide.actions.ContextHelpAction;
+import com.intellij.openapi.actionSystem.*;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.content.Content;
+import com.intellij.ui.content.ContentManager;
+import com.intellij.xdebugger.XDebugSession;
+import com.intellij.xdebugger.XDebuggerBundle;
+import com.intellij.xdebugger.impl.XDebugSessionImpl;
+import com.intellij.xdebugger.impl.actions.XDebuggerActions;
+import com.intellij.xdebugger.impl.ui.XDebugSessionTab;
+import com.jetbrains.python.console.PythonDebugLanguageConsoleView;
+import com.jetbrains.python.debugger.PyDebugProcess;
+import com.jetbrains.python.debugger.PyDebugRunner;
+import com.jetbrains.python.debugger.PyLineBreakpointType;
+import com.jetbrains.python.run.PythonCommandLineState;
+import com.jetbrains.python.run.PythonRunConfiguration;
+import com.jetbrains.python.run.PythonTracebackFilter;
+import com.jetbrains.python.sdk.PythonSdkType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.net.ServerSocket;
+
+public class PyEduDebugRunner extends PyDebugRunner {
+  private static final Logger LOG = Logger.getInstance(PyEduDebugRunner.class);
+  public static final int NO_LINE = -1;
+
+  @Override
+  public boolean canRun(@NotNull String executorId, @NotNull RunProfile profile) {
+    return executorId.equals(PyEduDebugExecutor.ID);
+  }
+
+  @NotNull
+  @Override
+  protected PyDebugProcess createDebugProcess(@NotNull XDebugSession session,
+                                              ServerSocket serverSocket,
+                                              ExecutionResult result,
+                                              PythonCommandLineState pyState) {
+    ExecutionConsole executionConsole = result.getExecutionConsole();
+    ProcessHandler processHandler = result.getProcessHandler();
+    boolean isMultiProcess = pyState.isMultiprocessDebug();
+    String scriptName = getScriptName(pyState);
+    if (scriptName != null) {
+      VirtualFile file = VfsUtil.findFileByIoFile(new File(scriptName), true);
+      if (file != null) {
+        int line = getBreakpointLineNumber(file, session.getProject());
+        if (line != NO_LINE) {
+          return new PyEduDebugProcess(session, serverSocket,
+                                       executionConsole, processHandler,
+                                       isMultiProcess, scriptName, line + 1);
+        }
+      }
+    }
+    LOG.info("Failed to create PyEduDebugProcess. PyDebugProcess created instead.");
+    return new PyDebugProcess(session, serverSocket, executionConsole,
+                              processHandler, isMultiProcess);
+  }
+
+  @Nullable
+  private static String getScriptName(PythonCommandLineState pyState) {
+    ExecutionEnvironment environment = pyState.getEnvironment();
+    if (environment == null) {
+      return null;
+    }
+    RunProfile runProfile = environment.getRunProfile();
+    if (runProfile instanceof PythonRunConfiguration) {
+      return ((PythonRunConfiguration)runProfile).getScriptName();
+    }
+    return null;
+  }
+
+  /**
+   * @return the smallest line (from 0 to line number) suitable to set breakpoint on it, NO_LINE if there is no such line in the file
+   */
+  private static int getBreakpointLineNumber(@NotNull final VirtualFile file, @NotNull final Project project) {
+    Document document = FileDocumentManager.getInstance().getDocument(file);
+    if (document == null) {
+      return NO_LINE;
+    }
+    PyLineBreakpointType lineBreakpointType = new PyLineBreakpointType();
+    for (int line = 0; line < document.getLineCount(); line++) {
+      if (lineBreakpointType.canPutAt(file, line, project)) {
+        return line;
+      }
+    }
+    return NO_LINE;
+  }
+
+
+  @Override
+  protected void initSession(XDebugSession session, RunProfileState state, Executor executor) {
+    XDebugSessionTab tab = ((XDebugSessionImpl)session).getSessionTab();
+    if (tab != null) {
+      RunnerLayoutUi ui = tab.getUi();
+      ContentManager contentManager = ui.getContentManager();
+      Content content = findContent(contentManager, XDebuggerBundle.message("debugger.session.tab.watches.title"));
+      if (content != null) {
+        contentManager.removeContent(content, true);
+      }
+      content = findContent(contentManager, XDebuggerBundle.message("debugger.session.tab.console.content.name"));
+      if (content != null) {
+        contentManager.removeContent(content, true);
+      }
+      initEduConsole(session, ui);
+    }
+  }
+
+  private static void initEduConsole(@NotNull final XDebugSession session,
+                                     @NotNull final RunnerLayoutUi ui) {
+    Project project = session.getProject();
+    final Sdk sdk = PythonSdkType.findPythonSdk(ModuleManager.getInstance(project).getModules()[0]);
+    final PythonDebugLanguageConsoleView view = new PythonDebugLanguageConsoleView(project, sdk);
+    final ProcessHandler processHandler = session.getDebugProcess().getProcessHandler();
+
+    view.attachToProcess(processHandler);
+    view.addMessageFilter(new PythonTracebackFilter(project));
+    view.addMessageFilter(new UrlFilter());
+
+    view.enableConsole(false);
+
+    Content eduConsole =
+      ui.createContent("EduConsole", view.getComponent(),
+                       XDebuggerBundle.message("debugger.session.tab.console.content.name"),
+                       AllIcons.Debugger.ToolConsole, view.getPreferredFocusableComponent());
+    eduConsole.setCloseable(false);
+    ui.addContent(eduConsole, 0, PlaceInGrid.right, false);
+
+    PyDebugProcess process = (PyDebugProcess)session.getDebugProcess();
+    PyDebugRunner.initDebugConsoleView(project, process, view, processHandler, session);
+
+    patchLeftToolbar(session, ui);
+  }
+
+  private static void patchLeftToolbar(@NotNull XDebugSession session, @NotNull RunnerLayoutUi ui) {
+    DefaultActionGroup newLeftToolbar = new DefaultActionGroup();
+
+    DefaultActionGroup firstGroup = new DefaultActionGroup();
+    addActionToGroup(firstGroup, XDebuggerActions.RESUME);
+    addActionToGroup(firstGroup, IdeActions.ACTION_STOP_PROGRAM);
+    newLeftToolbar.addAll(firstGroup);
+
+    newLeftToolbar.addSeparator();
+
+    Executor executor = PyEduDebugExecutor.getInstance();
+    newLeftToolbar.add(new CloseAction(executor, session.getRunContentDescriptor(), session.getProject()));
+    //TODO: return proper helpID
+    newLeftToolbar.add(new ContextHelpAction(executor.getHelpId()));
+
+    ui.getOptions().setLeftToolbar(newLeftToolbar, ActionPlaces.DEBUGGER_TOOLBAR);
+  }
+
+  private static void addActionToGroup(DefaultActionGroup group, String actionId) {
+    AnAction action = ActionManager.getInstance().getAction(actionId);
+    if (action != null) {
+      action.getTemplatePresentation().setEnabled(true);
+      group.add(action, Constraints.LAST);
+    }
+  }
+
+  @Nullable
+  private static Content findContent(ContentManager manager, String name) {
+    for (Content content : manager.getContents()) {
+      if (content.getDisplayName().equals(name)) {
+        return content;
+      }
+    }
+    return null;
+  }
+}
\ No newline at end of file
diff --git a/python/edu/src/com/jetbrains/python/edu/debugger/PyEduStackFrame.java b/python/edu/src/com/jetbrains/python/edu/debugger/PyEduStackFrame.java
new file mode 100644 (file)
index 0000000..743a696
--- /dev/null
@@ -0,0 +1,113 @@
+package com.jetbrains.python.edu.debugger;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.ColoredTextContainer;
+import com.intellij.ui.SimpleTextAttributes;
+import com.intellij.xdebugger.XSourcePosition;
+import com.intellij.xdebugger.frame.XCompositeNode;
+import com.intellij.xdebugger.frame.XValue;
+import com.intellij.xdebugger.frame.XValueChildrenList;
+import com.intellij.xdebugger.frame.XValueGroup;
+import com.jetbrains.python.debugger.PyFrameAccessor;
+import com.jetbrains.python.debugger.PyStackFrame;
+import com.jetbrains.python.debugger.PyStackFrameInfo;
+import icons.PythonEducationalIcons;
+import icons.PythonPsiApiIcons;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.util.HashMap;
+import java.util.Map;
+
+public class PyEduStackFrame extends PyStackFrame {
+  public static final String MODULE = "<module>";
+  public static final String GLOBAL_FRAME = "Globals";
+  public static final String DOUBLE_UNDERSCORE = "__";
+  public static final String BUILTINS_VALUE_NAME = "__builtins__";
+
+  private final PyStackFrameInfo myFrameInfo;
+  private final XSourcePosition myPosition;
+
+  public PyEduStackFrame(@NotNull Project project,
+                         @NotNull PyFrameAccessor debugProcess,
+                         @NotNull PyStackFrameInfo frameInfo,
+                         XSourcePosition position) {
+    super(project, debugProcess, frameInfo, position);
+    myFrameInfo = frameInfo;
+    myPosition = position;
+  }
+
+  @Override
+  public void customizePresentation(@NotNull ColoredTextContainer component) {
+    if (myPosition == null) {
+      component.append("<frame not available>", SimpleTextAttributes.GRAY_ATTRIBUTES);
+      return;
+    }
+    final VirtualFile file = myPosition.getFile();
+    String frameName = myFrameInfo.getName();
+    component.setIcon(MODULE.equals(frameName) ? PythonPsiApiIcons.PythonFile : PythonEducationalIcons.Field);
+    if (MODULE.equals(frameName)) {
+      component.append(GLOBAL_FRAME, SimpleTextAttributes.REGULAR_ATTRIBUTES);
+      component.append(" (" + file.getName() + ")", getGrayAttributes(SimpleTextAttributes.REGULAR_ATTRIBUTES));
+    }
+    else {
+      component.append(frameName, SimpleTextAttributes.REGULAR_ATTRIBUTES);
+    }
+  }
+
+  @Override
+  protected void addChildren(@NotNull final XCompositeNode node,
+                             @Nullable final XValueChildrenList children) {
+    if (children == null) {
+      node.addChildren(XValueChildrenList.EMPTY, true);
+      return;
+    }
+    final Map<String, XValue> specialValues = new HashMap<String, XValue>();
+    XValueChildrenList filteredChildren = new XValueChildrenList();
+    for (int i = 0; i < children.size(); i++) {
+      String name = children.getName(i);
+      XValue value = children.getValue(i);
+      if (name.startsWith(DOUBLE_UNDERSCORE) && name.endsWith(DOUBLE_UNDERSCORE)) {
+        specialValues.put(name, value);
+        continue;
+      }
+      filteredChildren.add(name, value);
+    }
+    node.addChildren(filteredChildren, specialValues.isEmpty());
+    if (specialValues.isEmpty()) {
+      return;
+    }
+    addSpecialVars(node, specialValues);
+  }
+
+  private static void addSpecialVars(@NotNull XCompositeNode node, Map<String, XValue> specialValues) {
+      XValue builtins = specialValues.get(BUILTINS_VALUE_NAME);
+      if (builtins != null) {
+        specialValues.remove(BUILTINS_VALUE_NAME);
+        node.addChildren(XValueChildrenList.singleton("Builtins", builtins), false);
+      }
+      node.addChildren(XValueChildrenList.bottomGroup(createSpecialVarsGroup(specialValues)), true);
+  }
+
+  @NotNull
+  private static XValueGroup createSpecialVarsGroup(final Map<String, XValue> specialValues) {
+    return new XValueGroup("Special Variables") {
+      @Nullable
+      @Override
+      public Icon getIcon() {
+        return PythonEducationalIcons.SpecialVar;
+      }
+
+      @Override
+      public void computeChildren(@NotNull XCompositeNode node) {
+        XValueChildrenList list = new XValueChildrenList();
+        for (Map.Entry<String, XValue> entry : specialValues.entrySet()) {
+          list.add(entry.getKey(), entry.getValue());
+        }
+        node.addChildren(list, true);
+      }
+    };
+  }
+}
index eb04c491c0ef9e21c7d52610494adda610ff0636..32089560e22f1e315e08ef5b3681a6aff7a09d32 100644 (file)
@@ -56,6 +56,11 @@ public abstract class CCRunTestsAction extends AnAction {
 
   @Override
   public void update(@NotNull AnActionEvent e) {
+    if (!CCProjectService.setCCActionAvailable(e)) {
+      EduUtils.enableAction(e, false);
+      return;
+    }
+
     Presentation presentation = e.getPresentation();
     presentation.setText("");
     presentation.setVisible(false);
index 0ab8651f56fd90ee81a57aab2644c802773d3ae7..83e44db7ff2c4149cab2e2d85bf1df0f48e71f49 100644 (file)
@@ -668,6 +668,12 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
     }
   }
 
+  public void addTemporaryBreakpoint(String typeId, String file, int line) {
+    if (isConnected()) {
+      myDebugger.setTempBreakpoint(typeId, file, line);
+    }
+  }
+
   public void removeBreakpoint(final PySourcePosition position) {
     XLineBreakpoint breakpoint = myRegisteredBreakpoints.get(position);
     if (breakpoint != null) {
@@ -703,7 +709,7 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
 
       final List<PyStackFrameInfo> frames = threadInfo.getFrames();
       if (frames != null) {
-        final PySuspendContext suspendContext = new PySuspendContext(this, threadInfo);
+        final PySuspendContext suspendContext = createSuspendContext(threadInfo);
 
         XBreakpoint<?> breakpoint = null;
         if (threadInfo.isStopOnBreakpoint()) {
@@ -733,6 +739,11 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
     }
   }
 
+  @NotNull
+  protected PySuspendContext createSuspendContext(PyThreadInfo threadInfo) {
+    return new PySuspendContext(this, threadInfo);
+  }
+
   @Override
   public void threadResumed(final PyThreadInfo threadInfo) {
     mySuspendedThreads.remove(threadInfo);
index cfe7701e5e0c9794565b0905966da3cc876fc73c..728936052ced33e8aa088c698a9956ed15ffb130 100644 (file)
@@ -18,6 +18,7 @@ package com.jetbrains.python.debugger;
 import com.google.common.collect.Lists;
 import com.intellij.execution.ExecutionException;
 import com.intellij.execution.ExecutionResult;
+import com.intellij.execution.Executor;
 import com.intellij.execution.configurations.*;
 import com.intellij.execution.console.LanguageConsoleBuilder;
 import com.intellij.execution.executors.DefaultDebugExecutor;
@@ -81,8 +82,8 @@ public class PyDebugRunner extends GenericProgramRunner {
            ((AbstractPythonRunConfiguration)profile).canRunWithCoverage();
   }
 
-  @Override
-  protected RunContentDescriptor doExecute(@NotNull RunProfileState state, @NotNull final ExecutionEnvironment environment) throws ExecutionException {
+
+  protected XDebugSession createSession(@NotNull RunProfileState state, @NotNull final ExecutionEnvironment environment) throws ExecutionException {
     FileDocumentManager.getInstance().saveAllDocuments();
 
     final PythonCommandLineState pyState = (PythonCommandLineState)state;
@@ -91,24 +92,39 @@ public class PyDebugRunner extends GenericProgramRunner {
     RunProfile profile = environment.getRunProfile();
     final ExecutionResult result = pyState.execute(environment.getExecutor(), createCommandLinePatchers(environment.getProject(), pyState, profile, serverLocalPort));
 
-    final XDebugSession session = XDebuggerManager.getInstance(environment.getProject()).
+    return XDebuggerManager.getInstance(environment.getProject()).
       startSession(environment, new XDebugProcessStarter() {
         @Override
         @NotNull
         public XDebugProcess start(@NotNull final XDebugSession session) {
           PyDebugProcess pyDebugProcess =
-            new PyDebugProcess(session, serverSocket, result.getExecutionConsole(), result.getProcessHandler(),
-                               pyState.isMultiprocessDebug());
+            createDebugProcess(session, serverSocket, result, pyState);
 
           createConsoleCommunicationAndSetupActions(environment.getProject(), result, pyDebugProcess, session);
-
-
           return pyDebugProcess;
         }
       });
+  }
+
+  @NotNull
+  protected PyDebugProcess createDebugProcess(@NotNull XDebugSession session,
+                                              ServerSocket serverSocket,
+                                              ExecutionResult result,
+                                              PythonCommandLineState pyState) {
+    return new PyDebugProcess(session, serverSocket, result.getExecutionConsole(), result.getProcessHandler(),
+                              pyState.isMultiprocessDebug());
+  }
+
+  @Override
+  protected RunContentDescriptor doExecute(@NotNull RunProfileState state, @NotNull final ExecutionEnvironment environment) throws ExecutionException {
+    XDebugSession session = createSession(state, environment);
+    initSession(session, state, environment.getExecutor());
     return session.getRunContentDescriptor();
   }
 
+  protected void initSession(XDebugSession session, RunProfileState state, Executor executor) {
+  }
+
   public static int findIndex(List<String> paramList, String paramName) {
     for (int i = 0; i < paramList.size(); i++) {
       if (paramName.equals(paramList.get(i))) {
index 9f1f0234694c12a36e4ae1032bb5855042c9ac74..4ada2e7fc112d487204bd7c14da4711f288e311d 100644 (file)
@@ -31,6 +31,7 @@ import com.intellij.xdebugger.frame.XCompositeNode;
 import com.intellij.xdebugger.frame.XStackFrame;
 import com.intellij.xdebugger.frame.XValueChildrenList;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 
 public class PyStackFrame extends XStackFrame {
@@ -96,11 +97,15 @@ public class PyStackFrame extends XStackFrame {
       return attributes;
     }
     else {
-      return (attributes.getStyle() & SimpleTextAttributes.STYLE_ITALIC) != 0
-             ? SimpleTextAttributes.GRAY_ITALIC_ATTRIBUTES : SimpleTextAttributes.GRAYED_ATTRIBUTES;
+      return getGrayAttributes(attributes);
     }
   }
 
+  protected static SimpleTextAttributes getGrayAttributes(SimpleTextAttributes attributes) {
+    return (attributes.getStyle() & SimpleTextAttributes.STYLE_ITALIC) != 0
+           ? SimpleTextAttributes.GRAY_ITALIC_ATTRIBUTES : SimpleTextAttributes.GRAYED_ATTRIBUTES;
+  }
+
   @Override
   public void computeChildren(@NotNull final XCompositeNode node) {
     if (node.isObsolete()) return;
@@ -108,9 +113,9 @@ public class PyStackFrame extends XStackFrame {
       @Override
       public void run() {
         try {
-          final XValueChildrenList values = myDebugProcess.loadFrame();
+          XValueChildrenList values = myDebugProcess.loadFrame();
           if (!node.isObsolete()) {
-            node.addChildren(values != null ? values : XValueChildrenList.EMPTY, true);
+            addChildren(node, values);
           }
         }
         catch (PyDebuggerException e) {
@@ -123,6 +128,10 @@ public class PyStackFrame extends XStackFrame {
     });
   }
 
+  protected void addChildren(@NotNull final XCompositeNode node, @Nullable final XValueChildrenList children) {
+    node.addChildren(children != null ? children : XValueChildrenList.EMPTY, true);
+  }
+
   public String getThreadId() {
     return myFrameInfo.getThreadId();
   }