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 &&