diff --git a/python/edu/gen/icons/PythonEducationalIcons.java b/python/edu/gen/icons/PythonEducationalIcons.java
new file mode 100644
index 000000000000..fb04abc3c3eb
--- /dev/null
+++ b/python/edu/gen/icons/PythonEducationalIcons.java
@@ -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
index 000000000000..c6ebcdbff50a
--- /dev/null
+++ b/python/edu/resources/icon-robots.txt
@@ -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
index 000000000000..f4a6122dbe7b
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
index 000000000000..8ad521ffe291
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
index 000000000000..25612ae2077c
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
index 000000000000..343e0b707e03
Binary files /dev/null and b/python/edu/resources/icons/com/jetbrains/edu/specialVar@2x.png differ
diff --git a/python/edu/src/META-INF/PyCharmEduPlugin.xml b/python/edu/src/META-INF/PyCharmEduPlugin.xml
index b2ee67e705e0..65eb9ca1bed0 100644
--- a/python/edu/src/META-INF/PyCharmEduPlugin.xml
+++ b/python/edu/src/META-INF/PyCharmEduPlugin.xml
@@ -29,17 +29,12 @@
+
+
+
-
-
-
-
-
diff --git a/python/edu/src/com/jetbrains/python/edu/PyCharmEduInitialConfigurator.java b/python/edu/src/com/jetbrains/python/edu/PyCharmEduInitialConfigurator.java
index 18197fe169d7..d8c78bb1deda 100644
--- a/python/edu/src/com/jetbrains/python/edu/PyCharmEduInitialConfigurator.java
+++ b/python/edu/src/com/jetbrains/python/edu/PyCharmEduInitialConfigurator.java
@@ -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 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
index 000000000000..f9f9a6883e5e
--- /dev/null
+++ b/python/edu/src/com/jetbrains/python/edu/PyDebugCurrentFile.java
@@ -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());
+ }
+}
+
diff --git a/python/edu/src/com/jetbrains/python/edu/PyExecuteFileExtensionPoint.java b/python/edu/src/com/jetbrains/python/edu/PyExecuteFileExtensionPoint.java
index 7f5a077c2650..d0c54fe86b3b 100644
--- a/python/edu/src/com/jetbrains/python/edu/PyExecuteFileExtensionPoint.java
+++ b/python/edu/src/com/jetbrains/python/edu/PyExecuteFileExtensionPoint.java
@@ -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 EP_NAME = ExtensionPointName.create("Edu.executeFile");
- @NotNull
+ @Nullable
AnAction getRunAction();
}
diff --git a/python/edu/src/com/jetbrains/python/edu/PyExecuteFileLineMarkerProvider.java b/python/edu/src/com/jetbrains/python/edu/PyExecuteFileLineMarkerProvider.java
index 0c0208b1f144..e0c76446ce4e 100644
--- a/python/edu/src/com/jetbrains/python/edu/PyExecuteFileLineMarkerProvider.java
+++ b/python/edu/src/com/jetbrains/python/edu/PyExecuteFileLineMarkerProvider.java
@@ -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 elements, @NotNull Collection result) {
- for (PsiElement element : elements) {
- if (isFirstCodeLine(element)) {
- final LineMarkerInfo markerInfo = new LineMarkerInfo(
- element, element.getTextRange(), AllIcons.Actions.Execute, Pass.UPDATE_OVERRIDEN_MARKERS,
- new Function() {
- @Override
- public String fun(PsiElement e) {
- return "Execute '" + e.getContainingFile().getName() + "'";
- }
- }, null,
- GutterIconRenderer.Alignment.RIGHT) {
+ if (elements.isEmpty()) {
+ return;
+ }
+ PsiElement element = elements.get(0).getContainingFile();
+ final RunContextAction runAction = new RunContextAction(DefaultRunExecutor.getRunExecutorInstance());
+ final LineMarkerInfo markerInfo = new LineMarkerInfo(
+ element, 0, AllIcons.Actions.Execute, Pass.UPDATE_OVERRIDEN_MARKERS,
+ new Function() {
+ @Override
+ public String fun(PsiElement e) {
+ return "Execute '" + e.getContainingFile().getName() + "'";
+ }
+ }, null,
+ GutterIconRenderer.Alignment.RIGHT) {
+ @Nullable
+ @Override
+ public GutterIconRenderer createGutterRenderer() {
+ return new LineMarkerGutterIconRenderer(this) {
+ @Override
+ public AnAction getClickAction() {
+ return runAction;
+ }
+
@Nullable
@Override
- public GutterIconRenderer createGutterRenderer() {
- return new LineMarkerGutterIconRenderer(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 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;
}
-
- @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;
- }
- };
+ 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
index 4d30fa29a3b3..000000000000
--- a/python/edu/src/com/jetbrains/python/edu/PyRunCurrentFileAction.java
+++ /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
index 000000000000..0f74c0277511
--- /dev/null
+++ b/python/edu/src/com/jetbrains/python/edu/debugger/PyEduConsoleInputFilterProvider.java
@@ -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> 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
index 000000000000..4c05b0df8d17
--- /dev/null
+++ b/python/edu/src/com/jetbrains/python/edu/debugger/PyEduDebugExecutor.java
@@ -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
index 000000000000..0904ec772846
--- /dev/null
+++ b/python/edu/src/com/jetbrains/python/edu/debugger/PyEduDebugProcess.java
@@ -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(filterFrames(threadInfo.getFrames())));
+ return new PySuspendContext(this, threadInfo);
+ }
+
+ public Collection filterFrames(@Nullable List frames) {
+ if (frames == null) {
+ return Collections.emptyList();
+ }
+ final String debugger = PythonHelpersLocator.getHelperPath(PyDebugRunner.DEBUGGER_MAIN);
+ return Collections2.filter(frames, new Predicate() {
+ @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
index 000000000000..c8cb537f4edb
--- /dev/null
+++ b/python/edu/src/com/jetbrains/python/edu/debugger/PyEduDebugRunner.java
@@ -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
index 000000000000..743a696144e1
--- /dev/null
+++ b/python/edu/src/com/jetbrains/python/edu/debugger/PyEduStackFrame.java
@@ -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 = "";
+ 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("", 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 specialValues = new HashMap();
+ 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 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 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 entry : specialValues.entrySet()) {
+ list.add(entry.getKey(), entry.getValue());
+ }
+ node.addChildren(list, true);
+ }
+ };
+ }
+}
diff --git a/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCRunTestsAction.java b/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCRunTestsAction.java
index eb04c491c0ef..32089560e22f 100644
--- a/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCRunTestsAction.java
+++ b/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCRunTestsAction.java
@@ -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);
diff --git a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java
index 0ab8651f56fd..83e44db7ff2c 100644
--- a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java
+++ b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java
@@ -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 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);
diff --git a/python/src/com/jetbrains/python/debugger/PyDebugRunner.java b/python/src/com/jetbrains/python/debugger/PyDebugRunner.java
index cfe7701e5e0c..728936052ced 100644
--- a/python/src/com/jetbrains/python/debugger/PyDebugRunner.java
+++ b/python/src/com/jetbrains/python/debugger/PyDebugRunner.java
@@ -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 paramList, String paramName) {
for (int i = 0; i < paramList.size(); i++) {
if (paramName.equals(paramList.get(i))) {
diff --git a/python/src/com/jetbrains/python/debugger/PyStackFrame.java b/python/src/com/jetbrains/python/debugger/PyStackFrame.java
index 9f1f0234694c..4ada2e7fc112 100644
--- a/python/src/com/jetbrains/python/debugger/PyStackFrame.java
+++ b/python/src/com/jetbrains/python/debugger/PyStackFrame.java
@@ -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();
}