separate switch step and student file creation
[idea/community.git] / python / educational-core / student / src / com / jetbrains / edu / learning / core / EduUtils.java
1 package com.jetbrains.edu.learning.core;
2
3 import com.intellij.ide.SaveAndSyncHandler;
4 import com.intellij.openapi.actionSystem.AnActionEvent;
5 import com.intellij.openapi.actionSystem.Presentation;
6 import com.intellij.openapi.application.ApplicationManager;
7 import com.intellij.openapi.application.Result;
8 import com.intellij.openapi.command.CommandProcessor;
9 import com.intellij.openapi.command.UndoConfirmationPolicy;
10 import com.intellij.openapi.command.WriteCommandAction;
11 import com.intellij.openapi.command.undo.UndoManager;
12 import com.intellij.openapi.command.undo.UndoableAction;
13 import com.intellij.openapi.diagnostic.Logger;
14 import com.intellij.openapi.editor.Document;
15 import com.intellij.openapi.fileEditor.FileDocumentManager;
16 import com.intellij.openapi.project.Project;
17 import com.intellij.openapi.util.Pair;
18 import com.intellij.openapi.util.TextRange;
19 import com.intellij.openapi.vfs.VfsUtilCore;
20 import com.intellij.openapi.vfs.VirtualFile;
21 import com.intellij.openapi.vfs.VirtualFileManager;
22 import com.intellij.psi.PsiDirectory;
23 import com.intellij.util.containers.ContainerUtil;
24 import com.jetbrains.edu.learning.StudyUtils;
25 import com.jetbrains.edu.learning.courseFormat.*;
26 import org.jetbrains.annotations.NonNls;
27 import org.jetbrains.annotations.NotNull;
28 import org.jetbrains.annotations.Nullable;
29
30 import javax.imageio.ImageIO;
31 import java.io.FileOutputStream;
32 import java.io.IOException;
33 import java.io.PrintWriter;
34 import java.util.*;
35
36 public class EduUtils {
37   private EduUtils() {
38   }
39
40   private static final Logger LOG = Logger.getInstance(EduUtils.class.getName());
41
42   public static final Comparator<StudyItem> INDEX_COMPARATOR = (o1, o2) -> o1.getIndex() - o2.getIndex();
43
44   public static void enableAction(@NotNull final AnActionEvent event, boolean isEnable) {
45     final Presentation presentation = event.getPresentation();
46     presentation.setVisible(isEnable);
47     presentation.setEnabled(isEnable);
48   }
49
50   /**
51    * Gets number index in directory names like "task1", "lesson2"
52    *
53    * @param fullName    full name of directory
54    * @param logicalName part of name without index
55    * @return index of object
56    */
57   public static int getIndex(@NotNull final String fullName, @NotNull final String logicalName) {
58     if (!fullName.startsWith(logicalName)) {
59       return -1;
60     }
61     try {
62       return Integer.parseInt(fullName.substring(logicalName.length())) - 1;
63     }
64     catch (NumberFormatException e) {
65       return -1;
66     }
67   }
68
69   public static boolean indexIsValid(int index, Collection collection) {
70     int size = collection.size();
71     return index >= 0 && index < size;
72   }
73
74   @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
75   @Nullable
76   public static VirtualFile flushWindows(@NotNull final TaskFile taskFile, @NotNull final VirtualFile file) {
77     final VirtualFile taskDir = file.getParent();
78     VirtualFile fileWindows = null;
79     final Document document = FileDocumentManager.getInstance().getDocument(file);
80     if (document == null) {
81       LOG.debug("Couldn't flush windows");
82       return null;
83     }
84     if (taskDir != null) {
85       final String name = file.getNameWithoutExtension() + EduNames.WINDOWS_POSTFIX;
86       deleteWindowsFile(taskDir, name);
87       PrintWriter printWriter = null;
88       try {
89         fileWindows = taskDir.createChildData(taskFile, name);
90         printWriter = new PrintWriter(new FileOutputStream(fileWindows.getPath()));
91         for (AnswerPlaceholder answerPlaceholder : taskFile.getActivePlaceholders()) {
92           int length = answerPlaceholder.getRealLength();
93           int start = answerPlaceholder.getOffset();
94           final String windowDescription = document.getText(new TextRange(start, start + length));
95           printWriter.println("#educational_plugin_window = " + windowDescription);
96         }
97         ApplicationManager.getApplication().runWriteAction(() -> FileDocumentManager.getInstance().saveDocument(document));
98       }
99       catch (IOException e) {
100         LOG.error(e);
101       }
102       finally {
103         if (printWriter != null) {
104           printWriter.close();
105         }
106         synchronize();
107       }
108     }
109     return fileWindows;
110   }
111
112   public static void synchronize() {
113     FileDocumentManager.getInstance().saveAllDocuments();
114     SaveAndSyncHandler.getInstance().refreshOpenFiles();
115     VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
116   }
117
118
119   public static VirtualFile copyFile(Object requestor, VirtualFile toDir, VirtualFile file) {
120     Document document = FileDocumentManager.getInstance().getDocument(file);
121     if (document != null) {
122       FileDocumentManager.getInstance().saveDocument(document);
123     }
124     String name = file.getName();
125     try {
126       VirtualFile userFile = toDir.findChild(name);
127       if (userFile != null) {
128         userFile.delete(requestor);
129       }
130       return VfsUtilCore.copyFile(requestor, file, toDir);
131     }
132     catch (IOException e) {
133       LOG.info("Failed to create file " + name + "  in folder " + toDir.getPath(), e);
134     }
135     return null;
136   }
137
138   @Nullable
139   public static Pair<VirtualFile, TaskFile> createStudentFile(Object requestor,
140                                                               Project project,
141                                                               VirtualFile answerFile,
142                                                               VirtualFile parentDir,
143                                                               @Nullable Task task,
144                                                               int toSubtaskIndex) {
145
146     VirtualFile studentFile = copyFile(requestor, parentDir, answerFile);
147     if (studentFile == null) {
148       return null;
149     }
150     Document studentDocument = FileDocumentManager.getInstance().getDocument(studentFile);
151     if (studentDocument == null) {
152       return null;
153     }
154     if (task == null) {
155       task = StudyUtils.getTaskForFile(project, answerFile);
156       if (task == null) {
157         return null;
158       }
159       task = task.copy();
160     }
161     TaskFile taskFile = task.getTaskFile(answerFile.getName());
162     if (taskFile == null) {
163       return null;
164     }
165     EduDocumentListener listener = new EduDocumentListener(taskFile, false);
166     studentDocument.addDocumentListener(listener);
167     taskFile.setTrackLengths(false);
168     for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) {
169       int fromSubtask = task.getActiveSubtaskIndex();
170       placeholder.switchSubtask(project, studentDocument, fromSubtask, toSubtaskIndex);
171     }
172     for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) {
173       replaceWithTaskText(project, studentDocument, placeholder, toSubtaskIndex);
174     }
175     taskFile.setTrackChanges(true);
176     studentDocument.removeDocumentListener(listener);
177     return Pair.create(studentFile, taskFile);
178   }
179
180   private static void replaceWithTaskText(Project project, Document studentDocument, AnswerPlaceholder placeholder, int toSubtaskIndex) {
181     AnswerPlaceholderSubtaskInfo info = placeholder.getSubtaskInfos().get(toSubtaskIndex);
182     if (info == null) {
183       return;
184     }
185     String replacementText;
186     if (Collections.min(placeholder.getSubtaskInfos().keySet()) == toSubtaskIndex) {
187       replacementText = info.getPlaceholderText();
188     }
189     else {
190       Integer max = Collections.max(ContainerUtil.filter(placeholder.getSubtaskInfos().keySet(), i -> i < toSubtaskIndex));
191       replacementText = placeholder.getSubtaskInfos().get(max).getPossibleAnswer();
192     }
193     replaceAnswerPlaceholder(project, studentDocument, placeholder, placeholder.getVisibleLength(toSubtaskIndex), replacementText);
194   }
195
196   public static void replaceAnswerPlaceholder(@NotNull final Project project,
197                                               @NotNull final Document document,
198                                               @NotNull final AnswerPlaceholder answerPlaceholder,
199                                               int length,
200                                               String replacementText) {
201     final int offset = answerPlaceholder.getOffset();
202     CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> {
203       document.replaceString(offset, offset + length, replacementText);
204       FileDocumentManager.getInstance().saveDocument(document);
205     }), "Replace Answer Placeholders", "Replace Answer Placeholders");
206   }
207
208   public static void deleteWindowDescriptions(@NotNull final Task task, @NotNull final VirtualFile taskDir) {
209     for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
210       String name = entry.getKey();
211       VirtualFile virtualFile = taskDir.findChild(name);
212       if (virtualFile == null) {
213         continue;
214       }
215       String windowsFileName = virtualFile.getNameWithoutExtension() + EduNames.WINDOWS_POSTFIX;
216       deleteWindowsFile(taskDir, windowsFileName);
217     }
218   }
219
220   private static void deleteWindowsFile(@NotNull final VirtualFile taskDir, @NotNull final String name) {
221     final VirtualFile fileWindows = taskDir.findChild(name);
222     if (fileWindows != null && fileWindows.exists()) {
223       ApplicationManager.getApplication().runWriteAction(() -> {
224         try {
225           fileWindows.delete(taskDir);
226         }
227         catch (IOException e) {
228           LOG.warn("Tried to delete non existed _windows file");
229         }
230       });
231     }
232   }
233
234   @Nullable
235   public static Task getTask(@NotNull final PsiDirectory directory, @NotNull final Course course) {
236     PsiDirectory lessonDir = directory.getParent();
237     if (lessonDir == null) {
238       return null;
239     }
240     Lesson lesson = course.getLesson(lessonDir.getName());
241     if (lesson == null) {
242       return null;
243     }
244     return lesson.getTask(directory.getName());
245   }
246
247   public static boolean isImage(String fileName) {
248     final String[] readerFormatNames = ImageIO.getReaderFormatNames();
249     for (@NonNls String format : readerFormatNames) {
250       final String ext = format.toLowerCase();
251       if (fileName.endsWith(ext)) {
252         return true;
253       }
254     }
255     return false;
256   }
257
258   public static void runUndoableAction(Project project, String name, UndoableAction action) {
259     runUndoableAction(project, name, action, UndoConfirmationPolicy.DO_NOT_REQUEST_CONFIRMATION);
260   }
261
262   public static void runUndoableAction(Project project, String name, UndoableAction action, UndoConfirmationPolicy confirmationPolicy) {
263     new WriteCommandAction(project, name) {
264       protected void run(@NotNull final Result result) throws Throwable {
265         action.redo();
266         UndoManager.getInstance(project).undoableActionPerformed(action);
267       }
268
269       @Override
270       protected UndoConfirmationPolicy getUndoConfirmationPolicy() {
271         return confirmationPolicy;
272       }
273     }.execute();
274   }
275 }