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