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