diff --git a/python/edu/interactive-learning-python/interactive-learning-python.iml b/python/edu/interactive-learning-python/interactive-learning-python.iml index 96fc1b873f54..e92551376fe3 100644 --- a/python/edu/interactive-learning-python/interactive-learning-python.iml +++ b/python/edu/interactive-learning-python/interactive-learning-python.iml @@ -25,5 +25,6 @@ + \ No newline at end of file diff --git a/python/edu/interactive-learning-python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java b/python/edu/interactive-learning-python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java index a1252c5aef71..89d1b91f2826 100644 --- a/python/edu/interactive-learning-python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java +++ b/python/edu/interactive-learning-python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java @@ -8,7 +8,6 @@ import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.platform.DirectoryProjectGenerator; -import com.jetbrains.edu.learning.course.CourseInfo; import com.jetbrains.edu.learning.ui.StudyNewProjectPanel; import com.jetbrains.python.newProject.PythonProjectGenerator; import icons.InteractiveLearningPythonIcons; diff --git a/python/edu/interactive-learning-python/src/com/jetbrains/edu/learning/PyStudyExecutor.java b/python/edu/interactive-learning-python/src/com/jetbrains/edu/learning/PyStudyExecutor.java index f3950f7469cf..571edef4a54c 100644 --- a/python/edu/interactive-learning-python/src/com/jetbrains/edu/learning/PyStudyExecutor.java +++ b/python/edu/interactive-learning-python/src/com/jetbrains/edu/learning/PyStudyExecutor.java @@ -28,7 +28,7 @@ import com.intellij.openapi.ui.popup.Balloon; import com.intellij.openapi.ui.popup.BalloonBuilder; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.learning.course.Task; +import com.jetbrains.edu.courseFormat.Task; import com.jetbrains.python.run.PythonTracebackFilter; import com.jetbrains.python.sdk.PythonSdkType; import org.jetbrains.annotations.NotNull; diff --git a/python/edu/interactive-learning-python/src/com/jetbrains/edu/learning/PyStudyTestRunner.java b/python/edu/interactive-learning-python/src/com/jetbrains/edu/learning/PyStudyTestRunner.java index 267d14f72b22..4ad9d4162724 100644 --- a/python/edu/interactive-learning-python/src/com/jetbrains/edu/learning/PyStudyTestRunner.java +++ b/python/edu/interactive-learning-python/src/com/jetbrains/edu/learning/PyStudyTestRunner.java @@ -8,8 +8,8 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.learning.course.Course; -import com.jetbrains.edu.learning.course.Task; +import com.jetbrains.edu.courseFormat.Course; +import com.jetbrains.edu.courseFormat.Task; import com.jetbrains.python.sdk.PythonSdkType; import org.jetbrains.annotations.NotNull; diff --git a/python/edu/interactive-learning-python/src/com/jetbrains/edu/learning/actions/PyStudyIntroductionCourseAction.java b/python/edu/interactive-learning-python/src/com/jetbrains/edu/learning/actions/PyStudyIntroductionCourseAction.java index 4a65e37219b5..56378398d747 100644 --- a/python/edu/interactive-learning-python/src/com/jetbrains/edu/learning/actions/PyStudyIntroductionCourseAction.java +++ b/python/edu/interactive-learning-python/src/com/jetbrains/edu/learning/actions/PyStudyIntroductionCourseAction.java @@ -21,9 +21,9 @@ 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.edu.learning.CourseInfo; import com.jetbrains.edu.learning.PyStudyDirectoryProjectGenerator; import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.course.CourseInfo; import com.jetbrains.python.configuration.PyConfigurableInterpreterList; import com.jetbrains.python.newProject.actions.GenerateProjectCallback; import com.jetbrains.python.newProject.actions.ProjectSpecificSettingsStep; diff --git a/python/edu/interactive-learning-python/tests/JsonParserTest.java b/python/edu/interactive-learning-python/tests/JsonParserTest.java index b9fd9654a90e..b12b7ad85347 100644 --- a/python/edu/interactive-learning-python/tests/JsonParserTest.java +++ b/python/edu/interactive-learning-python/tests/JsonParserTest.java @@ -1,8 +1,8 @@ import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.jetbrains.edu.courseFormat.Course; import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.course.Course; import com.jetbrains.python.PythonHelpersLocator; import junit.framework.TestCase; diff --git a/python/edu/interactive-learning-python/tests/StudyDocumentListenerTest.java b/python/edu/interactive-learning-python/tests/StudyDocumentListenerTest.java index 87d9294b6bc5..b0f893ba1a64 100644 --- a/python/edu/interactive-learning-python/tests/StudyDocumentListenerTest.java +++ b/python/edu/interactive-learning-python/tests/StudyDocumentListenerTest.java @@ -3,9 +3,9 @@ import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.psi.PsiFile; +import com.jetbrains.edu.courseFormat.AnswerPlaceholder; +import com.jetbrains.edu.courseFormat.TaskFile; import com.jetbrains.edu.learning.StudyDocumentListener; -import com.jetbrains.edu.learning.course.TaskFile; -import com.jetbrains.edu.learning.course.AnswerPlaceholder; import java.util.ArrayList; import java.util.List; diff --git a/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/format/AnswerPlaceholder.java b/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/format/AnswerPlaceholder.java index daae19c12a00..5645e7547a14 100644 --- a/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/format/AnswerPlaceholder.java +++ b/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/format/AnswerPlaceholder.java @@ -27,9 +27,10 @@ public class AnswerPlaceholder implements Comparable { @Expose private String hint; @Expose private String possible_answer; @Expose private int length; + private int myIndex; + private String myTaskText; private int myReplacementLength; - private int myIndex; public String getHint() { return hint; diff --git a/python/educational/educational.iml b/python/educational/educational.iml index c90834f2d607..1bf31625ebef 100644 --- a/python/educational/educational.iml +++ b/python/educational/educational.iml @@ -7,5 +7,7 @@ + + \ No newline at end of file diff --git a/python/educational/interactive-learning/interactive-learning.iml b/python/educational/interactive-learning/interactive-learning.iml index 7413f5da4ba1..c901a4022546 100644 --- a/python/educational/interactive-learning/interactive-learning.iml +++ b/python/educational/interactive-learning/interactive-learning.iml @@ -5,10 +5,12 @@ + + \ No newline at end of file diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/CourseInfo.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/CourseInfo.java new file mode 100644 index 000000000000..2c354b183a28 --- /dev/null +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/CourseInfo.java @@ -0,0 +1,61 @@ +package com.jetbrains.edu.learning; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * Implementation of class which contains information to be shawn in course description in tool window + * and when project is being created + */ +public class CourseInfo { + boolean is_public; + public List sections; + @SerializedName("title") + private String myName; + @SerializedName("summary") + private String myDescription; + + private String myAuthor; + public static CourseInfo INVALID_COURSE = new CourseInfo("", "", ""); + + public CourseInfo(String name, String author, String description) { + myName = name; + myAuthor = author; + myDescription = description; + } + + public String getName() { + return myName; + } + + public String getAuthor() { + return myAuthor; + } + + public String getDescription() { + return myDescription; + } + + @Override + public String toString() { + return myName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CourseInfo that = (CourseInfo)o; + return that.getName().equals(myName) && that.getAuthor().equals(myAuthor) + && that.getDescription().equals(myDescription); + } + + @Override + public int hashCode() { + int result = myName != null ? myName.hashCode() : 0; + result = 31 * result + (myAuthor != null ? myAuthor.hashCode() : 0); + result = 31 * result + (myDescription != null ? myDescription.hashCode() : 0); + return result; + } +} diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyCourseResourceManager.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyCourseResourceManager.java new file mode 100644 index 000000000000..893b347c44e8 --- /dev/null +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyCourseResourceManager.java @@ -0,0 +1,26 @@ +package com.jetbrains.edu.learning; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.jetbrains.edu.courseFormat.Task; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class StudyCourseResourceManager { + + /** + * Gets text of resource file such as test input file or task text in needed format + * + * @param fileName name of resource file which should exist in task directory + * @param wrapHTML if it's necessary to wrap text with html tags + * @return text of resource file wrapped with html tags if necessary + */ + @Nullable + public String getResourceText(@NotNull final Project project, @NotNull final Task task, @NotNull final String fileName, boolean wrapHTML) { + VirtualFile taskDir = task.getTaskDir(project); + if (taskDir != null) { + return StudyUtils.getFileText(taskDir.getCanonicalPath(), fileName, wrapHTML, "UTF-8"); + } + return null; + } +} diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyDocumentListener.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyDocumentListener.java index 9c364ff6b9c1..aec45ce130ab 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyDocumentListener.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyDocumentListener.java @@ -5,8 +5,8 @@ import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.event.DocumentAdapter; import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.impl.event.DocumentEventImpl; -import com.jetbrains.edu.learning.course.TaskFile; -import com.jetbrains.edu.learning.course.AnswerPlaceholder; +import com.jetbrains.edu.courseFormat.AnswerPlaceholder; +import com.jetbrains.edu.courseFormat.TaskFile; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyEditorFactoryListener.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyEditorFactoryListener.java index 71a80953098c..6bec75980b34 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyEditorFactoryListener.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyEditorFactoryListener.java @@ -13,9 +13,10 @@ import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.problems.WolfTheProblemSolver; -import com.jetbrains.edu.learning.course.AnswerPlaceholder; -import com.jetbrains.edu.learning.course.TaskFile; +import com.jetbrains.edu.courseFormat.AnswerPlaceholder; +import com.jetbrains.edu.courseFormat.TaskFile; import com.jetbrains.edu.learning.editor.StudyEditor; +import com.jetbrains.edu.learning.navigation.StudyNavigator; import org.jetbrains.annotations.NotNull; import java.awt.*; @@ -41,10 +42,10 @@ class StudyEditorFactoryListener implements EditorFactoryListener { final AnswerPlaceholder answerPlaceholder = myTaskFile.getAnswerPlaceholder(editor.getDocument(), pos); if (answerPlaceholder != null) { myTaskFile.setSelectedAnswerPlaceholder(answerPlaceholder); - answerPlaceholder.draw(editor); + StudyPainter.drawAnswerPlaceholder(editor, answerPlaceholder); } else { - myTaskFile.drawAllWindows(editor); + StudyPainter.drawAllWindows(editor, myTaskFile); } } } @@ -70,11 +71,11 @@ class StudyEditorFactoryListener implements EditorFactoryListener { final StudyTaskManager taskManager = StudyTaskManager.getInstance(project); final TaskFile taskFile = taskManager.getTaskFile(openedFile); if (taskFile != null) { - taskFile.navigateToFirstTaskWindow(editor); + StudyNavigator.navigateToFirstTaskWindow(editor, taskFile); editor.addEditorMouseListener(new WindowSelectionListener(taskFile)); StudyEditor.addDocumentListener(document, new StudyDocumentListener(taskFile)); WolfTheProblemSolver.getInstance(project).clearProblems(openedFile); - taskFile.drawAllWindows(editor); + StudyPainter.drawAllWindows(editor, taskFile); } } } diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyExecutor.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyExecutor.java index 4db7566d8638..d834d94a00a2 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyExecutor.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyExecutor.java @@ -22,7 +22,7 @@ import com.intellij.lang.LanguageExtension; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.learning.course.Task; +import com.jetbrains.edu.courseFormat.Task; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyGenerator.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyGenerator.java new file mode 100644 index 000000000000..62628525f6c7 --- /dev/null +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyGenerator.java @@ -0,0 +1,138 @@ +package com.jetbrains.edu.learning; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.jetbrains.edu.StudyNames; +import com.jetbrains.edu.courseFormat.Course; +import com.jetbrains.edu.courseFormat.Lesson; +import com.jetbrains.edu.courseFormat.Task; +import com.jetbrains.edu.courseFormat.TaskFile; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public class StudyGenerator { + private StudyGenerator() { + + } + private static final Logger LOG = Logger.getInstance(StudyGenerator.class.getName()); + + /** + * Creates task files in its task folder in project user created + * + * @param taskDir project directory of task which task file belongs to + * @param resourceRoot directory where original task file stored + * @throws IOException + */ + public static void createTaskFile(@NotNull final VirtualFile taskDir, @NotNull final File resourceRoot, + @NotNull final String name) throws IOException { + String systemIndependentName = FileUtil.toSystemIndependentName(name); + final int index = systemIndependentName.lastIndexOf("/"); + if (index > 0) { + systemIndependentName = systemIndependentName.substring(index + 1); + } + File resourceFile = new File(resourceRoot, name); + File fileInProject = new File(taskDir.getPath(), systemIndependentName); + FileUtil.copy(resourceFile, fileInProject); + } + + /** + * Creates task directory in its lesson folder in project user created + * + * @param lessonDir project directory of lesson which task belongs to + * @param resourceRoot directory where original task file stored + * @throws IOException + */ + public static void createTask(@NotNull final Task task, @NotNull final VirtualFile lessonDir, @NotNull final File resourceRoot, + @NotNull final Project project) throws IOException { + VirtualFile taskDir = lessonDir.createChildDirectory(project, StudyNames.TASK_DIR + Integer.toString(task.getIndex() + 1)); + StudyUtils.markDirAsSourceRoot(taskDir, project); + File newResourceRoot = new File(resourceRoot, taskDir.getName()); + int i = 0; + for (Map.Entry taskFile : task.getTaskFiles().entrySet()) { + TaskFile taskFileContent = taskFile.getValue(); + taskFileContent.setIndex(i); + i++; + createTaskFile(taskDir, newResourceRoot, taskFile.getKey()); + } + File[] filesInTask = newResourceRoot.listFiles(); + if (filesInTask != null) { + for (File file : filesInTask) { + String fileName = file.getName(); + if (!task.isTaskFile(fileName)) { + File resourceFile = new File(newResourceRoot, fileName); + File fileInProject = new File(taskDir.getCanonicalPath(), fileName); + FileUtil.copy(resourceFile, fileInProject); + } + } + } + } + + /** + * Creates lesson directory in its course folder in project user created + * + * @param courseDir project directory of course + * @param resourceRoot directory where original lesson stored + * @throws IOException + */ + public static void createLesson(@NotNull final Lesson lesson, @NotNull final VirtualFile courseDir, @NotNull final File resourceRoot, + @NotNull final Project project) throws IOException { + String lessonDirName = StudyNames.LESSON_DIR + Integer.toString(lesson.getIndex() + 1); + VirtualFile lessonDir = courseDir.createChildDirectory(project, lessonDirName); + final List taskList = lesson.getTaskList(); + for (int i = 0; i < taskList.size(); i++) { + Task task = taskList.get(i); + task.setIndex(i); + createTask(task, lessonDir, new File(resourceRoot, lessonDir.getName()), project); + } + } + + /** + * Creates course directory in project user created + * + * @param baseDir project directory + * @param resourceRoot directory where original course is stored + */ + public static void createCourse(@NotNull final Course course, @NotNull final VirtualFile baseDir, @NotNull final File resourceRoot, + @NotNull final Project project) { + ApplicationManager.getApplication().invokeLater( + new Runnable() { + @Override + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + try { + final List lessons = course.getLessons(); + for (int i = 0; i < lessons.size(); i++) { + Lesson lesson = lessons.get(i); + lesson.setIndex(i); + createLesson(lesson, baseDir, resourceRoot, project); + } + baseDir.createChildDirectory(this, StudyNames.SANDBOX_DIR); + File[] files = resourceRoot.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return !name.contains(StudyNames.LESSON_DIR) && !name.equals("course.json") && !name.equals("hints"); + } + }); + for (File file : files) { + FileUtil.copy(file, new File(baseDir.getPath(), file.getName())); + } + } + catch (IOException e) { + LOG.error(e); + } + } + }); + } + }); + } +} diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyHighlightErrorFilter.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyHighlightErrorFilter.java index f1fdbf4bae41..bb5bad753930 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyHighlightErrorFilter.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyHighlightErrorFilter.java @@ -4,7 +4,7 @@ import com.intellij.codeInsight.highlighting.HighlightErrorFilter; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiErrorElement; import com.intellij.psi.PsiFile; -import com.jetbrains.edu.learning.course.TaskFile; +import com.jetbrains.edu.courseFormat.TaskFile; import org.jetbrains.annotations.NotNull; public class StudyHighlightErrorFilter extends HighlightErrorFilter{ diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyPainter.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyPainter.java new file mode 100644 index 000000000000..8ffb5b71d4b0 --- /dev/null +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyPainter.java @@ -0,0 +1,100 @@ +package com.jetbrains.edu.learning; + +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.RangeMarker; +import com.intellij.openapi.editor.actionSystem.EditorActionManager; +import com.intellij.openapi.editor.colors.EditorColors; +import com.intellij.openapi.editor.colors.EditorColorsManager; +import com.intellij.openapi.editor.colors.EditorColorsScheme; +import com.intellij.openapi.editor.impl.DocumentImpl; +import com.intellij.openapi.editor.markup.*; +import com.intellij.ui.JBColor; +import com.jetbrains.edu.courseFormat.AnswerPlaceholder; +import com.jetbrains.edu.courseFormat.TaskFile; +import org.jetbrains.annotations.NotNull; + +import java.awt.*; +import java.util.List; + +public class StudyPainter { + private StudyPainter() { + + } + + /** + * Draw task window with color according to its status + */ + public static void drawAnswerPlaceholder(@NotNull final Editor editor, AnswerPlaceholder answerPlaceholder) { + Document document = editor.getDocument(); + if (!answerPlaceholder.isValid(document)) { + return; + } + EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme(); + final TextAttributes defaultTestAttributes = new TextAttributes(scheme.getDefaultForeground(), scheme.getDefaultBackground(), null, + EffectType.BOXED, Font.PLAIN); + final JBColor color = answerPlaceholder.getColor(); + int startOffset = answerPlaceholder.getRealStartOffset(document); + RangeHighlighter + highlighter = editor.getMarkupModel().addRangeHighlighter(startOffset, startOffset + answerPlaceholder.getLength(), HighlighterLayer.LAST + 1, + defaultTestAttributes, HighlighterTargetArea.EXACT_RANGE); + highlighter.setCustomRenderer(new CustomHighlighterRenderer() { + @Override + public void paint(@NotNull Editor editor, @NotNull RangeHighlighter highlighter, @NotNull Graphics g) { + g.setColor(color); + Point point = editor.logicalPositionToXY(editor.offsetToLogicalPosition(highlighter.getStartOffset())); + Point pointEnd = editor.logicalPositionToXY(editor.offsetToLogicalPosition(highlighter.getEndOffset())); + g.drawRect(point.x, point.y - 2, (pointEnd.x - point.x), editor.getLineHeight() + 1); + } + }); + editor.getCaretModel().moveToOffset(startOffset); + highlighter.setGreedyToLeft(true); + highlighter.setGreedyToRight(true); + } + + + public static void drawAllWindows(Editor editor, TaskFile taskFile) { + editor.getMarkupModel().removeAllHighlighters(); + for (AnswerPlaceholder answerPlaceholder : taskFile.getAnswerPlaceholders()) { + drawAnswerPlaceholder(editor, answerPlaceholder); + } + final Document document = editor.getDocument(); + EditorActionManager.getInstance() + .setReadonlyFragmentModificationHandler(document, new TaskWindowDeleteHandler(editor)); + createGuardedBlocks(editor, taskFile); + editor.getColorsScheme().setColor(EditorColors.READONLY_FRAGMENT_BACKGROUND_COLOR, null); + } + + + /** + * Marks symbols adjacent to task windows as read-only fragments + */ + public static void createGuardedBlocks(@NotNull final Editor editor, TaskFile taskFile) { + final Document document = editor.getDocument(); + if (document instanceof DocumentImpl) { + DocumentImpl documentImpl = (DocumentImpl)document; + List blocks = documentImpl.getGuardedBlocks(); + for (AnswerPlaceholder answerPlaceholder : taskFile.getAnswerPlaceholders()) { + if (!answerPlaceholder.isValid(document)) { + return; + } + int start = answerPlaceholder.getRealStartOffset(document); + int end = start + answerPlaceholder.getLength(); + if (start != 0) { + createGuardedBlock(editor, blocks, start - 1, start); + } + if (end != document.getTextLength()) { + createGuardedBlock(editor, blocks, end, end + 1); + } + } + } + } + + private static void createGuardedBlock(Editor editor, List blocks, int start, int end) { + RangeHighlighter rh = editor.getMarkupModel() + .addRangeHighlighter(start, end, HighlighterLayer.LAST + 1, null, HighlighterTargetArea.EXACT_RANGE); + blocks.add(rh); + } + + +} diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyProjectGenerator.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyProjectGenerator.java index ff8c4f4af509..f9d657046da2 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyProjectGenerator.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyProjectGenerator.java @@ -24,7 +24,10 @@ import com.intellij.openapi.wm.ex.ToolWindowManagerEx; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.util.containers.ContainerUtil; -import com.jetbrains.edu.learning.course.*; +import com.jetbrains.edu.courseFormat.Course; +import com.jetbrains.edu.courseFormat.Lesson; +import com.jetbrains.edu.courseFormat.Task; +import com.jetbrains.edu.courseFormat.TaskFile; import com.jetbrains.edu.learning.stepic.StudyStepicConnector; import org.jetbrains.annotations.NotNull; @@ -56,7 +59,7 @@ public class StudyProjectGenerator { flushCourse(course); course.init(false); final File courseDirectory = new File(myCoursesDir, course.getName()); - course.create(baseDir, courseDirectory, project); + StudyGenerator.createCourse(course, baseDir, courseDirectory, project); course.setCourseDirectory(new File(myCoursesDir, mySelectedCourseInfo.getName()).getAbsolutePath()); VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); StudyTaskManager.getInstance(project).setCourse(course); diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudySmartChecker.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudySmartChecker.java new file mode 100644 index 000000000000..bd5c12412133 --- /dev/null +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudySmartChecker.java @@ -0,0 +1,85 @@ +package com.jetbrains.edu.learning; + +import com.intellij.execution.ExecutionException; +import com.intellij.execution.process.CapturingProcessHandler; +import com.intellij.execution.process.ProcessOutput; +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.vfs.VirtualFile; +import com.jetbrains.edu.StudyNames; +import com.jetbrains.edu.courseFormat.AnswerPlaceholder; +import com.jetbrains.edu.courseFormat.StudyStatus; +import com.jetbrains.edu.courseFormat.TaskFile; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; + +public class StudySmartChecker { + private StudySmartChecker() { + + } + private static final Logger LOG = Logger.getInstance(StudySmartChecker.class); + + public static void smartCheck(@NotNull final AnswerPlaceholder placeholder, + @NotNull final Project project, + @NotNull final VirtualFile answerFile, + @NotNull final TaskFile answerTaskFile, + @NotNull final TaskFile usersTaskFile, + @NotNull final StudyTestRunner testRunner, + @NotNull final VirtualFile virtualFile, + @NotNull final Document usersDocument) { + + try { + final int index = placeholder.getIndex(); + final VirtualFile windowCopy = + answerFile.copy(project, answerFile.getParent(), answerFile.getNameWithoutExtension() + index + StudyNames.WINDOW_POSTFIX); + final FileDocumentManager documentManager = FileDocumentManager.getInstance(); + final Document windowDocument = documentManager.getDocument(windowCopy); + if (windowDocument != null) { + final File resourceFile = StudyUtils.copyResourceFile(virtualFile.getName(), windowCopy.getName(), project, usersTaskFile.getTask()); + final TaskFile windowTaskFile = new TaskFile(); + TaskFile.copy(answerTaskFile, windowTaskFile); + StudyDocumentListener listener = new StudyDocumentListener(windowTaskFile); + windowDocument.addDocumentListener(listener); + int start = placeholder.getRealStartOffset(windowDocument); + int end = start + placeholder.getLength(); + final AnswerPlaceholder userAnswerPlaceholder = usersTaskFile.getAnswerPlaceholders().get(placeholder.getIndex()); + int userStart = userAnswerPlaceholder.getRealStartOffset(usersDocument); + int userEnd = userStart + userAnswerPlaceholder.getLength(); + String text = usersDocument.getText(new TextRange(userStart, userEnd)); + windowDocument.replaceString(start, end, text); + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + documentManager.saveDocument(windowDocument); + } + }); + VirtualFile fileWindows = StudyUtils.flushWindows(windowTaskFile, windowCopy); + Process smartTestProcess = testRunner.createCheckProcess(project, windowCopy.getPath()); + final CapturingProcessHandler handler = new CapturingProcessHandler(smartTestProcess); + final ProcessOutput output = handler.runProcess(); + boolean res = testRunner.getTestsOutput(output).equals(StudyTestRunner.TEST_OK); + userAnswerPlaceholder.setStatus(res ? StudyStatus.Solved : StudyStatus.Failed, StudyStatus.Unchecked); + StudyUtils.deleteFile(windowCopy); + if (fileWindows != null) { + StudyUtils.deleteFile(fileWindows); + } + if (!resourceFile.delete()) { + LOG.error("failed to delete", resourceFile.getPath()); + } + } + } + catch (ExecutionException e) { + LOG.error(e); + } + catch (IOException e) { + LOG.error(e); + } + } + +} diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyState.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyState.java index bf6d20029255..4aa06c5fb609 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyState.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyState.java @@ -3,8 +3,8 @@ package com.jetbrains.edu.learning; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.learning.course.Task; -import com.jetbrains.edu.learning.course.TaskFile; +import com.jetbrains.edu.courseFormat.Task; +import com.jetbrains.edu.courseFormat.TaskFile; import com.jetbrains.edu.learning.editor.StudyEditor; import org.jetbrains.annotations.Nullable; diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyTaskManager.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyTaskManager.java index efc68ca8ba97..3bd234de9735 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyTaskManager.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyTaskManager.java @@ -27,11 +27,12 @@ import com.intellij.openapi.vfs.VirtualFileEvent; import com.intellij.openapi.vfs.VirtualFileManager; import com.intellij.openapi.wm.*; import com.intellij.util.xmlb.XmlSerializer; +import com.jetbrains.edu.StudyNames; +import com.jetbrains.edu.courseFormat.Course; +import com.jetbrains.edu.courseFormat.Lesson; +import com.jetbrains.edu.courseFormat.Task; +import com.jetbrains.edu.courseFormat.TaskFile; import com.jetbrains.edu.learning.actions.*; -import com.jetbrains.edu.learning.course.Course; -import com.jetbrains.edu.learning.course.Lesson; -import com.jetbrains.edu.learning.course.Task; -import com.jetbrains.edu.learning.course.TaskFile; import com.jetbrains.edu.learning.ui.StudyCondition; import com.jetbrains.edu.learning.ui.StudyToolWindowFactory; import org.jdom.Element; @@ -326,7 +327,7 @@ public class StudyTaskManager implements ProjectComponent, PersistentStateCompon return null; } final String taskDirName = taskDir.getName(); - if (taskDirName.contains(Task.TASK_DIR)) { + if (taskDirName.contains(StudyNames.TASK_DIR)) { final VirtualFile lessonDir = taskDir.getParent(); if (lessonDir != null) { int lessonIndex = StudyUtils.getIndex(lessonDir.getName(), StudyNames.LESSON_DIR); @@ -335,7 +336,7 @@ public class StudyTaskManager implements ProjectComponent, PersistentStateCompon return null; } final Lesson lesson = lessons.get(lessonIndex); - int taskIndex = StudyUtils.getIndex(taskDirName, Task.TASK_DIR); + int taskIndex = StudyUtils.getIndex(taskDirName, StudyNames.TASK_DIR); final List tasks = lesson.getTaskList(); if (!StudyUtils.indexIsValid(taskIndex, tasks)) { return null; @@ -352,8 +353,8 @@ public class StudyTaskManager implements ProjectComponent, PersistentStateCompon public void fileCreated(@NotNull VirtualFileEvent event) { final VirtualFile createdFile = event.getFile(); final VirtualFile taskDir = createdFile.getParent(); - if (taskDir != null && taskDir.getName().contains(Task.TASK_DIR)) { - int taskIndex = StudyUtils.getIndex(taskDir.getName(), Task.TASK_DIR); + if (taskDir != null && taskDir.getName().contains(StudyNames.TASK_DIR)) { + int taskIndex = StudyUtils.getIndex(taskDir.getName(), StudyNames.TASK_DIR); final VirtualFile lessonDir = taskDir.getParent(); if (lessonDir != null && lessonDir.getName().contains(StudyNames.LESSON_DIR)) { int lessonIndex = StudyUtils.getIndex(lessonDir.getName(), StudyNames.LESSON_DIR); diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyTestRunner.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyTestRunner.java index af401afdf6b0..6f1f64812157 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyTestRunner.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyTestRunner.java @@ -4,7 +4,7 @@ import com.intellij.execution.ExecutionException; import com.intellij.execution.process.ProcessOutput; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.learning.course.Task; +import com.jetbrains.edu.courseFormat.Task; import org.jetbrains.annotations.NotNull; public abstract class StudyTestRunner { diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyUtils.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyUtils.java index 64f74ce7bc04..633cc650153d 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyUtils.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyUtils.java @@ -29,7 +29,11 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.util.ui.UIUtil; -import com.jetbrains.edu.learning.course.*; +import com.jetbrains.edu.StudyNames; +import com.jetbrains.edu.courseFormat.AnswerPlaceholder; +import com.jetbrains.edu.courseFormat.Course; +import com.jetbrains.edu.courseFormat.Task; +import com.jetbrains.edu.courseFormat.TaskFile; import com.jetbrains.edu.learning.editor.StudyEditor; import com.jetbrains.edu.learning.ui.StudyToolWindowFactory; import org.jetbrains.annotations.NotNull; @@ -197,7 +201,7 @@ public class StudyUtils { int taskNum = task.getIndex() + 1; int lessonNum = task.getLesson().getIndex() + 1; assert course != null; - final String pathToResource = FileUtil.join(course.getCourseDirectory(), StudyNames.LESSON_DIR + lessonNum, Task.TASK_DIR + taskNum); + final String pathToResource = FileUtil.join(course.getCourseDirectory(), StudyNames.LESSON_DIR + lessonNum, StudyNames.TASK_DIR + taskNum); final File resourceFile = new File(pathToResource, copyName); FileUtil.copy(new File(pathToResource, sourceName), resourceFile); return resourceFile; diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyCheckAction.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyCheckAction.java index 004b58399a24..37db1925b7a1 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyCheckAction.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyCheckAction.java @@ -30,15 +30,13 @@ import com.intellij.openapi.wm.IdeFrame; import com.intellij.openapi.wm.WindowManager; import com.intellij.openapi.wm.ex.StatusBarEx; import com.intellij.openapi.wm.ex.WindowManagerEx; -import com.jetbrains.edu.learning.StudyDocumentListener; -import com.jetbrains.edu.learning.StudyState; -import com.jetbrains.edu.learning.StudyTestRunner; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.course.StudyStatus; -import com.jetbrains.edu.learning.course.Task; -import com.jetbrains.edu.learning.course.TaskFile; -import com.jetbrains.edu.learning.course.AnswerPlaceholder; +import com.jetbrains.edu.courseFormat.AnswerPlaceholder; +import com.jetbrains.edu.courseFormat.StudyStatus; +import com.jetbrains.edu.courseFormat.Task; +import com.jetbrains.edu.courseFormat.TaskFile; +import com.jetbrains.edu.learning.*; import com.jetbrains.edu.learning.editor.StudyEditor; +import com.jetbrains.edu.learning.navigation.StudyNavigator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -98,7 +96,7 @@ public class StudyCheckAction extends DumbAwareAction { FileEditor fileEditor = FileEditorManager.getInstance(project).getSelectedEditor(virtualFile); if (fileEditor instanceof StudyEditor) { StudyEditor studyEditor = (StudyEditor)fileEditor; - taskFile.drawAllWindows(studyEditor.getEditor()); + StudyPainter.drawAllWindows(studyEditor.getEditor(), taskFile); } } } @@ -303,7 +301,8 @@ public class StudyCheckAction extends DumbAwareAction { IdeFocusManager.getInstance(project).requestFocus(editorToNavigate.getContentComponent(), true); } }); - taskFileToNavigate.navigateToFirstFailedTaskWindow(editor); + + StudyNavigator.navigateToFirstFailedTaskWindow(editor, taskFileToNavigate); } private void runSmartTestProcess(@NotNull final VirtualFile taskDir, @@ -325,7 +324,8 @@ public class StudyCheckAction extends DumbAwareAction { if (!answerPlaceholder.isValid(document)) { continue; } - answerPlaceholder.smartCheck(project, answerFile, answerTaskFile, taskFile, testRunner, virtualFile, document); + StudySmartChecker.smartCheck(answerPlaceholder, project, answerFile, answerTaskFile, taskFile, testRunner, + virtualFile, document); } StudyUtils.deleteFile(answerFile); } diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyEditInputAction.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyEditInputAction.java index a5b20ed3c645..70b600c03c65 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyEditInputAction.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyEditInputAction.java @@ -20,12 +20,12 @@ import com.intellij.ui.tabs.TabInfo; import com.intellij.ui.tabs.TabsListener; import com.intellij.ui.tabs.impl.JBEditorTabs; import com.intellij.util.PlatformIcons; -import com.jetbrains.edu.learning.StudyNames; +import com.jetbrains.edu.StudyNames; +import com.jetbrains.edu.courseFormat.Task; +import com.jetbrains.edu.courseFormat.TaskFile; +import com.jetbrains.edu.courseFormat.UserTest; import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.course.Task; -import com.jetbrains.edu.learning.course.TaskFile; -import com.jetbrains.edu.learning.course.UserTest; import com.jetbrains.edu.learning.editor.StudyEditor; import com.jetbrains.edu.learning.ui.StudyTestContentPanel; import org.jetbrains.annotations.NotNull; @@ -62,7 +62,7 @@ public class StudyEditInputAction extends DumbAwareAction { if (newSelection.getIcon() != null) { int tabCount = tabbedPane.getTabCount(); VirtualFile taskDir = openedFile.getParent(); - VirtualFile testsDir = taskDir.findChild(Task.USER_TESTS); + VirtualFile testsDir = taskDir.findChild(StudyNames.USER_TESTS); assert testsDir != null; UserTest userTest = createUserTest(testsDir, currentTask); userTest.setEditable(true); diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyNextStudyTaskAction.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyNextStudyTaskAction.java index 831208fcf5fa..458039259122 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyNextStudyTaskAction.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyNextStudyTaskAction.java @@ -1,7 +1,8 @@ package com.jetbrains.edu.learning.actions; -import com.jetbrains.edu.learning.course.Task; +import com.jetbrains.edu.courseFormat.Task; import com.jetbrains.edu.learning.editor.StudyEditor; +import com.jetbrains.edu.learning.navigation.StudyNavigator; import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -23,6 +24,6 @@ public class StudyNextStudyTaskAction extends StudyTaskNavigationAction { @Override protected Task getTargetTask(@NotNull final Task sourceTask) { - return sourceTask.next(); + return StudyNavigator.nextTask(sourceTask); } } \ No newline at end of file diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyNextWindowAction.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyNextWindowAction.java index fd94a66b8944..a509f57b6bc3 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyNextWindowAction.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyNextWindowAction.java @@ -1,8 +1,8 @@ package com.jetbrains.edu.learning.actions; import com.intellij.icons.AllIcons; +import com.jetbrains.edu.courseFormat.AnswerPlaceholder; import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.course.AnswerPlaceholder; import org.jetbrains.annotations.NotNull; import java.util.List; diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyPrevWindowAction.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyPrevWindowAction.java index ccbf614d2c88..bbdaabd885fe 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyPrevWindowAction.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyPrevWindowAction.java @@ -1,7 +1,7 @@ package com.jetbrains.edu.learning.actions; +import com.jetbrains.edu.courseFormat.AnswerPlaceholder; import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.course.AnswerPlaceholder; import icons.InteractiveLearningIcons; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyPreviousStudyTaskAction.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyPreviousStudyTaskAction.java index 1c3dc5a95cb6..35e8048f49ca 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyPreviousStudyTaskAction.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyPreviousStudyTaskAction.java @@ -1,8 +1,9 @@ package com.jetbrains.edu.learning.actions; -import com.jetbrains.edu.learning.course.Task; +import com.jetbrains.edu.courseFormat.Task; import com.jetbrains.edu.learning.editor.StudyEditor; +import com.jetbrains.edu.learning.navigation.StudyNavigator; import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -23,6 +24,6 @@ public class StudyPreviousStudyTaskAction extends StudyTaskNavigationAction { @Override protected Task getTargetTask(@NotNull final Task sourceTask) { - return sourceTask.prev(); + return StudyNavigator.previousTask(sourceTask); } } \ No newline at end of file diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyRefreshTaskFileAction.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyRefreshTaskFileAction.java index 12449413280a..2c08b78424f2 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyRefreshTaskFileAction.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyRefreshTaskFileAction.java @@ -19,12 +19,15 @@ import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.IdeFocusManager; -import com.jetbrains.edu.learning.StudyNames; import com.intellij.problems.WolfTheProblemSolver; +import com.jetbrains.edu.StudyNames; +import com.jetbrains.edu.courseFormat.*; +import com.jetbrains.edu.courseFormat.info.LessonInfo; +import com.jetbrains.edu.learning.StudyPainter; import com.jetbrains.edu.learning.StudyState; import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.course.*; import com.jetbrains.edu.learning.editor.StudyEditor; +import com.jetbrains.edu.learning.navigation.StudyNavigator; import org.jetbrains.annotations.NotNull; import java.io.File; @@ -63,15 +66,16 @@ public class StudyRefreshTaskFileAction extends DumbAwareAction { } WolfTheProblemSolver.getInstance(project).clearProblems(studyState.getVirtualFile()); taskFile.setHighlightErrors(false); - taskFile.drawAllWindows(editor); - taskFile.createGuardedBlocks(editor); + StudyPainter.drawAllWindows(editor, taskFile); + StudyPainter.createGuardedBlocks(editor, taskFile); ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { IdeFocusManager.getInstance(project).requestFocus(editor.getContentComponent(), true); } }); - taskFile.navigateToFirstTaskWindow(editor); + + StudyNavigator.navigateToFirstTaskWindow(editor, taskFile); showBalloon(project, "You can start again now", MessageType.INFO); } @@ -121,7 +125,7 @@ public class StudyRefreshTaskFileAction extends DumbAwareAction { clearDocument(document); Task task = taskFile.getTask(); String lessonDir = StudyNames.LESSON_DIR + String.valueOf(task.getLesson().getIndex() + 1); - String taskDir = Task.TASK_DIR + String.valueOf(task.getIndex() + 1); + String taskDir = StudyNames.TASK_DIR + String.valueOf(task.getIndex() + 1); Course course = task.getLesson().getCourse(); File resourceFile = new File(course.getCourseDirectory()); if (!resourceFile.exists()) { diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyRunAction.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyRunAction.java index 19013eccdf7e..5f76fe2b939e 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyRunAction.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyRunAction.java @@ -14,10 +14,10 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.vfs.VirtualFile; +import com.jetbrains.edu.courseFormat.Task; +import com.jetbrains.edu.courseFormat.TaskFile; import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.course.Task; -import com.jetbrains.edu.learning.course.TaskFile; import com.jetbrains.edu.learning.editor.StudyEditor; import org.jetbrains.annotations.NotNull; diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyShowHintAction.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyShowHintAction.java index a42a203c84b9..ab3a25e10eef 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyShowHintAction.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyShowHintAction.java @@ -13,11 +13,11 @@ import com.intellij.openapi.util.Disposer; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; +import com.jetbrains.edu.courseFormat.AnswerPlaceholder; +import com.jetbrains.edu.courseFormat.Course; import com.jetbrains.edu.learning.StudyState; import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.course.AnswerPlaceholder; -import com.jetbrains.edu.learning.course.Course; import com.jetbrains.edu.learning.editor.StudyEditor; import icons.InteractiveLearningIcons; import org.jetbrains.annotations.NotNull; diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyTaskNavigationAction.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyTaskNavigationAction.java index 98cbf3d6f3e3..3fc949b366fa 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyTaskNavigationAction.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyTaskNavigationAction.java @@ -14,10 +14,10 @@ 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.edu.learning.StudyNames; +import com.jetbrains.edu.StudyNames; +import com.jetbrains.edu.courseFormat.Task; +import com.jetbrains.edu.courseFormat.TaskFile; import com.jetbrains.edu.learning.StudyState; -import com.jetbrains.edu.learning.course.Task; -import com.jetbrains.edu.learning.course.TaskFile; import com.jetbrains.edu.learning.editor.StudyEditor; import org.jetbrains.annotations.NotNull; @@ -60,7 +60,7 @@ abstract public class StudyTaskNavigationAction extends DumbAwareAction { if (lessonDir == null) { return; } - String taskDirName = Task.TASK_DIR + String.valueOf(nextTaskIndex + 1); + String taskDirName = StudyNames.TASK_DIR + String.valueOf(nextTaskIndex + 1); VirtualFile taskDir = lessonDir.findChild(taskDirName); if (taskDir == null) { return; diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyWindowNavigationAction.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyWindowNavigationAction.java index c6b0685d8233..ee41db88628d 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyWindowNavigationAction.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyWindowNavigationAction.java @@ -6,11 +6,12 @@ import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; +import com.jetbrains.edu.courseFormat.AnswerPlaceholder; +import com.jetbrains.edu.courseFormat.TaskFile; import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.course.AnswerPlaceholder; -import com.jetbrains.edu.learning.course.TaskFile; import com.jetbrains.edu.learning.editor.StudyEditor; +import com.jetbrains.edu.learning.navigation.StudyNavigator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -39,7 +40,7 @@ abstract public class StudyWindowNavigationAction extends DumbAwareAction { if (nextAnswerPlaceholder == null) { return; } - selectedTaskFile.navigateToTaskWindow(selectedEditor, nextAnswerPlaceholder); + StudyNavigator.navigateToTaskWindow(selectedEditor, nextAnswerPlaceholder, selectedTaskFile); selectedTaskFile.setSelectedAnswerPlaceholder(nextAnswerPlaceholder); } } diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/editor/StudyEditor.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/editor/StudyEditor.java index 6c7e3c04c238..3138f91a145f 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/editor/StudyEditor.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/editor/StudyEditor.java @@ -33,11 +33,11 @@ import com.intellij.ui.HideableTitledPanel; import com.intellij.ui.JBColor; import com.intellij.util.ui.EmptyClipboardOwner; import com.intellij.util.ui.UIUtil; +import com.jetbrains.edu.courseFormat.Task; +import com.jetbrains.edu.courseFormat.TaskFile; import com.jetbrains.edu.learning.StudyDocumentListener; import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.actions.*; -import com.jetbrains.edu.learning.course.Task; -import com.jetbrains.edu.learning.course.TaskFile; import icons.InteractiveLearningIcons; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/editor/StudyFileEditorProvider.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/editor/StudyFileEditorProvider.java index ae8df1652681..61c7ab4f335e 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/editor/StudyFileEditorProvider.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/editor/StudyFileEditorProvider.java @@ -8,8 +8,8 @@ import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; +import com.jetbrains.edu.courseFormat.TaskFile; import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.course.TaskFile; import org.jdom.Element; import org.jetbrains.annotations.NotNull; diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/navigation/StudyNavigator.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/navigation/StudyNavigator.java new file mode 100644 index 000000000000..cbe08bff52f9 --- /dev/null +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/navigation/StudyNavigator.java @@ -0,0 +1,85 @@ +package com.jetbrains.edu.learning.navigation; + +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.LogicalPosition; +import com.jetbrains.edu.courseFormat.*; +import com.jetbrains.edu.learning.StudyUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class StudyNavigator { + private StudyNavigator() { + + } + + public static Task nextTask(@NotNull final Task task) { + Lesson currentLesson = task.getLesson(); + List taskList = currentLesson.getTaskList(); + if (task.getIndex() + 1 < taskList.size()) { + return taskList.get(task.getIndex() + 1); + } + Lesson nextLesson = nextLesson(currentLesson); + if (nextLesson == null) { + return null; + } + return StudyUtils.getFirst(nextLesson.getTaskList()); + } + + public static Task previousTask(@NotNull final Task task) { + Lesson currentLesson = task.getLesson(); + if (task.getIndex() - 1 >= 0) { + return currentLesson.getTaskList().get(task.getIndex() - 1); + } + Lesson prevLesson = previousLesson(currentLesson); + if (prevLesson == null) { + return null; + } + //getting last task in previous lesson + return prevLesson.getTaskList().get(prevLesson.getTaskList().size() - 1); + } + + public static Lesson nextLesson(@NotNull final Lesson lesson) { + List lessons = lesson.getCourse().getLessons(); + if (lesson.getIndex() + 1 >= lessons.size()) { + return null; + } + return lessons.get(lesson.getIndex() + 1); + } + + public static Lesson previousLesson(@NotNull final Lesson lesson) { + if (lesson.getIndex() - 1 < 0) { + return null; + } + return lesson.getCourse().getLessons().get(lesson.getIndex() - 1); + } + + public static void navigateToFirstFailedTaskWindow(@NotNull final Editor editor, @NotNull final TaskFile taskFile) { + for (AnswerPlaceholder answerPlaceholder : taskFile.getAnswerPlaceholders()) { + if (answerPlaceholder.getStatus() != StudyStatus.Failed) { + continue; + } + navigateToTaskWindow(editor, answerPlaceholder, taskFile); + break; + } + } + + public static void navigateToTaskWindow(@NotNull final Editor editor, @NotNull final AnswerPlaceholder answerPlaceholder, + @NotNull final TaskFile taskFile) { + if (!answerPlaceholder.isValid(editor.getDocument())) { + return; + } + taskFile.setSelectedAnswerPlaceholder(answerPlaceholder); + LogicalPosition taskWindowStart = new LogicalPosition(answerPlaceholder.getLine(), answerPlaceholder.getStart()); + editor.getCaretModel().moveToLogicalPosition(taskWindowStart); + } + + + public static void navigateToFirstTaskWindow(@NotNull final Editor editor, @NotNull final TaskFile taskFile) { + if (!taskFile.getAnswerPlaceholders().isEmpty()) { + AnswerPlaceholder firstAnswerPlaceholder = StudyUtils.getFirst(taskFile.getAnswerPlaceholders()); + navigateToTaskWindow(editor, firstAnswerPlaceholder, taskFile); + } + } + +} diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/projectView/StudyDirectoryNode.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/projectView/StudyDirectoryNode.java index dff83de84d5d..a4a69835bd0b 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/projectView/StudyDirectoryNode.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/projectView/StudyDirectoryNode.java @@ -11,10 +11,10 @@ import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiElement; import com.intellij.ui.JBColor; import com.intellij.ui.SimpleTextAttributes; -import com.jetbrains.edu.learning.StudyNames; +import com.jetbrains.edu.StudyNames; +import com.jetbrains.edu.courseFormat.*; import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.course.*; import icons.InteractiveLearningIcons; import org.jetbrains.annotations.NotNull; @@ -49,7 +49,7 @@ public class StudyDirectoryNode extends PsiDirectoryNode { data.addText(" (" + valueName + ")", SimpleTextAttributes.GRAYED_ATTRIBUTES); return; } - if (valueName.contains(Task.TASK_DIR)) { + if (valueName.contains(StudyNames.TASK_DIR)) { TaskFile file = null; for (PsiElement child : myValue.getChildren()) { VirtualFile virtualFile = child.getContainingFile().getVirtualFile(); @@ -69,10 +69,10 @@ public class StudyDirectoryNode extends PsiDirectoryNode { setStudyAttributes(lesson, data, lesson.getName()); } - if (valueName.contains(Course.SANDBOX_DIR)) { + if (valueName.contains(StudyNames.SANDBOX_DIR)) { if (myValue.getParent() != null) { - if (!myValue.getParent().getName().contains(Course.SANDBOX_DIR)) { - data.setPresentableText(Course.SANDBOX_DIR); + if (!myValue.getParent().getName().contains(StudyNames.SANDBOX_DIR)) { + data.setPresentableText(StudyNames.SANDBOX_DIR); data.setIcon(InteractiveLearningIcons.Sandbox); return; } @@ -84,14 +84,14 @@ public class StudyDirectoryNode extends PsiDirectoryNode { @Override public int getTypeSortWeight(boolean sortByType) { String name = myValue.getName(); - if (name.contains(StudyNames.LESSON_DIR) || name.contains(Task.TASK_DIR)) { - String logicalName = name.contains(StudyNames.LESSON_DIR) ? StudyNames.LESSON_DIR : Task.TASK_DIR; + if (name.contains(StudyNames.LESSON_DIR) || name.contains(StudyNames.TASK_DIR)) { + String logicalName = name.contains(StudyNames.LESSON_DIR) ? StudyNames.LESSON_DIR : StudyNames.TASK_DIR; return StudyUtils.getIndex(name, logicalName) + 1; } - return name.contains(Course.SANDBOX_DIR) ? 0 : 3; + return name.contains(StudyNames.SANDBOX_DIR) ? 0 : 3; } - private static void setStudyAttributes(Stateful stateful, PresentationData data, String additionalName) { + private static void setStudyAttributes(StudyStateful stateful, PresentationData data, String additionalName) { StudyStatus taskStatus = stateful.getStatus(); switch (taskStatus) { case Unchecked: { @@ -127,7 +127,7 @@ public class StudyDirectoryNode extends PsiDirectoryNode { @Override public void navigate(boolean requestFocus) { - if (myValue.getName().contains(Task.TASK_DIR)) { + if (myValue.getName().contains(StudyNames.TASK_DIR)) { TaskFile taskFile = null; VirtualFile virtualFile = null; for (PsiElement child : myValue.getChildren()) { @@ -170,7 +170,7 @@ public class StudyDirectoryNode extends PsiDirectoryNode { @Override public boolean expandOnDoubleClick() { - if (myValue.getName().contains(Task.TASK_DIR)) { + if (myValue.getName().contains(StudyNames.TASK_DIR)) { return false; } return super.expandOnDoubleClick(); diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/projectView/StudyTreeStructureProvider.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/projectView/StudyTreeStructureProvider.java index 4c92d2316b9c..54e4ef12e5cd 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/projectView/StudyTreeStructureProvider.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/projectView/StudyTreeStructureProvider.java @@ -8,10 +8,9 @@ import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; +import com.jetbrains.edu.StudyNames; +import com.jetbrains.edu.courseFormat.TaskFile; import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.course.Course; -import com.jetbrains.edu.learning.course.Task; -import com.jetbrains.edu.learning.course.TaskFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -33,7 +32,7 @@ public class StudyTreeStructureProvider implements TreeStructureProvider, DumbAw if (project != null) { if (node.getValue() instanceof PsiDirectory) { final PsiDirectory nodeValue = (PsiDirectory)node.getValue(); - if (!nodeValue.getName().contains(Task.USER_TESTS)) { + if (!nodeValue.getName().contains(StudyNames.USER_TESTS)) { StudyDirectoryNode newNode = new StudyDirectoryNode(project, nodeValue, settings); nodes.add(newNode); } @@ -51,7 +50,7 @@ public class StudyTreeStructureProvider implements TreeStructureProvider, DumbAw } final String parentName = parent.getName(); if (parentName != null) { - if (parentName.equals(Course.SANDBOX_DIR)) { + if (parentName.equals(StudyNames.SANDBOX_DIR)) { nodes.add(node); } } diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/stepic/StudyStepicConnector.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/stepic/StudyStepicConnector.java index 2d00af9f911d..7a67cf79875f 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/stepic/StudyStepicConnector.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/stepic/StudyStepicConnector.java @@ -5,7 +5,11 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.intellij.openapi.diagnostic.Logger; import com.intellij.util.io.HttpRequests; -import com.jetbrains.edu.learning.course.*; +import com.jetbrains.edu.courseFormat.Course; +import com.jetbrains.edu.courseFormat.Lesson; +import com.jetbrains.edu.courseFormat.Task; +import com.jetbrains.edu.courseFormat.TaskFile; +import com.jetbrains.edu.learning.CourseInfo; import org.jetbrains.annotations.NotNull; import java.io.BufferedReader; diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java index 82af389036fd..117e9e91b553 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java @@ -3,9 +3,9 @@ package com.jetbrains.edu.learning.ui; import com.intellij.facet.ui.FacetValidatorsManager; import com.intellij.facet.ui.ValidationResult; import com.intellij.icons.AllIcons; +import com.jetbrains.edu.learning.CourseInfo; import com.jetbrains.edu.learning.StudyProjectGenerator; import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.course.CourseInfo; import org.jetbrains.annotations.NotNull; import javax.swing.*; diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/ui/StudyTestContentPanel.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/ui/StudyTestContentPanel.java index 8d9a284b0aee..fbfa809785b4 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/ui/StudyTestContentPanel.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/ui/StudyTestContentPanel.java @@ -3,7 +3,7 @@ package com.jetbrains.edu.learning.ui; import com.intellij.openapi.diagnostic.Logger; import com.intellij.ui.DocumentAdapter; import com.intellij.ui.components.JBScrollPane; -import com.jetbrains.edu.learning.course.UserTest; +import com.jetbrains.edu.courseFormat.UserTest; import org.jetbrains.annotations.NotNull; import javax.swing.*; diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/ui/StudyToolWindowFactory.java b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/ui/StudyToolWindowFactory.java index 180af572da85..35bf321f6331 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/ui/StudyToolWindowFactory.java +++ b/python/educational/interactive-learning/src/com/jetbrains/edu/learning/ui/StudyToolWindowFactory.java @@ -7,11 +7,11 @@ import com.intellij.openapi.wm.ToolWindowFactory; import com.intellij.ui.content.Content; import com.intellij.ui.content.ContentFactory; import com.intellij.util.ui.UIUtil; +import com.jetbrains.edu.courseFormat.Course; +import com.jetbrains.edu.courseFormat.Lesson; +import com.jetbrains.edu.courseFormat.StudyStatus; +import com.jetbrains.edu.courseFormat.info.LessonInfo; import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.course.Course; -import com.jetbrains.edu.learning.course.Lesson; -import com.jetbrains.edu.learning.course.LessonInfo; -import com.jetbrains.edu.learning.course.StudyStatus; import org.jetbrains.annotations.NotNull; import javax.swing.*; diff --git a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyNames.java b/python/educational/src/com/jetbrains/edu/StudyNames.java similarity index 71% rename from python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyNames.java rename to python/educational/src/com/jetbrains/edu/StudyNames.java index 0755e2a99809..c725513b8696 100644 --- a/python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyNames.java +++ b/python/educational/src/com/jetbrains/edu/StudyNames.java @@ -13,18 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.jetbrains.edu.learning; +package com.jetbrains.edu; import org.jetbrains.annotations.NonNls; @NonNls public class StudyNames { public static final String TASK_HTML = "task.html"; + public static final String TASK_TESTS = "tests.py"; public static final String LESSON = "lesson"; + public static final String TEST_HELPER = "test_helper.py"; + public static final String USER_TESTER = "user_tester.py"; public static final String LESSON_DIR = "lesson"; public static final String TEST_TAB_NAME = "test"; public static final String USER_TEST_INPUT = "input"; public static final String USER_TEST_OUTPUT = "output"; + public static final String WINDOW_POSTFIX = "_window.py"; + public static final String TASK_DIR = "task"; + public static final String USER_TESTS = "userTests"; + public static final String SANDBOX_DIR = "Sandbox"; private StudyNames() { } diff --git a/python/educational/src/com/jetbrains/edu/courseFormat/AnswerPlaceholder.java b/python/educational/src/com/jetbrains/edu/courseFormat/AnswerPlaceholder.java new file mode 100644 index 000000000000..8b1dcff642e5 --- /dev/null +++ b/python/educational/src/com/jetbrains/edu/courseFormat/AnswerPlaceholder.java @@ -0,0 +1,163 @@ +package com.jetbrains.edu.courseFormat; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; +import com.intellij.openapi.editor.Document; +import com.intellij.ui.JBColor; +import org.jetbrains.annotations.NotNull; + +/** + * Implementation of windows which user should type in + */ + +public class AnswerPlaceholder implements Comparable, StudyStateful { + + @Expose private int line = 0; + @Expose private int start = 0; + @Expose private String hint = ""; + @SerializedName("possible_answer") + @Expose private String possibleAnswer = ""; + @Expose private int length = 0; + private int myIndex = -1; + private StudyStatus myStatus = StudyStatus.Unchecked; + private String myTaskText; + private MyInitialState myInitialState; + + + private TaskFile myTaskFile; + + public void init(final TaskFile file, boolean isRestarted) { + if (!isRestarted) { + myInitialState = new MyInitialState(line, length, start); + } + myTaskFile = file; + } + + public StudyStatus getStatus() { + return myStatus; + } + + public void setStatus(StudyStatus status, StudyStatus oldStatus) { + myStatus = status; + } + + public int getIndex() { + return myIndex; + } + + public void setIndex(int index) { + myIndex = index; + } + + public int getLength() { + return length; + } + + public void setLength(int length) { + this.length = length; + } + + public int getStart() { + return start; + } + + public void setStart(int start) { + this.start = start; + } + + public int getLine() { + return line; + } + + public void setLine(int line) { + this.line = line; + } + + public String getHint() { + return hint; + } + + public void setHint(@NotNull final String hint) { + this.hint = hint; + } + + public String getPossibleAnswer() { + return possibleAnswer; + } + + public void setPossibleAnswer(String possibleAnswer) { + this.possibleAnswer = possibleAnswer; + } + + public MyInitialState getInitialState() { + return myInitialState; + } + + public void setInitialState(@NotNull final MyInitialState initialState) { + myInitialState = initialState; + } + + public TaskFile getTaskFile() { + return myTaskFile; + } + + public JBColor getColor() { + if (myStatus == StudyStatus.Solved) { + return JBColor.GREEN; + } + if (myStatus == StudyStatus.Failed) { + return JBColor.RED; + } + return JBColor.BLUE; + } + + public int getRealStartOffset(@NotNull final Document document) { + return document.getLineStartOffset(line) + start; + } + + public boolean isValid(@NotNull final Document document) { + boolean isLineValid = line < document.getLineCount() && line >= 0; + if (!isLineValid) return false; + boolean isStartValid = start >= 0 && start < document.getLineEndOffset(line); + boolean isLengthValid = (getRealStartOffset(document) + length) <= document.getTextLength(); + return isLengthValid && isStartValid; + } + + @Override + public int compareTo(@NotNull Object o) { + AnswerPlaceholder answerPlaceholder = (AnswerPlaceholder)o; + if (answerPlaceholder.getTaskFile() != myTaskFile) { + throw new ClassCastException(); + } + int lineDiff = line - answerPlaceholder.line; + if (lineDiff == 0) { + return start - answerPlaceholder.start; + } + return lineDiff; + } + + /** + * Returns window to its initial state + */ + public void reset() { + myStatus = StudyStatus.Unchecked; + line = myInitialState.myLine; + start = myInitialState.myStart; + length = myInitialState.myLength; + } + + private static class MyInitialState { + public int myLine = -1; + public int myLength = -1; + public int myStart = -1; + + public MyInitialState() { + } + + public MyInitialState(int line, int length, int start) { + myLine = line; + myLength = length; + myStart = start; + } + } +} diff --git a/python/educational/src/com/jetbrains/edu/courseFormat/Course.java b/python/educational/src/com/jetbrains/edu/courseFormat/Course.java new file mode 100644 index 000000000000..ad732c24d4cc --- /dev/null +++ b/python/educational/src/com/jetbrains/edu/courseFormat/Course.java @@ -0,0 +1,83 @@ +package com.jetbrains.edu.courseFormat; + +import com.intellij.lang.Language; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public class Course { + public List lessons = new ArrayList(); + + private String description; + private String name; + private String myCourseDirectory = ""; + private String author=""; + private boolean myUpToDate; + private String myLanguage; + + /** + * Initializes state of course + */ + public void init(boolean isRestarted) { + for (Lesson lesson : lessons) { + lesson.init(this, isRestarted); + } + } + + public List getLessons() { + return lessons; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCourseDirectory() { + return myCourseDirectory; + } + + public void setCourseDirectory(@NotNull final String courseDirectory) { + myCourseDirectory = courseDirectory; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public boolean isUpToDate() { + return myUpToDate; + } + + public void setUpToDate(boolean upToDate) { + myUpToDate = upToDate; + } + + public Language getLanguageById() { + return Language.findLanguageByID(myLanguage); + } + + public String getLanguage() { + return myLanguage; + } + + public void setLanguage(@NotNull final String language) { + myLanguage = language; + } +} diff --git a/python/educational/src/com/jetbrains/edu/courseFormat/Lesson.java b/python/educational/src/com/jetbrains/edu/courseFormat/Lesson.java new file mode 100644 index 000000000000..5db440fb1cfc --- /dev/null +++ b/python/educational/src/com/jetbrains/edu/courseFormat/Lesson.java @@ -0,0 +1,82 @@ +package com.jetbrains.edu.courseFormat; + +import com.google.gson.annotations.SerializedName; +import com.intellij.util.xmlb.annotations.Transient; +import com.jetbrains.edu.courseFormat.info.LessonInfo; + +import java.util.ArrayList; +import java.util.List; + +public class Lesson implements StudyStateful { + @Transient + String id; + @Transient + public List steps; + @Transient + public List tags; + @Transient + Boolean is_public; + @SerializedName("title") + private String name; + + public List taskList = new ArrayList(); + private Course myCourse = null; + private int myIndex = -1; + private LessonInfo myLessonInfo = new LessonInfo(); + + + public void init(final Course course, boolean isRestarted) { + myCourse = course; + myLessonInfo.setTaskNum(taskList.size()); + myLessonInfo.setTaskUnchecked(taskList.size()); + for (Task task : taskList) { + task.init(this, isRestarted); + } + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getIndex() { + return myIndex; + } + + public void setIndex(int index) { + myIndex = index; + } + + public LessonInfo getLessonInfo() { + return myLessonInfo; + } + + @Transient + public StudyStatus getStatus() { + for (Task task : taskList) { + StudyStatus taskStatus = task.getStatus(); + if (taskStatus == StudyStatus.Unchecked || taskStatus == StudyStatus.Failed) { + return StudyStatus.Unchecked; + } + } + return StudyStatus.Solved; + } + + @Override + public void setStatus(StudyStatus status, StudyStatus oldStatus) { + for (Task task : taskList) { + task.setStatus(status, oldStatus); + } + } + + public List getTaskList() { + return taskList; + } + + public Course getCourse() { + return myCourse; + } +} diff --git a/python/educational/src/com/jetbrains/edu/courseFormat/StudyStateful.java b/python/educational/src/com/jetbrains/edu/courseFormat/StudyStateful.java new file mode 100644 index 000000000000..57984fd5b6da --- /dev/null +++ b/python/educational/src/com/jetbrains/edu/courseFormat/StudyStateful.java @@ -0,0 +1,6 @@ +package com.jetbrains.edu.courseFormat; + +public interface StudyStateful { + StudyStatus getStatus(); + void setStatus(StudyStatus status, StudyStatus oldStatus); +} diff --git a/python/educational/src/com/jetbrains/edu/courseFormat/StudyStatus.java b/python/educational/src/com/jetbrains/edu/courseFormat/StudyStatus.java new file mode 100644 index 000000000000..917d299330d7 --- /dev/null +++ b/python/educational/src/com/jetbrains/edu/courseFormat/StudyStatus.java @@ -0,0 +1,8 @@ +package com.jetbrains.edu.courseFormat; + +/** + * @see {@link AnswerPlaceholder#myStatus} + */ +public enum StudyStatus { + Unchecked, Solved, Failed +} diff --git a/python/educational/src/com/jetbrains/edu/courseFormat/Task.java b/python/educational/src/com/jetbrains/edu/courseFormat/Task.java new file mode 100644 index 000000000000..c0345bde49a1 --- /dev/null +++ b/python/educational/src/com/jetbrains/edu/courseFormat/Task.java @@ -0,0 +1,141 @@ +package com.jetbrains.edu.courseFormat; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.xmlb.annotations.Transient; +import com.jetbrains.edu.StudyNames; +import com.jetbrains.edu.courseFormat.info.LessonInfo; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Implementation of task which contains task files, tests, input file for tests + */ +public class Task implements StudyStateful { + private String name; + private int myIndex; + public Map taskFiles = new HashMap(); + + private String text; + private String testsText; + + private Lesson myLesson; + private List userTests = new ArrayList(); + + + /** + * Initializes state of task file + * + * @param lesson lesson which task belongs to + */ + public void init(final Lesson lesson, boolean isRestarted) { + myLesson = lesson; + for (TaskFile taskFile : taskFiles.values()) { + taskFile.init(this, isRestarted); + } + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getText() { + return text; + } + + public void setText(@NotNull final String text) { + this.text = text; + } + + public int getIndex() { + return myIndex; + } + + public void setIndex(int index) { + myIndex = index; + } + + public String getTestsText() { + return testsText; + } + + public void setTestsText(@NotNull final String testsText) { + this.testsText = testsText; + } + + + + public Map getTaskFiles() { + return taskFiles; + } + + @Transient + public StudyStatus getStatus() { + for (TaskFile taskFile : taskFiles.values()) { + StudyStatus taskFileStatus = taskFile.getStatus(); + if (taskFileStatus == StudyStatus.Unchecked) { + return StudyStatus.Unchecked; + } + if (taskFileStatus == StudyStatus.Failed) { + return StudyStatus.Failed; + } + } + return StudyStatus.Solved; + } + + public void setStatus(@NotNull final StudyStatus status, @NotNull final StudyStatus oldStatus) { + LessonInfo lessonInfo = myLesson.getLessonInfo(); + if (status != oldStatus) { + lessonInfo.update(oldStatus, -1); + lessonInfo.update(status, +1); + } + for (TaskFile taskFile : taskFiles.values()) { + taskFile.setStatus(status, oldStatus); + } + } + + public List getUserTests() { + return userTests; + } + + public void setUserTests(@NotNull final List userTests) { + this.userTests = userTests; + } + + public boolean isTaskFile(@NotNull final String fileName) { + return taskFiles.get(fileName) != null; + } + + @Nullable + public TaskFile getFile(@NotNull final String fileName) { + return taskFiles.get(fileName); + } + + public Lesson getLesson() { + return myLesson; + } + + + @Nullable + public VirtualFile getTaskDir(Project project) { + String lessonDirName = StudyNames.LESSON_DIR + String.valueOf(myLesson.getIndex() + 1); + String taskDirName = StudyNames.TASK_DIR + String.valueOf(myIndex + 1); + VirtualFile courseDir = project.getBaseDir(); + if (courseDir != null) { + VirtualFile lessonDir = courseDir.findChild(lessonDirName); + if (lessonDir != null) { + return lessonDir.findChild(taskDirName); + } + } + return null; + } +} diff --git a/python/educational/src/com/jetbrains/edu/courseFormat/TaskFile.java b/python/educational/src/com/jetbrains/edu/courseFormat/TaskFile.java new file mode 100644 index 000000000000..8820a747b0a9 --- /dev/null +++ b/python/educational/src/com/jetbrains/edu/courseFormat/TaskFile.java @@ -0,0 +1,172 @@ +package com.jetbrains.edu.courseFormat; + +import com.google.gson.annotations.SerializedName; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.LogicalPosition; +import com.intellij.util.xmlb.annotations.Transient; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Implementation of task file which contains task windows for student to type in and + * which is visible to student in project view + */ + +public class TaskFile implements StudyStateful { + @SerializedName("placeholders") + private List myAnswerPlaceholders = new ArrayList(); + private int myIndex = -1; + + + public String name; + public String text; + private Task myTask; + @Transient + private AnswerPlaceholder mySelectedAnswerPlaceholder = null; + private boolean myUserCreated = false; + private boolean myTrackChanges = true; + private boolean myHighlightErrors = false; + + public void init(final Task task, boolean isRestarted) { + myTask = task; + for (AnswerPlaceholder answerPlaceholder : myAnswerPlaceholders) { + answerPlaceholder.init(this, isRestarted); + } + Collections.sort(myAnswerPlaceholders); + for (int i = 0; i < myAnswerPlaceholders.size(); i++) { + myAnswerPlaceholders.get(i).setIndex(i); + } + } + + /** + * @return if all the windows in task file are marked as resolved + */ + @Transient + public StudyStatus getStatus() { + for (AnswerPlaceholder answerPlaceholder : myAnswerPlaceholders) { + StudyStatus windowStatus = answerPlaceholder.getStatus(); + if (windowStatus == StudyStatus.Failed) { + return StudyStatus.Failed; + } + if (windowStatus == StudyStatus.Unchecked) { + return StudyStatus.Unchecked; + } + } + return StudyStatus.Solved; + } + + public void setStatus(@NotNull final StudyStatus status, @NotNull final StudyStatus oldStatus) { + for (AnswerPlaceholder answerPlaceholder : myAnswerPlaceholders) { + answerPlaceholder.setStatus(status, oldStatus); + } + } + + @Nullable + @Transient + public AnswerPlaceholder getSelectedAnswerPlaceholder() { + return mySelectedAnswerPlaceholder; + } + + public void setSelectedAnswerPlaceholder(@NotNull final AnswerPlaceholder selectedAnswerPlaceholder) { + if (selectedAnswerPlaceholder.getTaskFile() == this) { + mySelectedAnswerPlaceholder = selectedAnswerPlaceholder; + } + else { + throw new IllegalArgumentException("Window may be set as selected only in task file which it belongs to"); + } + } + + public List getAnswerPlaceholders() { + return myAnswerPlaceholders; + } + + public void setAnswerPlaceholders(List answerPlaceholders) { + this.myAnswerPlaceholders = answerPlaceholders; + } + + public int getIndex() { + return myIndex; + } + + public void setIndex(int index) { + myIndex = index; + } + + public Task getTask() { + return myTask; + } + + /** + * @param pos position in editor + * @return task window located in specified position or null if there is no task window in this position + */ + @Nullable + public AnswerPlaceholder getAnswerPlaceholder(@NotNull final Document document, @NotNull final LogicalPosition pos) { + int line = pos.line; + if (line >= document.getLineCount()) { + return null; + } + int column = pos.column; + int offset = document.getLineStartOffset(line) + column; + for (AnswerPlaceholder tw : myAnswerPlaceholders) { + if (tw.getLine() <= line) { + int twStartOffset = tw.getRealStartOffset(document); + final int length = tw.getLength() > 0 ? tw.getLength() : 0; + int twEndOffset = twStartOffset + length; + if (twStartOffset <= offset && offset <= twEndOffset) { + return tw; + } + } + } + return null; + } + + + public static void copy(@NotNull final TaskFile source, @NotNull final TaskFile target) { + List sourceAnswerPlaceholders = source.getAnswerPlaceholders(); + List windowsCopy = new ArrayList(sourceAnswerPlaceholders.size()); + for (AnswerPlaceholder answerPlaceholder : sourceAnswerPlaceholders) { + AnswerPlaceholder answerPlaceholderCopy = new AnswerPlaceholder(); + answerPlaceholderCopy.setLine(answerPlaceholder.getLine()); + answerPlaceholderCopy.setStart(answerPlaceholder.getStart()); + answerPlaceholderCopy.setLength(answerPlaceholder.getLength()); + answerPlaceholderCopy.setPossibleAnswer(answerPlaceholder.getPossibleAnswer()); + answerPlaceholderCopy.setIndex(answerPlaceholder.getIndex()); + windowsCopy.add(answerPlaceholderCopy); + } + target.setAnswerPlaceholders(windowsCopy); + } + + + public void setUserCreated(boolean userCreated) { + myUserCreated = userCreated; + } + + public boolean isUserCreated() { + return myUserCreated; + } + + public boolean hasFailedTaskWindows() { + return myAnswerPlaceholders.size() > 0 && getStatus() == StudyStatus.Failed; + } + + public boolean isTrackChanges() { + return myTrackChanges; + } + + public void setTrackChanges(boolean trackChanges) { + myTrackChanges = trackChanges; + } + + public boolean isHighlightErrors() { + return myHighlightErrors; + } + + public void setHighlightErrors(boolean highlightErrors) { + myHighlightErrors = highlightErrors; + } +} diff --git a/python/educational/src/com/jetbrains/edu/courseFormat/UserTest.java b/python/educational/src/com/jetbrains/edu/courseFormat/UserTest.java new file mode 100644 index 000000000000..0f950605af83 --- /dev/null +++ b/python/educational/src/com/jetbrains/edu/courseFormat/UserTest.java @@ -0,0 +1,41 @@ +package com.jetbrains.edu.courseFormat; + +public class UserTest { + private String input; + private String output; + private StringBuilder myInputBuffer = new StringBuilder(); + private StringBuilder myOutputBuffer = new StringBuilder(); + private boolean myEditable = false; + + public String getInput() { + return input; + } + + public void setInput(String input) { + this.input = input; + } + + public String getOutput() { + return output; + } + + public void setOutput(String output) { + this.output = output; + } + + public StringBuilder getInputBuffer() { + return myInputBuffer; + } + + public StringBuilder getOutputBuffer() { + return myOutputBuffer; + } + + public boolean isEditable() { + return myEditable; + } + + public void setEditable(boolean editable) { + myEditable = editable; + } +} diff --git a/python/educational/src/com/jetbrains/edu/courseFormat/info/LessonInfo.java b/python/educational/src/com/jetbrains/edu/courseFormat/info/LessonInfo.java new file mode 100644 index 000000000000..251777b7a74d --- /dev/null +++ b/python/educational/src/com/jetbrains/edu/courseFormat/info/LessonInfo.java @@ -0,0 +1,62 @@ +package com.jetbrains.edu.courseFormat.info; + +import com.jetbrains.edu.courseFormat.StudyStatus; + +/** + * Implementation of class which contains information about student progress in current lesson + */ +public class LessonInfo { + private int myTaskNum; + private int myTaskFailed; + private int myTaskSolved; + private int myTaskUnchecked; + + public int getTaskNum() { + return myTaskNum; + } + + public void setTaskNum(int taskNum) { + myTaskNum = taskNum; + } + + public int getTaskFailed() { + return myTaskFailed; + } + + public void setTaskFailed(int taskFailed) { + myTaskFailed = taskFailed; + } + + public int getTaskSolved() { + return myTaskSolved; + } + + public void setTaskSolved(int taskSolved) { + myTaskSolved = taskSolved; + } + + public int getTaskUnchecked() { + return myTaskUnchecked; + } + + public void setTaskUnchecked(int taskUnchecked) { + myTaskUnchecked = taskUnchecked; + } + + public void update(StudyStatus status, int delta) { + switch (status) { + case Solved: { + myTaskSolved += delta; + break; + } + case Failed: { + myTaskFailed += delta; + break; + } + case Unchecked: { + myTaskUnchecked += delta; + break; + } + } + } +}