extracted course format from interactive learning
authorEkaterina Tuzova <Ekaterina.Tuzova@jetbrains.com>
Fri, 13 Feb 2015 14:18:09 +0000 (17:18 +0300)
committerEkaterina Tuzova <Ekaterina.Tuzova@jetbrains.com>
Fri, 13 Feb 2015 14:18:09 +0000 (17:18 +0300)
54 files changed:
python/edu/interactive-learning-python/interactive-learning-python.iml
python/edu/interactive-learning-python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java
python/edu/interactive-learning-python/src/com/jetbrains/edu/learning/PyStudyExecutor.java
python/edu/interactive-learning-python/src/com/jetbrains/edu/learning/PyStudyTestRunner.java
python/edu/interactive-learning-python/src/com/jetbrains/edu/learning/actions/PyStudyIntroductionCourseAction.java
python/edu/interactive-learning-python/tests/JsonParserTest.java
python/edu/interactive-learning-python/tests/StudyDocumentListenerTest.java
python/educational/course-creator/src/com/jetbrains/edu/coursecreator/format/AnswerPlaceholder.java
python/educational/educational.iml
python/educational/interactive-learning/interactive-learning.iml
python/educational/interactive-learning/src/com/jetbrains/edu/learning/CourseInfo.java [new file with mode: 0644]
python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyCourseResourceManager.java [new file with mode: 0644]
python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyDocumentListener.java
python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyEditorFactoryListener.java
python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyExecutor.java
python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyGenerator.java [new file with mode: 0644]
python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyHighlightErrorFilter.java
python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyPainter.java [new file with mode: 0644]
python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyProjectGenerator.java
python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudySmartChecker.java [new file with mode: 0644]
python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyState.java
python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyTaskManager.java
python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyTestRunner.java
python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyUtils.java
python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyCheckAction.java
python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyEditInputAction.java
python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyNextStudyTaskAction.java
python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyNextWindowAction.java
python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyPrevWindowAction.java
python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyPreviousStudyTaskAction.java
python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyRefreshTaskFileAction.java
python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyRunAction.java
python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyShowHintAction.java
python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyTaskNavigationAction.java
python/educational/interactive-learning/src/com/jetbrains/edu/learning/actions/StudyWindowNavigationAction.java
python/educational/interactive-learning/src/com/jetbrains/edu/learning/editor/StudyEditor.java
python/educational/interactive-learning/src/com/jetbrains/edu/learning/editor/StudyFileEditorProvider.java
python/educational/interactive-learning/src/com/jetbrains/edu/learning/navigation/StudyNavigator.java [new file with mode: 0644]
python/educational/interactive-learning/src/com/jetbrains/edu/learning/projectView/StudyDirectoryNode.java
python/educational/interactive-learning/src/com/jetbrains/edu/learning/projectView/StudyTreeStructureProvider.java
python/educational/interactive-learning/src/com/jetbrains/edu/learning/stepic/StudyStepicConnector.java
python/educational/interactive-learning/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java
python/educational/interactive-learning/src/com/jetbrains/edu/learning/ui/StudyTestContentPanel.java
python/educational/interactive-learning/src/com/jetbrains/edu/learning/ui/StudyToolWindowFactory.java
python/educational/src/com/jetbrains/edu/StudyNames.java [moved from python/educational/interactive-learning/src/com/jetbrains/edu/learning/StudyNames.java with 71% similarity]
python/educational/src/com/jetbrains/edu/courseFormat/AnswerPlaceholder.java [new file with mode: 0644]
python/educational/src/com/jetbrains/edu/courseFormat/Course.java [new file with mode: 0644]
python/educational/src/com/jetbrains/edu/courseFormat/Lesson.java [new file with mode: 0644]
python/educational/src/com/jetbrains/edu/courseFormat/StudyStateful.java [new file with mode: 0644]
python/educational/src/com/jetbrains/edu/courseFormat/StudyStatus.java [new file with mode: 0644]
python/educational/src/com/jetbrains/edu/courseFormat/Task.java [new file with mode: 0644]
python/educational/src/com/jetbrains/edu/courseFormat/TaskFile.java [new file with mode: 0644]
python/educational/src/com/jetbrains/edu/courseFormat/UserTest.java [new file with mode: 0644]
python/educational/src/com/jetbrains/edu/courseFormat/info/LessonInfo.java [new file with mode: 0644]

index 96fc1b873f540868a56066a800a538b27ca1c8f8..e92551376fe304e54b3c377543bb158cbd7a0479 100644 (file)
@@ -25,5 +25,6 @@
       </library>
     </orderEntry>
     <orderEntry type="module" module-name="testFramework" scope="TEST" />
+    <orderEntry type="module" module-name="educational" />
   </component>
 </module>
\ No newline at end of file
index a1252c5aef71411387b3a9ed0274877401c0193f..89d1b91f282691598bdc5b2ef828414383322168 100644 (file)
@@ -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;
index f3950f7469cf3d8707fefd8645a661345f2ebfa8..571edef4a54c1335f431dfcfc42c1597ea5c3895 100644 (file)
@@ -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;
index 267d14f72b2286387d85ad16f4e46878dd0f116a..4ad9d4162724c48197fa3cee9cac2288cc5de084 100644 (file)
@@ -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;
 
index 4a65e37219b548e9b41189dfeb117389718e69a0..56378398d7477d011ce42f2c46ffede9b21b685f 100644 (file)
@@ -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;
index b9fd9654a90e7cd61575645d9ae23757b26c411c..b12b7ad85347e7161f67b6a6f9edaaaf0d629101 100644 (file)
@@ -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;
 
index 87d9294b6bc5bd4697a49ac2d7dbc7c9319a0651..b0f893ba1a6436f71834bbd889e00dca5c7743f8 100644 (file)
@@ -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;
index daae19c12a00f2794408cb57dd48b7d803866b52..5645e7547a14ee0c9eeeee65bc473940926c568c 100644 (file)
@@ -27,9 +27,10 @@ public class AnswerPlaceholder implements Comparable<AnswerPlaceholder> {
   @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;
index c90834f2d607afe55e6104d8aa2cdfffb713f688..1bf31625ebef723143eb552dbd286801486a3476 100644 (file)
@@ -7,5 +7,7 @@
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="module" module-name="platform-api" />
+    <orderEntry type="library" name="gson" level="project" />
   </component>
 </module>
\ No newline at end of file
index 7413f5da4ba1943b16f4c1cda83722d2b09a2afa..c901a402254697d917f4668577ae891e1b27b239 100644 (file)
@@ -5,10 +5,12 @@
     <content url="file://$MODULE_DIR$">
       <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
       <sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
+      <excludeFolder url="file://$MODULE_DIR$/src/com/jetbrains/edu/learning/course" />
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
     <orderEntry type="module" module-name="lang-impl" />
     <orderEntry type="library" name="gson" level="project" />
+    <orderEntry type="module" module-name="educational" />
   </component>
 </module>
\ 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 (file)
index 0000000..2c354b1
--- /dev/null
@@ -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<Integer> 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 (file)
index 0000000..893b347
--- /dev/null
@@ -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;
+  }
+}
index 9c364ff6b9c18e7ade360ae120ea52197494f35a..aec45ce130ab24b2b7bc70eaba09444d54bd955c 100644 (file)
@@ -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;
index 71a80953098c2ffbe9f77ea3e3df771c77f550d7..6bec75980b342a86d5d61281a7c508e9e0670873 100644 (file)
@@ -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);
                 }
               }
             }
index 4db7566d86381c669249e1b944b8b58ee9b2833f..d834d94a00a27342f44053c4819c1c6f24e63d46 100644 (file)
@@ -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 (file)
index 0000000..6262852
--- /dev/null
@@ -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<String, TaskFile> 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<Task> 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<Lesson> 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);
+              }
+            }
+          });
+        }
+      });
+  }
+}
index f1fdbf4bae41c23e08bc087d0409fa11ab5cda96..bb5bad7539303fbda0f7f82729d6e2c7019ae74e 100644 (file)
@@ -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 (file)
index 0000000..8ffb5b7
--- /dev/null
@@ -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<RangeMarker> 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<RangeMarker> blocks, int start, int end) {
+    RangeHighlighter rh = editor.getMarkupModel()
+      .addRangeHighlighter(start, end, HighlighterLayer.LAST + 1, null, HighlighterTargetArea.EXACT_RANGE);
+    blocks.add(rh);
+  }
+
+
+}
index ff8c4f4af509474bb77278a57670e526d8d83d06..f9d657046da2d142a206f2d95005757112c727f0 100644 (file)
@@ -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 (file)
index 0000000..bd5c124
--- /dev/null
@@ -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);
+    }
+  }
+
+}
index bf6d200292552e4034855ffc08177257a12783f8..4aa06c5fb609464a982582087b0e3952bd41497f 100644 (file)
@@ -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;
 
index efc68ca8ba9759b36832882a1c260851b06e49a7..3bd234de9735a39cc90cb34aff0f09515a237c01 100644 (file)
@@ -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<Task> 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);
index af401afdf6b0646e1c8f94666aba26ffb58654fe..6f1f64812157fff377a08281a4664e819f815716 100644 (file)
@@ -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 {
index 64f74ce7bc040fa6536771f50f9663263c9ee9d3..633cc650153d97c12356c82f6932177db4312d13 100644 (file)
@@ -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;
index 004b58399a24d07bd2a60bda27f6474ce2b06726..37db1925b7a1782d303fd39c3ee065950489791f 100644 (file)
@@ -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);
   }
index a5b20ed3c6456b7a4de59afeadae7fa04a7c363b..70b600c03c654a2eb83469719f2a0124a93ba730 100644 (file)
@@ -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);
index 831208fcf5fa1ba57150901c2372fdaa60656cf3..458039259122eaf3867b2d60d7aab60c9ccaea1e 100644 (file)
@@ -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
index fd94a66b89444759382a24b498e02b6406388d2e..a509f57b6bc3afe95f0742860012580a703ec302 100644 (file)
@@ -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;
index ccbf614d2c88f326a5e9878d03ad3a2ac5265e8a..bbdaabd885fe05f649fd292d5eb7ad6f2bd581d3 100644 (file)
@@ -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;
index 1c3dc5a95cb63176e87ed740f4bb669c3f6f3ac6..35e8048f49cac95563be50178e1dedc3a3697f98 100644 (file)
@@ -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
index 12449413280a035570554a6ce8cdc325db416b8e..2c08b78424f2236634fe2b0e6229ead990d66df8 100644 (file)
@@ -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()) {
index 19013eccdf7ee5af2106221b339cc528e92f233f..5f76fe2b939ed683face4501ba4202c6bb206ab2 100644 (file)
@@ -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;
 
index a42a203c84b90a204314630d1c1b4bc6488f4821..ab3a25e10eefb8628cb5a3e0b3cb528c5c7d326a 100644 (file)
@@ -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;
index 98cbf3d6f3e3d8da49f07fc328864fecb0bb1587..3fc949b366fa01c8d6bdc879352e84dd5fe6f553 100644 (file)
@@ -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;
index c6b0685d823378ceb6f666ac412611ed10d858b1..ee41db88628d93020f2eebebc90193cf14245f2d 100644 (file)
@@ -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);
             }
           }
index 6c7e3c04c238c6481dbab2e8b4e56e31a2b15727..3138f91a145fd6af15a5613c558ddba90d28fa68 100644 (file)
@@ -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;
index ae8df16526817e0e0d06bd8aae0d8c6205d20e91..61c7ab4f335ea5756509b77a6d224d87a02c03a1 100644 (file)
@@ -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 (file)
index 0000000..cbe08bf
--- /dev/null
@@ -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<Task> 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<Lesson> 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);
+    }
+  }
+
+}
index dff83de84d5defd8ff8e7db2ff8c822dc8b97db9..a4a69835bd0b0e120ea8b1118963d67430af0e16 100644 (file)
@@ -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();
index 4c92d2316b9c8bee6e21b2d4d7d97b91a9e6ca38..54e4ef12e5cd8521d0e2400fd145af81d7cd523c 100644 (file)
@@ -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);
               }
             }
index 2d00af9f911ddad98186d4e781ee4d656ec9df18..7a67cf79875f760555a1aa33a0b03e6abad48f7a 100644 (file)
@@ -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;
index 82af389036fd901c04d9a4ade8155b4a6351d468..117e9e91b5539c4c88fcba4b6133f1ca6545a96d 100644 (file)
@@ -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.*;
index 8d9a284b0aee842d0f2ee9f6d7ee689f182c6eca..fbfa809785b432c5cb5afbbcd7dc27aaf78ae7a6 100644 (file)
@@ -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.*;
index 180af572da8541ec7f934ebc5b3437e9c2b72c02..35bf321f6331a5ba48d9a912ee02c99409cf3550 100644 (file)
@@ -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.*;
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 0755e2a99809cb46fb9f76ec917db2e6f122d76c..c725513b8696e88f1245042da4a02d5e9507b00c 100644 (file)
  * 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 (file)
index 0000000..8b1dcff
--- /dev/null
@@ -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 (file)
index 0000000..ad732c2
--- /dev/null
@@ -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<Lesson> lessons = new ArrayList<Lesson>();
+
+  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<Lesson> 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 (file)
index 0000000..5db440f
--- /dev/null
@@ -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<Integer> steps;
+  @Transient
+  public List<String> tags;
+  @Transient
+  Boolean is_public;
+  @SerializedName("title")
+  private String name;
+
+  public List<Task> taskList = new ArrayList<Task>();
+  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<Task> 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 (file)
index 0000000..57984fd
--- /dev/null
@@ -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 (file)
index 0000000..917d299
--- /dev/null
@@ -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 (file)
index 0000000..c0345bd
--- /dev/null
@@ -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<String, TaskFile> taskFiles = new HashMap<String, TaskFile>();
+
+  private String text;
+  private String testsText;
+
+  private Lesson myLesson;
+  private List<UserTest> userTests = new ArrayList<UserTest>();
+
+
+  /**
+   * 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<String, TaskFile> 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<UserTest> getUserTests() {
+    return userTests;
+  }
+
+  public void setUserTests(@NotNull final List<UserTest> 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 (file)
index 0000000..8820a74
--- /dev/null
@@ -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<AnswerPlaceholder> myAnswerPlaceholders = new ArrayList<AnswerPlaceholder>();
+  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<AnswerPlaceholder> getAnswerPlaceholders() {
+    return myAnswerPlaceholders;
+  }
+
+  public void setAnswerPlaceholders(List<AnswerPlaceholder> 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<AnswerPlaceholder> sourceAnswerPlaceholders = source.getAnswerPlaceholders();
+    List<AnswerPlaceholder> windowsCopy = new ArrayList<AnswerPlaceholder>(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 (file)
index 0000000..0f95060
--- /dev/null
@@ -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 (file)
index 0000000..251777b
--- /dev/null
@@ -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;
+      }
+    }
+  }
+}