EDU-657 Initial implementation of subtasks
authorliana.bakradze <liana.bakradze@jetbrains.com>
Fri, 15 Jul 2016 18:46:29 +0000 (21:46 +0300)
committerliana.bakradze <liana.bakradze@jetbrains.com>
Fri, 15 Jul 2016 18:51:23 +0000 (21:51 +0300)
37 files changed:
python/educational-core/course-creator/resources/META-INF/plugin.xml
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCLanguageManager.java
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCStepEditorNotificationProvider.java [new file with mode: 0644]
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCStudyActionListener.java
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCTestsTabTitleProvider.java [new file with mode: 0644]
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCUtils.java
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCVirtualFileListener.java
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCAddAnswerPlaceholder.java
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCCreateCourseArchive.java
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCEditTaskTextAction.java
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCNewStepAction.java [new file with mode: 0644]
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCShowPreview.java
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCStudentInvisibleFileNode.java
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCTreeStructureProvider.java
python/educational-core/student/src/com/jetbrains/edu/learning/StudyProjectComponent.java
python/educational-core/student/src/com/jetbrains/edu/learning/StudyStepManager.java [new file with mode: 0644]
python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java
python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyFillPlaceholdersAction.java
python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshTaskFileAction.java
python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java
python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java
python/educational-core/student/src/com/jetbrains/edu/learning/core/EduAnswerPlaceholderPainter.java
python/educational-core/student/src/com/jetbrains/edu/learning/core/EduDocumentListener.java
python/educational-core/student/src/com/jetbrains/edu/learning/core/EduNames.java
python/educational-core/student/src/com/jetbrains/edu/learning/core/EduUtils.java
python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Course.java
python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Step.java [new file with mode: 0644]
python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Task.java
python/educational-core/student/src/com/jetbrains/edu/learning/editor/StudyEditorFactoryListener.java
python/educational-core/student/src/com/jetbrains/edu/learning/projectView/StudyDirectoryNode.java
python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java
python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java
python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/PyCCLanguageManager.java
python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCCommandLineState.java
python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestsConfigurationProducer.java
python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java
python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyTestRunner.java

index b026f3ce878e95d21e741c63d64577d7b153a092..94c1704c042943c08b4c4c636670ea8d80995bad 100644 (file)
@@ -25,6 +25,8 @@
     <renameHandler implementation="com.jetbrains.edu.coursecreator.handlers.CCLessonRenameHandler" order="first"/>
     <applicationService serviceInterface="com.jetbrains.edu.coursecreator.settings.CCSettings"
                         serviceImplementation="com.jetbrains.edu.coursecreator.settings.CCSettings"/>
+    <editorTabTitleProvider implementation="com.jetbrains.edu.coursecreator.CCTestsTabTitleProvider"/>
+    <editorNotificationProvider implementation="com.jetbrains.edu.coursecreator.CCStepEditorNotificationProvider"/>
   </extensions>
   <extensions defaultExtensionNs="Edu">
     <studyActionsProvider implementation="com.jetbrains.edu.coursecreator.CCStudyActionsProvider"/>
     </group>
 
     <action id="UnpackCourse" class="com.jetbrains.edu.coursecreator.actions.CCFromCourseArchive"/>
+    <action class="com.jetbrains.edu.coursecreator.actions.CCNewStepAction" id="CC.NewStep">
+      <add-to-group group-id="AnswerPlaceholderGroup" relative-to-action="DeleteAllPlaceholders" anchor="after"/>
+    </action>
   </actions>
 
+
 </idea-plugin>
\ No newline at end of file
index b6c8fd4ecf533aad3d0473f2bfa9a145981d4fb5..bd7ff173a4fbb20140d2b186c38f3a7af81d683a 100644 (file)
@@ -4,6 +4,7 @@ import com.intellij.ide.fileTemplates.FileTemplate;
 import com.intellij.lang.LanguageExtension;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.edu.learning.courseFormat.Task;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -26,4 +27,6 @@ public interface CCLanguageManager {
   default boolean isTestFile(VirtualFile file) {
     return false;
   }
+
+  default void createTestsForNewStep(@NotNull Project project, @NotNull Task task) {}
 }
diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCStepEditorNotificationProvider.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCStepEditorNotificationProvider.java
new file mode 100644 (file)
index 0000000..9c29d76
--- /dev/null
@@ -0,0 +1,171 @@
+package com.jetbrains.edu.coursecreator;
+
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.fileEditor.FileEditor;
+import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.popup.JBPopupFactory;
+import com.intellij.openapi.ui.popup.ListSeparator;
+import com.intellij.openapi.ui.popup.PopupStep;
+import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.EditorNotificationPanel;
+import com.intellij.ui.EditorNotifications;
+import com.intellij.ui.awt.RelativePoint;
+import com.jetbrains.edu.coursecreator.actions.CCNewStepAction;
+import com.jetbrains.edu.learning.StudyStepManager;
+import com.jetbrains.edu.learning.StudyUtils;
+import com.jetbrains.edu.learning.core.EduNames;
+import com.jetbrains.edu.learning.courseFormat.Task;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class CCStepEditorNotificationProvider extends EditorNotifications.Provider<EditorNotificationPanel> implements DumbAware {
+  private static final Key<EditorNotificationPanel> KEY = Key.create("edu.coursecreator.step");
+  public static final String SWITCH_STEP = "Switch step";
+  public static final Integer ADD_STEP_ID = -2;
+  private final Project myProject;
+
+  public CCStepEditorNotificationProvider(Project project) {
+    myProject = project;
+  }
+
+  @NotNull
+  @Override
+  public Key<EditorNotificationPanel> getKey() {
+    return KEY;
+  }
+
+  @Nullable
+  @Override
+  public EditorNotificationPanel createNotificationPanel(@NotNull VirtualFile file, @NotNull FileEditor fileEditor) {
+    if (!CCUtils.isCourseCreator(myProject)) {
+      return null;
+    }
+    boolean isTestFile = CCUtils.isTestsFile(myProject, file);
+    if (!isTestFile && StudyUtils.getTaskFile(myProject, file) == null) {
+      return null;
+    }
+    Task task = StudyUtils.getTaskForFile(myProject, file);
+    if (task == null || task.getAdditionalSteps().isEmpty()) {
+      return null;
+    }
+    int activeStepIndex = task.getActiveStepIndex() + 2;
+    int stepsNum = task.getAdditionalSteps().size() + 1;
+    EditorNotificationPanel panel = new EditorNotificationPanel() {
+      @Override
+      public Color getBackground() {
+        Color color = EditorColorsManager.getInstance().getGlobalScheme().getColor(EditorColors.GUTTER_BACKGROUND);
+        return color == null ? super.getBackground() : color;
+      }
+    };
+    String header = isTestFile ? "test" : "task file";
+    panel.setText("This is " + header + " for " + EduNames.STEP + " " + activeStepIndex + "/" + stepsNum);
+    panel.createActionLabel(SWITCH_STEP, () -> {
+      ArrayList<Integer> values = new ArrayList<>();
+      values.add(-1);
+      for (int i = 0; i < task.getAdditionalSteps().size(); i++) {
+        values.add(i);
+      }
+      values.add(ADD_STEP_ID);
+      JBPopupFactory.getInstance().createListPopup(new SwitchStepPopupStep(SWITCH_STEP, values, task, file)).show(RelativePoint.getSouthEastOf(panel));
+    });
+    return panel;
+  }
+
+  private class SwitchStepPopupStep extends BaseListPopupStep<Integer> {
+    private final Task myTask;
+    private final VirtualFile myFile;
+
+    public SwitchStepPopupStep(@Nullable String title,
+                               List<Integer> values,
+                               Task task, VirtualFile file) {
+      super(title, values);
+      myTask = task;
+      myFile = file;
+    }
+
+    @NotNull
+    @Override
+    public String getTextFor(Integer value) {
+      if (value.equals(ADD_STEP_ID)) {
+        return CCNewStepAction.NEW_STEP;
+      }
+      int stepNum = value + 2;
+      String text = EduNames.STEP + " " + stepNum;
+      if (value == myTask.getActiveStepIndex()) {
+        text +=  " (current step)";
+      }
+      return text;
+    }
+
+    @Override
+    public PopupStep onChosen(Integer selectedValue, boolean finalChoice) {
+      if (finalChoice) {
+        if (selectedValue.equals(ADD_STEP_ID)) {
+          return doFinalStep(() -> CCNewStepAction.addStep(myFile, myProject));
+        }
+        return doFinalStep(() -> StudyStepManager.switchStep(myProject, myTask, selectedValue));
+      } else {
+        if (hasSubstep(selectedValue)) {
+          return new ActionsPopupStep(myTask, selectedValue);
+        }
+      }
+      return super.onChosen(selectedValue, false);
+    }
+
+    @Override
+    public boolean hasSubstep(Integer selectedValue) {
+      return !selectedValue.equals(ADD_STEP_ID);
+    }
+
+    @Override
+    public int getDefaultOptionIndex() {
+      return myTask.getActiveStepIndex() + 1;
+    }
+
+    @Nullable
+    @Override
+    public ListSeparator getSeparatorAbove(Integer value) {
+      return value.equals(ADD_STEP_ID) ? new ListSeparator(): null;
+    }
+  }
+
+  private class ActionsPopupStep extends BaseListPopupStep<String> {
+
+    public static final String SELECT = "Select";
+    public static final String DELETE = "Delete";
+    private final Task myTask;
+    private final int myStepIndex;
+
+    public ActionsPopupStep(Task task, int stepIndex) {
+      super(null, Arrays.asList(SELECT, DELETE));
+      myTask = task;
+      myStepIndex = stepIndex;
+    }
+
+    @Override
+    public PopupStep onChosen(String selectedValue, boolean finalChoice) {
+      if (finalChoice) {
+        if (selectedValue.equals(SELECT)) {
+          StudyStepManager.switchStep(myProject, myTask, myStepIndex);
+        } else {
+          if (myStepIndex != myTask.getAdditionalSteps().size() - 1) {
+            //TODO: implement
+          } else {
+            StudyStepManager.deleteStep(myProject, myTask, myStepIndex);
+          }
+          return FINAL_CHOICE;
+        }
+      }
+      return super.onChosen(selectedValue, finalChoice);
+    }
+  }
+}
index 95c474681d549a2f364cac706b1c1a8f0db94b86..3d9a35c1595711a2adcf4a0034b17bc43d23ebaa 100644 (file)
@@ -34,6 +34,6 @@ public class CCStudyActionListener implements StudyActionListener {
     if (taskDir == null) {
       return;
     }
-    CCUtils.createResources(project, task, taskDir);
+    CCUtils.updateResources(project, task, taskDir);
   }
 }
diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCTestsTabTitleProvider.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCTestsTabTitleProvider.java
new file mode 100644 (file)
index 0000000..e888efc
--- /dev/null
@@ -0,0 +1,30 @@
+package com.jetbrains.edu.coursecreator;
+
+import com.intellij.openapi.fileEditor.impl.EditorTabTitleProvider;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.edu.learning.StudyLanguageManager;
+import com.jetbrains.edu.learning.StudyTaskManager;
+import com.jetbrains.edu.learning.StudyUtils;
+import com.jetbrains.edu.learning.courseFormat.Course;
+import org.jetbrains.annotations.Nullable;
+
+public class CCTestsTabTitleProvider implements EditorTabTitleProvider {
+  @Nullable
+  @Override
+  public String getEditorTabTitle(Project project, VirtualFile file) {
+    if (!CCUtils.isCourseCreator(project)) {
+      return null;
+    }
+    if (!CCUtils.isTestsFile(project, file)) {
+      return null;
+    }
+    Course course = StudyTaskManager.getInstance(project).getCourse();
+    assert course != null;
+    StudyLanguageManager manager = StudyUtils.getLanguageManager(course);
+    if (manager == null) {
+      return null;
+    }
+    return manager.getTestFileName();
+  }
+}
index c3cce2b1be2a9e3d7d737e8c3fb25fefd3ba64d9..2d68478b6845a9ea3c0d11e3461f84dc409170ea 100644 (file)
@@ -6,8 +6,6 @@ import com.intellij.ide.projectView.actions.MarkRootActionBase;
 import com.intellij.lang.Language;
 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.module.Module;
 import com.intellij.openapi.project.DumbModePermission;
 import com.intellij.openapi.project.DumbService;
@@ -17,14 +15,17 @@ import com.intellij.openapi.roots.ModifiableRootModel;
 import com.intellij.openapi.roots.ModuleRootManager;
 import com.intellij.openapi.util.Ref;
 import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.LocalFileSystem;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.psi.PsiDirectory;
-import com.intellij.util.DocumentUtil;
 import com.intellij.util.Function;
 import com.jetbrains.edu.learning.StudyTaskManager;
 import com.jetbrains.edu.learning.StudyUtils;
 import com.jetbrains.edu.learning.core.EduUtils;
-import com.jetbrains.edu.learning.courseFormat.*;
+import com.jetbrains.edu.learning.courseFormat.Course;
+import com.jetbrains.edu.learning.courseFormat.StudyItem;
+import com.jetbrains.edu.learning.courseFormat.Task;
+import com.jetbrains.edu.learning.courseFormat.TaskFile;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -213,30 +214,37 @@ public class CCUtils {
   }
 
 
-  public static void createResources(Project project, Task task, VirtualFile taskDir) {
-    Map<String, TaskFile> files = task.getTaskFiles();
+  public static void updateResources(Project project, Task task, VirtualFile taskDir) {
+    Course course = StudyTaskManager.getInstance(project).getCourse();
+    if (course == null) {
+      return;
+    }
+    VirtualFile lessonVF = taskDir.getParent();
+    if (lessonVF == null) {
+      return;
+    }
+
+    String taskResourcesPath = FileUtil.join(course.getCourseDirectory(), lessonVF.getName(), taskDir.getName());
+    File taskResourceFile = new File(taskResourcesPath);
+    if (!taskResourceFile.exists()) {
+      if (!taskResourceFile.mkdirs()) {
+        LOG.info("Failed to create resources for task " + taskResourcesPath);
+      }
+    }
+    VirtualFile studentDir = LocalFileSystem.getInstance().findFileByIoFile(taskResourceFile);
+    if (studentDir == null) {
+      return;
+    }
+    Map<String, TaskFile> files = StudyUtils.getTaskFiles(task);
     for (Map.Entry<String, TaskFile> entry : files.entrySet()) {
       String name = entry.getKey();
-      VirtualFile child = taskDir.findChild(name);
-      if (child == null) {
-        continue;
-      }
-      Document patternDocument = StudyUtils.getPatternDocument(entry.getValue(), name);
-      Document document = FileDocumentManager.getInstance().getDocument(child);
-      if (document == null || patternDocument == null) {
-        LOG.info("pattern file for " +  child.getPath() + " not found");
+      VirtualFile answerFile = taskDir.findChild(name);
+      if (answerFile == null) {
         continue;
       }
-      DocumentUtil.writeInRunUndoTransparentAction(() -> {
-        patternDocument.replaceString(0, patternDocument.getTextLength(), document.getCharsSequence());
-        FileDocumentManager.getInstance().saveDocument(patternDocument);
+      ApplicationManager.getApplication().runWriteAction(() -> {
+        EduUtils.createStudentFile(CCUtils.class, project, answerFile, task.getActiveStepIndex(), studentDir, null);
       });
-      TaskFile target = new TaskFile();
-      TaskFile.copy(entry.getValue(), target);
-      for (AnswerPlaceholder placeholder : target.getAnswerPlaceholders()) {
-        placeholder.setUseLength(false);
-      }
-      EduUtils.createStudentDocument(project, target, child, patternDocument);
     }
   }
 }
index eaa45861f1ca2371115a12a5c2d8603fbe57846b..0ac9fe8321916d04750fda689c0307e1293ef26f 100644 (file)
@@ -66,7 +66,8 @@ public class CCVirtualFileListener extends VirtualFileAdapter {
   @Override
   public void fileDeleted(@NotNull VirtualFileEvent event) {
     VirtualFile removedFile = event.getFile();
-    if (removedFile.getPath().contains(CCUtils.GENERATED_FILES_FOLDER)) {
+    String path = removedFile.getPath();
+    if (path.contains(CCUtils.GENERATED_FILES_FOLDER)) {
       return;
     }
 
@@ -75,7 +76,7 @@ public class CCVirtualFileListener extends VirtualFileAdapter {
       return;
     }
     Course course = StudyTaskManager.getInstance(project).getCourse();
-    if (course == null) {
+    if (course == null || path.contains(course.getCourseDirectory())) {
       return;
     }
     final TaskFile taskFile = StudyUtils.getTaskFile(project, removedFile);
@@ -123,6 +124,7 @@ public class CCVirtualFileListener extends VirtualFileAdapter {
     if (task == null) {
       return;
     }
+    //TODO: remove from steps as well
     task.getTaskFiles().remove(removedTaskFile.getName());
   }
 }
index 7de216ebcd345efc6a8442008ad1b4a3c61e744b..9944ca2b9fab23bfcbdd8e346508fcc1272dbf72 100644 (file)
@@ -5,7 +5,6 @@ import com.intellij.openapi.actionSystem.Presentation;
 import com.intellij.openapi.editor.Document;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.editor.SelectionModel;
-import com.intellij.openapi.fileEditor.FileDocumentManager;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.ui.DialogWrapper;
 import com.intellij.psi.PsiDocumentManager;
@@ -16,7 +15,6 @@ import com.jetbrains.edu.coursecreator.ui.CCCreateAnswerPlaceholderDialog;
 import com.jetbrains.edu.learning.StudyUtils;
 import com.jetbrains.edu.learning.core.EduAnswerPlaceholderPainter;
 import com.jetbrains.edu.learning.core.EduNames;
-import com.jetbrains.edu.learning.core.EduUtils;
 import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
 import com.jetbrains.edu.learning.courseFormat.TaskFile;
 import org.jetbrains.annotations.NotNull;
@@ -79,36 +77,11 @@ public class CCAddAnswerPlaceholder extends CCAnswerPlaceholderAction {
     answerPlaceholder.setTaskFile(taskFile);
     taskFile.sortAnswerPlaceholders();
 
-
-    computeInitialState(project, file, taskFile, document);
-
+    answerPlaceholder.setPossibleAnswer(model.hasSelection() ? model.getSelectedText() : EduNames.PLACEHOLDER);
     EduAnswerPlaceholderPainter.drawAnswerPlaceholder(editor, answerPlaceholder, JBColor.BLUE);
     EduAnswerPlaceholderPainter.createGuardedBlocks(editor, answerPlaceholder);
   }
 
-  private static void computeInitialState(Project project, PsiFile file, TaskFile taskFile, Document document) {
-    Document patternDocument = StudyUtils.getPatternDocument(taskFile, file.getName());
-    if (patternDocument == null) {
-      return;
-    }
-    DocumentUtil.writeInRunUndoTransparentAction(() -> {
-      patternDocument.replaceString(0, patternDocument.getTextLength(), document.getCharsSequence());
-      FileDocumentManager.getInstance().saveDocument(patternDocument);
-    });
-    TaskFile target = new TaskFile();
-    TaskFile.copy(taskFile, target);
-    List<AnswerPlaceholder> placeholders = target.getAnswerPlaceholders();
-    for (AnswerPlaceholder placeholder : placeholders) {
-      placeholder.setUseLength(false);
-    }
-    EduUtils.createStudentDocument(project, target, file.getVirtualFile(), patternDocument);
-
-    for (int i = 0; i < placeholders.size(); i++) {
-      AnswerPlaceholder fromPlaceholder = placeholders.get(i);
-      taskFile.getAnswerPlaceholders().get(i).setInitialState(new AnswerPlaceholder.MyInitialState(fromPlaceholder.getOffset(), fromPlaceholder.getLength()));
-    }
-  }
-
   @Override
   protected void performAnswerPlaceholderAction(@NotNull CCState state) {
     if (canAddPlaceholder(state)) {
index 013ccf4682ba56e131a63fa027fa09452cff70a9..988eeda00226e1a988f95923bd7348e099556f0b 100644 (file)
@@ -18,7 +18,6 @@ import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.vfs.VfsUtil;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.openapi.vfs.VirtualFileManager;
-import com.intellij.util.containers.HashMap;
 import com.intellij.util.io.ZipUtil;
 import com.jetbrains.edu.coursecreator.CCLanguageManager;
 import com.jetbrains.edu.coursecreator.CCUtils;
@@ -26,17 +25,19 @@ import com.jetbrains.edu.coursecreator.ui.CreateCourseArchiveDialog;
 import com.jetbrains.edu.learning.StudyTaskManager;
 import com.jetbrains.edu.learning.core.EduNames;
 import com.jetbrains.edu.learning.core.EduUtils;
-import com.jetbrains.edu.learning.courseFormat.*;
+import com.jetbrains.edu.learning.courseFormat.Course;
+import com.jetbrains.edu.learning.courseFormat.Lesson;
+import com.jetbrains.edu.learning.courseFormat.Task;
+import com.jetbrains.edu.learning.courseFormat.TaskFile;
 import com.jetbrains.edu.learning.statistics.EduUsagesCollector;
 import org.jetbrains.annotations.NotNull;
 
 import java.io.*;
-import java.util.List;
-import java.util.Map;
 import java.util.zip.ZipOutputStream;
 
 public class CCCreateCourseArchive extends DumbAwareAction {
   private static final Logger LOG = Logger.getInstance(CCCreateCourseArchive.class.getName());
+  public static final String GENERATE_COURSE_ARCHIVE = "Generate Course Archive";
   private String myZipName;
   private String myLocationDir;
 
@@ -49,7 +50,7 @@ public class CCCreateCourseArchive extends DumbAwareAction {
   }
 
   public CCCreateCourseArchive() {
-    super("Generate Course Archive", "Generate Course Archive", null);
+    super(GENERATE_COURSE_ARCHIVE, GENERATE_COURSE_ARCHIVE, null);
   }
 
   @Override
@@ -100,46 +101,39 @@ public class CCCreateCourseArchive extends DumbAwareAction {
       copyChild(archiveFolder, filter, child, fromFile);
     }
 
-    final List<Lesson> lessons = course.getLessons();
-
     ApplicationManager.getApplication().runWriteAction(new Runnable() {
       @Override
       public void run() {
-        final Map<TaskFile, TaskFile> savedTaskFiles = new HashMap<TaskFile, TaskFile>();
-        replaceAnswerFilesWithTaskFiles(savedTaskFiles);
-        generateJson(project, archiveFolder);
-        resetTaskFiles(savedTaskFiles);
+        Course courseCopy = course.copy();
+        replaceAnswerFilesWithTaskFiles(courseCopy);
+        generateJson(archiveFolder, courseCopy);
         VirtualFileManager.getInstance().refreshWithoutFileWatcher(false);
         packCourse(archiveFolder, locationDir, zipName, showMessage);
         synchronize(project);
       }
 
-      private void replaceAnswerFilesWithTaskFiles(Map<TaskFile, TaskFile> savedTaskFiles) {
-        for (Lesson lesson : lessons) {
+      private void replaceAnswerFilesWithTaskFiles(Course courseCopy) {
+        for (Lesson lesson : courseCopy.getLessons()) {
           final VirtualFile lessonDir = baseDir.findChild(EduNames.LESSON + String.valueOf(lesson.getIndex()));
           if (lessonDir == null) continue;
           for (Task task : lesson.getTaskList()) {
             final VirtualFile taskDir = lessonDir.findChild(EduNames.TASK + String.valueOf(task.getIndex()));
             if (taskDir == null) continue;
-            for (final Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
-              TaskFile taskFileCopy = new TaskFile();
-              TaskFile taskFile = entry.getValue();
-              TaskFile.copy(taskFile, taskFileCopy);
-              savedTaskFiles.put(taskFile, taskFileCopy);
-
-              VirtualFile userFileDir = VfsUtil.findRelativeFile(archiveFolder, lessonDir.getName(), taskDir.getName());
-              if (userFileDir == null) {
+            VirtualFile studentFileDir = VfsUtil.findRelativeFile(archiveFolder, lessonDir.getName(), taskDir.getName());
+            if (studentFileDir == null) {
+              continue;
+            }
+            for (TaskFile taskFile : task.getTaskFiles().values()) {
+              VirtualFile answerFile = taskDir.findChild(taskFile.name);
+              if (answerFile == null) {
                 continue;
               }
-              String taskFileName = entry.getKey();
-              EduUtils.createStudentFileFromAnswer(project, userFileDir, taskDir, taskFileName, taskFile);
+              EduUtils.createStudentFile(this, project, answerFile, -1, studentFileDir, task);
             }
-          }
         }
       }
+    }
     });
-
-
   }
 
   private static void copyChild(VirtualFile archiveFolder, FileFilter filter, VirtualFile child, File fromFile) {
@@ -160,16 +154,6 @@ public class CCCreateCourseArchive extends DumbAwareAction {
     }
   }
 
-  private static void resetTaskFiles(Map<TaskFile, TaskFile> savedTaskFiles) {
-    for (Map.Entry<TaskFile, TaskFile> entry : savedTaskFiles.entrySet()) {
-      List<AnswerPlaceholder> placeholders = entry.getValue().getAnswerPlaceholders();
-      for (AnswerPlaceholder placeholder : placeholders) {
-        placeholder.setUseLength(false);
-      }
-      entry.getKey().setAnswerPlaceholders(placeholders);
-    }
-  }
-
   private static void synchronize(@NotNull final Project project) {
     VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
     ProjectView.getInstance(project).refresh();
@@ -194,8 +178,7 @@ public class CCCreateCourseArchive extends DumbAwareAction {
   }
 
   @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
-  private static void generateJson(@NotNull final Project project, VirtualFile parentDir) {
-    final Course course = StudyTaskManager.getInstance(project).getCourse();
+  private static void generateJson(VirtualFile parentDir, Course course) {
     final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create();
     final String json = gson.toJson(course);
     final File courseJson = new File(parentDir.getPath(), EduNames.COURSE_META_FILE);
index 5786ebe51199b1fc333052c591f9c1bfbf6405a8..fe4ae15c8de3c8b67625594ec010ab9e28f71e1d 100644 (file)
@@ -50,7 +50,7 @@ public class CCEditTaskTextAction extends ToggleAction implements DumbAware {
       return;
     }
     final StudyState studyState = new StudyState(selectedEditor);
-    VirtualFile taskTextFile = StudyUtils.findTaskDescriptionVirtualFile(studyState.getTaskDir());
+    VirtualFile taskTextFile = StudyUtils.findTaskDescriptionVirtualFile(project, studyState.getTaskDir());
     if (taskTextFile == null) {
       LOG.info("Failed to find task.html");
       return;
diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCNewStepAction.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCNewStepAction.java
new file mode 100644 (file)
index 0000000..f821ba3
--- /dev/null
@@ -0,0 +1,119 @@
+package com.jetbrains.edu.coursecreator.actions;
+
+import com.intellij.ide.fileTemplates.FileTemplate;
+import com.intellij.ide.fileTemplates.FileTemplateManager;
+import com.intellij.ide.fileTemplates.FileTemplateUtil;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.io.FileUtilRt;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiManager;
+import com.jetbrains.edu.coursecreator.CCLanguageManager;
+import com.jetbrains.edu.coursecreator.CCUtils;
+import com.jetbrains.edu.coursecreator.settings.CCSettings;
+import com.jetbrains.edu.learning.StudyStepManager;
+import com.jetbrains.edu.learning.StudyTaskManager;
+import com.jetbrains.edu.learning.StudyUtils;
+import com.jetbrains.edu.learning.core.EduNames;
+import com.jetbrains.edu.learning.courseFormat.Course;
+import com.jetbrains.edu.learning.courseFormat.Step;
+import com.jetbrains.edu.learning.courseFormat.Task;
+import com.jetbrains.edu.learning.courseFormat.TaskFile;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+public class CCNewStepAction extends DumbAwareAction {
+  private static final Logger LOG = Logger.getInstance(CCNewStepAction.class);
+  public static final String NEW_STEP = "New Step";
+
+  public CCNewStepAction() {
+    super(NEW_STEP);
+  }
+
+  @Override
+  public void actionPerformed(AnActionEvent e) {
+    DataContext dataContext = e.getDataContext();
+    VirtualFile virtualFile = CommonDataKeys.VIRTUAL_FILE.getData(dataContext);
+    Editor editor = CommonDataKeys.EDITOR.getData(dataContext);
+    Project project = CommonDataKeys.PROJECT.getData(dataContext);
+    if (virtualFile == null || project == null || editor == null) {
+      return;
+    }
+
+    addStep(virtualFile, project);
+  }
+
+  public static void addStep(@NotNull VirtualFile virtualFile, @NotNull Project project) {
+    TaskFile taskFile = StudyUtils.getTaskFile(project, virtualFile);
+    if (taskFile == null) {
+      return;
+    }
+    VirtualFile taskDir = StudyUtils.getTaskDir(virtualFile);
+    if (taskDir == null) {
+      return;
+    }
+    Task task = taskFile.getTask();
+    List<Step> steps = task.getAdditionalSteps();
+    createTestsForNewStep(project, task);
+    createTaskDescriptionFile(project, taskDir, steps.size());
+    Step step = new Step(task);
+    steps.add(step);
+    StudyStepManager.switchStep(project, task, steps.size() - 1);
+  }
+
+  private static void createTestsForNewStep(Project project, Task task) {
+    Course course = StudyTaskManager.getInstance(project).getCourse();
+    if (course == null) {
+      return;
+    }
+    CCLanguageManager manager = CCUtils.getStudyLanguageManager(course);
+    if (manager == null) {
+      return;
+    }
+    manager.createTestsForNewStep(project, task);
+  }
+
+  private static void createTaskDescriptionFile(Project project, VirtualFile taskDir, int index) {
+    String taskDescriptionFileName = StudyUtils.getTaskDescriptionFileName(CCSettings.getInstance().useHtmlAsDefaultTaskFormat());
+    FileTemplate taskTextTemplate = FileTemplateManager.getInstance(project)
+      .getInternalTemplate(taskDescriptionFileName);
+    PsiDirectory taskPsiDir = PsiManager.getInstance(project).findDirectory(taskDir);
+    if (taskTextTemplate != null && taskPsiDir != null) {
+      String nextTaskTextName = FileUtil.getNameWithoutExtension(taskDescriptionFileName) +
+                                EduNames.STEP_MARKER +
+                                index + "." +
+                                FileUtilRt.getExtension(taskDescriptionFileName);
+      try {
+        FileTemplateUtil.createFromTemplate(taskTextTemplate, nextTaskTextName, null, taskPsiDir);
+      }
+      catch (Exception e) {
+        LOG.error(e);
+      }
+    }
+  }
+
+  @Override
+  public void update(AnActionEvent e) {
+    DataContext dataContext = e.getDataContext();
+    Presentation presentation = e.getPresentation();
+    presentation.setEnabledAndVisible(false);
+    VirtualFile virtualFile = CommonDataKeys.VIRTUAL_FILE.getData(dataContext);
+    Editor editor = CommonDataKeys.EDITOR.getData(dataContext);
+    Project project = CommonDataKeys.PROJECT.getData(dataContext);
+    if (virtualFile == null || project == null || editor == null) {
+      return;
+    }
+    if (CCUtils.isCourseCreator(project) && StudyUtils.getTaskForFile(project, virtualFile) != null) {
+      presentation.setEnabledAndVisible(true);
+    }
+  }
+}
index 6c278dbc04b4f46fc18645184b1ab7bec1a5674f..d869daa1b4f4b925853720a46a60ee2c5633038e 100644 (file)
@@ -21,7 +21,6 @@ import com.intellij.openapi.actionSystem.CommonDataKeys;
 import com.intellij.openapi.actionSystem.LangDataKeys;
 import com.intellij.openapi.actionSystem.Presentation;
 import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.diff.impl.util.LabeledEditor;
 import com.intellij.openapi.editor.Document;
 import com.intellij.openapi.editor.EditorFactory;
@@ -33,10 +32,12 @@ import com.intellij.openapi.project.Project;
 import com.intellij.openapi.ui.FrameWrapper;
 import com.intellij.openapi.ui.Messages;
 import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.util.Pair;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.psi.PsiDirectory;
 import com.intellij.psi.PsiFile;
 import com.intellij.ui.JBColor;
+import com.jetbrains.edu.coursecreator.CCUtils;
 import com.jetbrains.edu.learning.StudyTaskManager;
 import com.jetbrains.edu.learning.StudyUtils;
 import com.jetbrains.edu.learning.core.EduAnswerPlaceholderPainter;
@@ -44,7 +45,6 @@ import com.jetbrains.edu.learning.core.EduUtils;
 import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
 import com.jetbrains.edu.learning.courseFormat.Course;
 import com.jetbrains.edu.learning.courseFormat.TaskFile;
-import com.jetbrains.edu.coursecreator.CCUtils;
 import org.jetbrains.annotations.NotNull;
 
 import javax.swing.*;
@@ -54,10 +54,10 @@ import java.text.SimpleDateFormat;
 import java.util.Calendar;
 
 public class CCShowPreview extends DumbAwareAction {
-  private static final Logger LOG = Logger.getInstance(CCShowPreview.class.getName());
+  public static final String SHOW_PREVIEW = "Show Preview";
 
   public CCShowPreview() {
-    super("Show Preview", "Show Preview", null);
+    super(SHOW_PREVIEW, SHOW_PREVIEW, null);
   }
 
   @Override
@@ -111,9 +111,6 @@ public class CCShowPreview extends DumbAwareAction {
       Messages.showInfoMessage("Preview is available for task files with answer placeholders only", "No Preview for This File");
       return;
     }
-    final TaskFile taskFileCopy = new TaskFile();
-    TaskFile.copy(taskFile, taskFileCopy);
-
 
     VirtualFile generatedFilesFolder = CCUtils.getGeneratedFilesFolder(project, module);
 
@@ -121,15 +118,21 @@ public class CCShowPreview extends DumbAwareAction {
       return;
     }
 
-    ApplicationManager.getApplication().runWriteAction(() -> EduUtils.createStudentFileFromAnswer(project, generatedFilesFolder, taskDir.getVirtualFile(), virtualFile.getName(), taskFileCopy));
+    ApplicationManager.getApplication().runWriteAction(new Runnable() {
+      @Override
+      public void run() {
+        Pair<VirtualFile, TaskFile> pair =
+          EduUtils.createStudentFile(this, project, virtualFile, taskFile.getTask().getActiveStepIndex(), generatedFilesFolder, null);
+        if (pair != null) {
+          showPreviewDialog(project, pair.getFirst(), pair.getSecond());
+        }
+      }
+    });
+  }
 
-    VirtualFile userFile = generatedFilesFolder.findChild(virtualFile.getName());
-    if (userFile == null) {
-      LOG.info("Generated file " + virtualFile.getName() + "was not found");
-      return;
-    }
+  private static void showPreviewDialog(@NotNull Project project, @NotNull VirtualFile userFile, @NotNull TaskFile taskFile) {
     final FrameWrapper showPreviewFrame = new FrameWrapper(project);
-    showPreviewFrame.setTitle(virtualFile.getName());
+    showPreviewFrame.setTitle(userFile.getName());
     LabeledEditor labeledEditor = new LabeledEditor(null);
     final EditorFactory factory = EditorFactory.getInstance();
     Document document = FileDocumentManager.getInstance().getDocument(userFile);
@@ -142,7 +145,8 @@ public class CCShowPreview extends DumbAwareAction {
         factory.releaseEditor(createdEditor);
       }
     });
-    for (AnswerPlaceholder answerPlaceholder : taskFileCopy.getAnswerPlaceholders()) {
+    for (AnswerPlaceholder answerPlaceholder : taskFile.getAnswerPlaceholders()) {
+      answerPlaceholder.setUseLength(true);
       EduAnswerPlaceholderPainter.drawAnswerPlaceholder(createdEditor, answerPlaceholder, JBColor.BLUE);
     }
     JPanel header = new JPanel();
index 2882849f5eda347d807054932958b2f3a8978db6..eb5791b374cfee6cbda7d795abb9dbf3e81d5a9a 100644 (file)
@@ -11,17 +11,28 @@ import com.intellij.ui.SimpleTextAttributes;
  * represents a file which is invisible for student in student mode
  */
 public class CCStudentInvisibleFileNode extends PsiFileNode{
+  private final String myName;
+
   public CCStudentInvisibleFileNode(Project project,
                                     PsiFile value,
                                     ViewSettings viewSettings) {
     super(project, value, viewSettings);
+    myName = value.getName();
   }
 
+  public CCStudentInvisibleFileNode(Project project,
+                                    PsiFile value,
+                                    ViewSettings viewSettings,
+                                    String name) {
+    super(project, value, viewSettings);
+    myName = name;
+  }
+
+
   @Override
   protected void updateImpl(PresentationData data) {
     super.updateImpl(data);
-    String text = data.getPresentableText();
     data.clearText();
-    data.addText(text, SimpleTextAttributes.GRAY_ATTRIBUTES);
+    data.addText(myName, SimpleTextAttributes.GRAY_ATTRIBUTES);
   }
 }
index 39d0ab89145b33dcac471037fb3a1035eb84c9d0..9af3fa992608a7756606d822d9db8b69952f1efe 100644 (file)
@@ -7,8 +7,14 @@ import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.io.FileUtilRt;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiFile;
 import com.jetbrains.edu.coursecreator.CCUtils;
+import com.jetbrains.edu.learning.StudyLanguageManager;
+import com.jetbrains.edu.learning.StudyTaskManager;
 import com.jetbrains.edu.learning.StudyUtils;
+import com.jetbrains.edu.learning.core.EduNames;
+import com.jetbrains.edu.learning.courseFormat.Course;
+import com.jetbrains.edu.learning.courseFormat.Task;
 import com.jetbrains.edu.learning.projectView.StudyTreeStructureProvider;
 import org.jetbrains.annotations.NotNull;
 
@@ -27,28 +33,81 @@ public class CCTreeStructureProvider extends StudyTreeStructureProvider {
 
     for (AbstractTreeNode node : children) {
       Project project = node.getProject();
-        if (node.getValue() instanceof PsiDirectory) {
-          String name = ((PsiDirectory)node.getValue()).getName();
-          if ("zip".equals(FileUtilRt.getExtension(name))) {
-            modifiedChildren.add(node);
-            continue;
-          }
+      if (project == null) {
+        continue;
+      }
+      if (node.getValue() instanceof PsiDirectory) {
+        String name = ((PsiDirectory)node.getValue()).getName();
+        if ("zip".equals(FileUtilRt.getExtension(name))) {
+          modifiedChildren.add(node);
+          continue;
         }
-        if (node instanceof PsiFileNode) {
-          PsiFileNode fileNode = (PsiFileNode)node;
-          VirtualFile virtualFile = fileNode.getVirtualFile();
-          if (virtualFile == null) {
-            continue;
-          }
-          if (project != null && StudyUtils.getTaskFile(project, virtualFile) == null
-              && !StudyUtils.isTaskDescriptionFile(virtualFile.getName())) {
-            modifiedChildren.add(new CCStudentInvisibleFileNode(project, ((PsiFileNode)node).getValue(), settings));
-          }
+      }
+      if (node instanceof PsiFileNode) {
+        PsiFileNode fileNode = (PsiFileNode)node;
+        VirtualFile virtualFile = fileNode.getVirtualFile();
+        if (virtualFile == null) {
+          continue;
         }
+        if (StudyUtils.getTaskFile(project, virtualFile) != null || StudyUtils.isTaskDescriptionFile(virtualFile.getName())) {
+          continue;
+        }
+        PsiFile psiFile = ((PsiFileNode)node).getValue();
+        if (!handleTests(project, virtualFile, psiFile, modifiedChildren, settings)) {
+          modifiedChildren.add(new CCStudentInvisibleFileNode(project, psiFile, settings));
+        }
+      }
     }
     return modifiedChildren;
   }
 
+  private static boolean handleTests(Project project,
+                                     VirtualFile virtualFile,
+                                     PsiFile psiFile,
+                                     Collection<AbstractTreeNode> modifiedChildren,
+                                     ViewSettings settings) {
+    Course course = StudyTaskManager.getInstance(project).getCourse();
+    if (course == null) {
+      return false;
+    }
+    if (!CCUtils.isTestsFile(project, virtualFile)) {
+      return false;
+    }
+    VirtualFile taskDir = StudyUtils.getTaskDir(virtualFile);
+    if (taskDir == null) {
+      return false;
+    }
+    Task task = StudyUtils.getTask(project, taskDir);
+    if (task == null) {
+      return false;
+    }
+    if (isCurrentStep(task, virtualFile)) {
+      StudyLanguageManager manager = StudyUtils.getLanguageManager(course);
+      String testsFileName = manager != null ? manager.getTestFileName() : psiFile.getName();
+      modifiedChildren.add(new CCStudentInvisibleFileNode(project, psiFile, settings,
+                                                          testsFileName));
+    }
+    return true;
+  }
+
+  private static boolean isCurrentStep(Task task, VirtualFile virtualFile) {
+    if (task.getAdditionalSteps().isEmpty()) {
+      return true;
+    }
+
+    boolean isStepTestFile = virtualFile.getName().contains(EduNames.STEP_MARKER);
+    if (task.getActiveStepIndex() == -1) {
+      return !isStepTestFile;
+    }
+    if (!isStepTestFile) {
+      return false;
+    }
+    String nameWithoutExtension = virtualFile.getNameWithoutExtension();
+    int stepMarkerStart = nameWithoutExtension.indexOf(EduNames.STEP_MARKER);
+    int stepIndex = Integer.valueOf(nameWithoutExtension.substring(EduNames.STEP_MARKER.length() + stepMarkerStart));
+    return stepIndex == task.getActiveStepIndex();
+  }
+
   protected boolean needModify(@NotNull final AbstractTreeNode parent) {
     Project project = parent.getProject();
     if (project == null) {
index be6f45a3abcec73fc7873c0a8493d8a10f03a904..a7d737bfb0e1f10b99358a64f7e009302945841e 100644 (file)
@@ -305,6 +305,7 @@ public class StudyProjectComponent implements ProjectComponent {
               taskFile.setUserCreated(true);
               final String name = createdFile.getName();
               taskFile.name = name;
+              //TODO: put to other steps as well
               task.getTaskFiles().put(name, taskFile);
             }
           }
diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyStepManager.java b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyStepManager.java
new file mode 100644 (file)
index 0000000..25fdeb1
--- /dev/null
@@ -0,0 +1,94 @@
+package com.jetbrains.edu.learning;
+
+import com.intellij.ide.projectView.ProjectView;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.EditorNotifications;
+import com.jetbrains.edu.learning.checker.StudyCheckUtils;
+import com.jetbrains.edu.learning.core.EduNames;
+import com.jetbrains.edu.learning.courseFormat.Task;
+import com.jetbrains.edu.learning.courseFormat.TaskFile;
+import com.jetbrains.edu.learning.ui.StudyToolWindow;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Map;
+
+public class StudyStepManager {
+
+  private static final Logger LOG = Logger.getInstance(StudyStepManager.class);
+
+  public static void switchStep(@NotNull Project project, @NotNull Task task, int step) {
+    if (task.getActiveStepIndex() == step) {
+      return;
+    }
+    task.setActiveStepIndex(step);
+
+    VirtualFile taskDir = task.getTaskDir(project);
+    if (taskDir == null) {
+      return;
+    }
+    VirtualFile srcDir = taskDir.findChild(EduNames.SRC);
+    if (srcDir != null) {
+      taskDir = srcDir;
+    }
+    for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
+      String name = entry.getKey();
+      VirtualFile virtualFile = taskDir.findChild(name);
+      if (virtualFile == null) {
+        continue;
+      }
+      EditorNotifications.getInstance(project).updateNotifications(virtualFile);
+    }
+    update(project, task, taskDir);
+
+  }
+
+  private static void update(@NotNull Project project, @NotNull Task task, VirtualFile taskDir) {
+    StudyCheckUtils.drawAllPlaceholders(project, task, taskDir);
+    ProjectView.getInstance(project).refresh();
+    StudyToolWindow toolWindow = StudyUtils.getStudyToolWindow(project);
+    if (toolWindow != null) {
+      String text = StudyUtils.getTaskTextFromTask(taskDir, task);
+      if (text == null) {
+        toolWindow.setEmptyText(project);
+        return;
+      }
+      toolWindow.setTaskText(text, taskDir, project);
+    }
+  }
+
+  public static void deleteStep(@NotNull Project project, @NotNull Task task, int index) {
+    //TODO: delete not only the last step
+    VirtualFile taskDir = task.getTaskDir(project);
+    if (taskDir == null) {
+      return;
+    }
+
+    ArrayList<VirtualFile> filesToDelete = new ArrayList<>();
+    for (VirtualFile file : taskDir.getChildren()) {
+      String stepSuffix = EduNames.STEP_MARKER + index;
+      if (file.getName().contains(stepSuffix)) {
+        filesToDelete.add(file);
+      }
+    }
+    for (VirtualFile file : filesToDelete) {
+        ApplicationManager.getApplication().runWriteAction(() -> {
+          try {
+            file.delete(StudyStepManager.class);
+          }
+          catch (IOException e) {
+            LOG.error(e);
+          }
+        });
+    }
+
+    task.getAdditionalSteps().remove(index);
+    if (task.getActiveStepIndex() == index) {
+      switchStep(project, task, index - 1);
+    }
+  }
+}
index 0be991e4c931466c98c9192342958b530bc6255d..cd579561d7f5039c1a994288a5c56b9e5d2ea20c 100644 (file)
@@ -30,6 +30,7 @@ import com.intellij.openapi.ui.popup.Balloon;
 import com.intellij.openapi.ui.popup.JBPopupFactory;
 import com.intellij.openapi.util.Disposer;
 import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.io.FileUtilRt;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.vfs.LocalFileSystem;
 import com.intellij.openapi.vfs.VfsUtil;
@@ -65,9 +66,7 @@ import org.jetbrains.annotations.Nullable;
 import javax.swing.*;
 import java.awt.*;
 import java.io.*;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
+import java.util.*;
 import java.util.List;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
@@ -342,7 +341,13 @@ public class StudyUtils {
   }
 
   public static void drawAllWindows(Editor editor, TaskFile taskFile) {
-    editor.getMarkupModel().removeAllHighlighters();
+    drawAllWindows(editor, taskFile, true);
+  }
+
+  public static void drawAllWindows(Editor editor, TaskFile taskFile, boolean removePrevHighlighters) {
+    if (removePrevHighlighters) {
+      editor.getMarkupModel().removeAllHighlighters();
+    }
     final Project project = editor.getProject();
     if (project == null) return;
     final StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
@@ -474,21 +479,36 @@ public class StudyUtils {
     if (task == null) {
       return null;
     }
-    String text = task.getText();
-    if (text != null) {
+
+    String text = getFromTask(task);
+    if (text != null && !text.isEmpty()) {
       return text;
     }
     if (taskDirectory != null) {
+      String fileNameWithoutExtension  = FileUtil.getNameWithoutExtension(EduNames.TASK_HTML);
+      int activeStepIndex = task.getActiveStepIndex();
+      if (activeStepIndex != -1) {
+        fileNameWithoutExtension += EduNames.STEP_MARKER + activeStepIndex;
+      }
       final String prefix = String.format(ourPrefix, EditorColorsManager.getInstance().getGlobalScheme().getEditorFontSize());
-      final String taskTextFileHtml = getTaskTextFromTaskName(taskDirectory, EduNames.TASK_HTML);
+      final String taskTextFileHtml = getTaskTextFromTaskName(taskDirectory, fileNameWithoutExtension + "." + FileUtilRt.getExtension(EduNames.TASK_HTML));
       if (taskTextFileHtml != null) return prefix + taskTextFileHtml + ourPostfix;
       
-      final String taskTextFileMd = getTaskTextFromTaskName(taskDirectory, EduNames.TASK_MD);
+      final String taskTextFileMd = getTaskTextFromTaskName(taskDirectory, fileNameWithoutExtension + "." + FileUtilRt.getExtension(EduNames.TASK_MD));
       if (taskTextFileMd != null) return prefix + convertToHtml(taskTextFileMd) + ourPostfix;      
     }
     return null;
   }
 
+  @Nullable
+  private static String getFromTask(Task task) {
+    int index = task.getActiveStepIndex();
+    if (index == -1) {
+      return task.getText();
+    }
+    return task.getAdditionalSteps().get(index).getText();
+  }
+
   @Nullable
   private static String getTaskTextFromTaskName(@NotNull VirtualFile taskDirectory, @NotNull String taskTextFilename) {
     VirtualFile taskTextFile = taskDirectory.findChild(taskTextFilename);
@@ -635,6 +655,15 @@ public class StudyUtils {
     return null;
   }
 
+  @Nullable
+  public static Task getTaskForFile(@NotNull Project project, @NotNull VirtualFile taskFile) {
+    VirtualFile taskDir = getTaskDir(taskFile);
+    if (taskDir == null) {
+      return null;
+    }
+    return getTask(project, taskDir);
+  }
+
   // supposed to be called under progress
   @Nullable
   public static <T> T execCancelable(@NotNull final Callable<T> callable) {
@@ -674,12 +703,25 @@ public class StudyUtils {
   }
   
   public static boolean isTaskDescriptionFile(@NotNull final String fileName) {
-    return EduNames.TASK_HTML.equals(fileName) || EduNames.TASK_MD.equals(fileName);
+    if (EduNames.TASK_HTML.equals(fileName) || EduNames.TASK_MD.equals(fileName)) {
+      return true;
+    }
+    return fileName.contains(EduNames.TASK) && fileName.contains(EduNames.STEP_MARKER);
   }
   
   @Nullable
-  public static VirtualFile findTaskDescriptionVirtualFile(@NotNull final VirtualFile parent) {
-    return ObjectUtils.chooseNotNull(parent.findChild(EduNames.TASK_HTML), parent.findChild(EduNames.TASK_MD));
+  public static VirtualFile findTaskDescriptionVirtualFile(@NotNull Project project, @NotNull VirtualFile taskDir) {
+    Task task = getTask(project, taskDir.getName().contains(EduNames.TASK) ? taskDir: taskDir.getParent());
+    if (task == null) {
+      return null;
+    }
+    String fileNameWithoutExtension  = FileUtil.getNameWithoutExtension(EduNames.TASK_HTML);
+    int activeStepIndex = task.getActiveStepIndex();
+    if (activeStepIndex != -1) {
+      fileNameWithoutExtension += EduNames.STEP_MARKER + activeStepIndex;
+    }
+    return ObjectUtils.chooseNotNull(taskDir.findChild(fileNameWithoutExtension + "." + FileUtilRt.getExtension(EduNames.TASK_HTML)),
+                                     taskDir.findChild(fileNameWithoutExtension + "." + FileUtilRt.getExtension(EduNames.TASK_MD)));
   }
   
   @NotNull
@@ -715,4 +757,34 @@ public class StudyUtils {
       JBPopupFactory.getInstance().createHtmlTextBalloonBuilder("Couldn't post your reaction", MessageType.ERROR, null).createBalloon();
     showCheckPopUp(project, balloon);
   }
+
+  public static void drawPlaceholdersFromOtherSteps(Editor editor, TaskFile taskFile, Task task) {
+    for (int i = -1; i < task.getAdditionalSteps().size(); i++) {
+      if (i == task.getActiveStepIndex()) {
+        continue;
+      }
+      List<AnswerPlaceholder> placeholders = getPlaceholders(i, task, taskFile);
+      for (AnswerPlaceholder placeholder : placeholders) {
+        EduAnswerPlaceholderPainter.drawAnswerPlaceholderFromPrevStep(editor, placeholder);
+      }
+    }
+  }
+
+  private static List<AnswerPlaceholder> getPlaceholders(int index, Task task, TaskFile taskFile) {
+    TaskFile prevTaskFile = index == -1 ? taskFile.getTask().getTaskFile(taskFile.name)
+                                        : task.getAdditionalSteps().get(index).getTaskFiles().get(taskFile.name);
+    if (prevTaskFile == null) {
+      return Collections.emptyList();
+    }
+    return prevTaskFile.getAnswerPlaceholders();
+  }
+
+
+  public static Map<String, TaskFile> getTaskFiles(@NotNull Task task) {
+    int activeStepIndex = task.getActiveStepIndex();
+    if (activeStepIndex == -1) {
+      return task.getTaskFiles();
+    }
+    return task.getAdditionalSteps().get(activeStepIndex).getTaskFiles();
+  }
 }
index 0427f690c82558da9455f3ce8f82d7371ed75682..8be051098cb4850592e604e3aec149ddfb9bd4c4 100644 (file)
@@ -11,6 +11,7 @@ import com.jetbrains.edu.learning.StudyTaskManager;
 import com.jetbrains.edu.learning.core.EduNames;
 import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
 import com.jetbrains.edu.learning.courseFormat.Course;
+import com.jetbrains.edu.learning.courseFormat.Task;
 import com.jetbrains.edu.learning.courseFormat.TaskFile;
 import com.jetbrains.edu.learning.StudyState;
 import com.jetbrains.edu.learning.StudyUtils;
@@ -26,10 +27,20 @@ public class StudyFillPlaceholdersAction extends AnAction {
       if (!studyState.isValid()) {
         return;
       }
-      final TaskFile taskFile = studyState.getTaskFile();
+      TaskFile taskFile = studyState.getTaskFile();
+      Task task = taskFile.getTask();
+      int stepIndex = task.getActiveStepIndex();
+      if (stepIndex != -1) {
+        TaskFile stepTaskFile = task.getAdditionalSteps().get(stepIndex).getTaskFiles().get(taskFile.name);
+        if (stepTaskFile == null) {
+          return;
+        }
+        taskFile = stepTaskFile;
+      }
       final Document document = studyState.getEditor().getDocument();
+      final TaskFile realTaskFile = taskFile;
       CommandProcessor.getInstance().runUndoTransparentAction(() -> ApplicationManager.getApplication().runWriteAction(() -> {
-        for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) {
+        for (AnswerPlaceholder placeholder : realTaskFile.getAnswerPlaceholders()) {
           String answer = placeholder.getPossibleAnswer();
           if (answer == null) {
             continue;
index c4046496b736954c61d3189eb67ebd817d7de3b4..4eecaec1d47d7a762c09a2e9a5d896219a9a777e 100644 (file)
@@ -20,16 +20,10 @@ import com.intellij.openapi.ui.popup.JBPopupFactory;
 import com.intellij.openapi.util.Disposer;
 import com.intellij.openapi.wm.IdeFocusManager;
 import com.intellij.problems.WolfTheProblemSolver;
-import com.jetbrains.edu.learning.StudyActionListener;
-import com.jetbrains.edu.learning.StudyState;
-import com.jetbrains.edu.learning.StudyTaskManager;
-import com.jetbrains.edu.learning.StudyUtils;
+import com.jetbrains.edu.learning.*;
 import com.jetbrains.edu.learning.core.EduAnswerPlaceholderPainter;
 import com.jetbrains.edu.learning.core.EduNames;
-import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
-import com.jetbrains.edu.learning.courseFormat.Course;
-import com.jetbrains.edu.learning.courseFormat.StudyStatus;
-import com.jetbrains.edu.learning.courseFormat.TaskFile;
+import com.jetbrains.edu.learning.courseFormat.*;
 import com.jetbrains.edu.learning.editor.StudyEditor;
 import com.jetbrains.edu.learning.navigation.StudyNavigator;
 import icons.InteractiveLearningIcons;
@@ -37,6 +31,7 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import javax.swing.*;
+import java.util.List;
 
 public class StudyRefreshTaskFileAction extends StudyActionWithShortcut {
   public static final String ACTION_ID = "RefreshTaskAction";
@@ -82,11 +77,16 @@ public class StudyRefreshTaskFileAction extends StudyActionWithShortcut {
                                        @NotNull final Project project,
                                        TaskFile taskFile,
                                        String name) {
+    Task task = taskFile.getTask();
+    List<Step> additionalSteps = task.getAdditionalSteps();
+    if (!additionalSteps.isEmpty() && task.getActiveStepIndex() != -1) {
+      StudyStepManager.switchStep(project, task, -1);
+    }
     if (!resetDocument(document, taskFile, name)) {
       return false;
     }
-    taskFile.getTask().setStatus(StudyStatus.Unchecked);
-    resetAnswerPlaceholders(taskFile, project);
+    task.setStatus(StudyStatus.Unchecked);
+    resetAnswerPlaceholders(taskFile);
     ProjectView.getInstance(project).refresh();
     StudyUtils.updateToolWindows(project);
     return true;
@@ -102,11 +102,23 @@ public class StudyRefreshTaskFileAction extends StudyActionWithShortcut {
     Disposer.register(project, balloon);
   }
 
-  private static void resetAnswerPlaceholders(TaskFile selectedTaskFile, Project project) {
-    final StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
+  private static void resetAnswerPlaceholders(TaskFile selectedTaskFile) {
     for (AnswerPlaceholder answerPlaceholder : selectedTaskFile.getAnswerPlaceholders()) {
       answerPlaceholder.reset();
-      taskManager.setStatus(answerPlaceholder, StudyStatus.Unchecked);
+      answerPlaceholder.setStatus(StudyStatus.Unchecked);
+    }
+    List<Step> additionalSteps = selectedTaskFile.getTask().getAdditionalSteps();
+    if (!additionalSteps.isEmpty()) {
+      for (Step step : additionalSteps) {
+        TaskFile stepTaskFile = step.getTaskFiles().get(selectedTaskFile.name);
+        if (stepTaskFile == null) {
+          continue;
+        }
+        for (AnswerPlaceholder placeholder : stepTaskFile.getAnswerPlaceholders()) {
+          placeholder.reset();
+          placeholder.setStatus(StudyStatus.Unchecked);
+        }
+      }
     }
   }
 
index 9a655260129211128585a72923736225c8f8d344..ddfea35c6925ce839840291f95c4fe1d160ff05d 100644 (file)
@@ -12,22 +12,19 @@ import com.intellij.openapi.util.Pair;
 import com.intellij.openapi.util.Ref;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.vfs.VirtualFile;
-import com.jetbrains.edu.learning.StudyPluginConfigurator;
-import com.jetbrains.edu.learning.StudyState;
-import com.jetbrains.edu.learning.StudyTaskManager;
-import com.jetbrains.edu.learning.StudyUtils;
+import com.jetbrains.edu.learning.*;
 import com.jetbrains.edu.learning.actions.StudyAfterCheckAction;
 import com.jetbrains.edu.learning.core.EduNames;
 import com.jetbrains.edu.learning.core.EduUtils;
-import com.jetbrains.edu.learning.courseFormat.Course;
-import com.jetbrains.edu.learning.courseFormat.StudyStatus;
-import com.jetbrains.edu.learning.courseFormat.Task;
+import com.jetbrains.edu.learning.courseFormat.*;
 import com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector;
 import com.jetbrains.edu.learning.stepic.EduStepicConnector;
 import com.jetbrains.edu.learning.stepic.StepicUser;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import java.util.List;
+
 public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroundable {
 
   private static final Logger LOG = Logger.getInstance(StudyCheckTask.class);
@@ -61,6 +58,19 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun
     StudyCheckUtils.drawAllPlaceholders(myProject, myTask, myTaskDir);
     ProjectView.getInstance(myProject).refresh();
     clearState();
+
+    List<Step> additionalSteps = myTask.getAdditionalSteps();
+    if (additionalSteps.isEmpty() || myTask.getActiveStepIndex() == additionalSteps.size() - 1) {
+      return;
+    }
+    for (TaskFile taskFile : myTask.getTaskFiles().values()) {
+      for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) {
+        if (placeholder.getStatus() != StudyStatus.Solved) {
+          return;
+        }
+      }
+    }
+    ApplicationManager.getApplication().invokeLater(() -> StudyStepManager.switchStep(myProject, myTask, myTask.getActiveStepIndex() + 1));
   }
 
   protected void clearState() {
@@ -89,7 +99,7 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun
 
   private void checkForEduCourse(@NotNull ProgressIndicator indicator) {
     final StudyTestsOutputParser.TestsOutput testsOutput = getTestOutput(indicator);
-    
+
     if (testsOutput != null) {
       if (testsOutput.isSuccess()) {
         onTaskSolved(testsOutput.getMessage());
@@ -178,7 +188,18 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun
 
   protected void onTaskSolved(String message) {
     final Course course = StudyTaskManager.getInstance(myProject).getCourse();
-    myTask.setStatus(StudyStatus.Solved);
+    List<Step> additionalSteps = myTask.getAdditionalSteps();
+    boolean noMoreSteps = additionalSteps.isEmpty() || myTask.getActiveStepIndex() == additionalSteps.size() - 1;
+    if (noMoreSteps) {
+      myTask.setStatus(StudyStatus.Solved);
+    }
+    else {
+      for (TaskFile taskFile : myTask.getTaskFiles().values()) {
+        for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) {
+          placeholder.setStatus(StudyStatus.Solved);
+        }
+      }
+    }
     if (course != null) {
       if (course.isAdaptive()) {
         ApplicationManager.getApplication().invokeLater(
@@ -188,8 +209,15 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun
           });
       }
       else {
+        String successMessage = message;
+        if (!noMoreSteps) {
+          int stepNum = additionalSteps.size() + 1;
+          int currentStep = myTask.getActiveStepIndex() + 2;
+          successMessage = "Step " + currentStep + " from " +  stepNum + " solved";
+        }
+        String finalSuccessMessage = successMessage;
         ApplicationManager.getApplication()
-          .invokeLater(() -> StudyCheckUtils.showTestResultPopUp(message, MessageType.INFO.getPopupBackground(), myProject));
+          .invokeLater(() -> StudyCheckUtils.showTestResultPopUp(finalSuccessMessage, MessageType.INFO.getPopupBackground(), myProject));
       }
     }
   }
index 73812b9be5bc0a5497e3483e99e4e7ce192f67eb..89ea305838062dd017a59ca76a6d8c6b6a3dace5 100644 (file)
@@ -51,15 +51,23 @@ public class StudyCheckUtils {
   public static void drawAllPlaceholders(@NotNull final Project project, @NotNull final Task task, @NotNull final VirtualFile taskDir) {
     for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
       String name = entry.getKey();
-      TaskFile taskFile = entry.getValue();
       VirtualFile virtualFile = taskDir.findChild(name);
       if (virtualFile == null) {
         continue;
       }
-      FileEditor fileEditor = FileEditorManager.getInstance(project).getSelectedEditor(virtualFile);
-      if (fileEditor instanceof StudyEditor) {
-        StudyEditor studyEditor = (StudyEditor)fileEditor;
-        StudyUtils.drawAllWindows(studyEditor.getEditor(), taskFile);
+      if (FileEditorManager.getInstance(project).isFileOpen(virtualFile)) {
+        FileEditor fileEditor = FileEditorManager.getInstance(project).getSelectedEditor(virtualFile);
+        if (fileEditor instanceof StudyEditor) {
+          Editor editor = ((StudyEditor)fileEditor).getEditor();
+          editor.getMarkupModel().removeAllHighlighters();
+          for (TaskFile taskFile : task.getTaskFiles().values()) {
+            StudyUtils.drawPlaceholdersFromOtherSteps(editor, taskFile, task);
+          }
+          TaskFile currentTaskFile = StudyUtils.getTaskFile(project, virtualFile);
+          if (currentTaskFile != null) {
+            StudyUtils.drawAllWindows(editor, currentTaskFile, false);
+          }
+        }
       }
     }
   }
@@ -74,7 +82,7 @@ public class StudyCheckUtils {
     VirtualFile fileToNavigate = studyState.getVirtualFile();
     final StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
     if (!taskManager.hasFailedAnswerPlaceholders(selectedTaskFile)) {
-      for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
+      for (Map.Entry<String, TaskFile> entry : StudyUtils.getTaskFiles(task).entrySet()) {
         String name = entry.getKey();
         TaskFile taskFile = entry.getValue();
         if (taskManager.hasFailedAnswerPlaceholders(taskFile)) {
@@ -178,7 +186,7 @@ public class StudyCheckUtils {
 
 
   public static void flushWindows(@NotNull final Task task, @NotNull final VirtualFile taskDir) {
-    for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
+    for (Map.Entry<String, TaskFile> entry : StudyUtils.getTaskFiles(task).entrySet()) {
       String name = entry.getKey();
       TaskFile taskFile = entry.getValue();
       VirtualFile virtualFile = taskDir.findChild(name);
index a9d8685603f358a64441a4f1630608cb8270f56b..3dcdb43c64a4210a93d9cab69e1a67f175724138 100644 (file)
@@ -3,12 +3,14 @@ package com.jetbrains.edu.learning.core;
 import com.intellij.openapi.editor.Document;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.editor.RangeMarker;
+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.openapi.project.Project;
 import com.intellij.ui.JBColor;
+import com.intellij.ui.SimpleTextAttributes;
 import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
 import com.jetbrains.edu.learning.courseFormat.TaskFile;
 import org.jetbrains.annotations.NotNull;
@@ -30,6 +32,14 @@ public class EduAnswerPlaceholderPainter {
     EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme();
     final TextAttributes textAttributes = new TextAttributes(scheme.getDefaultForeground(), scheme.getDefaultBackground(), null,
                                                                     EffectType.BOXED, Font.PLAIN);
+    textAttributes.setEffectColor(color);
+    drawAnswerPlaceholder(editor, placeholder, textAttributes, PLACEHOLDERS_LAYER);
+  }
+
+  public static void drawAnswerPlaceholder(@NotNull Editor editor,
+                                           @NotNull AnswerPlaceholder placeholder,
+                                           TextAttributes textAttributes,
+                                           int placeholdersLayer) {
     final Project project = editor.getProject();
     assert project != null;
     final int startOffset = placeholder.getOffset();
@@ -38,14 +48,23 @@ public class EduAnswerPlaceholderPainter {
     }
     final int length = placeholder.getRealLength();
     final int endOffset = startOffset + length;
-    textAttributes.setEffectColor(color);
     RangeHighlighter
-      highlighter = editor.getMarkupModel().addRangeHighlighter(startOffset, endOffset, PLACEHOLDERS_LAYER,
+      highlighter = editor.getMarkupModel().addRangeHighlighter(startOffset, endOffset, placeholdersLayer,
                                                                 textAttributes, HighlighterTargetArea.EXACT_RANGE);
     highlighter.setGreedyToLeft(true);
     highlighter.setGreedyToRight(true);
   }
 
+  public static void drawAnswerPlaceholderFromPrevStep(@NotNull Editor editor, @NotNull AnswerPlaceholder placeholder) {
+    EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme();
+    Color color = scheme.getColor(EditorColors.TEARLINE_COLOR);
+    SimpleTextAttributes attributes = SimpleTextAttributes.GRAY_ATTRIBUTES;
+    final TextAttributes textAttributes = new TextAttributes(attributes.getFgColor(), color, null,
+                                                             null, attributes.getFontStyle());
+
+    drawAnswerPlaceholder(editor, placeholder, textAttributes, HighlighterLayer.LAST);
+  }
+
   public static void createGuardedBlock(Editor editor, List<RangeMarker> blocks, int start, int end) {
     RangeHighlighter rh = editor.getMarkupModel()
       .addRangeHighlighter(start, end, PLACEHOLDERS_LAYER, null, HighlighterTargetArea.EXACT_RANGE);
index 836ecf9c4e33126ac5728d145bc008709ab4f3a3..4a9d68ad626107cf30a180c5f7b6037c75563205 100644 (file)
@@ -6,6 +6,8 @@ import com.intellij.openapi.editor.event.DocumentEvent;
 import com.intellij.openapi.editor.impl.event.DocumentEventImpl;
 import com.intellij.openapi.util.TextRange;
 import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
+import com.jetbrains.edu.learning.courseFormat.Step;
+import com.jetbrains.edu.learning.courseFormat.Task;
 import com.jetbrains.edu.learning.courseFormat.TaskFile;
 
 import java.util.ArrayList;
@@ -31,8 +33,6 @@ public class EduDocumentListener extends DocumentAdapter {
     myTrackLength = trackLength;
   }
 
-  //remembering old end before document change because of problems
-  // with fragments containing "\n"
   @Override
   public void beforeDocumentChange(DocumentEvent e) {
     if (!myTaskFile.isTrackChanges()) {
@@ -40,7 +40,7 @@ public class EduDocumentListener extends DocumentAdapter {
     }
     myTaskFile.setHighlightErrors(true);
     myAnswerPlaceholders.clear();
-    for (AnswerPlaceholder answerPlaceholder : myTaskFile.getAnswerPlaceholders()) {
+    for (AnswerPlaceholder answerPlaceholder : getAllPlaceholders()) {
       int twStart = answerPlaceholder.getOffset();
       int length = answerPlaceholder.getRealLength();
       int twEnd = twStart + length;
@@ -48,6 +48,28 @@ public class EduDocumentListener extends DocumentAdapter {
     }
   }
 
+  private List<AnswerPlaceholder> getAllPlaceholders() {
+    Task task = myTaskFile.getTask();
+    if (task == null || task.getAdditionalSteps().isEmpty()) {
+      return myTaskFile.getAnswerPlaceholders();
+    }
+    List<AnswerPlaceholder> placeholders = new ArrayList<>();
+    String name = myTaskFile.name;
+    TaskFile initialStepTaskFile = task.getTaskFile(name);
+    if (initialStepTaskFile == null) {
+      return placeholders;
+    }
+    placeholders.addAll(initialStepTaskFile.getAnswerPlaceholders());
+    for (Step step : task.getAdditionalSteps()) {
+      TaskFile stepTaskFile = step.getTaskFiles().get(name);
+      if (stepTaskFile == null) {
+        continue;
+      }
+      placeholders.addAll(stepTaskFile.getAnswerPlaceholders());
+    }
+    return placeholders;
+  }
+
   @Override
   public void documentChanged(DocumentEvent e) {
     if (!myTaskFile.isTrackChanges()) {
@@ -71,11 +93,12 @@ public class EduDocumentListener extends DocumentAdapter {
         AnswerPlaceholder answerPlaceholder = answerPlaceholderWrapper.getAnswerPlaceholder();
         int length = twEnd - twStart;
         answerPlaceholder.setOffset(twStart);
-        if (!answerPlaceholder.getUseLength()) {
-          answerPlaceholder.setPossibleAnswer(document.getText(TextRange.create(twStart, twStart + length)));
-        }
-        else if (myTrackLength) {
-          answerPlaceholder.setLength(length);
+        if (myTrackLength) {
+          if (answerPlaceholder.getUseLength()) {
+            answerPlaceholder.setLength(length);
+          } else {
+            answerPlaceholder.setPossibleAnswer(document.getText(TextRange.create(twStart, twStart + answerPlaceholder.getRealLength())));
+          }
         }
       }
     }
index 48fb4c23ef4aafda7603aba669d1169daee0b1ce..6ca5adb3832cfb6abbf28a10c6993455b26e67ad 100644 (file)
@@ -49,6 +49,9 @@ public class EduNames {
   public static final String ANSWER_PLACEHOLDER = "Answer Placeholder";
   public static final String PLACEHOLDER = "placeholder";
   public static final String SRC = "src";
+
+  public static final String STEP_MARKER = "_step";
+  public static final String STEP = "step";
   private EduNames() {
   }
 
index edad8e41cacdbdcaffb835af1367c9d61fe1bf5f..c8fb5a7868d3208e0c2034f33b4bdb8518de4334 100644 (file)
@@ -9,10 +9,13 @@ 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.Pair;
 import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.vfs.VfsUtilCore;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.openapi.vfs.VirtualFileManager;
 import com.intellij.psi.PsiDirectory;
+import com.jetbrains.edu.learning.StudyUtils;
 import com.jetbrains.edu.learning.courseFormat.*;
 import org.jetbrains.annotations.NonNls;
 import org.jetbrains.annotations.NotNull;
@@ -22,13 +25,12 @@ import javax.imageio.ImageIO;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.Map;
+import java.util.*;
 
 public class EduUtils {
   private EduUtils() {
   }
+
   private static final Logger LOG = Logger.getInstance(EduUtils.class.getName());
 
   public static final Comparator<StudyItem> INDEX_COMPARATOR = (o1, o2) -> o1.getIndex() - o2.getIndex();
@@ -52,7 +54,8 @@ public class EduUtils {
     }
     try {
       return Integer.parseInt(fullName.substring(logicalName.length())) - 1;
-    } catch(NumberFormatException e) {
+    }
+    catch (NumberFormatException e) {
       return -1;
     }
   }
@@ -106,81 +109,81 @@ public class EduUtils {
     VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
   }
 
-  public static void createStudentFileFromAnswer(@NotNull final Project project,
-                                                 @NotNull final VirtualFile userFileDir,
-                                                 @NotNull final VirtualFile answerFileDir,
-                                                 @NotNull final String taskFileName, @NotNull final TaskFile taskFile) {
-    VirtualFile file = userFileDir.findChild(taskFileName);
-    if (file != null) {
-      try {
-        file.delete(project);
-      }
-      catch (IOException e) {
-        LOG.error(e);
-      }
-    }
 
-    if (taskFile.getAnswerPlaceholders().isEmpty()) {
-      //do not need to replace anything, just copy
-      VirtualFile answerFile = answerFileDir.findChild(taskFileName);
-      if (answerFile != null) {
-        try {
-          answerFile.copy(answerFileDir, userFileDir, taskFileName);
-        }
-        catch (IOException e) {
-          LOG.error(e);
-        }
-      }
-      return;
-    }
+  public static VirtualFile copyFile(Object requestor, VirtualFile toDir, VirtualFile file) {
+    String name = file.getName();
     try {
-      userFileDir.createChildData(project, taskFileName);
+      VirtualFile userFile = toDir.findChild(name);
+      if (userFile != null) {
+        userFile.delete(requestor);
+      }
+      return VfsUtilCore.copyFile(requestor, file, toDir);
     }
     catch (IOException e) {
-      LOG.error(e);
+      LOG.info("Failed to create file " + name + "  in folder " + toDir.getPath(), e);
     }
+    return null;
+  }
 
-    file = userFileDir.findChild(taskFileName);
-    if (file == null) {
-      LOG.info("Failed to find task file " + taskFileName);
-      return;
+  @Nullable
+  public static Pair<VirtualFile, TaskFile> createStudentFile(Object requestor,
+                                                              Project project,
+                                                              VirtualFile answerFile,
+                                                              int stepIndex,
+                                                              VirtualFile parentDir,
+                                                              @Nullable Task task) {
+
+    VirtualFile studentFile = copyFile(requestor, parentDir, answerFile);
+    if (studentFile == null) {
+      return null;
+    }
+    Document studentDocument = FileDocumentManager.getInstance().getDocument(studentFile);
+    if (studentDocument == null) {
+      return null;
     }
-    VirtualFile answerFile = answerFileDir.findChild(taskFileName);
-    if (answerFile == null) {
-      return;
+    if (task == null) {
+      task = StudyUtils.getTaskForFile(project, answerFile);
+      if (task == null) {
+        return null;
+      }
+      task = task.copy();
     }
-    final Document answerDocument = FileDocumentManager.getInstance().getDocument(answerFile);
-    if (answerDocument == null) {
-      return;
+    Map<Integer, TaskFile> taskFileSteps = getTaskFileSteps(task, answerFile.getName());
+    TaskFile initialTaskFile = taskFileSteps.get(-1);
+    if (initialTaskFile == null) {
+      return null;
     }
-    final Document document = FileDocumentManager.getInstance().getDocument(file);
-    if (document == null) return;
+    EduDocumentListener listener = new EduDocumentListener(initialTaskFile, false);
+    studentDocument.addDocumentListener(listener);
 
-    CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> {
-      document.replaceString(0, document.getTextLength(), answerDocument.getCharsSequence());
-      FileDocumentManager.getInstance().saveDocument(document);
-    }), "Create Student File", "Create Student File");
-    createStudentDocument(project, taskFile, file, document);
+    Pair<VirtualFile, TaskFile> result = null;
+    for (Map.Entry<Integer, TaskFile> entry : taskFileSteps.entrySet()) {
+      Integer index = entry.getKey();
+      if (index < stepIndex) {
+        continue;
+      }
+      TaskFile stepTaskFile = entry.getValue();
+      if (index == stepIndex) {
+        result = Pair.createNonNull(studentFile, stepTaskFile);
+      }
+      for (AnswerPlaceholder placeholder : stepTaskFile.getAnswerPlaceholders()) {
+        replaceAnswerPlaceholder(project, studentDocument, placeholder);
+      }
+    }
+    studentDocument.removeDocumentListener(listener);
+    return result;
   }
 
-  public static void createStudentDocument(@NotNull Project project,
-                                           @NotNull TaskFile taskFile,
-                                           VirtualFile file,
-                                           final Document document) {
-    EduDocumentListener listener = new EduDocumentListener(taskFile, false);
-    document.addDocumentListener(listener);
-    taskFile.sortAnswerPlaceholders();
-    for (int i = taskFile.getAnswerPlaceholders().size() - 1; i >= 0; i--) {
-      final AnswerPlaceholder answerPlaceholder = taskFile.getAnswerPlaceholders().get(i);
-      int offset = answerPlaceholder.getOffset();
-      if (offset > document.getTextLength() || offset + answerPlaceholder.getPossibleAnswerLength() > document.getTextLength()) {
-        LOG.error("Wrong startOffset: " + answerPlaceholder.getOffset() + "; document: " + file.getPath());
-        return;
+  public static Map<Integer, TaskFile> getTaskFileSteps(Task task, String name) {
+    Map<Integer, TaskFile> files = new HashMap<>();
+    files.put( -1, task.getTaskFile(name));
+    List<Step> additionalSteps = task.getAdditionalSteps();
+    if (!additionalSteps.isEmpty()) {
+      for (int i = 0; i < additionalSteps.size(); i++) {
+        files.put(i, additionalSteps.get(i).getTaskFiles().get(name));
       }
-      replaceAnswerPlaceholder(project, document, answerPlaceholder);
     }
-    CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> FileDocumentManager.getInstance().saveDocument(document)), "Create Student File", "Create Student File");
-    document.removeDocumentListener(listener);
+    return files;
   }
 
   private static void replaceAnswerPlaceholder(@NotNull final Project project,
@@ -189,13 +192,13 @@ public class EduUtils {
     final String taskText = answerPlaceholder.getTaskText();
     final int offset = answerPlaceholder.getOffset();
     CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> {
-      document.replaceString(offset, offset + answerPlaceholder.getPossibleAnswerLength(), taskText);
+      document.replaceString(offset, offset + answerPlaceholder.getRealLength(), taskText);
       FileDocumentManager.getInstance().saveDocument(document);
     }), "Replace Answer Placeholders", "Replace Answer Placeholders");
   }
 
   public static void deleteWindowDescriptions(@NotNull final Task task, @NotNull final VirtualFile taskDir) {
-    for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
+    for (Map.Entry<String, TaskFile> entry : StudyUtils.getTaskFiles(task).entrySet()) {
       String name = entry.getKey();
       VirtualFile virtualFile = taskDir.findChild(name);
       if (virtualFile == null) {
index a5b88246230c7bf6a16bb2b66af4bc865300a77a..fa4f4759475717635b2c9b3a5f02ef9139410528 100644 (file)
@@ -4,9 +4,11 @@ import com.google.gson.annotations.Expose;
 import com.google.gson.annotations.SerializedName;
 import com.intellij.lang.Language;
 import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.util.xmlb.XmlSerializer;
 import com.jetbrains.edu.learning.core.EduNames;
 import com.jetbrains.edu.learning.core.EduUtils;
 import com.jetbrains.edu.learning.stepic.StepicUser;
+import org.jdom.Element;
 import org.jetbrains.annotations.NotNull;
 
 import java.util.ArrayList;
@@ -167,4 +169,14 @@ public class Course {
   public void setCourseMode(String courseMode) {
     this.courseMode = courseMode;
   }
+
+  public Course copy() {
+    Element element = XmlSerializer.serialize(this);
+    Course copy = XmlSerializer.deserialize(element, Course.class);
+    if (copy == null) {
+      return null;
+    }
+    copy.initCourse(true);
+    return copy;
+  }
 }
diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Step.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Step.java
new file mode 100644 (file)
index 0000000..a8d1877
--- /dev/null
@@ -0,0 +1,51 @@
+package com.jetbrains.edu.learning.courseFormat;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+import com.intellij.util.containers.HashMap;
+
+import java.io.Serializable;
+import java.util.Map;
+
+public class Step implements Serializable{
+
+  @SerializedName("task_files")
+  @Expose
+  private Map<String, TaskFile> myTaskFiles = new HashMap<>();
+  private String myText = "";
+
+  public Step() {
+  }
+
+  public Step(Task task) {
+    for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
+      String name = entry.getKey();
+      TaskFile taskFile = new TaskFile();
+      taskFile.name = name;
+      taskFile.setTask(task);
+      myTaskFiles.put(name, taskFile);
+    }
+  }
+
+  public Map<String, TaskFile> getTaskFiles() {
+    return myTaskFiles;
+  }
+
+  public void setTaskFiles(Map<String, TaskFile> taskFiles) {
+    myTaskFiles = taskFiles;
+  }
+
+  public void init(Task task, boolean isRestarted) {
+    for (TaskFile file : myTaskFiles.values()) {
+      file.initTaskFile(task, isRestarted);
+    }
+  }
+
+  public String getText() {
+    return myText;
+  }
+
+  public void setText(String text) {
+    myText = text;
+  }
+}
index 00b4b128dd625e8dd5fc5b1342298c239ab2a1e2..ac478f9387ccf32e355052eeab7666d240424c7f 100644 (file)
@@ -7,13 +7,17 @@ import com.intellij.openapi.fileEditor.FileDocumentManager;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.xmlb.XmlSerializer;
 import com.intellij.util.xmlb.annotations.Transient;
 import com.jetbrains.edu.learning.StudyUtils;
 import com.jetbrains.edu.learning.core.EduNames;
+import org.jdom.Element;
 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;
 
 /**
@@ -38,6 +42,11 @@ public class Task implements StudyItem {
 
   @Transient private Lesson myLesson;
 
+  @Expose
+  @SerializedName("additionalSteps")
+  private List<Step> myAdditionalSteps = new ArrayList<>();
+  private int myActiveStepIndex = -1;
+
   public Task() {}
 
   public Task(@NotNull final String name) {
@@ -55,6 +64,9 @@ public class Task implements StudyItem {
     for (TaskFile taskFile : getTaskFiles().values()) {
       taskFile.initTaskFile(this, isRestarted);
     }
+    for (Step step : myAdditionalSteps) {
+      step.init(this, isRestarted);
+    }
   }
 
   public String getName() {
@@ -116,7 +128,11 @@ public class Task implements StudyItem {
 
   @Nullable
   public TaskFile getFile(@NotNull final String fileName) {
-    return taskFiles.get(fileName);
+    if (myActiveStepIndex == -1) {
+      return taskFiles.get(fileName);
+    }
+    Step step = myAdditionalSteps.get(myActiveStepIndex);
+    return step.getTaskFiles().get(fileName);
   }
 
   @Transient
@@ -148,7 +164,7 @@ public class Task implements StudyItem {
     if (!StringUtil.isEmptyOrSpaces(text)) return text;
     final VirtualFile taskDir = getTaskDir(project);
     if (taskDir != null) {
-      final VirtualFile file = StudyUtils.findTaskDescriptionVirtualFile(taskDir);
+      final VirtualFile file = StudyUtils.findTaskDescriptionVirtualFile(project, taskDir);
       if (file == null) return "";
       final Document document = FileDocumentManager.getInstance().getDocument(file);
       if (document != null) {
@@ -214,10 +230,36 @@ public class Task implements StudyItem {
   
   public void setStatus(StudyStatus status) {
     myStatus = status;
-    for (TaskFile taskFile : taskFiles.values()) {
+    for (TaskFile taskFile : StudyUtils.getTaskFiles(this).values()) {
       for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) {
         placeholder.setStatus(status);
       }
     }
   }
+
+  public List<Step> getAdditionalSteps() {
+    return myAdditionalSteps;
+  }
+
+  public void setAdditionalSteps(List<Step> additionalSteps) {
+    myAdditionalSteps = additionalSteps;
+  }
+
+  public int getActiveStepIndex() {
+    return myActiveStepIndex;
+  }
+
+  public void setActiveStepIndex(int activeStepIndex) {
+    myActiveStepIndex = activeStepIndex;
+  }
+
+  public Task copy() {
+    Element element = XmlSerializer.serialize(this);
+    Task copy = XmlSerializer.deserialize(element, Task.class);
+    if (copy == null) {
+      return null;
+    }
+    copy.initTask(null, true);
+    return copy;
+  }
 }
index 826f6e36b3150612d1d223cca8f23e0e6f3b3601..31b62e9fd1cb0670a03134d4ee0557df84d897c0 100644 (file)
@@ -20,6 +20,7 @@ import com.jetbrains.edu.learning.core.EduDocumentListener;
 import com.jetbrains.edu.learning.core.EduNames;
 import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
 import com.jetbrains.edu.learning.courseFormat.Course;
+import com.jetbrains.edu.learning.courseFormat.Task;
 import com.jetbrains.edu.learning.courseFormat.TaskFile;
 import com.jetbrains.edu.learning.navigation.StudyNavigator;
 import com.jetbrains.edu.learning.ui.StudyToolWindowFactory;
@@ -87,6 +88,10 @@ public class StudyEditorFactoryListener implements EditorFactoryListener {
             editor.addEditorMouseListener(new WindowSelectionListener(taskFile));
           }
         }
+        Task task = taskFile.getTask();
+        if (!task.getAdditionalSteps().isEmpty()) {
+          StudyUtils.drawPlaceholdersFromOtherSteps(editor, taskFile, task);
+        }
       }
     }
   }
index 5439bce17d2e3c5c0f74ac425c0c611bd681e17e..8c98e4d976dd3452656f1d8ae846169725023857 100644 (file)
@@ -24,6 +24,7 @@ import java.awt.*;
 import java.util.Map;
 
 public class StudyDirectoryNode extends PsiDirectoryNode {
+  public static final JBColor LIGHT_GREEN = new JBColor(new Color(0, 134, 0), new Color(98, 150, 85));
   protected final PsiDirectory myValue;
   protected final Project myProject;
 
@@ -96,7 +97,7 @@ public class StudyDirectoryNode extends PsiDirectoryNode {
         break;
       }
       case Solved: {
-        updatePresentation(data, additionalName, new JBColor(new Color(0, 134, 0), new Color(98, 150, 85)), InteractiveLearningIcons.LessonCompl);
+        updatePresentation(data, additionalName, LIGHT_GREEN, InteractiveLearningIcons.LessonCompl);
         break;
       }
       case Failed: {
@@ -105,27 +106,40 @@ public class StudyDirectoryNode extends PsiDirectoryNode {
     }
   }
 
-  protected void setStudyAttributes(Task task, PresentationData data, String additionalName) {
+  protected void setStudyAttributes(Task task, PresentationData data, String name) {
     StudyStatus taskStatus = task.getStatus();
+    String additionalInfo = task.getAdditionalSteps().isEmpty() ? null : getStepInfo(task);
     switch (taskStatus) {
       case Unchecked: {
-        updatePresentation(data, additionalName, JBColor.BLACK, InteractiveLearningIcons.Task);
+        updatePresentation(data, name, JBColor.BLACK, InteractiveLearningIcons.Task, additionalInfo);
         break;
       }
       case Solved: {
-        updatePresentation(data, additionalName, new JBColor(new Color(0, 134, 0), new Color(98, 150, 85)),
-                           InteractiveLearningIcons.TaskCompl);
+        updatePresentation(data, name, LIGHT_GREEN, InteractiveLearningIcons.TaskCompl, additionalInfo);
         break;
       }
       case Failed: {
-        updatePresentation(data, additionalName, JBColor.RED, InteractiveLearningIcons.TaskProbl);
+        updatePresentation(data, name, JBColor.RED, InteractiveLearningIcons.TaskProbl, additionalInfo);
       }
     }
   }
 
-  protected static void updatePresentation(PresentationData data, String additionalName, JBColor color, Icon icon) {
+  private static String getStepInfo(Task task) {
+    int index = task.getActiveStepIndex() + 2;
+    int number = task.getAdditionalSteps().size() + 1;
+    return "step " + index + "/" + number;
+  }
+
+  protected static void updatePresentation(PresentationData data, String name, JBColor color, Icon icon) {
+    updatePresentation(data, name, color, icon, null);
+  }
+
+  protected static void updatePresentation(PresentationData data, String name, JBColor color, Icon icon, String additionalInfo) {
     data.clearText();
-    data.addText(additionalName, new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, color));
+    data.addText(name, new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, color));
+    if (additionalInfo != null) {
+      data.addText(" (" + additionalInfo + ")", SimpleTextAttributes.GRAYED_ATTRIBUTES);
+    }
     data.setIcon(icon);
   }
 
@@ -160,7 +174,7 @@ public class StudyDirectoryNode extends PsiDirectoryNode {
           FileEditorManager.getInstance(myProject).closeFile(openFile);
         }
         VirtualFile child = null;
-        Map<String, TaskFile> taskFiles = task.getTaskFiles();
+        Map<String, TaskFile> taskFiles = StudyUtils.getTaskFiles(task);
         for (Map.Entry<String, TaskFile> entry: taskFiles.entrySet()) {
           VirtualFile file = taskDir.findChild(entry.getKey());
           if (file != null) {
index c5820eaf95cf690dc3ac44b10af8b706f3204caa..5152bf6c3b4657624d38c446608a2eeafc407fdb 100644 (file)
@@ -4,6 +4,7 @@ import com.google.gson.annotations.Expose;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Pair;
 import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.jetbrains.edu.learning.core.EduNames;
@@ -17,6 +18,7 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -59,35 +61,29 @@ public class StepicWrappers {
       source.files = new ArrayList<TaskFile>();
       source.title = task.getName();
       for (final Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
-        final TaskFile taskFile = new TaskFile();
-        TaskFile.copy(entry.getValue(), taskFile);
         ApplicationManager.getApplication().runWriteAction(() -> {
           final VirtualFile taskDir = task.getTaskDir(project);
           assert taskDir != null;
           VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
           assert ideaDir != null;
-          EduUtils.createStudentFileFromAnswer(project, ideaDir, taskDir, entry.getKey(), taskFile);
-        });
-        taskFile.name = entry.getKey();
-
-        VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
-        if (ideaDir == null) return null;
-        final VirtualFile file = ideaDir.findChild(taskFile.name);
-        try {
-          if (file != null) {
-            if (EduUtils.isImage(taskFile.name)) {
-              taskFile.text = Base64.encodeBase64URLSafeString(FileUtil.loadBytes(file.getInputStream()));
-            }
-            else {
-              taskFile.text = FileUtil.loadTextAndClose(file.getInputStream());
-            }
+          String name = entry.getKey();
+          VirtualFile answerFile = taskDir.findChild(name);
+          Pair<VirtualFile, TaskFile> pair = EduUtils.createStudentFile(StepicWrappers.class, project, answerFile, -1, ideaDir, null);
+          if (pair == null) {
+            return;
           }
-        }
-        catch (IOException e) {
-          LOG.error("Can't find file " + file.getPath());
-        }
-
-        source.files.add(taskFile);
+          VirtualFile virtualFile = pair.getFirst();
+          TaskFile taskFile = pair.getSecond();
+          try {
+            InputStream stream = virtualFile.getInputStream();
+            taskFile.text =
+              EduUtils.isImage(name) ? Base64.encodeBase64URLSafeString(FileUtil.loadBytes(stream)) : FileUtil.loadTextAndClose(stream);
+          }
+          catch (IOException e) {
+            LOG.error("Can't find file " + virtualFile.getPath());
+          }
+          source.files.add(taskFile);
+        });
       }
       return source;
     }
@@ -389,12 +385,12 @@ public class StepicWrappers {
   static class AssignmentsWrapper {
     List<Assignment> assignments;
   }
-  
+
   static class Assignment {
     int id;
     int step;
   }
-  
+
   static class ViewsWrapper {
     View view;
 
@@ -402,7 +398,7 @@ public class StepicWrappers {
       this.view = new View(assignment, step);
     }
   }
-  
+
   static class View {
     int assignment;
     int step;
@@ -412,7 +408,7 @@ public class StepicWrappers {
       this.step = step;
     }
   }
-  
+
   static class Enrollment {
     String course;
 
@@ -420,6 +416,7 @@ public class StepicWrappers {
       course = courseId;
     }
   }
+
   static class EnrollmentWrapper {
     Enrollment enrollment;
 
index 86173571925e69aa3d09e17279c4c470476601f3..c206f6679bb2d7faffed5038727e9caf3bb0d32f 100644 (file)
@@ -203,15 +203,19 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D
   }
 
   public void setTaskText(String text, VirtualFile taskDirectory, Project project) {
-    if (StudyTaskManager.getInstance(project).isTurnEditingMode()) {
+    if (!EMPTY_TASK_TEXT.equals(text) && StudyTaskManager.getInstance(project).isTurnEditingMode()) {
       if (taskDirectory == null) {
         LOG.info("Failed to enter editing mode for StudyToolWindow");
         return;
       }
-      VirtualFile taskTextFile = StudyUtils.findTaskDescriptionVirtualFile(taskDirectory);
+      VirtualFile taskTextFile = StudyUtils.findTaskDescriptionVirtualFile(project, taskDirectory);
       enterEditingMode(taskTextFile, project);
       StudyTaskManager.getInstance(project).setTurnEditingMode(false);
     }
+    if (taskDirectory != null && StudyTaskManager.getInstance(project).getToolWindowMode() == StudyToolWindowMode.EDITING) {
+      VirtualFile taskTextFile = StudyUtils.findTaskDescriptionVirtualFile(project, taskDirectory);
+      enterEditingMode(taskTextFile, project);
+    }
     else {
       setText(text);
     }
index 8628bbdecc41700d2e10bd00e612fc6ca894eac2..8aff7f73d43a0cc2266e5c6b8b52ed5a1e3b3912 100644 (file)
@@ -2,15 +2,27 @@ package com.jetbrains.edu.coursecreator;
 
 import com.intellij.ide.fileTemplates.FileTemplate;
 import com.intellij.ide.fileTemplates.FileTemplateManager;
+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.io.FileUtil;
+import com.intellij.openapi.util.io.FileUtilRt;
 import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.DocumentUtil;
+import com.jetbrains.edu.learning.StudyTaskManager;
 import com.jetbrains.edu.learning.core.EduNames;
+import com.jetbrains.edu.learning.courseFormat.Task;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.io.File;
+import java.io.IOException;
 
 public class PyCCLanguageManager implements CCLanguageManager {
+  private static final Logger LOG = Logger.getInstance(PyCCLanguageManager.class);
+
   @Nullable
   @Override
   public String getDefaultTaskFileExtension() {
@@ -44,6 +56,63 @@ public class PyCCLanguageManager implements CCLanguageManager {
 
   @Override
   public boolean isTestFile(VirtualFile file) {
-    return EduNames.TESTS_FILE.equals(file.getName());
+    String name = file.getName();
+    if (EduNames.TESTS_FILE.equals(name)) {
+      return true;
+    }
+    return name.contains(FileUtil.getNameWithoutExtension(EduNames.TESTS_FILE)) && name.contains(EduNames.STEP_MARKER);
   }
+
+  @Override
+  public void createTestsForNewStep(@NotNull Project project, @NotNull Task task) {
+    VirtualFile taskDir = task.getTaskDir(project);
+    if (taskDir == null) {
+      return;
+    }
+
+    int prevStepIndex = task.getActiveStepIndex();
+    String name = prevStepIndex == -1 ? EduNames.TESTS_FILE : getStepTestsFileName(prevStepIndex);
+    VirtualFile testsFile = taskDir.findChild(name);
+    if (testsFile == null) {
+      return;
+    }
+    Document document = FileDocumentManager.getInstance().getDocument(testsFile);
+    if (document == null) {
+      return;
+    }
+    CharSequence prevTestText = document.getCharsSequence();
+    String nextStepTestsFileName = getStepTestsFileName(prevStepIndex + 1);
+    ApplicationManager.getApplication().runWriteAction(() -> {
+      try {
+        VirtualFile nextStepTestsFile = taskDir.createChildData(this, nextStepTestsFileName);
+        StudyTaskManager.getInstance(project).addInvisibleFiles(nextStepTestsFile.getPath());
+        Document nextStepDocument = FileDocumentManager.getInstance().getDocument(nextStepTestsFile);
+        if (nextStepDocument == null) {
+          return;
+        }
+        int index = prevStepIndex + 2;
+        //TODO: text for header
+        String header = "# This is test for step " + index + ". We've already copied tests from previous step here.\n\n";
+        DocumentUtil.writeInRunUndoTransparentAction(() -> {
+          nextStepDocument.insertString(0, header);
+          nextStepDocument.insertString(header.length(), prevTestText);
+          FileDocumentManager.getInstance().saveDocument(nextStepDocument);
+        });
+      }
+      catch (IOException e) {
+        LOG.error(e);
+      }
+    });
+  }
+
+    @NotNull
+    public static String getStepTestsFileName(int index) {
+      if (index == -1) {
+        return EduNames.TESTS_FILE;
+      }
+      return FileUtil.getNameWithoutExtension(EduNames.TESTS_FILE) +
+             EduNames.STEP_MARKER +
+             index + "." +
+             FileUtilRt.getExtension(EduNames.TESTS_FILE);
+    }
 }
index f392a76a11ea268364dc572c40802fe462c8fa84..3b1104243c6f216b43c466a86ba7ca56fdf1441f 100644 (file)
@@ -96,7 +96,7 @@ public class PyCCCommandLineState extends PythonCommandLineState {
 
   @Override
   public ExecutionResult execute(Executor executor, CommandLinePatcher... patchers) throws ExecutionException {
-    CCUtils.createResources(myRunConfiguration.getProject(), myTask, myTaskDir);
+    CCUtils.updateResources(myRunConfiguration.getProject(), myTask, myTaskDir);
     ApplicationManager.getApplication().runWriteAction(() -> StudyCheckUtils.flushWindows(myTask, myTaskDir));
 
     return super.execute(executor, patchers);
index 80f40044bca79ad719184b6cbd2615f730f05dfd..5abb028d18d25b5ae26a696617e89c1006f58faf 100644 (file)
@@ -10,6 +10,7 @@ import com.intellij.openapi.vfs.LocalFileSystem;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.psi.PsiElement;
 import com.jetbrains.edu.coursecreator.CCUtils;
+import com.jetbrains.edu.coursecreator.PyCCLanguageManager;
 import com.jetbrains.edu.learning.StudyUtils;
 import com.jetbrains.edu.learning.core.EduNames;
 import com.jetbrains.edu.learning.courseFormat.Task;
@@ -77,10 +78,15 @@ public class PyCCRunTestsConfigurationProducer extends RunConfigurationProducer<
       return null;
     }
 
+    Task task = StudyUtils.getTask(location.getProject(), taskDir);
+    if (task == null) {
+      return null;
+    }
+    String testsFileName = PyCCLanguageManager.getStepTestsFileName(task.getActiveStepIndex());
     String taskDirPath = FileUtil.toSystemDependentName(taskDir.getPath());
     String testsPath = taskDir.findChild(EduNames.SRC) != null ?
-                       FileUtil.join(taskDirPath, EduNames.SRC, EduNames.TESTS_FILE) :
-                       FileUtil.join(taskDirPath, EduNames.TESTS_FILE);
+                       FileUtil.join(taskDirPath, EduNames.SRC, testsFileName) :
+                       FileUtil.join(taskDirPath, testsFileName);
     String filePath = FileUtil.toSystemDependentName(file.getPath());
     return filePath.equals(testsPath) ? testsPath : null;
   }
index 9bea1b5a731a45233b29281bdb92e1d5c804b9c5..992b741ef125cea79e2f49d49db8d997b0c575aa 100644 (file)
@@ -98,7 +98,8 @@ public class PyStudyCheckAction extends StudyCheckAction {
         ApplicationManager.getApplication().invokeLater(() -> {
           if (myTaskDir == null) return;
           myTask.setStatus(StudyStatus.Failed);
-          for (Map.Entry<String, TaskFile> entry : myTask.getTaskFiles().entrySet()) {
+          Map<String, TaskFile> taskFiles = StudyUtils.getTaskFiles(myTask);
+          for (Map.Entry<String, TaskFile> entry : taskFiles.entrySet()) {
             final String name = entry.getKey();
             final TaskFile taskFile = entry.getValue();
             if (taskFile.getAnswerPlaceholders().size() < 2) {
index ba4e268da13c399de2641c34606f1f35ef4d60ad..468e8f1dafe51fbf0e397338a60ffcb45b6ce8dd 100644 (file)
@@ -7,10 +7,12 @@ import com.intellij.openapi.module.ModuleManager;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.projectRoots.Sdk;
 import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.io.FileUtilRt;
 import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.edu.learning.checker.StudyTestRunner;
+import com.jetbrains.edu.learning.core.EduNames;
 import com.jetbrains.edu.learning.courseFormat.Course;
 import com.jetbrains.edu.learning.courseFormat.Task;
-import com.jetbrains.edu.learning.checker.StudyTestRunner;
 import com.jetbrains.python.sdk.PythonSdkType;
 import org.jetbrains.annotations.NotNull;
 
@@ -33,7 +35,13 @@ public class PyStudyTestRunner extends StudyTestRunner {
       LOG.info("Language manager is null for " + course.getLanguageById().getDisplayName());
       return null;
     }
-    final File testRunner = new File(myTaskDir.getPath(), manager.getTestFileName());
+
+    String testsFileName = manager.getTestFileName();
+    int activeStepIndex = myTask.getActiveStepIndex();
+    if (activeStepIndex != -1) {
+      testsFileName = FileUtil.getNameWithoutExtension(testsFileName) + EduNames.STEP_MARKER + activeStepIndex + "." + FileUtilRt.getExtension(testsFileName);
+    }
+    final File testRunner = new File(myTaskDir.getPath(), testsFileName);
     final GeneralCommandLine commandLine = new GeneralCommandLine();
     commandLine.withWorkDirectory(myTaskDir.getPath());
     final Map<String, String> env = commandLine.getEnvironment();