diff options
author | Tor Norbye <tnorbye@google.com> | 2014-09-18 13:38:58 -0700 |
---|---|---|
committer | Tor Norbye <tnorbye@google.com> | 2014-09-18 13:38:58 -0700 |
commit | b5fb31ef6a38f19404859755dbd2e345215b97bf (patch) | |
tree | e8787c45e494dfcc558faf0f75956f8785c39b94 /python/edu | |
parent | e222a9e1e66670a56e926a6b0f3e10231eeeb1fb (diff) | |
parent | e782c57d74000722f9db4c9426317410520670c6 (diff) | |
download | idea-b5fb31ef6a38f19404859755dbd2e345215b97bf.tar.gz |
Merge remote-tracking branch 'aosp/upstream-master' into merge
Conflicts:
.idea/libraries/asm_tools.xml
.idea/libraries/bouncy_castle.xml
.idea/libraries/builder_model.xml
.idea/libraries/commons_compress.xml
.idea/libraries/easymock_tools.xml
.idea/libraries/freemarker_2_3_20.xml
.idea/libraries/guava_tools.xml
.idea/libraries/kxml2.xml
.idea/libraries/lombok_ast.xml
.idea/libraries/mockito.xml
.idea/modules.xml
.idea/vcs.xml
build/scripts/layouts.gant
updater/src/com/intellij/updater/Runner.java
Change-Id: I8e1c173e00cd76c855b8a98543b0a0edfdd99d12
Diffstat (limited to 'python/edu')
44 files changed, 1723 insertions, 268 deletions
diff --git a/python/edu/build/pycharm_edu_build.gant b/python/edu/build/pycharm_edu_build.gant index 28d829e68b34..356df8c7d4c7 100644 --- a/python/edu/build/pycharm_edu_build.gant +++ b/python/edu/build/pycharm_edu_build.gant @@ -158,7 +158,6 @@ public layoutEducational(String classesPath, Set usedJars) { usedJars = collectUsedJars(modules(), approvedJars(), ["/ant/"], null) } - def appInfo = appInfoFile() def paths = new Paths(home) buildSearchableOptions("${projectBuilder.moduleOutput(findModule("platform-resources"))}/search", [], { projectBuilder.moduleRuntimeClasspath(findModule("main_pycharm_edu"), false).each { @@ -166,8 +165,9 @@ public layoutEducational(String classesPath, Set usedJars) { } }, "-Didea.platform.prefix=PyCharmEdu -Didea.no.jre.check=true") + def appInfo = appInfoFile() if (!dryRun) { - wireBuildDate("PE-${buildNumber}", appInfo) + wireBuildDate(buildNumber, appInfo) } Map args = [ @@ -223,7 +223,7 @@ private layoutPlugins(layouts) { } private String appInfoFile() { - return "$pythonEduHome/resources/idea/PyCharmEduApplicationInfo.xml" + return "$home/out/pycharmEDU/classes/production/python-educational/idea/PyCharmEduApplicationInfo.xml" } private layoutFull(Map args, String target, Set usedJars) { @@ -405,7 +405,7 @@ private layoutMac(Map _args, String target) { args.icns = "$pythonCommunityHome/resources/PyCharmCore.icns" args.bundleIdentifier = "com.jetbrains.pycharm" args.platform_prefix = "PyCharmEdu" - args.help_id = "PY" + args.help_id = "PE" args."idea.properties.path" = "${paths.distAll}/bin/idea.properties" args."idea.properties" = ["idea.no.jre.check": true, "ide.mac.useNativeClipboard": "false"]; layoutMacApp(target, ch, args) diff --git a/python/edu/build/upload_pythonInfo.xml b/python/edu/build/upload_pythonInfo.xml index f8d9477d1a3f..0633ad82f8c0 100644 --- a/python/edu/build/upload_pythonInfo.xml +++ b/python/edu/build/upload_pythonInfo.xml @@ -20,7 +20,7 @@ </fileset> <fileset dir="${home}/community/lib"> <include name="commons-net-3.1.jar"/> - <include name="jsch-0.1.50.jar"/> + <include name="jsch-0.1.51.jar"/> </fileset> </classpath> diff --git a/python/edu/course-creator/resources/META-INF/plugin.xml b/python/edu/course-creator/resources/META-INF/plugin.xml index 6a9a9ea90276..0e7f75c90051 100644 --- a/python/edu/course-creator/resources/META-INF/plugin.xml +++ b/python/edu/course-creator/resources/META-INF/plugin.xml @@ -26,6 +26,10 @@ <codeInsight.lineMarkerProvider language="Python" implementationClass="org.jetbrains.plugins.coursecreator.highlighting.CCTaskLineMarkerProvider"/> <treeStructureProvider implementation="org.jetbrains.plugins.coursecreator.projectView.CCTreeStructureProvider"/> + <codeInsight.lineMarkerProvider language="Python" implementationClass="org.jetbrains.plugins.coursecreator.RunTestsLineMarker"/> + <fileTypeFactory implementation="org.jetbrains.plugins.coursecreator.AnswerFileTypeFactory" /> + <refactoring.elementListenerProvider implementation="org.jetbrains.plugins.coursecreator.CCRefactoringElementListenerProvider"/> + <errorHandler implementation="com.intellij.diagnostic.ITNReporter"/> </extensions> <application-components> @@ -52,9 +56,19 @@ <action id="AddTaskWindow" class="org.jetbrains.plugins.coursecreator.actions.AddTaskWindow"> <add-to-group group-id="EditorPopupMenu" anchor="before" relative-to-action="CopyReference"/> </action> + <action id="ShowPreview" class="org.jetbrains.plugins.coursecreator.actions.CCShowPreview"> + <add-to-group group-id="ProjectViewPopupMenu" anchor="first"/> + </action> + <action id="RenameLesson" class="org.jetbrains.plugins.coursecreator.actions.CCRenameLesson"> + <add-to-group group-id="ProjectViewPopupMenu" anchor="before" relative-to-action="CutCopyPasteGroup"/> + </action> + <action id="RenameTask" class="org.jetbrains.plugins.coursecreator.actions.CCRenameTask"> + <add-to-group group-id="ProjectViewPopupMenu" anchor="before" relative-to-action="CutCopyPasteGroup"/> + </action> <action id="PackCourse" class="org.jetbrains.plugins.coursecreator.actions.CreateCourseArchive"> <add-to-group group-id="MainToolBar" anchor="last" /> </action> + <action id="CCRunTests" class="org.jetbrains.plugins.coursecreator.CCRunTests" text="Run tests" description="Run tests"/> </actions> </idea-plugin>
\ No newline at end of file diff --git a/python/edu/course-creator/resources/fileTemplates/internal/task.answer.ft b/python/edu/course-creator/resources/fileTemplates/internal/task.answer.ft new file mode 100644 index 000000000000..f0a4bcecdd24 --- /dev/null +++ b/python/edu/course-creator/resources/fileTemplates/internal/task.answer.ft @@ -0,0 +1 @@ +# TODO: type solution here diff --git a/python/edu/course-creator/resources/fileTemplates/internal/task.py.ft b/python/edu/course-creator/resources/fileTemplates/internal/task.py.ft deleted file mode 100644 index 0256e108786c..000000000000 --- a/python/edu/course-creator/resources/fileTemplates/internal/task.py.ft +++ /dev/null @@ -1 +0,0 @@ -# TODO: type solution here
\ No newline at end of file diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/AnswerFileType.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/AnswerFileType.java new file mode 100644 index 000000000000..3b8189aadffc --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/AnswerFileType.java @@ -0,0 +1,40 @@ +/* + * 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.jetbrains.python.PythonFileType; +import org.jetbrains.annotations.NotNull; + +public class AnswerFileType extends PythonFileType { + + @NotNull + @Override + public String getDefaultExtension() { + return "answer"; + } + + @NotNull + @Override + public String getDescription() { + return "Answer file"; + } + + @NotNull + @Override + public String getName() { + return "Answer"; + } +} diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/AnswerFileTypeFactory.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/AnswerFileTypeFactory.java new file mode 100644 index 000000000000..e4adcbc5b386 --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/AnswerFileTypeFactory.java @@ -0,0 +1,28 @@ +/* + * 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.openapi.fileTypes.FileTypeConsumer; +import com.intellij.openapi.fileTypes.FileTypeFactory; +import org.jetbrains.annotations.NotNull; + +public class AnswerFileTypeFactory extends FileTypeFactory { + @Override + public void createFileTypes(@NotNull FileTypeConsumer fileTypeConsumer) { + fileTypeConsumer.consume(AnswerFileType.INSTANCE, "answer"); + } +} diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCEditorFactoryListener.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCEditorFactoryListener.java index 1eb8690aad22..acb0d43d0d12 100644 --- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCEditorFactoryListener.java +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCEditorFactoryListener.java @@ -35,6 +35,9 @@ public class CCEditorFactoryListener implements EditorFactoryListener { final Lesson lesson = course.getLesson(lessonDir.getName()); final Task task = lesson.getTask(taskDir.getName()); final TaskFile taskFile = task.getTaskFile(virtualFile.getName()); + if (taskFile == null) { + return; + } TaskFileModificationListener listener = new TaskFileModificationListener(taskFile); CCProjectService.addDocumentListener(editor.getDocument(), listener); editor.getDocument().addDocumentListener(listener); @@ -54,13 +57,10 @@ public class CCEditorFactoryListener implements EditorFactoryListener { editor.getSelectionModel().removeSelection(); } - private class TaskFileModificationListener extends StudyDocumentListener { - - private final TaskFile myTaskFile; + private static class TaskFileModificationListener extends StudyDocumentListener { public TaskFileModificationListener(TaskFile taskFile) { super(taskFile); - myTaskFile = taskFile; } @Override @@ -71,10 +71,5 @@ public class CCEditorFactoryListener implements EditorFactoryListener { taskWindow.setReplacementLength(taskWindow.getLength() + 1); } } - - @Override - protected boolean needModify() { - return myTaskFile.isTrackChanges(); - } } } diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectComponent.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectComponent.java index 34de943d2ad0..8b524dbffd4b 100644 --- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectComponent.java +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectComponent.java @@ -1,22 +1,33 @@ package org.jetbrains.plugins.coursecreator; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.ProjectComponent; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.editor.event.EditorFactoryEvent; import com.intellij.openapi.editor.impl.EditorFactoryImpl; -import com.intellij.openapi.fileEditor.FileEditor; -import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.*; import com.intellij.openapi.fileEditor.impl.text.PsiAwareTextEditorImpl; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.startup.StartupManager; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileAdapter; +import com.intellij.openapi.vfs.VirtualFileEvent; +import com.intellij.openapi.vfs.VirtualFileManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.coursecreator.format.Course; +import org.jetbrains.plugins.coursecreator.format.Lesson; +import org.jetbrains.plugins.coursecreator.format.Task; +import org.jetbrains.plugins.coursecreator.format.TaskFile; + +import java.io.IOException; public class CCProjectComponent implements ProjectComponent { + private static final Logger LOG = Logger.getInstance(CCProjectComponent.class.getName()); private final Project myProject; + private FileDeletedListener myListener; public CCProjectComponent(Project project) { myProject = project; @@ -37,15 +48,46 @@ public class CCProjectComponent implements ProjectComponent { StartupManager.getInstance(myProject).runWhenProjectIsInitialized(new Runnable() { @Override public void run() { - Course course = CCProjectService.getInstance(myProject).getCourse(); + final Course course = CCProjectService.getInstance(myProject).getCourse(); if (course != null) { - EditorFactory.getInstance().addEditorFactoryListener(new CCEditorFactoryListener(), myProject); + myProject.getMessageBus().connect(myProject).subscribe( + FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerAdapter() { + @Override + public void selectionChanged(@NotNull FileEditorManagerEvent event) { + final VirtualFile oldFile = event.getOldFile(); + if (oldFile == null) { + return; + } + if (CCProjectService.getInstance(myProject).isTaskFile(oldFile)) { + FileEditorManager.getInstance(myProject).closeFile(oldFile); + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + try { + oldFile.delete(myProject); + } + catch (IOException e) { + LOG.error(e); + } + } + }); + } + } + }); + myListener = new FileDeletedListener(); + VirtualFileManager.getInstance().addVirtualFileListener(myListener); + final CCEditorFactoryListener editorFactoryListener = new CCEditorFactoryListener(); + EditorFactory.getInstance().addEditorFactoryListener(editorFactoryListener, myProject); VirtualFile[] files = FileEditorManager.getInstance(myProject).getOpenFiles(); for (VirtualFile file : files) { + if (CCProjectService.getInstance(myProject).isTaskFile(file)) { + FileEditorManager.getInstance(myProject).closeFile(file); + continue; + } FileEditor fileEditor = FileEditorManager.getInstance(myProject).getSelectedEditor(file); if (fileEditor instanceof PsiAwareTextEditorImpl) { Editor editor = ((PsiAwareTextEditorImpl)fileEditor).getEditor(); - new CCEditorFactoryListener().editorCreated(new EditorFactoryEvent(new EditorFactoryImpl(ProjectManager.getInstance()), editor )); + editorFactoryListener.editorCreated(new EditorFactoryEvent(new EditorFactoryImpl(ProjectManager.getInstance()), editor)); } } } @@ -54,5 +96,94 @@ public class CCProjectComponent implements ProjectComponent { } public void projectClosed() { + if (myListener != null) { + VirtualFileManager.getInstance().removeVirtualFileListener(myListener); + } + } + + private class FileDeletedListener extends VirtualFileAdapter { + + @Override + public void fileDeleted(@NotNull VirtualFileEvent event) { + if (myProject.isDisposed() || !myProject.isOpen()) { + return; + } + Course course = CCProjectService.getInstance(myProject).getCourse(); + if (course == null) { + return; + } + VirtualFile removedFile = event.getFile(); + if (removedFile.getName().contains(".answer")) { + deleteTaskFile(course, removedFile); + } + if (removedFile.getName().contains("task")) { + deleteTask(course, removedFile); + } + if (removedFile.getName().contains("lesson")) { + deleteLesson(course, removedFile); + } + } + + private void deleteLesson(Course course, VirtualFile file) { + VirtualFile courseDir = file.getParent(); + if (!courseDir.getName().equals(myProject.getName())) { + return; + } + Lesson lesson = course.getLesson(file.getName()); + if (lesson != null) { + course.getLessons().remove(lesson); + course.getLessonsMap().remove(file.getName()); + } + } + + private void deleteTask(Course course, VirtualFile removedFile) { + VirtualFile lessonDir = removedFile.getParent(); + if (lessonDir == null || !lessonDir.getName().contains("lesson")) { + return; + } + VirtualFile courseDir = lessonDir.getParent(); + if (!courseDir.getName().equals(myProject.getName())) { + return; + } + Lesson lesson = course.getLesson(lessonDir.getName()); + if (lesson == null) { + return; + } + Task task = lesson.getTask(removedFile.getName()); + if (task == null) { + return; + } + lesson.getTaskList().remove(task); + lesson.getTasksMap().remove(removedFile.getName()); + } + + private void deleteTaskFile(Course course, VirtualFile removedFile) { + VirtualFile taskDir = removedFile.getParent(); + if (taskDir == null || !taskDir.getName().contains("task")) { + return; + } + VirtualFile lessonDir = taskDir.getParent(); + if (lessonDir == null || !lessonDir.getName().contains("lesson")) { + return; + } + VirtualFile courseDir = lessonDir.getParent(); + if (!courseDir.getName().equals(myProject.getName())) { + return; + } + Lesson lesson = course.getLesson(lessonDir.getName()); + if (lesson == null) { + return; + } + Task task = lesson.getTask(taskDir.getName()); + if (task == null) { + return; + } + TaskFile taskFile = task.getTaskFile(removedFile.getName()); + if (taskFile == null) { + return; + } + String name = CCProjectService.getRealTaskFileName(removedFile.getName()); + task.getTaskFiles().remove(name); + } } } diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectService.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectService.java index 1e38bab865cb..c06925e9a453 100644 --- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectService.java +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectService.java @@ -32,6 +32,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.coursecreator.format.*; import java.io.File; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -70,6 +71,9 @@ public class CCProjectService implements PersistentStateComponent<Element> { @Override public void loadState(Element el) { myCourse = XmlSerializer.deserialize(el.getChild(COURSE_ELEMENT), Course.class); + if (myCourse != null) { + myCourse.init(); + } } public static CCProjectService getInstance(@NotNull Project project) { @@ -115,7 +119,7 @@ public class CCProjectService implements PersistentStateComponent<Element> { } List<TaskWindow> taskWindows = taskFile.getTaskWindows(); for (TaskWindow taskWindow : taskWindows) { - taskWindow.drawHighlighter(editor); + taskWindow.drawHighlighter(editor, false); } } @@ -131,8 +135,52 @@ public class CCProjectService implements PersistentStateComponent<Element> { myDocumentListeners.remove(document); } - public static boolean indexIsValid(int index, List<TaskWindow> collection) { + public static boolean indexIsValid(int index, Collection collection) { int size = collection.size(); return index >= 0 && index < size; } + + public boolean isTaskFile(VirtualFile file) { + if (myCourse == null || file == null) { + return false; + } + VirtualFile taskDir = file.getParent(); + if (taskDir != null) { + String taskDirName = taskDir.getName(); + if (taskDirName.contains("task")) { + VirtualFile lessonDir = taskDir.getParent(); + if (lessonDir != null) { + String lessonDirName = lessonDir.getName(); + int lessonIndex = getIndex(lessonDirName, "lesson"); + List<Lesson> lessons = myCourse.getLessons(); + if (!indexIsValid(lessonIndex, lessons)) { + return false; + } + Lesson lesson = lessons.get(lessonIndex); + int taskIndex = getIndex(taskDirName, "task"); + List<Task> tasks = lesson.getTaskList(); + if (!indexIsValid(taskIndex, tasks)) { + return false; + } + Task task = tasks.get(taskIndex); + return task.isTaskFile(file.getName()); + } + } + } + return false; + } + + public static int getIndex(@NotNull final String fullName, @NotNull final String logicalName) { + if (!fullName.contains(logicalName)) { + throw new IllegalArgumentException(); + } + return Integer.parseInt(fullName.substring(logicalName.length())) - 1; + } + public static String getRealTaskFileName(String name) { + if (!name.contains(".answer")) { + return null; + } + int nameEnd = name.indexOf(".answer"); + return name.substring(0, nameEnd) + ".py"; + } } 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 new file mode 100644 index 000000000000..601e4fc7cb40 --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCRefactoringElementListenerProvider.java @@ -0,0 +1,96 @@ +/* + * 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.psi.PsiDirectory; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.refactoring.listeners.RefactoringElementAdapter; +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.format.Course; +import org.jetbrains.plugins.coursecreator.format.Lesson; +import org.jetbrains.plugins.coursecreator.format.Task; +import org.jetbrains.plugins.coursecreator.format.TaskFile; + +import java.util.Map; + +public class CCRefactoringElementListenerProvider implements RefactoringElementListenerProvider { + @Nullable + @Override + public RefactoringElementListener getListener(PsiElement element) { + return new CCRenameListener(element); + } + + + static class CCRenameListener extends RefactoringElementAdapter { + + private String myElementName; + + public CCRenameListener(PsiElement element) { + if (element instanceof PsiFile) { + PsiFile psiFile = (PsiFile)element; + myElementName = psiFile.getName(); + } + } + + @Override + protected void elementRenamedOrMoved(@NotNull PsiElement newElement) { + if (newElement instanceof PsiFile && myElementName != null) { + PsiFile psiFile = (PsiFile)newElement; + if (myElementName.contains(".answer")) { + //this is task file + renameTaskFile(psiFile, myElementName); + } + } + } + + private static void renameTaskFile(PsiFile file, String oldName) { + PsiDirectory taskDir = file.getContainingDirectory(); + Course course = CCProjectService.getInstance(file.getProject()).getCourse(); + if (course == null) { + return; + } + if (taskDir == null || !taskDir.getName().contains("task")) { + return; + } + PsiDirectory lessonDir = taskDir.getParent(); + if (lessonDir == null || !lessonDir.getName().contains("lesson")) { + return; + } + Lesson lesson = course.getLesson(lessonDir.getName()); + if (lesson == null) { + return; + } + Task task = lesson.getTask(taskDir.getName()); + if (task == null) { + return; + } + Map<String, TaskFile> taskFiles = task.getTaskFiles(); + TaskFile taskFile = task.getTaskFile(oldName); + String realTaskFileName = CCProjectService.getRealTaskFileName(oldName); + taskFiles.remove(realTaskFileName); + taskFiles.put(CCProjectService.getRealTaskFileName(file.getName()), taskFile); + } + + @Override + public void undoElementMovedOrRenamed(@NotNull PsiElement newElement, @NotNull String oldQualifiedName) { + + } + } +} 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 new file mode 100644 index 000000000000..d078972e3f56 --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCRunTests.java @@ -0,0 +1,284 @@ +/* + * 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.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.PsiElement; +import com.intellij.psi.PsiFile; +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.actions.CreateCourseArchive; +import org.jetbrains.plugins.coursecreator.format.*; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Map; + +public class CCRunTests extends AnAction { + private static final Logger LOG = Logger.getInstance(CCRunTests.class.getName()); + + public CCRunTests() { + getTemplatePresentation().setIcon(AllIcons.Actions.Lightning); + } + + @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(); + if (psiFile != null && psiFile.getName().contains(".answer")) { + presentation.setEnabled(true); + presentation.setText("Run tests from '" + psiFile.getName() + "'"); + } + else { + presentation.setEnabled(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; + } + for (final Map.Entry<String, TaskFile> 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); + clearTestEnvironment(taskDir, project); + } + } + }); + } + + 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); + } + } + + private 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) { + return; + } + 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(courseDir.getPath() + " " + 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 taskResourceDir = ideaDir.createChildDirectory(project, "course").createChildDirectory(project, lessonDir.getName()) + .createChildDirectory(project, 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<TaskFile, TaskFile> taskFilesCopy = new HashMap<TaskFile, TaskFile>(); + for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) { + CreateCourseArchive.createUserFile(project, taskFilesCopy, taskResourceDir, taskDir, entry); + CreateCourseArchive.resetTaskFiles(taskFilesCopy); + } + } + } + } + catch (IOException e) { + LOG.error(e); + } + } + + @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("#study_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/RunTestsLineMarker.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/RunTestsLineMarker.java new file mode 100644 index 000000000000..93e0a706f367 --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/RunTestsLineMarker.java @@ -0,0 +1,109 @@ +/* + * 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 RunTestsLineMarker implements LineMarkerProvider { + @Nullable + @Override + public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement element) { + return null; + } + + @Override + public void collectSlowLineMarkers(@NotNull List<PsiElement> elements, @NotNull Collection<LineMarkerInfo> result) { + for (PsiElement element : elements) { + if (isFirstCodeLine(element)) { + PsiFile psiFile = element.getContainingFile(); + if (psiFile == null || !psiFile.getName().contains(".answer")) { + continue; + } + result.add(new LineMarkerInfo<PsiElement>( + element, element.getTextRange(), AllIcons.Actions.Lightning, Pass.UPDATE_OVERRIDEN_MARKERS, + new Function<PsiElement, String>() { + @Override + public String fun(PsiElement e) { + return "Run tests from file '" + e.getContainingFile().getName() + "'"; + } + }, + new GutterIconNavigationHandler<PsiElement>() { + @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/StudyDocumentListener.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/StudyDocumentListener.java index d803e0e8fd97..965bd31771a0 100644 --- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/StudyDocumentListener.java +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/StudyDocumentListener.java @@ -44,9 +44,6 @@ public abstract class StudyDocumentListener extends DocumentAdapter { @Override public void documentChanged(DocumentEvent e) { if (e instanceof DocumentEventImpl) { - if (!needModify()) { - return; - } DocumentEventImpl event = (DocumentEventImpl)e; Document document = e.getDocument(); int offset = e.getOffset(); @@ -65,7 +62,5 @@ public abstract class StudyDocumentListener extends DocumentAdapter { } protected abstract void updateTaskWindowLength(CharSequence fragment, TaskWindow taskWindow, int change); - - protected abstract boolean needModify(); } diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/AddTaskWindow.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/AddTaskWindow.java index ff88cea5fd42..0bc631dbd784 100644 --- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/AddTaskWindow.java +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/AddTaskWindow.java @@ -13,6 +13,7 @@ import com.intellij.openapi.ui.DialogWrapper; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.coursecreator.CCProjectService; import org.jetbrains.plugins.coursecreator.format.*; import org.jetbrains.plugins.coursecreator.ui.CreateTaskWindowDialog; @@ -59,12 +60,12 @@ public class AddTaskWindow extends DumbAwareAction { } int index = taskFile.getTaskWindows().size() + 1; taskFile.addTaskWindow(taskWindow, index); - taskWindow.drawHighlighter(editor); + taskWindow.drawHighlighter(editor, false); DaemonCodeAnalyzerImpl.getInstance(project).restart(file); } @Override - public void update(AnActionEvent event) { + public void update(@NotNull AnActionEvent event) { final Presentation presentation = event.getPresentation(); final Project project = event.getData(CommonDataKeys.PROJECT); if (project == null) { diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCRename.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCRename.java new file mode 100644 index 000000000000..321a86a5ba35 --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCRename.java @@ -0,0 +1,97 @@ +/* + * 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.ide.IdeView; +import com.intellij.ide.projectView.ProjectView; +import com.intellij.ide.util.DirectoryChooserUtil; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.LangDataKeys; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.coursecreator.CCProjectService; +import org.jetbrains.plugins.coursecreator.format.Course; + +import javax.swing.*; + +public abstract class CCRename extends DumbAwareAction { + public CCRename(String text, String description, Icon icon) { + super(text, description, icon); + } + + @Override + public void update(@NotNull AnActionEvent event) { + final Presentation presentation = event.getPresentation(); + final Project project = event.getData(CommonDataKeys.PROJECT); + if (project == null) { + presentation.setVisible(false); + presentation.setEnabled(false); + return; + } + + final IdeView view = event.getData(LangDataKeys.IDE_VIEW); + if (view == null) { + presentation.setVisible(false); + presentation.setEnabled(false); + return; + } + + final PsiDirectory[] directories = view.getDirectories(); + if (directories.length == 0) { + presentation.setVisible(false); + presentation.setEnabled(false); + return; + } + final PsiFile file = CommonDataKeys.PSI_FILE.getData(event.getDataContext()); + final PsiDirectory directory = DirectoryChooserUtil.getOrChooseDirectory(view); + if (file != null ||directory == null || !directory.getName().contains(getFolderName())) { + presentation.setEnabled(false); + presentation.setVisible(false); + return; + } + presentation.setVisible(true); + presentation.setEnabled(true); + } + + public abstract String getFolderName(); + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + final IdeView view = e.getData(LangDataKeys.IDE_VIEW); + final Project project = e.getData(CommonDataKeys.PROJECT); + + if (view == null || project == null) { + return; + } + final PsiDirectory directory = DirectoryChooserUtil.getOrChooseDirectory(view); + if (directory == null || !directory.getName().contains(getFolderName())) { + return; + } + Course course = CCProjectService.getInstance(project).getCourse(); + if (course == null) { + return; + } + if (!processRename(project, directory, course)) return; + ProjectView.getInstance(project).refresh(); + } + + public abstract boolean processRename(Project project, PsiDirectory directory, Course course); +} diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCRenameLesson.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCRenameLesson.java new file mode 100644 index 000000000000..3f580454ed6e --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCRenameLesson.java @@ -0,0 +1,48 @@ +/* + * 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.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.psi.PsiDirectory; +import org.jetbrains.plugins.coursecreator.format.Course; +import org.jetbrains.plugins.coursecreator.format.Lesson; + +public class CCRenameLesson extends CCRename { + + public CCRenameLesson() { + super("Rename Lesson", "Rename Lesson", null); + } + + @Override + public String getFolderName() { + return "lesson"; + } + + @Override + public boolean processRename(Project project, PsiDirectory directory, Course course) { + Lesson lesson = course.getLesson(directory.getName()); + if (lesson == null) { + return false; + } + String newName = Messages.showInputDialog(project, "Enter new name", "Rename " + getFolderName(), null); + if (newName == null) { + return false; + } + lesson.setName(newName); + return true; + } +} diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCRenameTask.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCRenameTask.java new file mode 100644 index 000000000000..342621bf0d47 --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCRenameTask.java @@ -0,0 +1,57 @@ +/* + * 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.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.psi.PsiDirectory; +import org.jetbrains.plugins.coursecreator.format.Course; +import org.jetbrains.plugins.coursecreator.format.Lesson; +import org.jetbrains.plugins.coursecreator.format.Task; + +public class CCRenameTask extends CCRename { + public CCRenameTask() { + super("Rename Task", "Rename Task", null); + } + + @Override + public String getFolderName() { + return "task"; + } + + @Override + public boolean processRename(Project project, PsiDirectory directory, Course course) { + PsiDirectory lessonDir = directory.getParent(); + if (lessonDir == null || !lessonDir.getName().contains("lesson")) { + return false; + } + Lesson lesson = course.getLesson(lessonDir.getName()); + if (lesson == null) { + return false; + } + Task task = lesson.getTask(directory.getName()); + if (task == null) { + return false; + } + String newName = Messages.showInputDialog(project, "Enter new name", "Rename " + getFolderName(), null); + if (newName == null) { + return false; + } + task.setName(newName); + return true; + + } +} diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCShowPreview.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCShowPreview.java new file mode 100644 index 000000000000..bccaacaf1957 --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCShowPreview.java @@ -0,0 +1,104 @@ +/* + * 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.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiFile; +import com.intellij.util.containers.hash.HashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.coursecreator.CCProjectService; +import org.jetbrains.plugins.coursecreator.format.*; + +import java.util.Map; + +public class CCShowPreview extends DumbAwareAction { + public CCShowPreview() { + super("Show preview","Show preview", null); + } + + @Override + public void update(@NotNull AnActionEvent e) { + Presentation presentation = e.getPresentation(); + presentation.setEnabled(false); + presentation.setVisible(false); + final PsiFile file = CommonDataKeys.PSI_FILE.getData(e.getDataContext()); + if (file != null && file.getName().contains(".answer")) { + presentation.setEnabled(true); + presentation.setVisible(true); + } + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + final Project project = e.getProject(); + if (project == null) { + return; + } + final PsiFile file = CommonDataKeys.PSI_FILE.getData(e.getDataContext()); + if (file == null || !file.getName().contains(".answer")) { + return; + } + final PsiDirectory taskDir = file.getContainingDirectory(); + if (taskDir == null) { + return; + } + PsiDirectory lessonDir = taskDir.getParentDirectory(); + if (lessonDir == null) { + return; + } + Course course = CCProjectService.getInstance(project).getCourse(); + if (course == null) { + return; + } + Lesson lesson = course.getLesson(lessonDir.getName()); + Task task = lesson.getTask(taskDir.getName()); + TaskFile taskFile = task.getTaskFile(file.getName()); + final Map<TaskFile, TaskFile> taskFilesCopy = new HashMap<TaskFile, TaskFile>(); + for (final Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) { + if (entry.getValue() == taskFile) { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + CreateCourseArchive.createUserFile(project, taskFilesCopy, taskDir.getVirtualFile(), taskDir.getVirtualFile(), entry); + } + }); + } + } + String userFileName = FileUtil.getNameWithoutExtension(file.getName()) + ".py"; + VirtualFile userFile = taskDir.getVirtualFile().findChild(userFileName); + if (userFile != null) { + FileEditorManager.getInstance(project).openFile(userFile, true); + Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor(); + if (editor == null) { + return; + } + for (TaskWindow taskWindow : taskFile.getTaskWindows()) { + taskWindow.drawHighlighter(editor, true); + } + CreateCourseArchive.resetTaskFiles(taskFilesCopy); + } + } +}
\ No newline at end of file diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateCourseArchive.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateCourseArchive.java index 05428f4e82d1..8db49156c987 100644 --- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateCourseArchive.java +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateCourseArchive.java @@ -18,6 +18,7 @@ import com.intellij.openapi.ui.Messages; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.intellij.util.io.ZipUtil; +import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.coursecreator.CCProjectService; import org.jetbrains.plugins.coursecreator.StudyDocumentListener; import org.jetbrains.plugins.coursecreator.format.*; @@ -29,8 +30,8 @@ import java.util.zip.ZipOutputStream; public class CreateCourseArchive extends DumbAwareAction { private static final Logger LOG = Logger.getInstance(CreateCourseArchive.class.getName()); - String myZipName; - String myLocationDir; + private String myZipName; + private String myLocationDir; public void setZipName(String zipName) { myZipName = zipName; @@ -60,46 +61,124 @@ public class CreateCourseArchive extends DumbAwareAction { } final VirtualFile baseDir = project.getBaseDir(); final Map<String, Lesson> lessons = course.getLessonsMap(); - //List<FileEditor> editorList = new ArrayList<FileEditor>(); - Map<VirtualFile, TaskFile> taskFiles = new HashMap<VirtualFile, TaskFile>(); + //map to store initial task file + final Map<TaskFile, TaskFile> taskFiles = new HashMap<TaskFile, TaskFile>(); for (Map.Entry<String, Lesson> lesson : lessons.entrySet()) { final VirtualFile lessonDir = baseDir.findChild(lesson.getKey()); if (lessonDir == null) continue; for (Map.Entry<String, Task> task : lesson.getValue().myTasksMap.entrySet()) { final VirtualFile taskDir = lessonDir.findChild(task.getKey()); if (taskDir == null) continue; - for (Map.Entry<String, TaskFile> entry : task.getValue().task_files.entrySet()) { - final VirtualFile file = taskDir.findChild(entry.getKey()); - if (file == null) continue; - final Document document = FileDocumentManager.getInstance().getDocument(file); - if (document == null) continue; - final TaskFile taskFile = entry.getValue(); - document.addDocumentListener(new InsertionListener(taskFile)); - taskFiles.put(file, taskFile); - taskFile.setTrackChanges(false); - Collections.sort(taskFile.getTaskWindows()); - for (int i = taskFile.getTaskWindows().size() - 1; i >=0 ; i--) { - final TaskWindow taskWindow = taskFile.getTaskWindows().get(i); - final String taskText = taskWindow.getTaskText(); - final int lineStartOffset = document.getLineStartOffset(taskWindow.line); - final int offset = lineStartOffset + taskWindow.start; - CommandProcessor.getInstance().executeCommand(project, new Runnable() { - @Override - public void run() { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - document.replaceString(offset, offset + taskWindow.getReplacementLength(), taskText); - FileDocumentManager.getInstance().saveDocument(document); - } - }); - } - }, "x", "qwe"); - } + for (final Map.Entry<String, TaskFile> entry : task.getValue().task_files.entrySet()) { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + createUserFile(project, taskFiles, taskDir, taskDir, entry); + } + }); } } } generateJson(project); + packCourse(baseDir, lessons); + resetTaskFiles(taskFiles); + synchronize(project); + } + + public static void createUserFile(@NotNull final Project project, + @NotNull final Map<TaskFile, TaskFile> taskFilesCopy, + @NotNull final VirtualFile userFileDir, + @NotNull final VirtualFile answerFileDir, + @NotNull final Map.Entry<String, TaskFile> taskFiles) { + final String name = taskFiles.getKey(); + VirtualFile file = userFileDir.findChild(name); + if (file != null) { + try { + file.delete(project); + } + catch (IOException e) { + LOG.error(e); + } + } + try { + userFileDir.createChildData(project, name); + } + catch (IOException e) { + LOG.error(e); + } + + file = userFileDir.findChild(name); + assert file != null; + String answerFileName = file.getNameWithoutExtension() + ".answer"; + VirtualFile answerFile = answerFileDir.findChild(answerFileName); + if (answerFile == null) { + return; + } + final Document answerDocument = FileDocumentManager.getInstance().getDocument(answerFile); + if (answerDocument == null) { + return; + } + final Document document = FileDocumentManager.getInstance().getDocument(file); + if (document == null) return; + final TaskFile taskFile = taskFiles.getValue(); + TaskFile taskFileSaved = new TaskFile(); + taskFile.copy(taskFileSaved); + CommandProcessor.getInstance().executeCommand(project, new Runnable() { + @Override + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + document.replaceString(0, document.getTextLength(), answerDocument.getText()); + } + }); + } + }, "x", "qwe"); + InsertionListener listener = new InsertionListener(taskFile); + document.addDocumentListener(listener); + taskFilesCopy.put(taskFile, taskFileSaved); + Collections.sort(taskFile.getTaskWindows()); + for (int i = taskFile.getTaskWindows().size() - 1; i >= 0; i--) { + final TaskWindow taskWindow = taskFile.getTaskWindows().get(i); + replaceTaskWindow(project, document, taskWindow); + } + document.removeDocumentListener(listener); + } + + private static void replaceTaskWindow(@NotNull final Project project, + @NotNull final Document document, + @NotNull final TaskWindow taskWindow) { + final String taskText = taskWindow.getTaskText(); + final int lineStartOffset = document.getLineStartOffset(taskWindow.line); + final int offset = lineStartOffset + taskWindow.start; + CommandProcessor.getInstance().executeCommand(project, new Runnable() { + @Override + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + document.replaceString(offset, offset + taskWindow.getReplacementLength(), taskText); + FileDocumentManager.getInstance().saveDocument(document); + } + }); + } + }, "x", "qwe"); + } + + private static void synchronize(@NotNull final Project project) { + VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); + ProjectView.getInstance(project).refresh(); + } + + public static void resetTaskFiles(@NotNull final Map<TaskFile, TaskFile> taskFiles) { + for (Map.Entry<TaskFile, TaskFile> entry : taskFiles.entrySet()) { + TaskFile realTaskFile = entry.getKey(); + TaskFile savedTaskFile = entry.getValue(); + realTaskFile.update(savedTaskFile); + } + } + + private void packCourse(@NotNull final VirtualFile baseDir, @NotNull final Map<String, Lesson> lessons) { try { File zipFile = new File(myLocationDir, myZipName + ".zip"); ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile))); @@ -108,7 +187,12 @@ public class CreateCourseArchive extends DumbAwareAction { final VirtualFile lessonDir = baseDir.findChild(entry.getKey()); if (lessonDir == null) continue; - ZipUtil.addFileOrDirRecursively(zos, null, new File(lessonDir.getPath()), lessonDir.getName(), null, null); + ZipUtil.addFileOrDirRecursively(zos, null, new File(lessonDir.getPath()), lessonDir.getName(), new FileFilter() { + @Override + public boolean accept(File pathname) { + return !pathname.getName().contains(".answer"); + } + }, null); } ZipUtil.addFileOrDirRecursively(zos, null, new File(baseDir.getPath(), "hints"), "hints", null, null); ZipUtil.addFileOrDirRecursively(zos, null, new File(baseDir.getPath(), "course.json"), "course.json", null, null); @@ -119,36 +203,9 @@ public class CreateCourseArchive extends DumbAwareAction { catch (IOException e1) { LOG.error(e1); } - - for (Map.Entry<VirtualFile, TaskFile> entry: taskFiles.entrySet()) { - TaskFile value = entry.getValue(); - final Document document = FileDocumentManager.getInstance().getDocument(entry.getKey()); - if (document == null) { - continue; - } - for (final TaskWindow taskWindow : value.getTaskWindows()){ - final int lineStartOffset = document.getLineStartOffset(taskWindow.line); - final int offset = lineStartOffset + taskWindow.start; - CommandProcessor.getInstance().executeCommand(project, new Runnable() { - @Override - public void run() { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - document.replaceString(offset, offset + taskWindow.length, taskWindow.getPossibleAnswer()); - FileDocumentManager.getInstance().saveDocument(document); - } - }); - } - }, "x", "qwe"); - } - value.setTrackChanges(true); - } - VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); - ProjectView.getInstance(project).refresh(); } - private void generateJson(Project project) { + private static void generateJson(@NotNull final Project project) { final CCProjectService service = CCProjectService.getInstance(project); final Course course = service.getCourse(); final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create(); @@ -179,7 +236,7 @@ public class CreateCourseArchive extends DumbAwareAction { } } - private class InsertionListener extends StudyDocumentListener { + private static class InsertionListener extends StudyDocumentListener { public InsertionListener(TaskFile taskFile) { super(taskFile); @@ -187,12 +244,7 @@ public class CreateCourseArchive extends DumbAwareAction { @Override protected void updateTaskWindowLength(CharSequence fragment, TaskWindow taskWindow, int change) { - //we don't need to update task window length - } - - @Override - protected boolean needModify() { - return true; + //we don't need to update task window length } } }
\ No newline at end of file diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTask.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTask.java index 0940135b97be..57a37b3f4194 100644 --- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTask.java +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTask.java @@ -18,6 +18,7 @@ import com.intellij.openapi.ui.Messages; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiElement; import com.intellij.util.PlatformIcons; +import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.coursecreator.CCProjectService; import org.jetbrains.plugins.coursecreator.format.Course; import org.jetbrains.plugins.coursecreator.format.Lesson; @@ -29,7 +30,7 @@ public class CreateTask extends DumbAwareAction { } @Override - public void actionPerformed(AnActionEvent e) { + public void actionPerformed(final AnActionEvent e) { final IdeView view = e.getData(LangDataKeys.IDE_VIEW); final Project project = e.getData(CommonDataKeys.PROJECT); @@ -42,7 +43,7 @@ public class CreateTask extends DumbAwareAction { final CCProjectService service = CCProjectService.getInstance(project); final Course course = service.getCourse(); final Lesson lesson = course.getLesson(directory.getName()); - final int size = lesson.getTasklist().size(); + final int size = lesson.getTaskList().size(); final String taskName = Messages.showInputDialog("Name:", "Task Name", null, "task" + (size + 1), null); if (taskName == null) return; @@ -54,17 +55,16 @@ public class CreateTask extends DumbAwareAction { if (taskDirectory != null) { final FileTemplate template = FileTemplateManager.getInstance().getInternalTemplate("task.html"); final FileTemplate testsTemplate = FileTemplateManager.getInstance().getInternalTemplate("tests"); - final FileTemplate taskTemplate = FileTemplateManager.getInstance().getInternalTemplate("task.py"); + final FileTemplate taskTemplate = FileTemplateManager.getInstance().getInternalTemplate("task.answer"); try { final PsiElement taskFile = FileTemplateUtil.createFromTemplate(template, "task.html", null, taskDirectory); final PsiElement testsFile = FileTemplateUtil.createFromTemplate(testsTemplate, "tests.py", null, taskDirectory); - final PsiElement taskPyFile = FileTemplateUtil.createFromTemplate(taskTemplate, "file1" + ".py", null, taskDirectory); + final PsiElement taskPyFile = FileTemplateUtil.createFromTemplate(taskTemplate, "file1", null, taskDirectory); final Task task = new Task(taskName); - task.addTaskFile(taskPyFile.getContainingFile().getName(), size + 1); + task.addTaskFile("file1.py", size + 1); task.setIndex(size + 1); lesson.addTask(task, taskDirectory); - ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { @@ -84,7 +84,7 @@ public class CreateTask extends DumbAwareAction { } @Override - public void update(AnActionEvent event) { + public void update(@NotNull AnActionEvent event) { final Presentation presentation = event.getPresentation(); final Project project = event.getData(CommonDataKeys.PROJECT); if (project == null) { diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTaskFile.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTaskFile.java index 5aafcebefd6e..368be275a5a4 100644 --- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTaskFile.java +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTaskFile.java @@ -55,10 +55,10 @@ public class CreateTaskFile extends DumbAwareAction { ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { - final FileTemplate taskTemplate = FileTemplateManager.getInstance().getInternalTemplate("task.py"); + final FileTemplate taskTemplate = FileTemplateManager.getInstance().getInternalTemplate("task.answer"); try { - final PsiElement taskPyFile = FileTemplateUtil.createFromTemplate(taskTemplate, taskFileName + ".py", null, taskDir); - task.addTaskFile(taskPyFile.getContainingFile().getName(), index); + final PsiElement taskPyFile = FileTemplateUtil.createFromTemplate(taskTemplate, taskFileName, null, taskDir); + task.addTaskFile(taskFileName + ".py", index); ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Course.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Course.java index eb62d59cd9b1..e124a6eb305d 100644 --- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Course.java +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Course.java @@ -4,10 +4,7 @@ import com.google.gson.annotations.Expose; import com.intellij.psi.PsiDirectory; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; public class Course { @Expose public List<Lesson> lessons = new ArrayList<Lesson>(); @@ -52,4 +49,13 @@ public class Course { public String getDescription() { return description; } + + public void init() { + lessons.clear(); + for (Lesson lesson: myLessonsMap.values()) { + lessons.add(lesson); + lesson.init(); + } + Collections.sort(lessons); + } } diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Lesson.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Lesson.java index 38720140caf1..bd91e8ec30af 100644 --- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Lesson.java +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Lesson.java @@ -4,12 +4,9 @@ import com.google.gson.annotations.Expose; import com.intellij.psi.PsiDirectory; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; -public class Lesson { +public class Lesson implements Comparable{ @Expose public String name; @Expose public List<Task> task_list = new ArrayList<Task>(); @@ -27,11 +24,15 @@ public class Lesson { task_list.add(task); } + public void setName(String name) { + this.name = name; + } + public Task getTask(@NotNull final String name) { return myTasksMap.get(name); } - public List<Task> getTasklist() { + public List<Task> getTaskList() { return task_list; } @@ -42,4 +43,22 @@ public class Lesson { public int getIndex() { return myIndex; } + + public Map<String, Task> getTasksMap() { + return myTasksMap; + } + + public void init() { + task_list.clear(); + for (Task task : myTasksMap.values()) { + task_list.add(task); + } + Collections.sort(task_list); + } + + @Override + public int compareTo(@NotNull Object o) { + Lesson lesson = (Lesson) o; + return myIndex - lesson.getIndex(); + } } diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Task.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Task.java index e6c085b5d6a1..886add86ceb4 100644 --- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Task.java +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Task.java @@ -2,11 +2,12 @@ package org.jetbrains.plugins.coursecreator.format; import com.google.gson.annotations.Expose; import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.coursecreator.CCProjectService; import java.util.HashMap; import java.util.Map; -public class Task { +public class Task implements Comparable{ @Expose public String name; @Expose public Map<String, TaskFile> task_files = new HashMap<String, TaskFile>(); public int myIndex; @@ -28,7 +29,8 @@ public class Task { } public TaskFile getTaskFile(@NotNull final String name) { - return task_files.get(name); + String fileName = CCProjectService.getRealTaskFileName(name); + return fileName != null ? task_files.get(fileName) : null; } public void setIndex(int index) { @@ -38,4 +40,18 @@ public class Task { public Map<String, TaskFile> getTaskFiles() { return task_files; } + + public boolean isTaskFile(String name) { + return task_files.get(name) != null; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public int compareTo(@NotNull Object o) { + Task task = (Task) o; + return myIndex - task.getIndex(); + } } diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskFile.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskFile.java index 85f0d91983f2..b88e375bc265 100644 --- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskFile.java +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskFile.java @@ -13,15 +13,6 @@ import java.util.List; public class TaskFile { @Expose public List<TaskWindow> task_windows = new ArrayList<TaskWindow>(); public int myIndex; - public boolean myTrackChanges = true; - - public boolean isTrackChanges() { - return myTrackChanges; - } - - public void setTrackChanges(boolean trackChanges) { - myTrackChanges = trackChanges; - } public TaskFile() {} @@ -108,4 +99,22 @@ public class TaskFile { } } } + + public void copy(@NotNull final TaskFile target) { + target.setIndex(myIndex); + for (TaskWindow taskWindow : task_windows) { + TaskWindow savedWindow = new TaskWindow(taskWindow.getLine(), taskWindow.getStart(), + taskWindow.getLength(), ""); + target.getTaskWindows().add(savedWindow); + savedWindow.setIndex(taskWindow.getIndex()); + } + } + + public void update(@NotNull final TaskFile source) { + for (TaskWindow taskWindow : source.getTaskWindows()) { + TaskWindow taskWindowUpdated = task_windows.get(taskWindow.getIndex() - 1); + taskWindowUpdated.setLine(taskWindow.getLine()); + taskWindowUpdated.setStart(taskWindow.getStart()); + } + } } diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskWindow.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskWindow.java index cb6418ec75d7..6b1be7ef3e7d 100644 --- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskWindow.java +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskWindow.java @@ -68,16 +68,17 @@ public class TaskWindow implements Comparable{ } } - public void drawHighlighter(@NotNull final Editor editor) { + public void drawHighlighter(@NotNull final Editor editor, boolean useLength) { int startOffset = editor.getDocument().getLineStartOffset(line) + start; - int endOffset = startOffset + myReplacementLength; + int highlighterLength = useLength ? length : myReplacementLength; + int endOffset = startOffset + highlighterLength; TextAttributes defaultTestAttributes = EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.LIVE_TEMPLATE_ATTRIBUTES); RangeHighlighter highlighter = editor.getMarkupModel().addRangeHighlighter(startOffset, endOffset, HighlighterLayer.LAST + 1, defaultTestAttributes, HighlighterTargetArea.EXACT_RANGE); highlighter.setGreedyToLeft(true); - highlighter.setGreedyToRight(true); + highlighter.setGreedyToRight(false); } public int getIndex() { @@ -123,10 +124,6 @@ public class TaskWindow implements Comparable{ return lineDiff; } - public String getPossibleAnswer() { - return possible_answer; - } - public int getLength() { return length; } diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCDirectoryNode.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCDirectoryNode.java index 1a7304123f0a..0969c54563c2 100644 --- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCDirectoryNode.java +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCDirectoryNode.java @@ -26,6 +26,8 @@ public class CCDirectoryNode extends PsiDirectoryNode { @Override protected void updateImpl(PresentationData data) { + //TODO:change presentable name for files with suffix _answer + String valueName = myValue.getName(); final Course course = CCProjectService.getInstance(myProject).getCourse(); if (course == null) return; diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCTreeStructureProvider.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCTreeStructureProvider.java index 69b78ec9a92b..b4fb50a96dc4 100644 --- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCTreeStructureProvider.java +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCTreeStructureProvider.java @@ -2,9 +2,11 @@ package org.jetbrains.plugins.coursecreator.projectView; import com.intellij.ide.projectView.TreeStructureProvider; import com.intellij.ide.projectView.ViewSettings; +import com.intellij.ide.projectView.impl.nodes.PsiFileNode; import com.intellij.ide.util.treeView.AbstractTreeNode; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -27,11 +29,21 @@ public class CCTreeStructureProvider implements TreeStructureProvider, DumbAware Project project = node.getProject(); if (project != null) { if (node.getValue() instanceof PsiDirectory) { - PsiDirectory directory = (PsiDirectory) node.getValue(); + PsiDirectory directory = (PsiDirectory)node.getValue(); nodes.add(new CCDirectoryNode(project, directory, settings)); - } else { - nodes.add(node); + continue; } + if (node instanceof PsiFileNode) { + PsiFileNode fileNode = (PsiFileNode)node; + VirtualFile virtualFile = fileNode.getVirtualFile(); + if (virtualFile == null) { + continue; + } + if (CCProjectService.getInstance(project).isTaskFile(virtualFile)) { + continue; + } + } + nodes.add(node); } } return nodes; diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchivePanel.form b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchivePanel.form index 920dcb9494a7..096a85f8da59 100644 --- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchivePanel.form +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchivePanel.form @@ -27,7 +27,7 @@ <component id="160bb" class="javax.swing.JTextField" binding="myNameField" default-binding="true"> <constraints> <grid row="0" column="1" row-span="2" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false"> - <preferred-size width="150" height="-1"/> + <preferred-size width="300" height="-1"/> </grid> </constraints> <properties/> diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowDialog.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowDialog.java index c7e8f715672c..53a4a77b97f7 100644 --- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowDialog.java +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowDialog.java @@ -150,4 +150,10 @@ public class CreateTaskWindowDialog extends DialogWrapper { public void validateInput() { super.initValidation(); } + + @Nullable + @Override + public JComponent getPreferredFocusedComponent() { + return myPanel.getPreferredFocusedComponent(); + } } diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowPanel.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowPanel.java index 21a7eb063c63..a50840deaf2f 100644 --- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowPanel.java +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowPanel.java @@ -37,6 +37,7 @@ public class CreateTaskWindowPanel extends JPanel { } }); + myTaskWindowText.grabFocus(); myHintName.getDocument().addDocumentListener(new DocumentAdapter() { @Override protected void textChanged(DocumentEvent e) { @@ -93,4 +94,8 @@ public class CreateTaskWindowPanel extends JPanel { public void setGeneratedHintName(String generatedHintName) { myGeneratedHintName = generatedHintName; } + + public JComponent getPreferredFocusedComponent() { + return myTaskWindowText; + } } diff --git a/python/edu/learn-python/resources/META-INF/plugin.xml b/python/edu/learn-python/resources/META-INF/plugin.xml index 8e8bddcce5e5..b6ff0ce5d0d7 100644 --- a/python/edu/learn-python/resources/META-INF/plugin.xml +++ b/python/edu/learn-python/resources/META-INF/plugin.xml @@ -40,7 +40,7 @@ <actions> <action id="CheckAction" class="com.jetbrains.python.edu.actions.StudyCheckAction" text="check" - description="Runs tests for current tasks" icon="/icons/icon.jpg"> + description="Runs tests for current tasks"> </action> <action id="PrevWindowAction" class="com.jetbrains.python.edu.actions.StudyPrevWindowAction" text="PrevWindowAction" description="prev"> </action> @@ -60,10 +60,12 @@ <add-to-group group-id="MainToolBar" anchor="last"/> </action> - <action id="WelcomeScreen.LearnPython" class="com.jetbrains.python.edu.actions.StudyNewProject" icon="StudyIcons.EducationalProjectType"> + <action id="WelcomeScreen.PythonIntro" class="com.jetbrains.python.edu.actions.StudyIntroductionCourseAction" icon="StudyIcons.EducationalProjectType"> <add-to-group group-id="WelcomeScreen.QuickStart" anchor="first"/> </action> + <action id="ReloadCourseAction" class="com.jetbrains.python.edu.actions.StudyReloadCourseAction"/> + </actions> <extensions defaultExtensionNs="com.intellij"> @@ -74,6 +76,7 @@ <highlightErrorFilter implementation="com.jetbrains.python.edu.StudyHighlightErrorFilter"/> <applicationService serviceInterface="com.intellij.openapi.fileEditor.impl.EditorEmptyTextPainter" serviceImplementation="com.jetbrains.python.edu.StudyInstructionPainter" overrides="true"/> + <errorHandler implementation="com.intellij.diagnostic.ITNReporter"/> </extensions> <extensions defaultExtensionNs="Pythonid"> <visitorFilter language="Python" implementationClass="com.jetbrains.python.edu.highlighting.StudyVisitorFilter"/> diff --git a/python/edu/learn-python/resources/courses/introduction_course.zip b/python/edu/learn-python/resources/courses/introduction_course.zip Binary files differindex c39695e0c380..4fd2dab43830 100644 --- a/python/edu/learn-python/resources/courses/introduction_course.zip +++ b/python/edu/learn-python/resources/courses/introduction_course.zip diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDirectoryProjectGenerator.java b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDirectoryProjectGenerator.java index 59bd8bc2ea7c..b558667734bc 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDirectoryProjectGenerator.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDirectoryProjectGenerator.java @@ -53,7 +53,7 @@ public class StudyDirectoryProjectGenerator extends PythonProjectGenerator imple @NotNull @Override public String getName() { - return "Learn Python"; + return "Educational"; } diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyTestRunner.java b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyTestRunner.java index b0cd5ba89fed..1800deb0a0d9 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyTestRunner.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyTestRunner.java @@ -61,7 +61,9 @@ public class StudyTestRunner { try { while ((line = testOutputReader.readLine()) != null) { if (line.contains(TEST_FAILED)) { - return line.substring(TEST_FAILED.length(), line.length()); + String res = line.substring(TEST_FAILED.length(), line.length()); + StudyUtils.closeSilently(testOutputReader); + return res; } } } diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyIntroductionCourseAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyIntroductionCourseAction.java new file mode 100644 index 000000000000..ed1fff22878f --- /dev/null +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyIntroductionCourseAction.java @@ -0,0 +1,75 @@ +/* + * Copyright 2000-2013 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.actions; + +import com.intellij.ide.impl.ProjectUtil; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.projectRoots.Sdk; +import com.jetbrains.python.configuration.PyConfigurableInterpreterList; +import com.jetbrains.python.edu.StudyDirectoryProjectGenerator; +import com.jetbrains.python.edu.StudyUtils; +import com.jetbrains.python.edu.course.CourseInfo; +import com.jetbrains.python.newProject.actions.GenerateProjectCallback; +import com.jetbrains.python.newProject.actions.ProjectSpecificSettingsStep; +import icons.StudyIcons; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.List; +import java.util.Map; + +public class StudyIntroductionCourseAction extends AnAction { + + public StudyIntroductionCourseAction() { + super("Introduction to Python", "Introduction to Python", StudyIcons.EducationalProjectType); + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + final File projectDir = new File(ProjectUtil.getBaseDir(), "PythonIntroduction"); + if (projectDir.exists()) { + ProjectUtil.openProject(projectDir.getPath(), null, false); + } + else { + final GenerateProjectCallback callback = new GenerateProjectCallback(null); + final StudyDirectoryProjectGenerator generator = new StudyDirectoryProjectGenerator(); + final Map<CourseInfo, File> courses = generator.getCourses(); + CourseInfo introCourse = null; + for (CourseInfo info : courses.keySet()) { + if ("Introduction to Python".equals(info.getName())) { + introCourse = info; + } + } + if (introCourse == null) { + introCourse = StudyUtils.getFirst(courses.keySet()); + } + generator.setSelectedCourse(introCourse); + final ProjectSpecificSettingsStep step = new ProjectSpecificSettingsStep(generator, callback, true); + + step.createPanel(); // initialize panel to set location + step.setLocation(projectDir.toString()); + + final Project project = ProjectManager.getInstance().getDefaultProject(); + final List<Sdk> sdks = PyConfigurableInterpreterList.getInstance(project).getAllPythonSdks(); + Sdk sdk = sdks.isEmpty() ? null : sdks.iterator().next(); + step.setSdk(sdk); + callback.consume(step); + } + } +} diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNewProject.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNewProject.java deleted file mode 100644 index 0b75c4bc01c4..000000000000 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNewProject.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2000-2013 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.actions; - -import com.jetbrains.python.edu.StudyDirectoryProjectGenerator; -import com.jetbrains.python.newProject.actions.GenerateProjectCallback; -import com.jetbrains.python.newProject.actions.ProjectSpecificAction; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class StudyNewProject extends ProjectSpecificAction { - - public StudyNewProject(@NotNull final String name, @Nullable final Runnable runnable) { - super(new GenerateProjectCallback(runnable), new StudyDirectoryProjectGenerator(), name, true); - } - - public StudyNewProject() { - this("Learn Python", null); - } - -} diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskFileAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskFileAction.java index a9448ddea0e3..a6c16d2553bf 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskFileAction.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskFileAction.java @@ -22,108 +22,139 @@ import com.jetbrains.python.edu.StudyTaskManager; import com.jetbrains.python.edu.StudyUtils; import com.jetbrains.python.edu.course.*; import com.jetbrains.python.edu.editor.StudyEditor; +import org.jetbrains.annotations.NotNull; import java.io.*; public class StudyRefreshTaskFileAction extends DumbAwareAction { private static final Logger LOG = Logger.getInstance(StudyRefreshTaskFileAction.class.getName()); - public void refresh(final Project project) { - ApplicationManager.getApplication().invokeLater(new Runnable() { + public static void refresh(final Project project) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") @Override public void run() { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") - @Override - public void run() { - final Editor editor = StudyEditor.getSelectedEditor(project); - assert editor != null; - final Document document = editor.getDocument(); - StudyDocumentListener listener = StudyEditor.getListener(document); - if (listener != null) { - document.removeDocumentListener(listener); - } - final int lineCount = document.getLineCount(); - if (lineCount != 0) { - CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() { - @Override - public void run() { - document.deleteString(0, document.getLineEndOffset(lineCount - 1)); - } - }); - } - StudyTaskManager taskManager = StudyTaskManager.getInstance(project); - Course course = taskManager.getCourse(); - assert course != null; - File resourceFile = new File(course.getResourcePath()); - File resourceRoot = resourceFile.getParentFile(); - FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance(); - VirtualFile openedFile = fileDocumentManager.getFile(document); - assert openedFile != null; - final TaskFile selectedTaskFile = taskManager.getTaskFile(openedFile); - assert selectedTaskFile != null; - Task currentTask = selectedTaskFile.getTask(); - String lessonDir = Lesson.LESSON_DIR + String.valueOf(currentTask.getLesson().getIndex() + 1); - String taskDir = Task.TASK_DIR + String.valueOf(currentTask.getIndex() + 1); - File pattern = new File(new File(new File(resourceRoot, lessonDir), taskDir), openedFile.getName()); - BufferedReader reader = null; - try { - reader = new BufferedReader(new InputStreamReader(new FileInputStream(pattern))); - String line; - StringBuilder patternText = new StringBuilder(); - while ((line = reader.readLine()) != null) { - patternText.append(line); - patternText.append("\n"); - } - int patternLength = patternText.length(); - if (patternText.charAt(patternLength - 1) == '\n') { - patternText.delete(patternLength - 1, patternLength); - } - document.setText(patternText); - StudyStatus oldStatus = currentTask.getStatus(); - LessonInfo lessonInfo = currentTask.getLesson().getLessonInfo(); - lessonInfo.update(oldStatus, -1); - lessonInfo.update(StudyStatus.Unchecked, +1); - StudyUtils.updateStudyToolWindow(project); - for (TaskWindow taskWindow : selectedTaskFile.getTaskWindows()) { - taskWindow.reset(); - } - ProjectView.getInstance(project).refresh(); - if (listener != null) { - document.addDocumentListener(listener); - } - selectedTaskFile.drawAllWindows(editor); - ApplicationManager.getApplication().invokeLater(new Runnable() { - @Override - public void run() { - IdeFocusManager.getInstance(project).requestFocus(editor.getContentComponent(), true); - } - }); - selectedTaskFile.navigateToFirstTaskWindow(editor); - BalloonBuilder balloonBuilder = - JBPopupFactory.getInstance().createHtmlTextBalloonBuilder("You can now start again", MessageType.INFO, null); - final Balloon balloon = balloonBuilder.createBalloon(); - StudyEditor selectedStudyEditor = StudyEditor.getSelectedStudyEditor(project); - assert selectedStudyEditor != null; - balloon.showInCenterOf(selectedStudyEditor.getRefreshButton()); - Disposer.register(project, balloon); - } - catch (FileNotFoundException e1) { - LOG.error(e1); - } - catch (IOException e1) { - LOG.error(e1); - } - finally { - StudyUtils.closeSilently(reader); - } - } - }); + final Editor editor = StudyEditor.getSelectedEditor(project); + assert editor != null; + final Document document = editor.getDocument(); + refreshFile(editor, document, project); } }); + } + }); } - public void actionPerformed(AnActionEvent e) { + public static void refreshFile(@NotNull final Editor editor, @NotNull final Document document, @NotNull final Project project) { + StudyTaskManager taskManager = StudyTaskManager.getInstance(project); + Course course = taskManager.getCourse(); + assert course != null; + FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance(); + VirtualFile openedFile = fileDocumentManager.getFile(document); + assert openedFile != null; + final TaskFile selectedTaskFile = taskManager.getTaskFile(openedFile); + assert selectedTaskFile != null; + String openedFileName = openedFile.getName(); + Task currentTask = selectedTaskFile.getTask(); + resetTaskFile(document, project, course, selectedTaskFile, openedFileName, currentTask); + selectedTaskFile.drawAllWindows(editor); + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + IdeFocusManager.getInstance(project).requestFocus(editor.getContentComponent(), true); + } + }); + selectedTaskFile.navigateToFirstTaskWindow(editor); + showBaloon(project); + } + + public static void resetTaskFile(Document document, Project project, Course course, TaskFile taskFile, String name, Task task) { + resetDocument(document, course, name, task); + updateLessonInfo(task); + StudyUtils.updateStudyToolWindow(project); + resetTaskWindows(taskFile); + ProjectView.getInstance(project).refresh(); + } + + private static void showBaloon(Project project) { + BalloonBuilder balloonBuilder = + JBPopupFactory.getInstance().createHtmlTextBalloonBuilder("You can now start again", MessageType.INFO, null); + final Balloon balloon = balloonBuilder.createBalloon(); + StudyEditor selectedStudyEditor = StudyEditor.getSelectedStudyEditor(project); + assert selectedStudyEditor != null; + balloon.showInCenterOf(selectedStudyEditor.getRefreshButton()); + Disposer.register(project, balloon); + } + + private static void resetTaskWindows(TaskFile selectedTaskFile) { + for (TaskWindow taskWindow : selectedTaskFile.getTaskWindows()) { + taskWindow.reset(); + } + } + + private static void updateLessonInfo(Task currentTask) { + StudyStatus oldStatus = currentTask.getStatus(); + LessonInfo lessonInfo = currentTask.getLesson().getLessonInfo(); + lessonInfo.update(oldStatus, -1); + lessonInfo.update(StudyStatus.Unchecked, +1); + } + + @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") + private static void resetDocument(Document document, Course course, String fileName, Task task) { + BufferedReader reader = null; + StudyDocumentListener listener = StudyEditor.getListener(document); + if (listener != null) { + document.removeDocumentListener(listener); + } + clearDocument(document); + try { + String lessonDir = Lesson.LESSON_DIR + String.valueOf(task.getLesson().getIndex() + 1); + String taskDir = Task.TASK_DIR + String.valueOf(task.getIndex() + 1); + File resourceFile = new File(course.getResourcePath()); + File resourceRoot = resourceFile.getParentFile(); + File pattern = new File(new File(new File(resourceRoot, lessonDir), taskDir), fileName); + reader = new BufferedReader(new InputStreamReader(new FileInputStream(pattern))); + String line; + StringBuilder patternText = new StringBuilder(); + while ((line = reader.readLine()) != null) { + patternText.append(line); + patternText.append("\n"); + } + int patternLength = patternText.length(); + if (patternText.charAt(patternLength - 1) == '\n') { + patternText.delete(patternLength - 1, patternLength); + } + document.setText(patternText); + } + catch (FileNotFoundException e) { + LOG.error(e); + } + catch (IOException e) { + LOG.error(e); + } + finally { + StudyUtils.closeSilently(reader); + } + if (listener != null) { + document.addDocumentListener(listener); + } + } + + private static void clearDocument(final Document document) { + final int lineCount = document.getLineCount(); + if (lineCount != 0) { + CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() { + @Override + public void run() { + document.deleteString(0, document.getLineEndOffset(lineCount - 1)); + } + }); + } + } + + public void actionPerformed(@NotNull AnActionEvent e) { refresh(e.getProject()); } } diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyReloadCourseAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyReloadCourseAction.java new file mode 100644 index 000000000000..beefaaa94f7e --- /dev/null +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyReloadCourseAction.java @@ -0,0 +1,134 @@ +/* + * 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.actions; + +import com.intellij.ide.projectView.ProjectView; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.ui.tree.TreeUtil; +import com.jetbrains.python.edu.StudyTaskManager; +import com.jetbrains.python.edu.StudyUtils; +import com.jetbrains.python.edu.course.Course; +import com.jetbrains.python.edu.course.Lesson; +import com.jetbrains.python.edu.course.Task; +import com.jetbrains.python.edu.course.TaskFile; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import javax.swing.tree.TreePath; +import java.util.List; +import java.util.Map; + +public class StudyReloadCourseAction extends DumbAwareAction { + + public StudyReloadCourseAction() { + super("Reload Course", "Reload Course", null); + } + + @Override + public void update(@NotNull AnActionEvent e) { + Presentation presentation = e.getPresentation(); + Project project = e.getProject(); + if (project != null) { + Course course = StudyTaskManager.getInstance(project).getCourse(); + if (course != null) { + presentation.setVisible(true); + presentation.setEnabled(true); + } + } + presentation.setVisible(false); + presentation.setEnabled(false); + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + Project project = e.getProject(); + if (project == null) { + return; + } + reloadCourse(project); + } + + public static void reloadCourse(@NotNull final Project project) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + Course course = StudyTaskManager.getInstance(project).getCourse(); + if (course == null) { + return; + } + for (VirtualFile file : FileEditorManager.getInstance(project).getOpenFiles()) { + FileEditorManager.getInstance(project).closeFile(file); + } + JTree tree = ProjectView.getInstance(project).getCurrentProjectViewPane().getTree(); + TreePath path = TreeUtil.getFirstNodePath(tree); + tree.collapsePath(path); + List<Lesson> lessons = course.getLessons(); + for (Lesson lesson : lessons) { + List<Task> tasks = lesson.getTaskList(); + VirtualFile lessonDir = project.getBaseDir().findChild(Lesson.LESSON_DIR + (lesson.getIndex() + 1)); + if (lessonDir == null) { + continue; + } + for (Task task : tasks) { + VirtualFile taskDir = lessonDir.findChild(Task.TASK_DIR + (task.getIndex() + 1)); + if (taskDir == null) { + continue; + } + Map<String, TaskFile> taskFiles = task.getTaskFiles(); + for (Map.Entry<String, TaskFile> entry : taskFiles.entrySet()) { + String name = entry.getKey(); + TaskFile taskFile = entry.getValue(); + VirtualFile file = taskDir.findChild(name); + if (file == null) { + continue; + } + Document document = FileDocumentManager.getInstance().getDocument(file); + if (document == null) { + continue; + } + StudyRefreshTaskFileAction.resetTaskFile(document, project, course, taskFile, name, task); + } + } + } + Lesson firstLesson = StudyUtils.getFirst(lessons); + if (firstLesson == null) { + return; + } + Task firstTask = StudyUtils.getFirst(firstLesson.getTaskList()); + VirtualFile lessonDir = project.getBaseDir().findChild(Lesson.LESSON_DIR + (firstLesson.getIndex() + 1)); + if (lessonDir != null) { + VirtualFile taskDir = lessonDir.findChild(Task.TASK_DIR + (firstTask.getIndex() + 1)); + if (taskDir != null) { + ProjectView.getInstance(project).select(taskDir, taskDir, true); + } + } + } + }); + } + }); + } +} diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyTaskNavigationAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyTaskNavigationAction.java index 46c0981cb964..b98bbd098776 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyTaskNavigationAction.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyTaskNavigationAction.java @@ -1,5 +1,6 @@ package com.jetbrains.python.edu.actions; +import com.intellij.ide.projectView.ProjectView; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.project.DumbAwareAction; @@ -12,6 +13,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindowId; import com.intellij.openapi.wm.ToolWindowManager; +import com.intellij.util.ui.tree.TreeUtil; import com.jetbrains.python.edu.StudyState; import com.jetbrains.python.edu.course.Lesson; import com.jetbrains.python.edu.course.Task; @@ -20,6 +22,7 @@ import com.jetbrains.python.edu.editor.StudyEditor; import org.jetbrains.annotations.NotNull; import javax.swing.*; +import javax.swing.tree.TreePath; import java.util.Map; @@ -74,7 +77,11 @@ abstract public class StudyTaskNavigationAction extends DumbAwareAction { } } } + JTree tree = ProjectView.getInstance(project).getCurrentProjectViewPane().getTree(); + TreePath path = TreeUtil.getFirstNodePath(tree); + tree.collapsePath(path); if (shouldBeActive != null) { + ProjectView.getInstance(project).select(shouldBeActive, shouldBeActive, false); FileEditorManager.getInstance(project).openFile(shouldBeActive, true); } ToolWindow runToolWindow = ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId.RUN); diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/projectView/StudyDirectoryNode.java b/python/edu/learn-python/src/com/jetbrains/python/edu/projectView/StudyDirectoryNode.java index 2f80dba12695..d8faacd23946 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/projectView/StudyDirectoryNode.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/projectView/StudyDirectoryNode.java @@ -1,8 +1,10 @@ package com.jetbrains.python.edu.projectView; import com.intellij.ide.projectView.PresentationData; +import com.intellij.ide.projectView.ProjectView; import com.intellij.ide.projectView.ViewSettings; import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode; +import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; @@ -17,6 +19,7 @@ import org.jetbrains.annotations.NotNull; import javax.swing.*; import java.awt.*; +import java.util.Set; public class StudyDirectoryNode extends PsiDirectoryNode { private final PsiDirectory myValue; @@ -110,4 +113,56 @@ public class StudyDirectoryNode extends PsiDirectoryNode { data.addText(additionalName, new SimpleTextAttributes(Font.PLAIN, color)); data.setIcon(icon); } + + @Override + public boolean canNavigate() { + return true; + } + + @Override + public boolean canNavigateToSource() { + return true; + } + + @Override + public void navigate(boolean requestFocus) { + if (myValue.getName().contains(Task.TASK_DIR)) { + TaskFile taskFile = null; + VirtualFile virtualFile = null; + for (PsiElement child : myValue.getChildren()) { + VirtualFile childFile = child.getContainingFile().getVirtualFile(); + taskFile = StudyTaskManager.getInstance(myProject).getTaskFile(childFile); + if (taskFile != null) { + virtualFile = childFile; + break; + } + } + if (taskFile != null) { + VirtualFile taskDir = virtualFile.getParent(); + Task task = taskFile.getTask(); + for (VirtualFile openFile : FileEditorManager.getInstance(myProject).getOpenFiles()) { + FileEditorManager.getInstance(myProject).closeFile(openFile); + } + VirtualFile child = null; + Set<String> fileNames = task.getTaskFiles().keySet(); + for (String name : fileNames) { + child = taskDir.findChild(name); + if (child != null) { + FileEditorManager.getInstance(myProject).openFile(child, true); + } + } + if (child != null) { + ProjectView.getInstance(myProject).select(child, child, false); + } + } + } + } + + @Override + public boolean expandOnDoubleClick() { + if (myValue.getName().contains(Task.TASK_DIR)) { + return false; + } + return super.expandOnDoubleClick(); + } } diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyToolWindowFactory.java b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyToolWindowFactory.java index a553978c416a..0f8c5a53511a 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyToolWindowFactory.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyToolWindowFactory.java @@ -9,6 +9,7 @@ import com.intellij.ui.content.Content; import com.intellij.ui.content.ContentFactory; import com.intellij.util.ui.UIUtil; import com.jetbrains.python.edu.StudyTaskManager; +import com.jetbrains.python.edu.actions.StudyReloadCourseAction; import com.jetbrains.python.edu.course.Course; import com.jetbrains.python.edu.course.Lesson; import com.jetbrains.python.edu.course.LessonInfo; @@ -17,6 +18,8 @@ import org.jetbrains.annotations.NotNull; import javax.swing.*; import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.util.List; public class StudyToolWindowFactory implements ToolWindowFactory, DumbAware { @@ -41,7 +44,15 @@ public class StudyToolWindowFactory implements ToolWindowFactory, DumbAware { contentPanel.add(new JLabel(authorLabel)); contentPanel.add(Box.createRigidArea(new Dimension(0, 10))); contentPanel.add(new JLabel(description)); + contentPanel.add(Box.createRigidArea(new Dimension(0, 10))); + JButton reloadCourseButton = new JButton("reload course"); + reloadCourseButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + StudyReloadCourseAction.reloadCourse(project); + } + }); + contentPanel.add(reloadCourseButton); int taskNum = 0; int taskSolved = 0; int lessonsCompleted = 0; diff --git a/python/edu/resources/idea/PyCharmEduApplicationInfo.xml b/python/edu/resources/idea/PyCharmEduApplicationInfo.xml index eed232dcd3c7..939c84129455 100644 --- a/python/edu/resources/idea/PyCharmEduApplicationInfo.xml +++ b/python/edu/resources/idea/PyCharmEduApplicationInfo.xml @@ -19,5 +19,5 @@ <feedback eap-url="http://www.jetbrains.com/feedback/feedback.jsp?product=PyCharm&build=$BUILD&timezone=$TIMEZONE&eval=$EVAL" release-url="http://www.jetbrains.com/feedback/feedback.jsp?product=PyCharm&build=$BUILD&timezone=$TIMEZONE&eval=$EVAL"/> - <help file="pycharm-eduhelp.jar" root="pycharm"/> + <help file="pycharm-eduhelp.jar" root="pycharm-edu"/> </component> |