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