project view refactoring in educational plugins
authorliana.bakradze <liana.bakradze@jetbrains.com>
Mon, 17 Oct 2016 18:05:51 +0000 (21:05 +0300)
committerliana.bakradze <liana.bakradze@jetbrains.com>
Thu, 17 Nov 2016 14:08:29 +0000 (17:08 +0300)
14 files changed:
python/educational-core/course-creator/resources/META-INF/plugin.xml
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCTaskFileActionBase.java
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCCourseDirectoryNode.java [new file with mode: 0644]
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCLessonDirectoryNode.java [new file with mode: 0644]
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCTaskDirectoryNode.java [new file with mode: 0644]
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCTreeStructureProvider.java
python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyTaskNavigationAction.java
python/educational-core/student/src/com/jetbrains/edu/learning/navigation/StudyNavigator.java
python/educational-core/student/src/com/jetbrains/edu/learning/projectView/CourseDirectoryNode.java [new file with mode: 0644]
python/educational-core/student/src/com/jetbrains/edu/learning/projectView/LessonDirectoryNode.java [new file with mode: 0644]
python/educational-core/student/src/com/jetbrains/edu/learning/projectView/SandboxDirectoryNode.java [new file with mode: 0644]
python/educational-core/student/src/com/jetbrains/edu/learning/projectView/StudyDirectoryNode.java
python/educational-core/student/src/com/jetbrains/edu/learning/projectView/StudyTreeStructureProvider.java
python/educational-core/student/src/com/jetbrains/edu/learning/projectView/TaskDirectoryNode.java [new file with mode: 0644]

index b4cc3f63b44fea07316ec2bae09c2418d145099e..b6c87988b86cef985bb1949c2de460e4ab85a2cb 100644 (file)
@@ -17,7 +17,7 @@
 
   <extensions defaultExtensionNs="com.intellij">
     <projectService serviceImplementation="com.jetbrains.edu.coursecreator.CCProjectService"/>
-    <treeStructureProvider implementation="com.jetbrains.edu.coursecreator.projectView.CCTreeStructureProvider"/>
+    <treeStructureProvider implementation="com.jetbrains.edu.coursecreator.projectView.CCTreeStructureProvider" order="last"/>
     <refactoring.elementListenerProvider implementation="com.jetbrains.edu.coursecreator.CCRefactoringElementListenerProvider"/>
     <refactoring.moveHandler implementation="com.jetbrains.edu.coursecreator.handlers.CCLessonMoveHandlerDelegate" order="first"/>
     <refactoring.moveHandler implementation="com.jetbrains.edu.coursecreator.handlers.CCTaskMoveHandlerDelegate" order="first"/>
index 649db137a19a4e1d53e8b8146c27bb04de674279..395e62e1c0b4d9c3834bdf693a155527227cfd75 100644 (file)
@@ -28,7 +28,7 @@ public abstract class CCTaskFileActionBase extends AnAction {
     if (file == null) {
       return;
     }
-    VirtualFile taskVF = file.getParent();
+    VirtualFile taskVF = StudyUtils.getTaskDir(file);
     if (taskVF == null) {
       return;
     }
diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCCourseDirectoryNode.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCCourseDirectoryNode.java
new file mode 100644 (file)
index 0000000..04cd3f4
--- /dev/null
@@ -0,0 +1,51 @@
+package com.jetbrains.edu.coursecreator.projectView;
+
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.projectView.impl.nodes.PsiFileNode;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtilRt;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
+import com.jetbrains.edu.learning.courseFormat.Course;
+import com.jetbrains.edu.learning.courseFormat.Lesson;
+import com.jetbrains.edu.learning.courseFormat.StudyItem;
+import com.jetbrains.edu.learning.projectView.CourseDirectoryNode;
+import com.jetbrains.edu.learning.projectView.StudyDirectoryNode;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class CCCourseDirectoryNode extends CourseDirectoryNode {
+
+  public CCCourseDirectoryNode(@NotNull Project project,
+                               PsiDirectory value,
+                               ViewSettings viewSettings,
+                               @NotNull Course course) {
+    super(project, value, viewSettings, course);
+  }
+
+  @Nullable
+  @Override
+  public AbstractTreeNode modifyChildNode(AbstractTreeNode childNode) {
+    AbstractTreeNode node = super.modifyChildNode(childNode);
+    if (node != null) {
+      return node;
+    }
+    if (childNode instanceof PsiFileNode) {
+      VirtualFile virtualFile = ((PsiFileNode)childNode).getVirtualFile();
+      if (virtualFile == null) {
+        return null;
+      }
+      if (FileUtilRt.getExtension(virtualFile.getName()).equals("iml")) {
+        return null;
+      }
+      return new CCStudentInvisibleFileNode(myProject, ((PsiFileNode)childNode).getValue(), myViewSettings);
+    }
+    return null;
+  }
+
+  @Override
+  public StudyDirectoryNode createChildDirectoryNode(StudyItem item, PsiDirectory directory) {
+    return new CCLessonDirectoryNode(myProject, directory, myViewSettings, ((Lesson)item));
+  }
+}
diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCLessonDirectoryNode.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCLessonDirectoryNode.java
new file mode 100644 (file)
index 0000000..1be30f7
--- /dev/null
@@ -0,0 +1,25 @@
+package com.jetbrains.edu.coursecreator.projectView;
+
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiDirectory;
+import com.jetbrains.edu.learning.courseFormat.Lesson;
+import com.jetbrains.edu.learning.courseFormat.StudyItem;
+import com.jetbrains.edu.learning.courseFormat.Task;
+import com.jetbrains.edu.learning.projectView.LessonDirectoryNode;
+import com.jetbrains.edu.learning.projectView.StudyDirectoryNode;
+import org.jetbrains.annotations.NotNull;
+
+public class CCLessonDirectoryNode extends LessonDirectoryNode {
+  public CCLessonDirectoryNode(@NotNull Project project,
+                               PsiDirectory value,
+                               ViewSettings viewSettings,
+                               @NotNull Lesson lesson) {
+    super(project, value, viewSettings, lesson);
+  }
+
+  @Override
+  public StudyDirectoryNode createChildDirectoryNode(StudyItem item, PsiDirectory directory) {
+    return new CCTaskDirectoryNode(myProject, directory, myViewSettings, ((Task)item));
+  }
+}
diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCTaskDirectoryNode.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCTaskDirectoryNode.java
new file mode 100644 (file)
index 0000000..b8279ab
--- /dev/null
@@ -0,0 +1,81 @@
+package com.jetbrains.edu.coursecreator.projectView;
+
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiElement;
+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.TaskDirectoryNode;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class CCTaskDirectoryNode extends TaskDirectoryNode {
+  public CCTaskDirectoryNode(@NotNull Project project,
+                             PsiDirectory value,
+                             ViewSettings viewSettings,
+                             @NotNull Task task) {
+    super(project, value, viewSettings, task);
+  }
+
+  @Nullable
+  @Override
+  public AbstractTreeNode modifyChildNode(AbstractTreeNode childNode) {
+    AbstractTreeNode node = super.modifyChildNode(childNode);
+    if (node != null) {
+      return node;
+    }
+    Object value = childNode.getValue();
+    if (value instanceof PsiElement) {
+      PsiFile psiFile = ((PsiElement)value).getContainingFile();
+      VirtualFile virtualFile = psiFile.getVirtualFile();
+      if (virtualFile == null) {
+        return null;
+      }
+      if (StudyUtils.isTaskDescriptionFile(virtualFile.getName())) {
+        return null;
+      }
+      if (!CCUtils.isTestsFile(myProject, virtualFile) || !myTask.hasSubtasks()) {
+        return new CCStudentInvisibleFileNode(myProject, psiFile, myViewSettings);
+      }
+
+      Course course = StudyTaskManager.getInstance(myProject).getCourse();
+      if (course == null) {
+        return null;
+      }
+      StudyLanguageManager manager = StudyUtils.getLanguageManager(course);
+      if (manager == null) {
+        return new CCStudentInvisibleFileNode(myProject, psiFile, myViewSettings);
+      }
+      String testFileName = manager.getTestFileName();
+      return isActiveSubtaskTest(virtualFile) ? new CCStudentInvisibleFileNode(myProject, psiFile, myViewSettings, testFileName) : null;
+    }
+    return null;
+  }
+
+  private boolean isActiveSubtaskTest(VirtualFile virtualFile) {
+    if (!myTask.hasSubtasks()) {
+      return true;
+    }
+
+    boolean isSubtaskTestFile = virtualFile.getName().contains(EduNames.SUBTASK_MARKER);
+    if (myTask.getActiveSubtaskIndex() == 0) {
+      return !isSubtaskTestFile;
+    }
+    if (!isSubtaskTestFile) {
+      return false;
+    }
+    String nameWithoutExtension = virtualFile.getNameWithoutExtension();
+    int stepMarkerStart = nameWithoutExtension.indexOf(EduNames.SUBTASK_MARKER);
+    int stepIndex = Integer.valueOf(nameWithoutExtension.substring(EduNames.SUBTASK_MARKER.length() + stepMarkerStart));
+    return stepIndex == myTask.getActiveSubtaskIndex();
+  }
+}
index 0b89eaea50521bab595f57b21c2e36e04c4f175d..5dd486b482358f491fd19ac499f1212004f914cd 100644 (file)
 package com.jetbrains.edu.coursecreator.projectView;
 
 import com.intellij.ide.projectView.ViewSettings;
-import com.intellij.ide.projectView.impl.nodes.PsiFileNode;
 import com.intellij.ide.util.treeView.AbstractTreeNode;
 import com.intellij.openapi.project.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.CourseDirectoryNode;
 import com.jetbrains.edu.learning.projectView.StudyTreeStructureProvider;
 import org.jetbrains.annotations.NotNull;
 
-import java.util.Collection;
-
 public class CCTreeStructureProvider extends StudyTreeStructureProvider {
-  @NotNull
   @Override
-  public Collection<AbstractTreeNode> modify(@NotNull AbstractTreeNode parent,
-                                             @NotNull Collection<AbstractTreeNode> children,
-                                             ViewSettings settings) {
-    if (!needModify(parent)) {
-      return children;
-    }
-    Collection<AbstractTreeNode> modifiedChildren = super.modify(parent, children, settings);
-
-    for (AbstractTreeNode node : children) {
-      Project project = node.getProject();
-      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 (StudyUtils.getTaskFile(project, virtualFile) != null || StudyUtils.isTaskDescriptionFile(virtualFile.getName())) {
-          continue;
-        }
-        PsiFile psiFile = ((PsiFileNode)node).getValue();
-        boolean handled = handleTests(project, virtualFile, psiFile, modifiedChildren, settings);
-        if (!handled) {
-          modifiedChildren.add(new CCStudentInvisibleFileNode(project, psiFile, settings));
-        }
-      }
-    }
-    return modifiedChildren;
-  }
-
-  protected boolean needModify(@NotNull final AbstractTreeNode parent) {
-    Project project = parent.getProject();
-    if (project == null) {
-      return false;
-    }
+  protected boolean shouldModify(@NotNull Project project) {
     return CCUtils.isCourseCreator(project);
   }
 
-  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 (isActiveSubtaskTest(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 isActiveSubtaskTest(Task task, VirtualFile virtualFile) {
-    if (!task.hasSubtasks()) {
-      return true;
-    }
-
-    boolean isSubtaskTestFile = virtualFile.getName().contains(EduNames.SUBTASK_MARKER);
-    if (task.getActiveSubtaskIndex() == 0) {
-      return !isSubtaskTestFile;
-    }
-    if (!isSubtaskTestFile) {
-      return false;
-    }
-    String nameWithoutExtension = virtualFile.getNameWithoutExtension();
-    int stepMarkerStart = nameWithoutExtension.indexOf(EduNames.SUBTASK_MARKER);
-    int stepIndex = Integer.valueOf(nameWithoutExtension.substring(EduNames.SUBTASK_MARKER.length() + stepMarkerStart));
-    return stepIndex == task.getActiveSubtaskIndex();
+  @NotNull
+  @Override
+  protected CourseDirectoryNode createCourseNode(Project project, AbstractTreeNode node, ViewSettings settings, Course course) {
+    return new CCCourseDirectoryNode(project, ((PsiDirectory)node.getValue()), settings, course);
   }
 }
index e926cc6a0d317cb8552f4de9883279b068523d3d..b4c4514472458b5bf982c90949705a0934498655 100644 (file)
@@ -1,30 +1,16 @@
 package com.jetbrains.edu.learning.actions;
 
-import com.intellij.ide.projectView.ProjectView;
 import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.fileEditor.FileEditorManager;
 import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.openapi.wm.ToolWindow;
-import com.intellij.openapi.wm.ToolWindowId;
-import com.intellij.openapi.wm.ToolWindowManager;
-import com.intellij.util.ui.tree.TreeUtil;
 import com.jetbrains.edu.learning.StudyState;
 import com.jetbrains.edu.learning.StudyUtils;
-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.editor.StudyEditor;
 import com.jetbrains.edu.learning.navigation.StudyNavigator;
-import com.jetbrains.edu.learning.statistics.EduUsagesCollector;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import javax.swing.*;
-import javax.swing.tree.TreePath;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
 
 
 abstract public class StudyTaskNavigationAction extends StudyActionWithShortcut {
@@ -42,80 +28,9 @@ abstract public class StudyTaskNavigationAction extends StudyActionWithShortcut
     if (targetTask == null) {
       return;
     }
-    for (VirtualFile file : FileEditorManager.getInstance(project).getOpenFiles()) {
-      FileEditorManager.getInstance(project).closeFile(file);
-    }
-    int nextTaskIndex = targetTask.getIndex();
-    int lessonIndex = targetTask.getLesson().getIndex();
-    Map<String, TaskFile> nextTaskFiles = targetTask.getTaskFiles();
-    VirtualFile projectDir = project.getBaseDir();
-    String lessonDirName = EduNames.LESSON + String.valueOf(lessonIndex);
-    if (projectDir == null) {
-      return;
-    }
-    VirtualFile lessonDir = projectDir.findChild(lessonDirName);
-    if (lessonDir == null) {
-      return;
-    }
-    String taskDirName = EduNames.TASK + String.valueOf(nextTaskIndex);
-    VirtualFile taskDir = lessonDir.findChild(taskDirName);
-    if (taskDir == null) {
-      return;
-    }
-    if (nextTaskFiles.isEmpty()) {
-      ProjectView.getInstance(project).select(taskDir, taskDir, false);
-      return;
-    }
-    EduUsagesCollector.taskNavigation();
-    VirtualFile shouldBeActive = getFileToActivate(project, nextTaskFiles, taskDir);
-
-    updateProjectView(project, shouldBeActive);
 
-    StudyUtils.selectFirstAnswerPlaceholder(StudyUtils.getSelectedStudyEditor(project), project);
-    ToolWindow runToolWindow = ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId.RUN);
-    if (runToolWindow != null) {
-      runToolWindow.hide(null);
-    }
-  }
-
-  public static void updateProjectView(@NotNull Project project, VirtualFile shouldBeActive) {
-    JTree tree = ProjectView.getInstance(project).getCurrentProjectViewPane().getTree();
-    if (shouldBeActive != null) {
-      ProjectView.getInstance(project).selectCB(shouldBeActive, shouldBeActive, false).doWhenDone(() -> {
-        List<TreePath> paths = TreeUtil.collectExpandedPaths(tree);
-        List<TreePath> toCollapse = new ArrayList<>();
-        TreePath selectedPath = tree.getSelectionPath();
-        for (TreePath treePath : paths) {
-          if (treePath.isDescendant(selectedPath)) {
-            continue;
-          }
-          if (toCollapse.isEmpty()) {
-            toCollapse.add(treePath);
-            continue;
-          }
-          for (int i = 0; i < toCollapse.size(); i++) {
-            TreePath path = toCollapse.get(i);
-            if (treePath.isDescendant(path)) {
-              toCollapse.set(i, treePath);
-            }  else {
-              if (!path.isDescendant(treePath)) {
-                toCollapse.add(treePath);
-              }
-            }
-          }
-        }
-        for (TreePath path : toCollapse) {
-          tree.collapsePath(path);
-          tree.fireTreeCollapsed(path);
-        }
-      });
-      FileEditorManager.getInstance(project).openFile(shouldBeActive, true);
-    }
-  }
+    StudyNavigator.navigateToTask(project, targetTask);
 
-  @Nullable
-  protected VirtualFile getFileToActivate(@NotNull Project project, Map<String, TaskFile> nextTaskFiles, VirtualFile taskDir) {
-    return StudyNavigator.getFileToActivate(project, nextTaskFiles, taskDir);
   }
 
   @Override
index 9a420b3dd20e4c1b88754afab53b14af3e542c53..748893afaacfaac99650f10d6341e55c49fd68db 100644 (file)
@@ -6,18 +6,24 @@ import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.fileEditor.FileEditorManager;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.wm.ToolWindow;
+import com.intellij.openapi.wm.ToolWindowId;
+import com.intellij.openapi.wm.ToolWindowManager;
+import com.intellij.util.ui.tree.TreeUtil;
 import com.jetbrains.edu.learning.StudyTaskManager;
 import com.jetbrains.edu.learning.StudyUtils;
 import com.jetbrains.edu.learning.core.EduNames;
 import com.jetbrains.edu.learning.courseFormat.*;
+import com.jetbrains.edu.learning.statistics.EduUsagesCollector;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import javax.swing.*;
+import javax.swing.tree.TreePath;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
-import static com.jetbrains.edu.learning.actions.StudyTaskNavigationAction.updateProjectView;
-
 public class StudyNavigator {
   private StudyNavigator() {
 
@@ -144,33 +150,77 @@ public class StudyNavigator {
     if (task == null) {
       return;
     }
-    ApplicationManager.getApplication().invokeLater(() -> {
-      for (VirtualFile file : FileEditorManager.getInstance(project).getOpenFiles()) {
-        FileEditorManager.getInstance(project).closeFile(file);
-      }
-      int nextTaskIndex = task.getIndex();
-      int lessonIndex = task.getLesson().getIndex();
-      Map<String, TaskFile> nextTaskFiles = task.getTaskFiles();
-      VirtualFile projectDir = project.getBaseDir();
-      String lessonDirName = EduNames.LESSON + String.valueOf(lessonIndex);
-      if (projectDir == null) {
-        return;
-      }
-      VirtualFile lessonDir = projectDir.findChild(lessonDirName);
-      if (lessonDir == null) {
-        return;
-      }
-      String taskDirName = EduNames.TASK + String.valueOf(nextTaskIndex);
-      VirtualFile taskDir = lessonDir.findChild(taskDirName);
-      if (taskDir == null) {
-        return;
-      }
-      if (nextTaskFiles.isEmpty()) {
-        ProjectView.getInstance(project).select(taskDir, taskDir, false);
+    ApplicationManager.getApplication().invokeLater(() -> navigateToTask(project, task));
+  }
+
+  public static void navigateToTask(@NotNull Project project, @NotNull Task task) {
+    for (VirtualFile file : FileEditorManager.getInstance(project).getOpenFiles()) {
+      FileEditorManager.getInstance(project).closeFile(file);
+    }
+    Map<String, TaskFile> nextTaskFiles = task.getTaskFiles();
+    VirtualFile taskDir = task.getTaskDir(project);
+    if (taskDir == null) {
+      return;
+    }
+    VirtualFile srcDir = taskDir.findChild(EduNames.SRC);
+    if (srcDir != null) {
+      taskDir = srcDir;
+    }
+    if (nextTaskFiles.isEmpty()) {
+      ProjectView.getInstance(project).select(taskDir, taskDir, false);
+      return;
+    }
+    for (String name : nextTaskFiles.keySet()) {
+      VirtualFile virtualFile = taskDir.findChild(name);
+      if (virtualFile == null) {
+        continue;
       }
-      VirtualFile toActivate = getFileToActivate(project, nextTaskFiles, taskDir);
+      FileEditorManager.getInstance(project).openFile(virtualFile, true);
+    }
+    EduUsagesCollector.taskNavigation();
+    VirtualFile shouldBeActive = getFileToActivate(project, nextTaskFiles, taskDir);
+
+    updateProjectView(project, shouldBeActive);
 
-      updateProjectView(project, toActivate);
-    });
+    StudyUtils.selectFirstAnswerPlaceholder(StudyUtils.getSelectedStudyEditor(project), project);
+    ToolWindow runToolWindow = ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId.RUN);
+    if (runToolWindow != null) {
+      runToolWindow.hide(null);
+    }
+  }
+
+  private static void updateProjectView(@NotNull Project project, VirtualFile shouldBeActive) {
+    JTree tree = ProjectView.getInstance(project).getCurrentProjectViewPane().getTree();
+    if (shouldBeActive != null) {
+      ProjectView.getInstance(project).selectCB(shouldBeActive, shouldBeActive, false).doWhenDone(() -> {
+        List<TreePath> paths = TreeUtil.collectExpandedPaths(tree);
+        List<TreePath> toCollapse = new ArrayList<>();
+        TreePath selectedPath = tree.getSelectionPath();
+        for (TreePath treePath : paths) {
+          if (treePath.isDescendant(selectedPath)) {
+            continue;
+          }
+          if (toCollapse.isEmpty()) {
+            toCollapse.add(treePath);
+            continue;
+          }
+          for (int i = 0; i < toCollapse.size(); i++) {
+            TreePath path = toCollapse.get(i);
+            if (treePath.isDescendant(path)) {
+              toCollapse.set(i, treePath);
+            }  else {
+              if (!path.isDescendant(treePath)) {
+                toCollapse.add(treePath);
+              }
+            }
+          }
+        }
+        for (TreePath path : toCollapse) {
+          tree.collapsePath(path);
+          tree.fireTreeCollapsed(path);
+        }
+      });
+      FileEditorManager.getInstance(project).openFile(shouldBeActive, true);
+    }
   }
 }
diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/CourseDirectoryNode.java b/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/CourseDirectoryNode.java
new file mode 100644 (file)
index 0000000..18923a0
--- /dev/null
@@ -0,0 +1,55 @@
+package com.jetbrains.edu.learning.projectView;
+
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiDirectory;
+import com.jetbrains.edu.learning.core.EduNames;
+import com.jetbrains.edu.learning.courseFormat.Course;
+import com.jetbrains.edu.learning.courseFormat.Lesson;
+import com.jetbrains.edu.learning.courseFormat.StudyItem;
+import icons.InteractiveLearningIcons;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+
+public class CourseDirectoryNode extends StudyDirectoryNode {
+  @NotNull protected final Project myProject;
+  protected final ViewSettings myViewSettings;
+  private final Course myCourse;
+
+  public CourseDirectoryNode(@NotNull Project project,
+                             PsiDirectory value,
+                             ViewSettings viewSettings,
+                             @NotNull Course course) {
+    super(project, value, viewSettings);
+    myProject = project;
+    myViewSettings = viewSettings;
+    myCourse = course;
+  }
+
+  @Override
+  protected void updateImpl(PresentationData data) {
+    setPresentation(data, myCourse.getName(), InteractiveLearningIcons.Course);
+  }
+
+  @Nullable
+  public AbstractTreeNode modifyChildNode(AbstractTreeNode childNode) {
+    Object value = childNode.getValue();
+    if (value instanceof PsiDirectory) {
+      PsiDirectory directory = (PsiDirectory)value;
+      if (EduNames.SANDBOX_DIR.equals(directory.getName())) {
+        return new SandboxDirectoryNode(myProject, directory, myViewSettings);
+      }
+      Lesson lesson = myCourse.getLesson(directory.getName());
+      return lesson != null ? createChildDirectoryNode(lesson, directory) : null;
+    }
+    return null;
+  }
+
+  @Override
+  public StudyDirectoryNode createChildDirectoryNode(StudyItem item, PsiDirectory directory) {
+    return new LessonDirectoryNode(myProject, directory, myViewSettings, (Lesson)item);
+  }
+}
diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/LessonDirectoryNode.java b/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/LessonDirectoryNode.java
new file mode 100644 (file)
index 0000000..4810bab
--- /dev/null
@@ -0,0 +1,78 @@
+package com.jetbrains.edu.learning.projectView;
+
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiManager;
+import com.intellij.ui.JBColor;
+import com.jetbrains.edu.learning.core.EduNames;
+import com.jetbrains.edu.learning.courseFormat.Lesson;
+import com.jetbrains.edu.learning.courseFormat.StudyItem;
+import com.jetbrains.edu.learning.courseFormat.StudyStatus;
+import com.jetbrains.edu.learning.courseFormat.Task;
+import icons.InteractiveLearningIcons;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+
+public class LessonDirectoryNode extends StudyDirectoryNode {
+  @NotNull protected final Project myProject;
+  protected final ViewSettings myViewSettings;
+  @NotNull protected final Lesson myLesson;
+
+  public LessonDirectoryNode(@NotNull Project project,
+                             PsiDirectory value,
+                             ViewSettings viewSettings,
+                             @NotNull Lesson lesson) {
+    super(project, value, viewSettings);
+    myProject = project;
+    myViewSettings = viewSettings;
+    myLesson = lesson;
+  }
+
+  @Override
+  protected void updateImpl(PresentationData data) {
+    StudyStatus status = myLesson.getStatus();
+    boolean isSolved = status != StudyStatus.Solved;
+    JBColor color = isSolved ? JBColor.BLACK : LIGHT_GREEN;
+    Icon icon = isSolved ? InteractiveLearningIcons.Lesson : InteractiveLearningIcons.LessonCompl;
+    updatePresentation(data, myLesson.getName(), color, icon, null);
+
+  }
+
+  @Override
+  public int getWeight() {
+    return myLesson.getIndex();
+  }
+
+  @Nullable
+  @Override
+  public AbstractTreeNode modifyChildNode(AbstractTreeNode childNode) {
+    Object value = childNode.getValue();
+    if (value instanceof PsiDirectory) {
+      PsiDirectory directory = (PsiDirectory)value;
+      Task task = myLesson.getTask(directory.getName());
+      if (task == null) {
+        return null;
+      }
+      VirtualFile srcDir = directory.getVirtualFile().findChild(EduNames.SRC);
+      if (srcDir != null) {
+        directory = PsiManager.getInstance(myProject).findDirectory(srcDir);
+        if (directory == null) {
+          return null;
+        }
+      }
+      return createChildDirectoryNode(task, directory);
+    }
+    return null;
+  }
+
+  @Override
+  public StudyDirectoryNode createChildDirectoryNode(StudyItem item, PsiDirectory directory) {
+    return new TaskDirectoryNode(myProject, directory, myViewSettings, ((Task)item));
+  }
+}
diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/SandboxDirectoryNode.java b/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/SandboxDirectoryNode.java
new file mode 100644 (file)
index 0000000..fd43760
--- /dev/null
@@ -0,0 +1,41 @@
+package com.jetbrains.edu.learning.projectView;
+
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiDirectory;
+import com.jetbrains.edu.learning.core.EduNames;
+import com.jetbrains.edu.learning.courseFormat.StudyItem;
+import icons.InteractiveLearningIcons;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class SandboxDirectoryNode extends StudyDirectoryNode {
+  public SandboxDirectoryNode(@NotNull Project project,
+                              PsiDirectory value,
+                              ViewSettings viewSettings) {
+    super(project, value, viewSettings);
+  }
+
+  @Nullable
+  @Override
+  public AbstractTreeNode modifyChildNode(AbstractTreeNode childNode) {
+    return childNode;
+  }
+
+  @Override
+  public StudyDirectoryNode createChildDirectoryNode(StudyItem item, PsiDirectory value) {
+    return null;
+  }
+
+  @Override
+  protected void updateImpl(PresentationData data) {
+    setPresentation(data, EduNames.SANDBOX_DIR, InteractiveLearningIcons.Sandbox);
+  }
+
+  @Override
+  public int getWeight() {
+    return Integer.MAX_VALUE;
+  }
+}
index e0dad96eb9d016870a27a372f9f1838120721a39..96ef6c64c501a4134ee460e10af2de042bea0218 100644 (file)
 package com.jetbrains.edu.learning.projectView;
 
 import com.intellij.ide.projectView.PresentationData;
-import com.intellij.ide.projectView.ProjectView;
 import com.intellij.ide.projectView.ViewSettings;
 import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode;
-import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
 import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.psi.PsiDirectory;
-import com.intellij.psi.PsiElement;
 import com.intellij.ui.JBColor;
 import com.intellij.ui.SimpleTextAttributes;
-import com.jetbrains.edu.learning.StudyTaskManager;
-import com.jetbrains.edu.learning.StudyUtils;
-import com.jetbrains.edu.learning.core.EduNames;
-import com.jetbrains.edu.learning.core.EduUtils;
 import com.jetbrains.edu.learning.courseFormat.*;
-import icons.InteractiveLearningIcons;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import javax.swing.*;
 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;
+public abstract class StudyDirectoryNode extends PsiDirectoryNode {
+  protected static final JBColor LIGHT_GREEN = new JBColor(new Color(0, 134, 0), new Color(98, 150, 85));
 
   public StudyDirectoryNode(@NotNull final Project project,
                             PsiDirectory value,
                             ViewSettings viewSettings) {
     super(project, value, viewSettings);
-    myValue = value;
-    myProject = project;
   }
 
-  @Override
-  protected void updateImpl(PresentationData data) {
-    String valueName = myValue.getName();
-    StudyTaskManager studyTaskManager = StudyTaskManager.getInstance(myProject);
-    Course course = studyTaskManager.getCourse();
-    if (course == null) {
-      return;
-    }
-    if (valueName.equals(myProject.getBaseDir().getName())) {
-      data.clearText();
-      data.setIcon(InteractiveLearningIcons.Course);
-      data.addText(course.getName(), new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, JBColor.BLACK));
-    }
-    else if (valueName.contains(EduNames.TASK)) {
-      VirtualFile taskVirtualFile = myValue.getVirtualFile();
-      VirtualFile lessonVirtualFile = taskVirtualFile.getParent();
-      if (lessonVirtualFile != null) {
-        Lesson lesson = course.getLesson(lessonVirtualFile.getName());
-        if (lesson != null) {
-          Task task = lesson.getTask(taskVirtualFile.getName());
-          if (task != null) {
-            setStudyAttributes(task, data, task.getName());
-          }
-        }
-      }
-    }
-    else if (valueName.contains(EduNames.LESSON)) {
-      int lessonIndex = Integer.parseInt(valueName.substring(EduNames.LESSON.length())) - 1;
-      Lesson lesson = course.getLessons().get(lessonIndex);
-      setStudyAttributes(lesson, data, lesson.getName());
-      data.setPresentableText(valueName);
-    }
-    else if (valueName.contains(EduNames.SANDBOX_DIR)) {
-      if (myValue.getParent() != null) {
-        final String parentName = myValue.getParent().getName();
-        if (!parentName.contains(EduNames.SANDBOX_DIR)) {
-          data.setPresentableText(EduNames.SANDBOX_DIR);
-          data.setIcon(InteractiveLearningIcons.Sandbox);
-        }
-      }
-    }
-    data.setPresentableText(valueName);
-  }
-
-  @Override
-  public int getTypeSortWeight(boolean sortByType) {
-    String name = myValue.getName();
-    if (name.contains(EduNames.LESSON) || name.contains(EduNames.TASK)) {
-      String logicalName = name.contains(EduNames.LESSON) ? EduNames.LESSON : EduNames.TASK;
-      return EduUtils.getIndex(name, logicalName) + 1;
-    }
-    return name.contains(EduNames.SANDBOX_DIR) ? 0 : 3;
-  }
-
-  private static void setStudyAttributes(Lesson lesson, PresentationData data, String additionalName) {
-    switch (lesson.getStatus()) {
-      case Unchecked: {
-        updatePresentation(data, additionalName, JBColor.BLACK, InteractiveLearningIcons.Lesson);
-        break;
-      }
-      case Solved: {
-        updatePresentation(data, additionalName, LIGHT_GREEN, InteractiveLearningIcons.LessonCompl);
-        break;
-      }
-      case Failed: {
-        updatePresentation(data, additionalName, JBColor.RED, InteractiveLearningIcons.Lesson);
-      }
-    }
-  }
-
-  protected void setStudyAttributes(Task task, PresentationData data, String name) {
-    String additionalInfo = task.hasSubtasks() ? getSubtaskInfo(task) : null;
-    StudyStatus taskStatus = task.getStatus();
-    switch (taskStatus) {
-      case Unchecked: {
-        updatePresentation(data, name, JBColor.BLACK, InteractiveLearningIcons.Task, additionalInfo);
-        break;
-      }
-      case Solved: {
-        updatePresentation(data, name, LIGHT_GREEN, InteractiveLearningIcons.TaskCompl, additionalInfo);
-        break;
-      }
-      case Failed: {
-        updatePresentation(data, name, JBColor.RED, InteractiveLearningIcons.TaskProbl, additionalInfo);
-      }
-    }
-  }
-
-  protected static void updatePresentation(PresentationData data, String name, JBColor color, Icon icon) {
-    updatePresentation(data, name, color, icon, null);
+  protected void setPresentation(PresentationData data, String name, Icon icon) {
+    data.setPresentableText(name);
+    data.setIcon(icon);
   }
 
   protected static void updatePresentation(PresentationData data, String name, JBColor color, Icon icon, @Nullable String additionalInfo) {
@@ -139,79 +39,13 @@ public class StudyDirectoryNode extends PsiDirectoryNode {
   }
 
   @Override
-  public boolean canNavigate() {
-    return true;
-  }
-
-  @Override
-  public boolean canNavigateToSource() {
-    return true;
-  }
-
-  @Override
-  public void navigate(boolean requestFocus) {
-    final String myValueName = myValue.getName();
-    if (myValueName.contains(EduNames.TASK)) {
-      TaskFile taskFile = null;
-      VirtualFile virtualFile =  null;
-      for (PsiElement child : myValue.getChildren()) {
-        VirtualFile childFile = child.getContainingFile().getVirtualFile();
-        taskFile = StudyUtils.getTaskFile(myProject, childFile);
-        if (taskFile != null) {
-          virtualFile = childFile;
-          break;
-        }
-      }
-      if (taskFile != null) {
-        VirtualFile taskDir = virtualFile.getParent();
-        Task task = taskFile.getTask();
-        for (VirtualFile openFile : FileEditorManager.getInstance(myProject).getOpenFiles()) {
-          FileEditorManager.getInstance(myProject).closeFile(openFile);
-        }
-        VirtualFile child = null;
-        for (Map.Entry<String, TaskFile> entry: task.getTaskFiles().entrySet()) {
-          VirtualFile file = taskDir.findChild(entry.getKey());
-          if (file != null) {
-            FileEditorManager.getInstance(myProject).openFile(file, true);
-          }
-          if (!entry.getValue().getActivePlaceholders().isEmpty()) {
-            child = file;
-          }
-        }
-        if (child != null) {
-          ProjectView.getInstance(myProject).select(child, child, false);
-          FileEditorManager.getInstance(myProject).openFile(child, true);
-        } else {
-          VirtualFile[] children = taskDir.getChildren();
-          if (children.length > 0) {
-            ProjectView.getInstance(myProject).select(children[0], children[0], false);
-          }
-        }
-      }
-    }
-  }
-
-  @Override
-  public boolean expandOnDoubleClick() {
-    final String myValueName = myValue.getName();
-    if (myValueName.contains(EduNames.TASK)) {
-      return false;
-    }
-    return super.expandOnDoubleClick();
-  }
-
-  @Override
   protected boolean hasProblemFileBeneath() {
     return false;
   }
 
-  @Override
-  public String getNavigateActionText(boolean focusEditor) {
-    return null;
-  }
+  @Nullable
+  public abstract AbstractTreeNode modifyChildNode(AbstractTreeNode childNode);
+
+  public abstract StudyDirectoryNode createChildDirectoryNode(StudyItem item, PsiDirectory value);
 
-  private static String getSubtaskInfo(Task task) {
-    int index = task.getActiveSubtaskIndex() + 1;
-    return EduNames.SUBTASK + " " + index + "/" + task.getSubtaskNum();
-  }
 }
index 3330a2e3b28cdeb43de25e411eda2023225315fc..2cdcdc0afaa18c0d7256ae00dfa9d5ff66dc3f58 100644 (file)
@@ -2,15 +2,13 @@ package com.jetbrains.edu.learning.projectView;
 
 import com.intellij.ide.projectView.TreeStructureProvider;
 import com.intellij.ide.projectView.ViewSettings;
-import com.intellij.ide.projectView.impl.nodes.PsiFileNode;
+import com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode;
+import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode;
 import com.intellij.ide.util.treeView.AbstractTreeNode;
 import com.intellij.openapi.project.DumbAware;
 import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.psi.PsiDirectory;
-import com.jetbrains.edu.learning.core.EduNames;
 import com.jetbrains.edu.learning.courseFormat.Course;
-import com.jetbrains.edu.learning.courseFormat.TaskFile;
 import com.jetbrains.edu.learning.StudyTaskManager;
 import com.jetbrains.edu.learning.StudyUtils;
 import org.jetbrains.annotations.NotNull;
@@ -25,78 +23,37 @@ public class StudyTreeStructureProvider implements TreeStructureProvider, DumbAw
   public Collection<AbstractTreeNode> modify(@NotNull AbstractTreeNode parent,
                                              @NotNull Collection<AbstractTreeNode> children,
                                              ViewSettings settings) {
-    if (!needModify(parent)) {
+    Project project = parent.getProject();
+    if (project == null || !shouldModify(project)) {
       return children;
     }
-    Collection<AbstractTreeNode> nodes = new ArrayList<>();
+    Course course = StudyTaskManager.getInstance(project).getCourse();
+    if (course == null) {
+      return children;
+    }
+    Collection<AbstractTreeNode> modifiedNodes = new ArrayList<>();
     for (AbstractTreeNode node : children) {
-      final Project project = node.getProject();
-      if (project != null) {
-        if (node.getValue() instanceof PsiDirectory) {
-          final PsiDirectory nodeValue = (PsiDirectory)node.getValue();
-          final String name = nodeValue.getName();
-          if (!name.contains(EduNames.USER_TESTS) && !name.startsWith(".") && !"lib".equals(name)) {
-            AbstractTreeNode newNode = createStudyDirectoryNode(settings, project, nodeValue);
-            nodes.add(newNode);
-          }
-        }
-        else {
-          if (parent instanceof StudyDirectoryNode && node instanceof PsiFileNode) {
-            final PsiFileNode psiFileNode = (PsiFileNode)node;
-            final VirtualFile virtualFile = psiFileNode.getVirtualFile();
-            if (virtualFile == null) {
-              return nodes;
-            }
-            final TaskFile taskFile = StudyUtils.getTaskFile(project, virtualFile);
-            if (taskFile != null) {
-              nodes.add(node);
-              continue;
-            }
-            final String parentName = parent.getName();
-            if (parentName != null) {
-              if (parentName.equals(EduNames.SANDBOX_DIR)) {
-                nodes.add(node);
-              }
-              if (parentName.startsWith(EduNames.TASK)) {
-                addNonInvisibleFiles(nodes, node, project, virtualFile);
-              }
-            }
-          }
+      if (parent instanceof ProjectViewProjectNode && node instanceof PsiDirectoryNode) {
+        modifiedNodes.add(createCourseNode(project, node, settings, course));
+        continue;
+      }
+      if (parent instanceof StudyDirectoryNode) {
+        AbstractTreeNode modifiedNode = ((StudyDirectoryNode)parent).modifyChildNode(node);
+        if (modifiedNode != null) {
+          modifiedNodes.add(modifiedNode);
         }
       }
     }
-    return nodes;
+    return modifiedNodes;
   }
 
   @NotNull
-  protected AbstractTreeNode createStudyDirectoryNode(ViewSettings settings, Project project, PsiDirectory nodeValue) {
-    return new StudyDirectoryNode(project, nodeValue, settings);
-  }
-
-  private static void addNonInvisibleFiles(@NotNull final Collection<AbstractTreeNode> nodes,
-                                           @NotNull final AbstractTreeNode node,
-                                           @NotNull final Project project,
-                                           @NotNull final VirtualFile virtualFile) {
-    if (!StudyTaskManager.getInstance(project).isInvisibleFile(virtualFile.getPath())) {
-      String fileName = virtualFile.getName();
-      if (!fileName.contains(EduNames.WINDOW_POSTFIX) && !fileName.contains(EduNames.WINDOWS_POSTFIX)
-          && !StudyUtils.isTestsFile(project, fileName) && !StudyUtils.isTaskDescriptionFile(fileName)) {
-        nodes.add(node);
-      }
-    }
+  protected CourseDirectoryNode createCourseNode(Project project, AbstractTreeNode node, ViewSettings settings, Course course) {
+    return new CourseDirectoryNode(project, ((PsiDirectory)node.getValue()), settings, course);
   }
 
-  protected boolean needModify(@NotNull final AbstractTreeNode parent) {
-    final Project project = parent.getProject();
-    if (project == null) {
-      return false;
-    }
-    final StudyTaskManager studyTaskManager = StudyTaskManager.getInstance(project);
-    Course course = studyTaskManager.getCourse();
-    if (course == null) {
-      return false;
-    }
-    return EduNames.STUDY.equals(course.getCourseMode());
+  protected boolean shouldModify(@NotNull final Project project) {
+    return StudyUtils.isStudentProject(project);
   }
 
   @Nullable
diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/TaskDirectoryNode.java b/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/TaskDirectoryNode.java
new file mode 100644 (file)
index 0000000..2f092a2
--- /dev/null
@@ -0,0 +1,97 @@
+package com.jetbrains.edu.learning.projectView;
+
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.ui.JBColor;
+import com.jetbrains.edu.learning.StudyUtils;
+import com.jetbrains.edu.learning.core.EduNames;
+import com.jetbrains.edu.learning.courseFormat.StudyItem;
+import com.jetbrains.edu.learning.courseFormat.StudyStatus;
+import com.jetbrains.edu.learning.courseFormat.Task;
+import com.jetbrains.edu.learning.navigation.StudyNavigator;
+import icons.InteractiveLearningIcons;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+
+public class TaskDirectoryNode extends StudyDirectoryNode {
+  @NotNull protected final Project myProject;
+  protected final ViewSettings myViewSettings;
+  @NotNull protected final Task myTask;
+
+  public TaskDirectoryNode(@NotNull Project project,
+                           PsiDirectory value,
+                           ViewSettings viewSettings,
+                           @NotNull Task task) {
+    super(project, value, viewSettings);
+    myProject = project;
+    myViewSettings = viewSettings;
+    myTask = task;
+  }
+
+  @Override
+  public int getWeight() {
+    return myTask.getIndex();
+  }
+
+  @Override
+  protected void updateImpl(PresentationData data) {
+    StudyStatus status = myTask.getStatus();
+    String subtaskInfo = myTask.hasSubtasks() ? getSubtaskInfo() : null;
+    if (status == StudyStatus.Unchecked) {
+      updatePresentation(data, myTask.getName(), JBColor.BLACK, InteractiveLearningIcons.Task, subtaskInfo);
+      return;
+    }
+    boolean isSolved = status == StudyStatus.Solved;
+    JBColor color = isSolved ? LIGHT_GREEN : JBColor.RED;
+    Icon icon = isSolved ? InteractiveLearningIcons.TaskCompl : InteractiveLearningIcons.TaskProbl;
+    updatePresentation(data, myTask.getName(), color, icon, subtaskInfo);
+  }
+
+  private String getSubtaskInfo() {
+    int index = myTask.getActiveSubtaskIndex() + 1;
+    return EduNames.SUBTASK + " " + index + "/" + myTask.getSubtaskNum();
+  }
+
+  @Override
+  public boolean expandOnDoubleClick() {
+    return false;
+  }
+
+  @Override
+  public boolean canNavigate() {
+    return true;
+  }
+
+  @Override
+  public void navigate(boolean requestFocus) {
+    StudyNavigator.navigateToTask(myProject, myTask);
+  }
+
+  @Nullable
+  @Override
+  public AbstractTreeNode modifyChildNode(AbstractTreeNode childNode) {
+    Object value = childNode.getValue();
+    if (value instanceof PsiElement) {
+      PsiFile psiFile = ((PsiElement) value).getContainingFile();
+      VirtualFile virtualFile = psiFile.getVirtualFile();
+      if (virtualFile == null) {
+        return null;
+      }
+      return StudyUtils.getTaskFile(myProject, virtualFile) != null ? childNode : null;
+    }
+    return null;
+  }
+
+  @Override
+  public StudyDirectoryNode createChildDirectoryNode(StudyItem item, PsiDirectory value) {
+    return null;
+  }
+}