always stop on the first line in debug
authorliana.bakradze <liana.bakradze@jetbrains.com>
Tue, 21 Jul 2015 10:40:58 +0000 (13:40 +0300)
committerliana.bakradze <liana.bakradze@jetbrains.com>
Tue, 21 Jul 2015 10:40:58 +0000 (13:40 +0300)
Added custom executor to make canRun method in pyDebugRunner return false. Also it can be useful to replace debug tool window with custom tool window

python/edu/src/META-INF/PyCharmEduPlugin.xml
python/edu/src/com/jetbrains/python/edu/PyCharmEduInitialConfigurator.java
python/edu/src/com/jetbrains/python/edu/PyDebugCurrentFileAction.java [new file with mode: 0644]
python/edu/src/com/jetbrains/python/edu/PyDebugFileLineMarkerProvider.java [new file with mode: 0644]
python/edu/src/com/jetbrains/python/edu/PyEduDebugExecutor.java [new file with mode: 0644]
python/edu/src/com/jetbrains/python/edu/PyEduDebugRunner.java [new file with mode: 0644]
python/edu/src/com/jetbrains/python/edu/PyEduUtils.java [new file with mode: 0644]
python/edu/src/com/jetbrains/python/edu/PyExecuteFileLineMarkerProvider.java
python/edu/src/com/jetbrains/python/edu/PyRunConfigurationForFileAction.java [new file with mode: 0644]
python/edu/src/com/jetbrains/python/edu/PyRunCurrentFileAction.java
python/src/com/jetbrains/python/debugger/PyDebugRunner.java

index d19b2cca1a676e97b9a77456bb540465004bd0e9..38415ba90f2d527bb7af639a53feeb9501f55a7b 100644 (file)
   </extensionPoints>
 
   <extensions defaultExtensionNs="com.intellij">
+      <codeInsight.lineMarkerProvider language="Python" implementationClass="com.jetbrains.python.edu.PyDebugFileLineMarkerProvider"/>
       <codeInsight.lineMarkerProvider language="Python" implementationClass="com.jetbrains.python.edu.PyExecuteFileLineMarkerProvider"/>
+      <programRunner implementation="com.jetbrains.python.edu.PyEduDebugRunner"/>
+      <executor implementation="com.jetbrains.python.edu.PyEduDebugExecutor" order="first,after run"/>
   </extensions>
 
   <actions>
index e8476ac7f78f8a3d7776bb3ed208e8798739894f..e0dc6544aaa0b95448c2727f0ba83161b5e2893f 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.RecentProjectsManager;
@@ -27,6 +30,9 @@ import com.intellij.ide.ui.UISettings;
 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.ex.EditorSettingsExternalizable;
 import com.intellij.openapi.extensions.Extensions;
@@ -210,6 +216,9 @@ 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);
     for (SelectInTarget target : Extensions.getExtensions(SelectInTarget.EP_NAME, project)) {
       if (ToolWindowId.FAVORITES_VIEW.equals(target.getToolWindowId())) {
         Extensions.getArea(project).getExtensionPoint(SelectInTarget.EP_NAME).unregisterExtension(target);
@@ -217,6 +226,15 @@ public class PyCharmEduInitialConfigurator {
     }
   }
 
+  private static void unregisterAction(String actionId, String groupId) {
+    ActionManager actionManager = ActionManager.getInstance();
+    AnAction action = actionManager.getAction(actionId);
+    if (action != null) {
+      ((DefaultActionGroup)actionManager.getAction(groupId)).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/PyDebugCurrentFileAction.java b/python/edu/src/com/jetbrains/python/edu/PyDebugCurrentFileAction.java
new file mode 100644 (file)
index 0000000..8e1f394
--- /dev/null
@@ -0,0 +1,22 @@
+package com.jetbrains.python.edu;
+
+import com.intellij.execution.RunnerAndConfigurationSettings;
+import com.intellij.execution.runners.ExecutionUtil;
+import com.intellij.icons.AllIcons;
+
+public class PyDebugCurrentFileAction extends PyRunConfigurationForFileAction {
+
+  public PyDebugCurrentFileAction() {
+    getTemplatePresentation().setIcon(AllIcons.Actions.StartDebugger);
+  }
+
+  @Override
+  protected String getConfigurationType() {
+    return "Debug";
+  }
+
+  @Override
+  protected void runConfiguration(RunnerAndConfigurationSettings configuration) {
+    ExecutionUtil.runConfiguration(configuration, PyEduDebugExecutor.getInstance());
+  }
+}
diff --git a/python/edu/src/com/jetbrains/python/edu/PyDebugFileLineMarkerProvider.java b/python/edu/src/com/jetbrains/python/edu/PyDebugFileLineMarkerProvider.java
new file mode 100644 (file)
index 0000000..94edd56
--- /dev/null
@@ -0,0 +1,53 @@
+package com.jetbrains.python.edu;
+
+import com.intellij.codeHighlighting.Pass;
+import com.intellij.codeInsight.daemon.GutterIconNavigationHandler;
+import com.intellij.codeInsight.daemon.LineMarkerInfo;
+import com.intellij.codeInsight.daemon.LineMarkerProvider;
+import com.intellij.execution.actions.ConfigurationContext;
+import com.intellij.icons.AllIcons;
+import com.intellij.ide.DataManager;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.markup.GutterIconRenderer;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.util.PsiUtilBase;
+import com.intellij.util.Function;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.awt.event.MouseEvent;
+import java.util.Collection;
+import java.util.List;
+
+public class PyDebugFileLineMarkerProvider implements LineMarkerProvider {
+  @Nullable
+  @Override
+  public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement element) {
+    return null;
+  }
+
+  @Override
+  public void collectSlowLineMarkers(@NotNull List<PsiElement> elements, @NotNull Collection<LineMarkerInfo> result) {
+     for (final PsiElement element : elements) {
+      if (PyEduUtils.isFirstCodeLine(element)) {
+        result.add(new LineMarkerInfo<PsiElement>(element, element.getTextRange(), AllIcons.Actions.StartDebugger,
+                                                  Pass.UPDATE_OVERRIDEN_MARKERS,
+                                                  new Function<PsiElement, String>() {
+                                                    @Override
+                                                    public String fun(PsiElement e) {
+                                                      return "Debug '" + e.getContainingFile().getName() + "'";
+                                                    }
+                                                  }, new GutterIconNavigationHandler<PsiElement>() {
+          @Override
+          public void navigate(MouseEvent e, PsiElement elt) {
+            final Editor editor = PsiUtilBase.findEditor(elt);
+            assert editor != null;
+            ConfigurationContext configurationContext =
+              ConfigurationContext.getFromContext(DataManager.getInstance().getDataContext(editor.getComponent()));
+            new PyDebugCurrentFileAction().run(configurationContext);
+          }
+        }, GutterIconRenderer.Alignment.RIGHT));
+      }
+    }
+  }
+}
diff --git a/python/edu/src/com/jetbrains/python/edu/PyEduDebugExecutor.java b/python/edu/src/com/jetbrains/python/edu/PyEduDebugExecutor.java
new file mode 100644 (file)
index 0000000..69c39f6
--- /dev/null
@@ -0,0 +1,31 @@
+package com.jetbrains.python.edu;
+
+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 "EduDebugClass";
+  }
+
+  @NotNull
+  @Override
+  public String getStartActionText() {
+    return "Step Through ";
+  }
+}
diff --git a/python/edu/src/com/jetbrains/python/edu/PyEduDebugRunner.java b/python/edu/src/com/jetbrains/python/edu/PyEduDebugRunner.java
new file mode 100644 (file)
index 0000000..169f1ac
--- /dev/null
@@ -0,0 +1,64 @@
+package com.jetbrains.python.edu;
+
+import com.intellij.codeInsight.daemon.impl.CollectHighlightsUtil;
+import com.intellij.execution.configurations.RunProfile;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.intellij.xdebugger.XDebuggerManager;
+import com.intellij.xdebugger.breakpoints.XBreakpointManager;
+import com.intellij.xdebugger.breakpoints.XBreakpointProperties;
+import com.intellij.xdebugger.impl.breakpoints.LineBreakpointState;
+import com.intellij.xdebugger.impl.breakpoints.XBreakpointManagerImpl;
+import com.intellij.xdebugger.impl.breakpoints.XLineBreakpointImpl;
+import com.jetbrains.python.debugger.PyDebugProcess;
+import com.jetbrains.python.debugger.PyDebugRunner;
+import com.jetbrains.python.debugger.PyLineBreakpointType;
+import com.jetbrains.python.debugger.PySourcePosition;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.List;
+
+public class PyEduDebugRunner extends PyDebugRunner {
+
+  @Override
+  public boolean canRun(@NotNull String executorId, @NotNull RunProfile profile) {
+    return executorId.equals(PyEduDebugExecutor.ID);
+  }
+
+  @Override
+  protected void initDebugProcess(String name, PyDebugProcess pyDebugProcess) {
+    VirtualFile file = VfsUtil.findFileByIoFile(new File(name), true);
+    assert file != null;
+
+    final Project project = pyDebugProcess.getProject();
+    PsiFile psiFile = PsiManager.getInstance(project).findFile(file);
+
+    assert psiFile != null;
+
+    List<PsiElement> psiElements = CollectHighlightsUtil.getElementsInRange(psiFile, 0, psiFile.getTextLength());
+    for (PsiElement element : psiElements) {
+      if (PyEduUtils.isFirstCodeLine(element)) {
+        int offset = element.getTextRange().getStartOffset();
+        Document document = FileDocumentManager.getInstance().getDocument(file);
+        assert document != null;
+        int line = document.getLineNumber(offset) + 1;
+        PySourcePosition sourcePosition = pyDebugProcess.getPositionConverter().create(file.getPath(), line);
+        XBreakpointManager breakpointManager = XDebuggerManager.getInstance(project).getBreakpointManager();
+        PyLineBreakpointType type = new PyLineBreakpointType();
+        XBreakpointProperties properties = type.createBreakpointProperties(file, line);
+        LineBreakpointState<XBreakpointProperties>
+          breakpointState = new LineBreakpointState<XBreakpointProperties>(true, type.getId(), file.getUrl(), line, false, file.getTimeStamp());
+        pyDebugProcess.addBreakpoint(sourcePosition, new XLineBreakpointImpl<XBreakpointProperties>(type,
+                                                                             ((XBreakpointManagerImpl)breakpointManager),
+                                                                             properties, breakpointState));
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/python/edu/src/com/jetbrains/python/edu/PyEduUtils.java b/python/edu/src/com/jetbrains/python/edu/PyEduUtils.java
new file mode 100644 (file)
index 0000000..bdc0d40
--- /dev/null
@@ -0,0 +1,33 @@
+package com.jetbrains.python.edu;
+
+import com.intellij.psi.PsiComment;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiWhiteSpace;
+import com.jetbrains.python.psi.PyFile;
+import com.jetbrains.python.psi.PyImportStatement;
+import com.jetbrains.python.psi.PyStatement;
+
+public class PyEduUtils {
+  public 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);
+  }
+}
index 0c0208b1f144edb5763f788450d4e172b8c8158e..e22713dbfa26d0f326182398a7bd958238ddf002 100644 (file)
@@ -11,14 +11,9 @@ 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;
 
@@ -38,7 +33,7 @@ public class PyExecuteFileLineMarkerProvider implements LineMarkerProvider {
   @Override
   public void collectSlowLineMarkers(@NotNull List<PsiElement> elements, @NotNull Collection<LineMarkerInfo> result) {
     for (PsiElement element : elements) {
-      if (isFirstCodeLine(element)) {
+      if (PyEduUtils.isFirstCodeLine(element)) {
         final LineMarkerInfo<PsiElement> markerInfo = new LineMarkerInfo<PsiElement>(
           element, element.getTextRange(), AllIcons.Actions.Execute, Pass.UPDATE_OVERRIDEN_MARKERS,
           new Function<PsiElement, String>() {
@@ -100,27 +95,4 @@ public class PyExecuteFileLineMarkerProvider implements LineMarkerProvider {
       }
     }
   }
-
-  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);
-  }
 }
diff --git a/python/edu/src/com/jetbrains/python/edu/PyRunConfigurationForFileAction.java b/python/edu/src/com/jetbrains/python/edu/PyRunConfigurationForFileAction.java
new file mode 100644 (file)
index 0000000..619ebe1
--- /dev/null
@@ -0,0 +1,51 @@
+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.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;
+
+public abstract class PyRunConfigurationForFileAction extends AnAction {
+
+  @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(getConfigurationType() + " '" + location.getPsiElement().getContainingFile().getName() + "'");
+    }
+  }
+
+  protected abstract String getConfigurationType();
+
+  @Override
+  public void actionPerformed(AnActionEvent e) {
+    final ConfigurationContext context = ConfigurationContext.getFromContext(e.getDataContext());
+
+    run(context);
+  }
+
+  public 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);
+
+    runConfiguration(configuration);
+  }
+
+  protected abstract void runConfiguration(RunnerAndConfigurationSettings configuration);
+}
index 4d30fa29a3b3d281878c3db05eeac8554bcab601..160bd757884de0d8570a714c3880bc4185294977 100644 (file)
@@ -1,56 +1,26 @@
 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 class PyRunCurrentFileAction extends PyRunConfigurationForFileAction {
+
   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() + "'");
-    }
+  protected String getConfigurationType() {
+    return "Run";
   }
 
   @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);
-
+  protected void runConfiguration(RunnerAndConfigurationSettings configuration) {
     ExecutionUtil.runConfiguration(configuration, DefaultRunExecutor.getRunExecutorInstance());
   }
 }
index cfe7701e5e0c9794565b0905966da3cc876fc73c..abd7f429e5c7fe211684343902f097048cc355fd 100644 (file)
@@ -44,6 +44,7 @@ import com.jetbrains.python.console.pydev.ConsoleCommunicationListener;
 import com.jetbrains.python.run.AbstractPythonRunConfiguration;
 import com.jetbrains.python.run.CommandLinePatcher;
 import com.jetbrains.python.run.PythonCommandLineState;
+import com.jetbrains.python.run.PythonRunConfiguration;
 import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -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,7 +92,7 @@ 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
@@ -101,12 +102,18 @@ public class PyDebugRunner extends GenericProgramRunner {
                                pyState.isMultiprocessDebug());
 
           createConsoleCommunicationAndSetupActions(environment.getProject(), result, pyDebugProcess, session);
-
-
+          initDebugProcess(((PythonRunConfiguration)environment.getRunProfile()).getScriptName(), pyDebugProcess);
           return pyDebugProcess;
         }
       });
-    return session.getRunContentDescriptor();
+  }
+
+  protected void initDebugProcess(String name, PyDebugProcess pyDebugProcess) {
+  }
+
+  @Override
+  protected RunContentDescriptor doExecute(@NotNull RunProfileState state, @NotNull final ExecutionEnvironment environment) throws ExecutionException {
+    return createSession(state, environment).getRunContentDescriptor();
   }
 
   public static int findIndex(List<String> paramList, String paramName) {