remove code related to subtasks
[idea/community.git] / python / educational-core / student / src / com / jetbrains / edu / learning / StudyUtils.java
1 package com.jetbrains.edu.learning;
2
3 import com.intellij.execution.RunContentExecutor;
4 import com.intellij.execution.configurations.GeneralCommandLine;
5 import com.intellij.execution.process.ProcessHandler;
6 import com.intellij.lang.Language;
7 import com.intellij.openapi.actionSystem.AnActionEvent;
8 import com.intellij.openapi.actionSystem.CommonDataKeys;
9 import com.intellij.openapi.actionSystem.DataContext;
10 import com.intellij.openapi.actionSystem.Presentation;
11 import com.intellij.openapi.application.ApplicationManager;
12 import com.intellij.openapi.diagnostic.Logger;
13 import com.intellij.openapi.editor.Document;
14 import com.intellij.openapi.editor.Editor;
15 import com.intellij.openapi.editor.RangeMarker;
16 import com.intellij.openapi.editor.actionSystem.EditorActionManager;
17 import com.intellij.openapi.editor.colors.EditorColors;
18 import com.intellij.openapi.editor.colors.EditorColorsManager;
19 import com.intellij.openapi.editor.impl.DocumentImpl;
20 import com.intellij.openapi.fileEditor.FileDocumentManager;
21 import com.intellij.openapi.fileEditor.FileEditor;
22 import com.intellij.openapi.fileEditor.FileEditorManager;
23 import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
24 import com.intellij.openapi.progress.ProgressManager;
25 import com.intellij.openapi.project.Project;
26 import com.intellij.openapi.project.ProjectManager;
27 import com.intellij.openapi.projectRoots.Sdk;
28 import com.intellij.openapi.ui.MessageType;
29 import com.intellij.openapi.ui.popup.Balloon;
30 import com.intellij.openapi.ui.popup.JBPopupFactory;
31 import com.intellij.openapi.util.Disposer;
32 import com.intellij.openapi.util.io.FileUtil;
33 import com.intellij.openapi.util.text.StringUtil;
34 import com.intellij.openapi.vfs.LocalFileSystem;
35 import com.intellij.openapi.vfs.VfsUtil;
36 import com.intellij.openapi.vfs.VirtualFile;
37 import com.intellij.openapi.wm.ToolWindow;
38 import com.intellij.openapi.wm.ToolWindowManager;
39 import com.intellij.psi.PsiDirectory;
40 import com.intellij.psi.PsiElement;
41 import com.intellij.psi.PsiFile;
42 import com.intellij.ui.JBColor;
43 import com.intellij.ui.awt.RelativePoint;
44 import com.intellij.ui.content.Content;
45 import com.intellij.util.ObjectUtils;
46 import com.intellij.util.TimeoutUtil;
47 import com.intellij.util.containers.ContainerUtil;
48 import com.intellij.util.text.MarkdownUtil;
49 import com.intellij.util.ui.UIUtil;
50 import com.jetbrains.edu.learning.checker.StudyExecutor;
51 import com.jetbrains.edu.learning.checker.StudyTestRunner;
52 import com.jetbrains.edu.learning.core.EduAnswerPlaceholderDeleteHandler;
53 import com.jetbrains.edu.learning.core.EduAnswerPlaceholderPainter;
54 import com.jetbrains.edu.learning.core.EduNames;
55 import com.jetbrains.edu.learning.core.EduUtils;
56 import com.jetbrains.edu.learning.courseFormat.*;
57 import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
58 import com.jetbrains.edu.learning.editor.StudyEditor;
59 import com.jetbrains.edu.learning.ui.StudyToolWindow;
60 import com.jetbrains.edu.learning.ui.StudyToolWindowFactory;
61 import com.petebevin.markdown.MarkdownProcessor;
62 import org.jetbrains.annotations.NotNull;
63 import org.jetbrains.annotations.Nullable;
64
65 import javax.swing.*;
66 import java.awt.*;
67 import java.io.*;
68 import java.util.*;
69 import java.util.List;
70 import java.util.concurrent.Callable;
71 import java.util.concurrent.ExecutionException;
72 import java.util.concurrent.Future;
73
74 public class StudyUtils {
75   private StudyUtils() {
76   }
77
78   private static final Logger LOG = Logger.getInstance(StudyUtils.class.getName());
79   private static final String EMPTY_TASK_TEXT = "Please, open any task to see task description";
80   private static final String ourPrefix = "<html><head><script type=\"text/x-mathjax-config\">\n" +
81                                           "            MathJax.Hub.Config({\n" +
82                                           "                tex2jax: {\n" +
83                                           "                    inlineMath: [ ['$','$'], [\"\\\\(\",\"\\\\)\"] ],\n" +
84                                           "                    displayMath: [ ['$$','$$'], [\"\\\\[\",\"\\\\]\"] ],\n" +
85                                           "                    processEscapes: true,\n" +
86                                           "                    processEnvironments: true\n" +
87                                           "                },\n" +
88                                           "                displayAlign: 'center',\n" +
89                                           "                \"HTML-CSS\": {\n" +
90                                           "                    styles: {'#mydiv': {\"font-size\": %s}},\n" +
91                                           "                    preferredFont: null,\n" +
92                                           "                    linebreaks: { automatic: true }\n" +
93                                           "                }\n" +
94                                           "            });\n" +
95                                           "</script><script type=\"text/javascript\"\n" +
96                                           " src=\"http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML-full\">\n" +
97                                           " </script></head><body><div id=\"mydiv\">";
98
99   private static final String ourPostfix = "</div></body></html>";
100
101   public static void closeSilently(@Nullable final Closeable stream) {
102     if (stream != null) {
103       try {
104         stream.close();
105       }
106       catch (IOException e) {
107         // close silently
108       }
109     }
110   }
111
112   public static boolean isZip(String fileName) {
113     return fileName.contains(".zip");
114   }
115
116   @Nullable
117   public static <T> T getFirst(@NotNull final Iterable<T> container) {
118     Iterator<T> iterator = container.iterator();
119     if (!iterator.hasNext()) {
120       return null;
121     }
122     return iterator.next();
123   }
124
125   public static boolean indexIsValid(int index, @NotNull final Collection collection) {
126     int size = collection.size();
127     return index >= 0 && index < size;
128   }
129
130   @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
131   @Nullable
132   public static String getFileText(@Nullable final String parentDir, @NotNull final String fileName, boolean wrapHTML,
133                                    @NotNull final String encoding) {
134     final File inputFile = parentDir != null ? new File(parentDir, fileName) : new File(fileName);
135     if (!inputFile.exists()) return null;
136     final StringBuilder taskText = new StringBuilder();
137     BufferedReader reader = null;
138     try {
139       reader = new BufferedReader(new InputStreamReader(new FileInputStream(inputFile), encoding));
140       String line;
141       while ((line = reader.readLine()) != null) {
142         taskText.append(line).append("\n");
143         if (wrapHTML) {
144           taskText.append("<br>");
145         }
146       }
147       return wrapHTML ? UIUtil.toHtml(taskText.toString()) : taskText.toString();
148     }
149     catch (IOException e) {
150       LOG.info("Failed to get file text from file " + fileName, e);
151     }
152     finally {
153       closeSilently(reader);
154     }
155     return null;
156   }
157
158   public static void updateAction(@NotNull final AnActionEvent e) {
159     final Presentation presentation = e.getPresentation();
160     presentation.setEnabled(false);
161     final Project project = e.getProject();
162     if (project != null) {
163       final StudyEditor studyEditor = getSelectedStudyEditor(project);
164       if (studyEditor != null) {
165         presentation.setEnabledAndVisible(true);
166       }
167     }
168   }
169
170   public static void updateToolWindows(@NotNull final Project project) {
171     final StudyToolWindow studyToolWindow = getStudyToolWindow(project);
172     if (studyToolWindow != null) {
173       String taskText = getTaskText(project);
174       if (taskText != null) {
175         studyToolWindow.setTaskText(taskText, null, project);
176       }
177       else {
178         LOG.warn("Task text is null");
179       }
180       studyToolWindow.updateCourseProgress(project);
181     }
182   }
183
184   public static void initToolWindows(@NotNull final Project project) {
185     final ToolWindowManager windowManager = ToolWindowManager.getInstance(project);
186     windowManager.getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW).getContentManager().removeAllContents(false);
187     StudyToolWindowFactory factory = new StudyToolWindowFactory();
188     factory.createToolWindowContent(project, windowManager.getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW));
189
190   }
191
192   @Nullable
193   public static StudyToolWindow getStudyToolWindow(@NotNull final Project project) {
194     ToolWindow toolWindow = ToolWindowManager.getInstance(project).getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW);
195     if (toolWindow != null) {
196       Content[] contents = toolWindow.getContentManager().getContents();
197       for (Content content: contents) {
198         JComponent component = content.getComponent();
199         if (component != null && component instanceof StudyToolWindow) {
200           return (StudyToolWindow)component;
201         }
202       }
203     }
204     return null;
205   }
206
207   public static void deleteFile(@NotNull final VirtualFile file) {
208     try {
209       file.delete(StudyUtils.class);
210     }
211     catch (IOException e) {
212       LOG.error(e);
213     }
214   }
215
216   public static File copyResourceFile(@NotNull final String sourceName, @NotNull final String copyName, @NotNull final Project project,
217                                       @NotNull final Task task)
218     throws IOException {
219     final StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
220     final Course course = taskManager.getCourse();
221     int taskNum = task.getIndex();
222     int lessonNum = task.getLesson().getIndex();
223     assert course != null;
224     final String pathToResource = FileUtil.join(course.getCourseDirectory(), EduNames.LESSON + lessonNum, EduNames.TASK + taskNum);
225     final File resourceFile = new File(pathToResource, copyName);
226     FileUtil.copy(new File(pathToResource, sourceName), resourceFile);
227     return resourceFile;
228   }
229
230   @Nullable
231   public static Sdk findSdk(@NotNull final Task task, @NotNull final Project project) {
232     final Language language = task.getLesson().getCourse().getLanguageById();
233     return StudyExecutor.INSTANCE.forLanguage(language).findSdk(project);
234   }
235
236   @NotNull
237   public static StudyTestRunner getTestRunner(@NotNull final Task task, @NotNull final VirtualFile taskDir) {
238     final Language language = task.getLesson().getCourse().getLanguageById();
239     return StudyExecutor.INSTANCE.forLanguage(language).getTestRunner(task, taskDir);
240   }
241
242   public static RunContentExecutor getExecutor(@NotNull final Project project, @NotNull final Task currentTask,
243                                                @NotNull final ProcessHandler handler) {
244     final Language language = currentTask.getLesson().getCourse().getLanguageById();
245     return StudyExecutor.INSTANCE.forLanguage(language).getExecutor(project, handler);
246   }
247
248   public static void setCommandLineParameters(@NotNull final GeneralCommandLine cmd,
249                                               @NotNull final Project project,
250                                               @NotNull final String filePath,
251                                               @NotNull final String sdkPath,
252                                               @NotNull final Task currentTask) {
253     final Language language = currentTask.getLesson().getCourse().getLanguageById();
254     StudyExecutor.INSTANCE.forLanguage(language).setCommandLineParameters(cmd, project, filePath, sdkPath, currentTask);
255   }
256
257   public static void showNoSdkNotification(@NotNull final Task currentTask, @NotNull final Project project) {
258     final Language language = currentTask.getLesson().getCourse().getLanguageById();
259     StudyExecutor.INSTANCE.forLanguage(language).showNoSdkNotification(project);
260   }
261
262
263   /**
264    * shows pop up in the center of "check task" button in study editor
265    */
266   public static void showCheckPopUp(@NotNull final Project project, @NotNull final Balloon balloon) {
267     final StudyEditor studyEditor = getSelectedStudyEditor(project);
268     assert studyEditor != null;
269
270     balloon.show(computeLocation(studyEditor.getEditor()), Balloon.Position.above);
271     Disposer.register(project, balloon);
272   }
273
274   public static RelativePoint computeLocation(Editor editor){
275
276     final Rectangle visibleRect = editor.getComponent().getVisibleRect();
277     Point point = new Point(visibleRect.x + visibleRect.width + 10,
278                             visibleRect.y + 10);
279     return new RelativePoint(editor.getComponent(), point);
280   }
281
282
283   /**
284    * returns language manager which contains all the information about language specific file names
285    */
286   @Nullable
287   public static StudyLanguageManager getLanguageManager(@NotNull final Course course) {
288     Language language = course.getLanguageById();
289     return language == null ? null : StudyLanguageManager.INSTANCE.forLanguage(language);
290   }
291
292   public static boolean isTestsFile(@NotNull Project project, @NotNull final String name) {
293     Course course = StudyTaskManager.getInstance(project).getCourse();
294     if (course == null) {
295       return false;
296     }
297     StudyLanguageManager manager = getLanguageManager(course);
298     if (manager == null) {
299       return false;
300     }
301     return manager.getTestFileName().equals(name);
302   }
303
304   @Nullable
305   public static TaskFile getTaskFile(@NotNull final Project project, @NotNull final VirtualFile file) {
306     final Course course = StudyTaskManager.getInstance(project).getCourse();
307     if (course == null) {
308       return null;
309     }
310     VirtualFile taskDir = file.getParent();
311     if (taskDir == null) {
312       return null;
313     }
314     //need this because of multi-module generation
315     if (EduNames.SRC.equals(taskDir.getName())) {
316       taskDir = taskDir.getParent();
317       if (taskDir == null) {
318         return null;
319       }
320     }
321     final String taskDirName = taskDir.getName();
322     if (taskDirName.contains(EduNames.TASK)) {
323       final VirtualFile lessonDir = taskDir.getParent();
324       if (lessonDir != null) {
325         int lessonIndex = EduUtils.getIndex(lessonDir.getName(), EduNames.LESSON);
326         List<Lesson> lessons = course.getLessons();
327         if (!indexIsValid(lessonIndex, lessons)) {
328           return null;
329         }
330         final Lesson lesson = lessons.get(lessonIndex);
331         int taskIndex = EduUtils.getIndex(taskDirName, EduNames.TASK);
332         final List<Task> tasks = lesson.getTaskList();
333         if (!indexIsValid(taskIndex, tasks)) {
334           return null;
335         }
336         final Task task = tasks.get(taskIndex);
337         return task.getFile(file.getName());
338       }
339     }
340     return null;
341   }
342
343   public static void drawAllWindows(Editor editor, TaskFile taskFile) {
344     editor.getMarkupModel().removeAllHighlighters();
345     final Project project = editor.getProject();
346     if (project == null) return;
347     final StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
348     for (AnswerPlaceholder answerPlaceholder : taskFile.getAnswerPlaceholders()) {
349       final JBColor color = taskManager.getColor(answerPlaceholder);
350       EduAnswerPlaceholderPainter.drawAnswerPlaceholder(editor, answerPlaceholder, color);
351     }
352     final Document document = editor.getDocument();
353     EditorActionManager.getInstance()
354       .setReadonlyFragmentModificationHandler(document, new EduAnswerPlaceholderDeleteHandler(editor));
355     EduAnswerPlaceholderPainter.createGuardedBlocks(editor, taskFile);
356     editor.getColorsScheme().setColor(EditorColors.READONLY_FRAGMENT_BACKGROUND_COLOR, null);
357   }
358
359   @Nullable
360   public static StudyEditor getSelectedStudyEditor(@NotNull final Project project) {
361     try {
362       final FileEditor fileEditor = FileEditorManagerEx.getInstanceEx(project).getSplitters().getCurrentWindow().
363         getSelectedEditor().getSelectedEditorWithProvider().getFirst();
364       if (fileEditor instanceof StudyEditor) {
365         return (StudyEditor)fileEditor;
366       }
367     }
368     catch (Exception e) {
369       return null;
370     }
371     return null;
372   }
373
374   @Nullable
375   public static Editor getSelectedEditor(@NotNull final Project project) {
376     final StudyEditor studyEditor = getSelectedStudyEditor(project);
377     if (studyEditor != null) {
378       return studyEditor.getEditor();
379     }
380     return null;
381   }
382
383   public static void deleteGuardedBlocks(@NotNull final Document document) {
384     if (document instanceof DocumentImpl) {
385       final DocumentImpl documentImpl = (DocumentImpl)document;
386       List<RangeMarker> blocks = documentImpl.getGuardedBlocks();
387       for (final RangeMarker block : blocks) {
388         ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> document.removeGuardedBlock(block)));
389       }
390     }
391   }
392
393
394   @Nullable
395   public static VirtualFile getPatternFile(@NotNull TaskFile taskFile, String name) {
396     Task task = taskFile.getTask();
397     String lessonDir = EduNames.LESSON + String.valueOf(task.getLesson().getIndex());
398     String taskDir = EduNames.TASK + String.valueOf(task.getIndex());
399     Course course = task.getLesson().getCourse();
400     File resourceFile = new File(course.getCourseDirectory());
401     if (!resourceFile.exists()) {
402       return null;
403     }
404     String patternPath = FileUtil.join(resourceFile.getPath(), lessonDir, taskDir, name);
405     VirtualFile patternFile = VfsUtil.findFileByIoFile(new File(patternPath), true);
406     if (patternFile == null) {
407       return null;
408     }
409     return patternFile;
410   }
411
412   @Nullable
413   public static Document getPatternDocument(@NotNull final TaskFile taskFile, String name) {
414     VirtualFile patternFile = getPatternFile(taskFile, name);
415     if (patternFile == null) {
416       return null;
417     }
418     return FileDocumentManager.getInstance().getDocument(patternFile);
419   }
420
421   public static boolean isRenameableOrMoveable(@NotNull final Project project, @NotNull final Course course, @NotNull final PsiElement element) {
422     if (element instanceof PsiFile) {
423       VirtualFile virtualFile = ((PsiFile)element).getVirtualFile();
424       if (project.getBaseDir().equals(virtualFile.getParent())) {
425         return false;
426       }
427       TaskFile file = getTaskFile(project, virtualFile);
428       if (file != null) {
429         return false;
430       }
431       String name = virtualFile.getName();
432       return !isTestsFile(project, name) && !isTaskDescriptionFile(name);
433     }
434     if (element instanceof PsiDirectory) {
435       VirtualFile virtualFile = ((PsiDirectory)element).getVirtualFile();
436       VirtualFile parent = virtualFile.getParent();
437       if (parent == null) {
438         return true;
439       }
440       if (project.getBaseDir().equals(parent)) {
441         return false;
442       }
443       Lesson lesson = course.getLesson(parent.getName());
444       if (lesson != null) {
445         Task task = lesson.getTask(virtualFile.getName());
446         if (task != null) {
447           return false;
448         }
449       }
450     }
451     return true;
452   }
453
454   public static boolean canRenameOrMove(DataContext dataContext) {
455     Project project = CommonDataKeys.PROJECT.getData(dataContext);
456     PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(dataContext);
457     if (element == null || project == null) {
458       return false;
459     }
460     Course course = StudyTaskManager.getInstance(project).getCourse();
461     if (course == null || !EduNames.STUDY.equals(course.getCourseMode())) {
462       return false;
463     }
464
465     if (!isRenameableOrMoveable(project, course, element)) {
466       return true;
467     }
468     return false;
469   }
470
471   @Nullable
472   public static String getTaskTextFromTask(@Nullable final VirtualFile taskDirectory, @Nullable final Task task) {
473     if (task == null) {
474       return null;
475     }
476
477     String text = task.getText();
478     if (text != null && !text.isEmpty()) {
479       return text;
480     }
481     if (taskDirectory != null) {
482       final String prefix = String.format(ourPrefix, EditorColorsManager.getInstance().getGlobalScheme().getEditorFontSize());
483       final String taskTextFileHtml = getTaskTextFromTaskName(taskDirectory, EduNames.TASK_HTML);
484       if (taskTextFileHtml != null) return prefix + taskTextFileHtml + ourPostfix;
485       
486       final String taskTextFileMd = getTaskTextFromTaskName(taskDirectory, EduNames.TASK_MD);
487       if (taskTextFileMd != null) return prefix + convertToHtml(taskTextFileMd) + ourPostfix;      
488     }
489     return null;
490   }
491
492   @Nullable
493   private static String getTaskTextFromTaskName(@NotNull VirtualFile taskDirectory, @NotNull String taskTextFilename) {
494     VirtualFile taskTextFile = taskDirectory.findChild(taskTextFilename);
495     if (taskTextFile == null) {
496       VirtualFile srcDir = taskDirectory.findChild(EduNames.SRC);
497       if (srcDir != null) {
498          taskTextFile = srcDir.findChild(taskTextFilename);
499       }
500     }
501     if (taskTextFile != null) {
502       try {
503         return FileUtil.loadTextAndClose(taskTextFile.getInputStream());
504       }
505       catch (IOException e) {
506         LOG.info(e);
507       }
508     }
509     return null;
510   }
511
512   @Nullable
513   public static StudyPluginConfigurator getConfigurator(@NotNull final Project project) {
514     StudyPluginConfigurator[] extensions = StudyPluginConfigurator.EP_NAME.getExtensions();
515     for (StudyPluginConfigurator extension: extensions) {
516       if (extension.accept(project)) {
517         return extension;
518       }
519     }
520     return null;
521   }
522
523   @Nullable
524   public static StudyTwitterPluginConfigurator getTwitterConfigurator(@NotNull final Project project) {
525     StudyTwitterPluginConfigurator[] extensions = StudyTwitterPluginConfigurator.EP_NAME.getExtensions();
526     for (StudyTwitterPluginConfigurator extension: extensions) {
527       if (extension.accept(project)) {
528         return extension;
529       }
530     }
531     return null;
532   }
533
534   @Nullable
535   public static String getTaskText(@NotNull final Project project) {
536     TaskFile taskFile = getSelectedTaskFile(project);
537     if (taskFile == null) {
538       return EMPTY_TASK_TEXT;
539     }
540     final Task task = taskFile.getTask();
541     if (task != null) {
542       return getTaskTextFromTask(task.getTaskDir(project), task);
543     }
544     return null;
545   }
546   @Nullable
547   public static TaskFile getSelectedTaskFile(@NotNull Project project) {
548     VirtualFile[] files = FileEditorManager.getInstance(project).getSelectedFiles();
549     TaskFile taskFile = null;
550     for (VirtualFile file : files) {
551       taskFile = getTaskFile(project, file);
552       if (taskFile != null) {
553         break;
554       }
555     }
556     return taskFile;
557   }
558   
559   @Nullable
560   public static Task getCurrentTask(@NotNull final Project project) {
561     final TaskFile taskFile = getSelectedTaskFile(project);
562     return taskFile != null ? taskFile.getTask() : null;
563   }
564
565   public static boolean isStudyProject(@NotNull Project project) {
566     return StudyTaskManager.getInstance(project).getCourse() != null;
567   }
568
569   @Nullable
570   public static Project getStudyProject() {
571     Project studyProject = null;
572     Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
573     for (Project project : openProjects) {
574       if (StudyTaskManager.getInstance(project).getCourse() != null) {
575          studyProject = project;
576       }
577     }
578     return studyProject;
579   }
580
581   @NotNull
582   public static File getCourseDirectory(@NotNull Project project, Course course) {
583     final File courseDirectory;
584     if (course.isAdaptive()) {
585       courseDirectory = new File(StudyProjectGenerator.OUR_COURSES_DIR,
586                                  StudyProjectGenerator.ADAPTIVE_COURSE_PREFIX + course.getName()
587                                  + "_" + StudyTaskManager.getInstance(project).getUser().getEmail());
588     }
589     else {
590       courseDirectory = new File(StudyProjectGenerator.OUR_COURSES_DIR, course.getName());
591     }
592     return courseDirectory;
593   }
594
595   public static boolean hasJavaFx() {
596     try {
597       Class.forName("javafx.application.Platform");
598       return true;
599     }
600     catch (ClassNotFoundException e) {
601       return false;
602     }
603   }
604
605   @Nullable
606   public static Task getTask(@NotNull Project project, @NotNull VirtualFile taskVF) {
607     Course course = StudyTaskManager.getInstance(project).getCourse();
608     if (course == null) {
609       return null;
610     }
611     VirtualFile lessonVF = taskVF.getParent();
612     if (lessonVF == null) {
613       return null;
614     }
615     Lesson lesson = course.getLesson(lessonVF.getName());
616     if (lesson == null) {
617       return null;
618     }
619     return lesson.getTask(taskVF.getName());
620   }
621
622   @Nullable
623   public static VirtualFile getTaskDir(@NotNull VirtualFile taskFile) {
624     VirtualFile parent = taskFile.getParent();
625     if (parent == null) {
626       return null;
627     }
628     String name = parent.getName();
629     if (name.contains(EduNames.TASK)) {
630       return parent;
631     }
632     if (EduNames.SRC.equals(name)) {
633       return parent.getParent();
634     }
635     return null;
636   }
637
638   @Nullable
639   public static Task getTaskForFile(@NotNull Project project, @NotNull VirtualFile taskFile) {
640     VirtualFile taskDir = getTaskDir(taskFile);
641     if (taskDir == null) {
642       return null;
643     }
644     return getTask(project, taskDir);
645   }
646
647   // supposed to be called under progress
648   @Nullable
649   public static <T> T execCancelable(@NotNull final Callable<T> callable) {
650     final Future<T> future = ApplicationManager.getApplication().executeOnPooledThread(callable);
651
652     while (!future.isCancelled() && !future.isDone()) {
653       ProgressManager.checkCanceled();
654       TimeoutUtil.sleep(500);
655     }
656     T result = null;
657     try {
658       result = future.get();
659     }
660     catch (InterruptedException | ExecutionException e) {
661       LOG.warn(e.getMessage());
662     }
663     return result;
664   }
665
666   @Nullable
667   public static Task getTaskFromSelectedEditor(Project project) {
668     final StudyEditor editor = getSelectedStudyEditor(project);
669     Task task = null;
670     if (editor != null) {
671       final TaskFile file = editor.getTaskFile();
672       task = file.getTask();
673     }
674     return task;
675   }
676
677   private static String convertToHtml(@NotNull final String content) {
678     ArrayList<String> lines = ContainerUtil.newArrayList(content.split("\n|\r|\r\n"));
679     MarkdownUtil.replaceHeaders(lines);
680     MarkdownUtil.replaceCodeBlock(lines);
681     
682     return new MarkdownProcessor().markdown(StringUtil.join(lines, "\n"));
683   }
684   
685   public static boolean isTaskDescriptionFile(@NotNull final String fileName) {
686     return EduNames.TASK_HTML.equals(fileName) || EduNames.TASK_MD.equals(fileName);
687   }
688   
689   @Nullable
690   public static VirtualFile findTaskDescriptionVirtualFile(@NotNull VirtualFile taskDir) {
691     return ObjectUtils.chooseNotNull(taskDir.findChild(EduNames.TASK_HTML), taskDir.findChild(EduNames.TASK_MD));
692   }
693   
694   @NotNull
695   public static String getTaskDescriptionFileName(final boolean useHtml) {
696     return useHtml ? EduNames.TASK_HTML : EduNames.TASK_MD;    
697   }
698   
699   @Nullable
700   public static File createTaskDescriptionFile(@NotNull final File parent) {
701     if(new File(parent, EduNames.TASK_HTML).exists()) {
702       return new File(parent, EduNames.TASK_HTML);
703     }
704     else {
705       return new File(parent, EduNames.TASK_MD);
706     }
707   }
708
709   @Nullable
710   public static Document getDocument(String basePath, int lessonIndex, int taskIndex, String fileName) {
711     String taskPath = FileUtil.join(basePath, EduNames.LESSON + lessonIndex, EduNames.TASK + taskIndex);
712     VirtualFile taskFile = LocalFileSystem.getInstance().findFileByPath(FileUtil.join(taskPath, fileName));
713     if (taskFile == null) {
714       taskFile = LocalFileSystem.getInstance().findFileByPath(FileUtil.join(taskPath, EduNames.SRC, fileName));
715     }
716     if (taskFile == null) {
717       return null;
718     }
719     return FileDocumentManager.getInstance().getDocument(taskFile);
720   }
721
722   public static void showErrorPopupOnToolbar(@NotNull Project project) {
723     final Balloon balloon =
724       JBPopupFactory.getInstance().createHtmlTextBalloonBuilder("Couldn't post your reaction", MessageType.ERROR, null).createBalloon();
725     showCheckPopUp(project, balloon);
726   }
727 }