compute initial state of placeholder correctly
[idea/community.git] / python / educational-core / course-creator / src / com / jetbrains / edu / coursecreator / CCUtils.java
1 package com.jetbrains.edu.coursecreator;
2
3 import com.google.common.base.Predicate;
4 import com.google.common.collect.Collections2;
5 import com.intellij.ide.projectView.actions.MarkRootActionBase;
6 import com.intellij.lang.Language;
7 import com.intellij.openapi.application.ApplicationManager;
8 import com.intellij.openapi.diagnostic.Logger;
9 import com.intellij.openapi.editor.Document;
10 import com.intellij.openapi.fileEditor.FileDocumentManager;
11 import com.intellij.openapi.module.Module;
12 import com.intellij.openapi.project.DumbModePermission;
13 import com.intellij.openapi.project.DumbService;
14 import com.intellij.openapi.project.Project;
15 import com.intellij.openapi.roots.ContentEntry;
16 import com.intellij.openapi.roots.ModifiableRootModel;
17 import com.intellij.openapi.roots.ModuleRootManager;
18 import com.intellij.openapi.util.Ref;
19 import com.intellij.openapi.util.io.FileUtil;
20 import com.intellij.openapi.vfs.VirtualFile;
21 import com.intellij.psi.PsiDirectory;
22 import com.intellij.util.DocumentUtil;
23 import com.intellij.util.Function;
24 import com.jetbrains.edu.learning.StudyTaskManager;
25 import com.jetbrains.edu.learning.StudyUtils;
26 import com.jetbrains.edu.learning.core.EduUtils;
27 import com.jetbrains.edu.learning.courseFormat.*;
28 import org.jetbrains.annotations.NotNull;
29 import org.jetbrains.annotations.Nullable;
30
31 import java.io.File;
32 import java.io.IOException;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.Collections;
36 import java.util.Map;
37
38 public class CCUtils {
39   public static final String ANSWER_EXTENSION_DOTTED = ".answer.";
40   private static final Logger LOG = Logger.getInstance(CCUtils.class);
41   public static final String GENERATED_FILES_FOLDER = ".coursecreator";
42   public static final String COURSE_MODE = "Course Creator";
43
44   @Nullable
45   public static CCLanguageManager getStudyLanguageManager(@NotNull final Course course) {
46     Language language = Language.findLanguageByID(course.getLanguage());
47     return language == null ? null : CCLanguageManager.INSTANCE.forLanguage(language);
48   }
49
50   /**
51    * This method decreases index and updates directory names of
52    * all tasks/lessons that have higher index than specified object
53    *
54    * @param dirs         directories that are used to get tasks/lessons
55    * @param getStudyItem function that is used to get task/lesson from VirtualFile. This function can return null
56    * @param threshold    index is used as threshold
57    * @param prefix       task or lesson directory name prefix
58    */
59   public static void updateHigherElements(VirtualFile[] dirs,
60                                           @NotNull final Function<VirtualFile, ? extends StudyItem> getStudyItem,
61                                           final int threshold,
62                                           final String prefix,
63                                           final int delta) {
64     ArrayList<VirtualFile> dirsToRename = new ArrayList<VirtualFile>
65       (Collections2.filter(Arrays.asList(dirs), new Predicate<VirtualFile>() {
66         @Override
67         public boolean apply(VirtualFile dir) {
68           final StudyItem item = getStudyItem.fun(dir);
69           if (item == null) {
70             return false;
71           }
72           int index = item.getIndex();
73           return index > threshold;
74         }
75       }));
76     Collections.sort(dirsToRename, (o1, o2) -> {
77       StudyItem item1 = getStudyItem.fun(o1);
78       StudyItem item2 = getStudyItem.fun(o2);
79       //if we delete some dir we should start increasing numbers in dir names from the end
80       return (-delta) * EduUtils.INDEX_COMPARATOR.compare(item1, item2);
81     });
82
83     for (final VirtualFile dir : dirsToRename) {
84       final StudyItem item = getStudyItem.fun(dir);
85       final int newIndex = item.getIndex() + delta;
86       item.setIndex(newIndex);
87       ApplicationManager.getApplication().runWriteAction(new Runnable() {
88         @Override
89         public void run() {
90           try {
91             dir.rename(this, prefix + newIndex);
92           }
93           catch (IOException e) {
94             LOG.error(e);
95           }
96         }
97       });
98     }
99   }
100
101   public static boolean isLessonDir(PsiDirectory sourceDirectory) {
102     if (sourceDirectory == null) {
103       return false;
104     }
105     Project project = sourceDirectory.getProject();
106     Course course = StudyTaskManager.getInstance(project).getCourse();
107     if (course != null && isCourseCreator(project) && course.getLesson(sourceDirectory.getName()) != null) {
108       return true;
109     }
110     return false;
111   }
112
113
114   public static VirtualFile getGeneratedFilesFolder(@NotNull Project project, @NotNull Module module) {
115     VirtualFile baseDir = project.getBaseDir();
116     VirtualFile folder = baseDir.findChild(GENERATED_FILES_FOLDER);
117     if (folder != null) {
118       return folder;
119     }
120     final Ref<VirtualFile> generatedRoot = new Ref<>();
121     DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, new Runnable() {
122       @Override
123       public void run() {
124         ApplicationManager.getApplication().runWriteAction(new Runnable() {
125           @Override
126           public void run() {
127             try {
128               generatedRoot.set(baseDir.createChildDirectory(this, GENERATED_FILES_FOLDER));
129               final ModifiableRootModel model = ModuleRootManager.getInstance(module).getModifiableModel();
130               ContentEntry entry = MarkRootActionBase.findContentEntry(model, generatedRoot.get());
131               if (entry == null) {
132                 LOG.info("Failed to find contentEntry for archive folder");
133                 return;
134               }
135               entry.addExcludeFolder(generatedRoot.get());
136               model.commit();
137               module.getProject().save();
138             }
139             catch (IOException e) {
140               LOG.info("Failed to create folder for generated files", e);
141             }
142           }
143         });
144       }
145     });
146     return generatedRoot.get();
147   }
148
149   @Nullable
150   public static VirtualFile generateFolder(@NotNull Project project, @NotNull Module module, String name) {
151     VirtualFile generatedRoot = getGeneratedFilesFolder(project, module);
152     if (generatedRoot == null) {
153       return null;
154     }
155
156     final Ref<VirtualFile> folder = new Ref<>(generatedRoot.findChild(name));
157     //need to delete old folder
158     ApplicationManager.getApplication().runWriteAction(() -> {
159       try {
160         if (folder.get() != null) {
161           folder.get().delete(null);
162         }
163         folder.set(generatedRoot.createChildDirectory(null, name));
164       }
165       catch (IOException e) {
166         LOG.info("Failed to generate folder " + name, e);
167       }
168     });
169     return folder.get();
170   }
171
172   public static boolean isCourseCreator(@NotNull Project project) {
173     Course course = StudyTaskManager.getInstance(project).getCourse();
174     if (course == null) {
175       return false;
176     }
177
178     return COURSE_MODE.equals(course.getCourseMode());
179   }
180
181   public static boolean isTestsFile(@NotNull Project project, @NotNull VirtualFile file) {
182     Course course = StudyTaskManager.getInstance(project).getCourse();
183     if (course == null) {
184       return false;
185     }
186     CCLanguageManager manager = getStudyLanguageManager(course);
187     if (manager == null) {
188       return false;
189     }
190     return manager.isTestFile(file);
191   }
192
193   public static void createResourceFile(VirtualFile createdFile, Course course, VirtualFile taskVF) {
194     VirtualFile lessonVF = taskVF.getParent();
195     if (lessonVF == null) {
196       return;
197     }
198
199     String taskResourcesPath = FileUtil.join(course.getCourseDirectory(), lessonVF.getName(), taskVF.getName());
200     File taskResourceFile = new File(taskResourcesPath);
201     if (!taskResourceFile.exists()) {
202       if (!taskResourceFile.mkdirs()) {
203         LOG.info("Failed to create resources for task " + taskResourcesPath);
204       }
205     }
206     try {
207       File toFile = new File(taskResourceFile, createdFile.getName());
208       FileUtil.copy(new File(createdFile.getPath()), toFile);
209     }
210     catch (IOException e) {
211       LOG.info("Failed to copy created task file to resources " + createdFile.getPath());
212     }
213   }
214
215
216   public static void createResources(Project project, Task task, VirtualFile taskDir) {
217     Map<String, TaskFile> files = task.getTaskFiles();
218     for (Map.Entry<String, TaskFile> entry : files.entrySet()) {
219       String name = entry.getKey();
220       VirtualFile child = taskDir.findChild(name);
221       if (child == null) {
222         continue;
223       }
224       Document patternDocument = StudyUtils.getPatternDocument(entry.getValue(), name);
225       Document document = FileDocumentManager.getInstance().getDocument(child);
226       if (document == null || patternDocument == null) {
227         return;
228       }
229       DocumentUtil.writeInRunUndoTransparentAction(() -> {
230         patternDocument.replaceString(0, patternDocument.getTextLength(), document.getCharsSequence());
231         FileDocumentManager.getInstance().saveDocument(patternDocument);
232       });
233       TaskFile target = new TaskFile();
234       TaskFile.copy(entry.getValue(), target);
235       for (AnswerPlaceholder placeholder : target.getAnswerPlaceholders()) {
236         placeholder.setUseLength(false);
237       }
238       EduUtils.createStudentDocument(project, target, child, patternDocument);
239     }
240   }
241 }