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;
+ }
+ }
+ }
+}