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(); }