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