diff --git a/python/edu/course-creator/course-creator.iml b/python/edu/course-creator/course-creator.iml
index 3667e3d0e606..18baaf4ed068 100644
--- a/python/edu/course-creator/course-creator.iml
+++ b/python/edu/course-creator/course-creator.iml
@@ -14,6 +14,6 @@
+
-
-
+
\ No newline at end of file
diff --git a/python/edu/course-creator/resources/META-INF/plugin.xml b/python/edu/course-creator/resources/META-INF/plugin.xml
index 2ef84014fe49..7173f9d1c15b 100644
--- a/python/edu/course-creator/resources/META-INF/plugin.xml
+++ b/python/edu/course-creator/resources/META-INF/plugin.xml
@@ -24,12 +24,15 @@
-
+
+
+
+
@@ -74,7 +77,7 @@
-
+
\ No newline at end of file
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCRefactoringElementListenerProvider.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCRefactoringElementListenerProvider.java
index 448cec94e2c4..9ff6aa77fd44 100644
--- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCRefactoringElementListenerProvider.java
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCRefactoringElementListenerProvider.java
@@ -24,6 +24,7 @@ import com.intellij.refactoring.listeners.RefactoringElementListener;
import com.intellij.refactoring.listeners.RefactoringElementListenerProvider;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.coursecreator.actions.CCRunTestsAction;
import org.jetbrains.plugins.coursecreator.format.Course;
import org.jetbrains.plugins.coursecreator.format.Lesson;
import org.jetbrains.plugins.coursecreator.format.Task;
@@ -85,7 +86,7 @@ public class CCRefactoringElementListenerProvider implements RefactoringElementL
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
- CCRunTests.clearTestEnvironment(taskDir.getVirtualFile(), taskDir.getProject());
+ CCRunTestsAction.clearTestEnvironment(taskDir.getVirtualFile(), taskDir.getProject());
}
});
Map taskFiles = task.getTaskFiles();
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCRunTests.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCRunTests.java
index 41cf74563063..cf1f7e48ea3d 100644
--- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCRunTests.java
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCRunTests.java
@@ -15,307 +15,16 @@
*/
package org.jetbrains.plugins.coursecreator;
-import com.intellij.execution.*;
-import com.intellij.execution.actions.ConfigurationContext;
-import com.intellij.execution.configurations.ConfigurationFactory;
-import com.intellij.execution.executors.DefaultRunExecutor;
-import com.intellij.icons.AllIcons;
-import com.intellij.ide.projectView.ProjectView;
import com.intellij.openapi.actionSystem.AnAction;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.actionSystem.Presentation;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.editor.Document;
-import com.intellij.openapi.fileEditor.FileDocumentManager;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.TextRange;
-import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.openapi.vfs.VirtualFileManager;
-import com.intellij.psi.PsiDirectory;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.util.PathUtil;
-import com.intellij.util.containers.HashMap;
-import com.jetbrains.python.run.PythonConfigurationType;
-import com.jetbrains.python.run.PythonRunConfiguration;
+import com.jetbrains.python.edu.PyExecuteFileExtensionPoint;
import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.jetbrains.plugins.coursecreator.actions.CCCreateCourseArchive;
-import org.jetbrains.plugins.coursecreator.format.*;
+import org.jetbrains.plugins.coursecreator.actions.CCRunTestsAction;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.Map;
+public class CCRunTests implements PyExecuteFileExtensionPoint {
-public class CCRunTests extends AnAction {
- private static final Logger LOG = Logger.getInstance(CCRunTests.class.getName());
-
- public CCRunTests() {
- getTemplatePresentation().setIcon(AllIcons.Actions.Lightning);
+ @NotNull
+ public AnAction getRunAction() {
+ return new CCRunTestsAction();
}
- @Override
- public void update(@NotNull AnActionEvent e) {
- Presentation presentation = e.getPresentation();
- final ConfigurationContext context = ConfigurationContext.getFromContext(e.getDataContext());
- Location location = context.getLocation();
- if (location == null) {
- return;
- }
- PsiElement psiElement = location.getPsiElement();
- PsiFile psiFile = psiElement.getContainingFile();
- Project project = e.getProject();
- if (project == null || psiFile == null) {
- presentation.setVisible(false);
- presentation.setEnabled(false);
- return;
- }
- final CCProjectService service = CCProjectService.getInstance(project);
- final Course course = service.getCourse();
- final PsiDirectory taskDir = psiFile.getContainingDirectory();
- final PsiDirectory lessonDir = taskDir.getParent();
- if (lessonDir == null) return;
- final Lesson lesson = course.getLesson(lessonDir.getName());
- final Task task = lesson.getTask(taskDir.getName());
- if (task == null) {
- presentation.setVisible(false);
- presentation.setEnabled(false);
- return;
- }
- TaskFile taskFile = task.getTaskFile(psiFile.getName());
- if (taskFile == null) {
- LOG.info("could not find task file");
- presentation.setVisible(false);
- presentation.setEnabled(false);
- return;
- }
- if (psiFile.getName().contains(".answer")) {
- presentation.setEnabled(true);
- presentation.setVisible(true);
- presentation.setText("Run tests from '" + psiFile.getName() + "'");
- }
- else {
- presentation.setEnabled(false);
- presentation.setVisible(false);
- }
- }
-
- public void actionPerformed(@NotNull AnActionEvent e) {
- final ConfigurationContext context = ConfigurationContext.getFromContext(e.getDataContext());
- run(context);
- }
-
- public static void run(final @NotNull ConfigurationContext context) {
- ApplicationManager.getApplication().runWriteAction(new Runnable() {
- @Override
- public void run() {
- final Project project = context.getProject();
- PsiElement location = context.getPsiLocation();
- final Course course = CCProjectService.getInstance(project).getCourse();
- if (course == null || location == null) {
- return;
- }
- PsiFile psiFile = location.getContainingFile();
- final VirtualFile virtualFile = psiFile.getVirtualFile();
- final VirtualFile taskDir = virtualFile.getParent();
- if (taskDir == null) {
- return;
- }
- final Task task = getTask(course, taskDir);
- if (task == null) {
- return;
- }
- clearTestEnvironment(taskDir, project);
- for (final Map.Entry entry : task.getTaskFiles().entrySet()) {
- final String name = entry.getKey();
- createTestEnvironment(taskDir, name, entry.getValue(), project);
- VirtualFile testFile = taskDir.findChild("tests.py");
- if (testFile == null) {
- return;
- }
- executeTests(project, virtualFile, taskDir, testFile);
- }
- }
- });
- }
-
- private static void createTestEnvironment(@NotNull final VirtualFile taskDir, final String fileName, @NotNull final TaskFile taskFile,
- @NotNull final Project project) {
- try {
- String answerFileName = FileUtil.getNameWithoutExtension(fileName) + ".answer";
- final VirtualFile answerFile = taskDir.findChild(answerFileName);
- if (answerFile == null) {
- LOG.debug("could not find answer file " + answerFileName);
- return;
- }
- ApplicationManager.getApplication().runWriteAction(new Runnable() {
- @Override
- public void run() {
- final FileDocumentManager documentManager = FileDocumentManager.getInstance();
- documentManager.saveAllDocuments();
- }
- });
- answerFile.copy(project, taskDir, fileName);
- flushWindows(taskFile, answerFile);
- createResourceFiles(answerFile, project);
- }
- catch (IOException e) {
- LOG.error(e);
- }
- }
-
- public static void clearTestEnvironment(@NotNull final VirtualFile taskDir, @NotNull final Project project) {
- try {
- VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
- if (ideaDir == null) {
- LOG.debug("idea directory doesn't exist");
- return;
- }
- VirtualFile courseResourceDir = ideaDir.findChild("course");
- if (courseResourceDir != null) {
- courseResourceDir.delete(project);
- }
- VirtualFile[] taskDirChildren = taskDir.getChildren();
- for (VirtualFile file : taskDirChildren) {
- if (file.getName().contains("_windows")) {
- file.delete(project);
- }
- if (CCProjectService.getInstance(project).isTaskFile(file)) {
- file.delete(project);
- }
- }
- }
- catch (IOException e) {
- LOG.error(e);
- }
- }
-
- private static void executeTests(@NotNull final Project project,
- @NotNull final VirtualFile virtualFile,
- @NotNull final VirtualFile taskDir,
- @NotNull final VirtualFile testFile) {
- final ConfigurationFactory factory = PythonConfigurationType.getInstance().getConfigurationFactories()[0];
- final RunnerAndConfigurationSettings settings =
- RunManager.getInstance(project).createRunConfiguration("test", factory);
-
- final PythonRunConfiguration configuration = (PythonRunConfiguration)settings.getConfiguration();
- configuration.setScriptName(testFile.getPath());
- VirtualFile userFile = taskDir.findChild(virtualFile.getNameWithoutExtension() + ".py");
- if (userFile == null) {
- return;
- }
- VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
- if (ideaDir == null) {
- return;
- }
- VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
- ProjectView.getInstance(project).refresh();
- VirtualFile courseDir = ideaDir.findChild("course");
- if (courseDir == null) {
- return;
- }
- configuration.setScriptParameters(PathUtil.toSystemDependentName(courseDir.getPath()) + " " + PathUtil.toSystemDependentName(userFile.getPath()));
- Executor executor = DefaultRunExecutor.getRunExecutorInstance();
- ProgramRunnerUtil.executeConfiguration(project, settings, executor);
- }
-
- @Nullable
- private static Task getTask(@NotNull final Course course, @NotNull final VirtualFile taskDir) {
- if (!taskDir.getName().contains("task")) {
- return null;
- }
- VirtualFile lessonDir = taskDir.getParent();
- if (lessonDir == null || !lessonDir.getName().contains("lesson")) {
- return null;
- }
- Lesson lesson = course.getLesson(lessonDir.getName());
- if (lesson == null) {
- return null;
- }
- return lesson.getTask(taskDir.getName());
- }
-
-
- //some tests could compare task files after user modifications with initial task files
- private static void createResourceFiles(@NotNull final VirtualFile file, @NotNull final Project project) {
- VirtualFile taskDir = file.getParent();
- int index = CCProjectService.getIndex(taskDir.getName(), "task");
- VirtualFile lessonDir = taskDir.getParent();
- int lessonIndex = CCProjectService.getIndex(lessonDir.getName(), "lesson");
- Course course = CCProjectService.getInstance(project).getCourse();
- if (course == null) {
- return;
- }
- VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
- assert ideaDir != null;
- try {
- VirtualFile courseResourceDir = findOrCreateDir(project, ideaDir, "course");
- VirtualFile lessonResourceDir = findOrCreateDir(project, courseResourceDir, lessonDir.getName());
- VirtualFile taskResourceDir = findOrCreateDir(project, lessonResourceDir, taskDir.getName());
- if (CCProjectService.indexIsValid(lessonIndex, course.getLessons())) {
- Lesson lesson = course.getLessons().get(lessonIndex);
- if (CCProjectService.indexIsValid(index, lesson.getTaskList())) {
- Task task = lesson.getTaskList().get(index);
- HashMap taskFilesCopy = new HashMap();
- for (Map.Entry entry : task.getTaskFiles().entrySet()) {
- CCCreateCourseArchive.createUserFile(project, taskFilesCopy, taskResourceDir, taskDir, entry);
- CCCreateCourseArchive.resetTaskFiles(taskFilesCopy);
- }
- }
- }
- }
- catch (IOException e) {
- LOG.error(e);
- }
- }
-
- private static VirtualFile findOrCreateDir(@NotNull final Project project, @NotNull final VirtualFile dir, String name) throws IOException {
- VirtualFile targetDir = dir.findChild(name);
- if (targetDir == null) {
- targetDir = dir.createChildDirectory(project, name);
- }
- return targetDir;
- }
-
- @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
- public static VirtualFile flushWindows(TaskFile taskFile, VirtualFile file) {
- VirtualFile taskDir = file.getParent();
- VirtualFile fileWindows = null;
- final Document document = FileDocumentManager.getInstance().getDocument(file);
- if (document == null) {
- LOG.debug("Couldn't flush windows");
- return null;
- }
- if (taskDir != null) {
- String name = file.getNameWithoutExtension() + "_windows";
- PrintWriter printWriter = null;
- try {
- fileWindows = taskDir.createChildData(taskFile, name);
- printWriter = new PrintWriter(new FileOutputStream(fileWindows.getPath()));
- for (TaskWindow taskWindow : taskFile.getTaskWindows()) {
- int start = taskWindow.getRealStartOffset(document);
- String windowDescription = document.getText(new TextRange(start, start + taskWindow.getReplacementLength()));
- printWriter.println("#educational_plugin_window = " + windowDescription);
- }
- ApplicationManager.getApplication().runWriteAction(new Runnable() {
- @Override
- public void run() {
- FileDocumentManager.getInstance().saveDocument(document);
- }
- });
- }
- catch (IOException e) {
- LOG.error(e);
- }
- finally {
- if (printWriter != null) {
- printWriter.close();
- }
- }
- }
- return fileWindows;
- }
}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCRunTestsLineMarker.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCRunTestsLineMarker.java
deleted file mode 100644
index a7730219cfed..000000000000
--- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCRunTestsLineMarker.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright 2000-2014 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.jetbrains.plugins.coursecreator;
-
-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.PsiComment;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.PsiWhiteSpace;
-import com.intellij.psi.util.PsiUtilBase;
-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;
-
-import java.awt.event.MouseEvent;
-import java.util.Collection;
-import java.util.List;
-
-public class CCRunTestsLineMarker implements LineMarkerProvider {
- @Nullable
- @Override
- public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement element) {
- return null;
- }
-
- @Override
- public void collectSlowLineMarkers(@NotNull List elements, @NotNull Collection result) {
- for (PsiElement element : elements) {
- if (isFirstCodeLine(element)) {
- PsiFile psiFile = element.getContainingFile();
- if (psiFile == null || !psiFile.getName().contains(".answer")) {
- continue;
- }
- result.add(new LineMarkerInfo(
- element, element.getTextRange(), AllIcons.Actions.Lightning, Pass.UPDATE_OVERRIDEN_MARKERS,
- new Function() {
- @Override
- public String fun(PsiElement e) {
- return "Run tests from file '" + e.getContainingFile().getName() + "'";
- }
- },
- new GutterIconNavigationHandler() {
- @Override
- public void navigate(MouseEvent e, PsiElement elt) {
- executeCurrentScript(elt);
- }
- },
- GutterIconRenderer.Alignment.RIGHT));
- }
- }
- }
-
- private static void executeCurrentScript(PsiElement elt) {
- Editor editor = PsiUtilBase.findEditor(elt);
- assert editor != null;
-
- final ConfigurationContext context =
- ConfigurationContext.getFromContext(DataManager.getInstance().getDataContext(editor.getComponent()));
- CCRunTests.run(context);
- }
-
- 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/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCRunTestsAction.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCRunTestsAction.java
new file mode 100644
index 000000000000..462678c477ad
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCRunTestsAction.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.coursecreator.actions;
+
+import com.intellij.execution.*;
+import com.intellij.execution.actions.ConfigurationContext;
+import com.intellij.execution.configurations.ConfigurationFactory;
+import com.intellij.execution.executors.DefaultRunExecutor;
+import com.intellij.icons.AllIcons;
+import com.intellij.ide.projectView.ProjectView;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.util.PathUtil;
+import com.intellij.util.containers.HashMap;
+import com.jetbrains.python.run.PythonConfigurationType;
+import com.jetbrains.python.run.PythonRunConfiguration;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.coursecreator.CCProjectService;
+import org.jetbrains.plugins.coursecreator.format.*;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Map;
+
+public class CCRunTestsAction extends AnAction {
+ private static final Logger LOG = Logger.getInstance(CCRunTestsAction.class.getName());
+
+ public CCRunTestsAction() {
+ getTemplatePresentation().setIcon(AllIcons.Actions.Lightning);
+ }
+
+ @Override
+ public void update(@NotNull AnActionEvent e) {
+ Presentation presentation = e.getPresentation();
+ presentation.setText("");
+ presentation.setVisible(false);
+ presentation.setEnabled(false);
+
+ final ConfigurationContext context = ConfigurationContext.getFromContext(e.getDataContext());
+ Location location = context.getLocation();
+ if (location == null) {
+ return;
+ }
+ PsiElement psiElement = location.getPsiElement();
+ PsiFile psiFile = psiElement.getContainingFile();
+ Project project = e.getProject();
+ if (project == null || psiFile == null) {
+ presentation.setVisible(false);
+ presentation.setEnabled(false);
+ return;
+ }
+ final CCProjectService service = CCProjectService.getInstance(project);
+ final Course course = service.getCourse();
+ final PsiDirectory taskDir = psiFile.getContainingDirectory();
+ final PsiDirectory lessonDir = taskDir.getParent();
+ if (lessonDir == null) return;
+ if (course == null) return;
+ final Lesson lesson = course.getLesson(lessonDir.getName());
+ final Task task = lesson.getTask(taskDir.getName());
+ if (task == null) {
+ presentation.setVisible(false);
+ presentation.setEnabled(false);
+ return;
+ }
+ TaskFile taskFile = task.getTaskFile(psiFile.getName());
+ if (taskFile == null) {
+ LOG.info("could not find task file");
+ presentation.setVisible(false);
+ presentation.setEnabled(false);
+ return;
+ }
+ if (psiFile.getName().contains(".answer")) {
+ presentation.setEnabled(true);
+ presentation.setVisible(true);
+ presentation.setText("Run tests from '" + psiFile.getName() + "'");
+ }
+ else {
+ presentation.setEnabled(false);
+ presentation.setVisible(false);
+ }
+ }
+
+ public void actionPerformed(@NotNull AnActionEvent e) {
+ final ConfigurationContext context = ConfigurationContext.getFromContext(e.getDataContext());
+ run(context);
+ }
+
+ public static void run(final @NotNull ConfigurationContext context) {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ final Project project = context.getProject();
+ PsiElement location = context.getPsiLocation();
+ final Course course = CCProjectService.getInstance(project).getCourse();
+ if (course == null || location == null) {
+ return;
+ }
+ PsiFile psiFile = location.getContainingFile();
+ final VirtualFile virtualFile = psiFile.getVirtualFile();
+ final VirtualFile taskDir = virtualFile.getParent();
+ if (taskDir == null) {
+ return;
+ }
+ final Task task = getTask(course, taskDir);
+ if (task == null) {
+ return;
+ }
+ clearTestEnvironment(taskDir, project);
+ for (final Map.Entry entry : task.getTaskFiles().entrySet()) {
+ final String name = entry.getKey();
+ createTestEnvironment(taskDir, name, entry.getValue(), project);
+ VirtualFile testFile = taskDir.findChild("tests.py");
+ if (testFile == null) {
+ return;
+ }
+ executeTests(project, virtualFile, taskDir, testFile);
+ }
+ }
+ });
+ }
+
+ private static void createTestEnvironment(@NotNull final VirtualFile taskDir, final String fileName, @NotNull final TaskFile taskFile,
+ @NotNull final Project project) {
+ try {
+ String answerFileName = FileUtil.getNameWithoutExtension(fileName) + ".answer";
+ final VirtualFile answerFile = taskDir.findChild(answerFileName);
+ if (answerFile == null) {
+ LOG.debug("could not find answer file " + answerFileName);
+ return;
+ }
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ final FileDocumentManager documentManager = FileDocumentManager.getInstance();
+ documentManager.saveAllDocuments();
+ }
+ });
+ answerFile.copy(project, taskDir, fileName);
+ flushWindows(taskFile, answerFile);
+ createResourceFiles(answerFile, project);
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ }
+
+ public static void clearTestEnvironment(@NotNull final VirtualFile taskDir, @NotNull final Project project) {
+ try {
+ VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
+ if (ideaDir == null) {
+ LOG.debug("idea directory doesn't exist");
+ return;
+ }
+ VirtualFile courseResourceDir = ideaDir.findChild("course");
+ if (courseResourceDir != null) {
+ courseResourceDir.delete(project);
+ }
+ VirtualFile[] taskDirChildren = taskDir.getChildren();
+ for (VirtualFile file : taskDirChildren) {
+ if (file.getName().contains("_windows")) {
+ file.delete(project);
+ }
+ if (CCProjectService.getInstance(project).isTaskFile(file)) {
+ file.delete(project);
+ }
+ }
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ }
+
+ private static void executeTests(@NotNull final Project project,
+ @NotNull final VirtualFile virtualFile,
+ @NotNull final VirtualFile taskDir,
+ @NotNull final VirtualFile testFile) {
+ final ConfigurationFactory factory = PythonConfigurationType.getInstance().getConfigurationFactories()[0];
+ final RunnerAndConfigurationSettings settings =
+ RunManager.getInstance(project).createRunConfiguration("test", factory);
+
+ final PythonRunConfiguration configuration = (PythonRunConfiguration)settings.getConfiguration();
+ configuration.setScriptName(testFile.getPath());
+ VirtualFile userFile = taskDir.findChild(virtualFile.getNameWithoutExtension() + ".py");
+ if (userFile == null) {
+ return;
+ }
+ VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
+ if (ideaDir == null) {
+ return;
+ }
+ VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
+ ProjectView.getInstance(project).refresh();
+ VirtualFile courseDir = ideaDir.findChild("course");
+ if (courseDir == null) {
+ return;
+ }
+ configuration.setScriptParameters(PathUtil.toSystemDependentName(courseDir.getPath()) + " " + PathUtil.toSystemDependentName(userFile.getPath()));
+ Executor executor = DefaultRunExecutor.getRunExecutorInstance();
+ ProgramRunnerUtil.executeConfiguration(project, settings, executor);
+ }
+
+ @Nullable
+ private static Task getTask(@NotNull final Course course, @NotNull final VirtualFile taskDir) {
+ if (!taskDir.getName().contains("task")) {
+ return null;
+ }
+ VirtualFile lessonDir = taskDir.getParent();
+ if (lessonDir == null || !lessonDir.getName().contains("lesson")) {
+ return null;
+ }
+ Lesson lesson = course.getLesson(lessonDir.getName());
+ if (lesson == null) {
+ return null;
+ }
+ return lesson.getTask(taskDir.getName());
+ }
+
+
+ //some tests could compare task files after user modifications with initial task files
+ private static void createResourceFiles(@NotNull final VirtualFile file, @NotNull final Project project) {
+ VirtualFile taskDir = file.getParent();
+ int index = CCProjectService.getIndex(taskDir.getName(), "task");
+ VirtualFile lessonDir = taskDir.getParent();
+ int lessonIndex = CCProjectService.getIndex(lessonDir.getName(), "lesson");
+ Course course = CCProjectService.getInstance(project).getCourse();
+ if (course == null) {
+ return;
+ }
+ VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
+ assert ideaDir != null;
+ try {
+ VirtualFile courseResourceDir = findOrCreateDir(project, ideaDir, "course");
+ VirtualFile lessonResourceDir = findOrCreateDir(project, courseResourceDir, lessonDir.getName());
+ VirtualFile taskResourceDir = findOrCreateDir(project, lessonResourceDir, taskDir.getName());
+ if (CCProjectService.indexIsValid(lessonIndex, course.getLessons())) {
+ Lesson lesson = course.getLessons().get(lessonIndex);
+ if (CCProjectService.indexIsValid(index, lesson.getTaskList())) {
+ Task task = lesson.getTaskList().get(index);
+ HashMap taskFilesCopy = new HashMap();
+ for (Map.Entry entry : task.getTaskFiles().entrySet()) {
+ CCCreateCourseArchive.createUserFile(project, taskFilesCopy, taskResourceDir, taskDir, entry);
+ CCCreateCourseArchive.resetTaskFiles(taskFilesCopy);
+ }
+ }
+ }
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ }
+
+ private static VirtualFile findOrCreateDir(@NotNull final Project project, @NotNull final VirtualFile dir, String name) throws IOException {
+ VirtualFile targetDir = dir.findChild(name);
+ if (targetDir == null) {
+ targetDir = dir.createChildDirectory(project, name);
+ }
+ return targetDir;
+ }
+
+ @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
+ public static VirtualFile flushWindows(TaskFile taskFile, VirtualFile file) {
+ VirtualFile taskDir = file.getParent();
+ VirtualFile fileWindows = null;
+ final Document document = FileDocumentManager.getInstance().getDocument(file);
+ if (document == null) {
+ LOG.debug("Couldn't flush windows");
+ return null;
+ }
+ if (taskDir != null) {
+ String name = file.getNameWithoutExtension() + "_windows";
+ PrintWriter printWriter = null;
+ try {
+ fileWindows = taskDir.createChildData(taskFile, name);
+ printWriter = new PrintWriter(new FileOutputStream(fileWindows.getPath()));
+ for (TaskWindow taskWindow : taskFile.getTaskWindows()) {
+ int start = taskWindow.getRealStartOffset(document);
+ String windowDescription = document.getText(new TextRange(start, start + taskWindow.getReplacementLength()));
+ printWriter.println("#educational_plugin_window = " + windowDescription);
+ }
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ FileDocumentManager.getInstance().saveDocument(document);
+ }
+ });
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ finally {
+ if (printWriter != null) {
+ printWriter.close();
+ }
+ }
+ }
+ return fileWindows;
+ }
+}
diff --git a/python/edu/src/META-INF/PyCharmEduPlugin.xml b/python/edu/src/META-INF/PyCharmEduPlugin.xml
index 93ae00b757e7..778b7116a6a3 100644
--- a/python/edu/src/META-INF/PyCharmEduPlugin.xml
+++ b/python/edu/src/META-INF/PyCharmEduPlugin.xml
@@ -23,6 +23,9 @@
+
+
+
diff --git a/python/edu/src/com/jetbrains/python/edu/PyExecuteFileExtensionPoint.java b/python/edu/src/com/jetbrains/python/edu/PyExecuteFileExtensionPoint.java
new file mode 100644
index 000000000000..1ae39738661d
--- /dev/null
+++ b/python/edu/src/com/jetbrains/python/edu/PyExecuteFileExtensionPoint.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.edu;
+
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.extensions.ExtensionPointName;
+import org.jetbrains.annotations.NotNull;
+
+public interface PyExecuteFileExtensionPoint {
+
+ ExtensionPointName EP_NAME = ExtensionPointName.create("PythonidEdu.executeFile");
+
+ @NotNull
+ 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 03522bb8cf8a..19acded8f238 100644
--- a/python/edu/src/com/jetbrains/python/edu/PyExecuteFileLineMarkerProvider.java
+++ b/python/edu/src/com/jetbrains/python/edu/PyExecuteFileLineMarkerProvider.java
@@ -1,18 +1,20 @@
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.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.psi.util.PsiUtilBase;
+import com.intellij.ui.popup.PopupFactoryImpl;
import com.intellij.util.Function;
import com.jetbrains.python.psi.PyFile;
import com.jetbrains.python.psi.PyImportStatement;
@@ -20,7 +22,6 @@ import com.jetbrains.python.psi.PyStatement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import java.awt.event.MouseEvent;
import java.util.Collection;
import java.util.List;
@@ -38,34 +39,63 @@ public class PyExecuteFileLineMarkerProvider implements LineMarkerProvider {
public void collectSlowLineMarkers(@NotNull List elements, @NotNull Collection result) {
for (PsiElement element : elements) {
if (isFirstCodeLine(element)) {
- result.add(new LineMarkerInfo(
+ 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() + "'";
}
- },
- new GutterIconNavigationHandler() {
- @Override
- public void navigate(MouseEvent e, PsiElement elt) {
- executeCurrentScript(elt);
- }
- },
- GutterIconRenderer.Alignment.RIGHT));
+ }, null,
+ GutterIconRenderer.Alignment.RIGHT) {
+ @Nullable
+ @Override
+ public GutterIconRenderer createGutterRenderer() {
+ return new LineMarkerGutterIconRenderer(this){
+ @Override
+ public AnAction getClickAction() {
+ 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);
+ }
+ if (group.getChildrenCount() == 1) {
+ return new PyRunCurrentFileAction();
+ }
+ return new AnAction() {
+ @Override
+ public void actionPerformed(@NotNull AnActionEvent e) {
+ final ListPopup popup =
+ new PopupFactoryImpl().createActionGroupPopup(null, group, e.getDataContext(), false, false, false, null, 5);
+ popup.showInBestPositionFor(e.getDataContext());
+ }
+ };
+ }
+
+ @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;
+ }
+ };
+ }
+ };
+ result.add(markerInfo);
}
}
}
- private static void executeCurrentScript(PsiElement elt) {
- Editor editor = PsiUtilBase.findEditor(elt);
- assert editor != null;
-
- final ConfigurationContext context =
- ConfigurationContext.getFromContext(DataManager.getInstance().getDataContext(editor.getComponent()));
- PyRunCurrentFileAction.run(context);
- }
-
private static boolean isFirstCodeLine(PsiElement element) {
return element instanceof PyStatement &&
element.getParent() instanceof PyFile &&