Resolve conflicts
[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.impl.DocumentImpl;
19 import com.intellij.openapi.fileEditor.FileDocumentManager;
20 import com.intellij.openapi.fileEditor.FileEditor;
21 import com.intellij.openapi.fileEditor.FileEditorManager;
22 import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
23 import com.intellij.openapi.project.Project;
24 import com.intellij.openapi.project.ProjectManager;
25 import com.intellij.openapi.projectRoots.Sdk;
26 import com.intellij.openapi.ui.popup.Balloon;
27 import com.intellij.openapi.util.Disposer;
28 import com.intellij.openapi.util.io.FileUtil;
29 import com.intellij.openapi.vfs.VfsUtil;
30 import com.intellij.openapi.vfs.VirtualFile;
31 import com.intellij.openapi.wm.ToolWindow;
32 import com.intellij.openapi.wm.ToolWindowManager;
33 import com.intellij.psi.PsiDirectory;
34 import com.intellij.psi.PsiElement;
35 import com.intellij.psi.PsiFile;
36 import com.intellij.ui.JBColor;
37 import com.intellij.ui.awt.RelativePoint;
38 import com.intellij.ui.content.Content;
39 import com.intellij.util.ui.UIUtil;
40 import com.jetbrains.edu.learning.checker.StudyExecutor;
41 import com.jetbrains.edu.learning.checker.StudyTestRunner;
42 import com.jetbrains.edu.learning.core.EduAnswerPlaceholderDeleteHandler;
43 import com.jetbrains.edu.learning.core.EduAnswerPlaceholderPainter;
44 import com.jetbrains.edu.learning.core.EduNames;
45 import com.jetbrains.edu.learning.core.EduUtils;
46 import com.jetbrains.edu.learning.courseFormat.*;
47 import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
48 import com.jetbrains.edu.learning.editor.StudyEditor;
49 import com.jetbrains.edu.learning.ui.StudyProgressToolWindowFactory;
50 import com.jetbrains.edu.learning.ui.StudyToolWindow;
51 import com.jetbrains.edu.learning.ui.StudyToolWindowFactory;
52 import org.jetbrains.annotations.NotNull;
53 import org.jetbrains.annotations.Nullable;
54
55 import javax.swing.*;
56 import java.awt.*;
57 import java.io.*;
58 import java.util.Collection;
59 import java.util.List;
60
61 public class StudyUtils {
62   private StudyUtils() {
63   }
64
65   private static final Logger LOG = Logger.getInstance(StudyUtils.class.getName());
66   private static final String EMPTY_TASK_TEXT = "Please, open any task to see task description";
67
68   public static void closeSilently(@Nullable final Closeable stream) {
69     if (stream != null) {
70       try {
71         stream.close();
72       }
73       catch (IOException e) {
74         // close silently
75       }
76     }
77   }
78
79   public static boolean isZip(String fileName) {
80     return fileName.contains(".zip");
81   }
82
83   public static <T> T getFirst(@NotNull final Iterable<T> container) {
84     return container.iterator().next();
85   }
86
87   public static boolean indexIsValid(int index, @NotNull final Collection collection) {
88     int size = collection.size();
89     return index >= 0 && index < size;
90   }
91
92   @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
93   @Nullable
94   public static String getFileText(@Nullable final String parentDir, @NotNull final String fileName, boolean wrapHTML,
95                                    @NotNull final String encoding) {
96     final File inputFile = parentDir != null ? new File(parentDir, fileName) : new File(fileName);
97     if (!inputFile.exists()) return null;
98     final StringBuilder taskText = new StringBuilder();
99     BufferedReader reader = null;
100     try {
101       reader = new BufferedReader(new InputStreamReader(new FileInputStream(inputFile), encoding));
102       String line;
103       while ((line = reader.readLine()) != null) {
104         taskText.append(line).append("\n");
105         if (wrapHTML) {
106           taskText.append("<br>");
107         }
108       }
109       return wrapHTML ? UIUtil.toHtml(taskText.toString()) : taskText.toString();
110     }
111     catch (IOException e) {
112       LOG.info("Failed to get file text from file " + fileName, e);
113     }
114     finally {
115       closeSilently(reader);
116     }
117     return null;
118   }
119
120   public static void updateAction(@NotNull final AnActionEvent e) {
121     final Presentation presentation = e.getPresentation();
122     presentation.setEnabled(false);
123     final Project project = e.getProject();
124     if (project != null) {
125       final StudyEditor studyEditor = getSelectedStudyEditor(project);
126       if (studyEditor != null) {
127         presentation.setEnabledAndVisible(true);
128       }
129     }
130   }
131
132   public static void updateToolWindows(@NotNull final Project project) {
133     updateStudyToolWindow(project);
134
135     final ToolWindowManager windowManager = ToolWindowManager.getInstance(project);
136     createProgressToolWindowContent(project, windowManager);
137   }
138
139   public static void initToolWindows(@NotNull final Project project) {
140     final ToolWindowManager windowManager = ToolWindowManager.getInstance(project);
141     windowManager.getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW).getContentManager().removeAllContents(false);
142     StudyToolWindowFactory factory = new StudyToolWindowFactory();
143     factory.createToolWindowContent(project, windowManager.getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW));
144
145     createProgressToolWindowContent(project, windowManager);
146   }
147
148   private static void createProgressToolWindowContent(@NotNull Project project, ToolWindowManager windowManager) {
149     windowManager.getToolWindow(StudyProgressToolWindowFactory.ID).getContentManager().removeAllContents(false);
150     StudyProgressToolWindowFactory windowFactory = new StudyProgressToolWindowFactory();
151     windowFactory.createToolWindowContent(project, windowManager.getToolWindow(StudyProgressToolWindowFactory.ID));
152   }
153   
154   @Nullable
155   public static StudyToolWindow getStudyToolWindow(@NotNull final Project project) {
156     ToolWindow toolWindow = ToolWindowManager.getInstance(project).getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW);
157     if (toolWindow != null) {
158       Content[] contents = toolWindow.getContentManager().getContents();
159       for (Content content: contents) {
160         JComponent component = content.getComponent();
161         if (component != null && component instanceof StudyToolWindow) {
162           return (StudyToolWindow)component;
163         }
164       }
165     }
166     return null;
167   }
168
169   public static void deleteFile(@NotNull final VirtualFile file) {
170     try {
171       file.delete(StudyUtils.class);
172     }
173     catch (IOException e) {
174       LOG.error(e);
175     }
176   }
177
178   public static File copyResourceFile(@NotNull final String sourceName, @NotNull final String copyName, @NotNull final Project project,
179                                       @NotNull final Task task)
180     throws IOException {
181     final StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
182     final Course course = taskManager.getCourse();
183     int taskNum = task.getIndex();
184     int lessonNum = task.getLesson().getIndex();
185     assert course != null;
186     final String pathToResource = FileUtil.join(course.getCourseDirectory(), EduNames.LESSON + lessonNum, EduNames.TASK + taskNum);
187     final File resourceFile = new File(pathToResource, copyName);
188     FileUtil.copy(new File(pathToResource, sourceName), resourceFile);
189     return resourceFile;
190   }
191
192   @Nullable
193   public static Sdk findSdk(@NotNull final Task task, @NotNull final Project project) {
194     final Language language = task.getLesson().getCourse().getLanguageById();
195     return StudyExecutor.INSTANCE.forLanguage(language).findSdk(project);
196   }
197
198   @NotNull
199   public static StudyTestRunner getTestRunner(@NotNull final Task task, @NotNull final VirtualFile taskDir) {
200     final Language language = task.getLesson().getCourse().getLanguageById();
201     return StudyExecutor.INSTANCE.forLanguage(language).getTestRunner(task, taskDir);
202   }
203
204   public static RunContentExecutor getExecutor(@NotNull final Project project, @NotNull final Task currentTask,
205                                                @NotNull final ProcessHandler handler) {
206     final Language language = currentTask.getLesson().getCourse().getLanguageById();
207     return StudyExecutor.INSTANCE.forLanguage(language).getExecutor(project, handler);
208   }
209
210   public static void setCommandLineParameters(@NotNull final GeneralCommandLine cmd,
211                                               @NotNull final Project project,
212                                               @NotNull final String filePath,
213                                               @NotNull final String sdkPath,
214                                               @NotNull final Task currentTask) {
215     final Language language = currentTask.getLesson().getCourse().getLanguageById();
216     StudyExecutor.INSTANCE.forLanguage(language).setCommandLineParameters(cmd, project, filePath, sdkPath, currentTask);
217   }
218
219   public static void showNoSdkNotification(@NotNull final Task currentTask, @NotNull final Project project) {
220     final Language language = currentTask.getLesson().getCourse().getLanguageById();
221     StudyExecutor.INSTANCE.forLanguage(language).showNoSdkNotification(project);
222   }
223
224
225   /**
226    * shows pop up in the center of "check task" button in study editor
227    */
228   public static void showCheckPopUp(@NotNull final Project project, @NotNull final Balloon balloon) {
229     final StudyEditor studyEditor = getSelectedStudyEditor(project);
230     assert studyEditor != null;
231
232     balloon.show(computeLocation(studyEditor.getEditor()), Balloon.Position.above);
233     Disposer.register(project, balloon);
234   }
235
236   public static RelativePoint computeLocation(Editor editor){
237
238     final Rectangle visibleRect = editor.getComponent().getVisibleRect();
239     Point point = new Point(visibleRect.x + visibleRect.width + 10,
240                             visibleRect.y + 10);
241     return new RelativePoint(editor.getComponent(), point);
242   }
243
244
245   /**
246    * returns language manager which contains all the information about language specific file names
247    */
248   @Nullable
249   public static StudyLanguageManager getLanguageManager(@NotNull final Course course) {
250     Language language = course.getLanguageById();
251     return language == null ? null : StudyLanguageManager.INSTANCE.forLanguage(language);
252   }
253
254   public static boolean isTestsFile(@NotNull Project project, @NotNull final String name) {
255     Course course = StudyTaskManager.getInstance(project).getCourse();
256     if (course == null) {
257       return false;
258     }
259     StudyLanguageManager manager = getLanguageManager(course);
260     if (manager == null) {
261       return false;
262     }
263     return manager.getTestFileName().equals(name);
264   }
265
266   @Nullable
267   public static TaskFile getTaskFile(@NotNull final Project project, @NotNull final VirtualFile file) {
268     final Course course = StudyTaskManager.getInstance(project).getCourse();
269     if (course == null) {
270       return null;
271     }
272     VirtualFile taskDir = file.getParent();
273     if (taskDir == null) {
274       return null;
275     }
276     //need this because of multi-module generation
277     if ("src".equals(taskDir.getName())) {
278       taskDir = taskDir.getParent();
279       if (taskDir == null) {
280         return null;
281       }
282     }
283     final String taskDirName = taskDir.getName();
284     if (taskDirName.contains(EduNames.TASK)) {
285       final VirtualFile lessonDir = taskDir.getParent();
286       if (lessonDir != null) {
287         int lessonIndex = EduUtils.getIndex(lessonDir.getName(), EduNames.LESSON);
288         List<Lesson> lessons = course.getLessons();
289         if (!indexIsValid(lessonIndex, lessons)) {
290           return null;
291         }
292         final Lesson lesson = lessons.get(lessonIndex);
293         int taskIndex = EduUtils.getIndex(taskDirName, EduNames.TASK);
294         final List<Task> tasks = lesson.getTaskList();
295         if (!indexIsValid(taskIndex, tasks)) {
296           return null;
297         }
298         final Task task = tasks.get(taskIndex);
299         return task.getFile(file.getName());
300       }
301     }
302     return null;
303   }
304
305   public static void drawAllWindows(Editor editor, TaskFile taskFile) {
306     editor.getMarkupModel().removeAllHighlighters();
307     final Project project = editor.getProject();
308     if (project == null) return;
309     final StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
310     for (AnswerPlaceholder answerPlaceholder : taskFile.getAnswerPlaceholders()) {
311       final JBColor color = taskManager.getColor(answerPlaceholder);
312       EduAnswerPlaceholderPainter.drawAnswerPlaceholder(editor, answerPlaceholder, color);
313     }
314     final Document document = editor.getDocument();
315     EditorActionManager.getInstance()
316       .setReadonlyFragmentModificationHandler(document, new EduAnswerPlaceholderDeleteHandler(editor));
317     EduAnswerPlaceholderPainter.createGuardedBlocks(editor, taskFile);
318     editor.getColorsScheme().setColor(EditorColors.READONLY_FRAGMENT_BACKGROUND_COLOR, null);
319   }
320
321   @Nullable
322   public static StudyEditor getSelectedStudyEditor(@NotNull final Project project) {
323     try {
324       final FileEditor fileEditor = FileEditorManagerEx.getInstanceEx(project).getSplitters().getCurrentWindow().
325         getSelectedEditor().getSelectedEditorWithProvider().getFirst();
326       if (fileEditor instanceof StudyEditor) {
327         return (StudyEditor)fileEditor;
328       }
329     }
330     catch (Exception e) {
331       return null;
332     }
333     return null;
334   }
335
336   @Nullable
337   public static Editor getSelectedEditor(@NotNull final Project project) {
338     final StudyEditor studyEditor = getSelectedStudyEditor(project);
339     if (studyEditor != null) {
340       return studyEditor.getEditor();
341     }
342     return null;
343   }
344
345   public static void deleteGuardedBlocks(@NotNull final Document document) {
346     if (document instanceof DocumentImpl) {
347       final DocumentImpl documentImpl = (DocumentImpl)document;
348       List<RangeMarker> blocks = documentImpl.getGuardedBlocks();
349       for (final RangeMarker block : blocks) {
350         ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> {
351           document.removeGuardedBlock(block);
352         }));
353       }
354     }
355   }
356
357
358   @Nullable
359   public static VirtualFile getPatternFile(@NotNull TaskFile taskFile, String name) {
360     Task task = taskFile.getTask();
361     String lessonDir = EduNames.LESSON + String.valueOf(task.getLesson().getIndex());
362     String taskDir = EduNames.TASK + String.valueOf(task.getIndex());
363     Course course = task.getLesson().getCourse();
364     File resourceFile = getCourseDirectory(project, course);
365     if (!resourceFile.exists()) {
366       return null;
367     }
368     String patternPath = FileUtil.join(resourceFile.getPath(), lessonDir, taskDir, name);
369     VirtualFile patternFile = VfsUtil.findFileByIoFile(new File(patternPath), true);
370     if (patternFile == null) {
371       return null;
372     }
373     return patternFile;
374   }
375
376   @Nullable
377   public static Document getPatternDocument(@NotNull final TaskFile taskFile, String name) {
378     VirtualFile patternFile = getPatternFile(taskFile, name);
379     if (patternFile == null) {
380       return null;
381     }
382     return FileDocumentManager.getInstance().getDocument(patternFile);
383   }
384
385   public static boolean isRenameableOrMoveable(@NotNull final Project project, @NotNull final Course course, @NotNull final PsiElement element) {
386     if (element instanceof PsiFile) {
387       VirtualFile virtualFile = ((PsiFile)element).getVirtualFile();
388       if (project.getBaseDir().equals(virtualFile.getParent())) {
389         return false;
390       }
391       TaskFile file = getTaskFile(project, virtualFile);
392       if (file != null) {
393         return false;
394       }
395       String name = virtualFile.getName();
396       return !isTestsFile(project, name) && !EduNames.TASK_HTML.equals(name);
397     }
398     if (element instanceof PsiDirectory) {
399       VirtualFile virtualFile = ((PsiDirectory)element).getVirtualFile();
400       VirtualFile parent = virtualFile.getParent();
401       if (parent == null) {
402         return true;
403       }
404       if (project.getBaseDir().equals(parent)) {
405         return false;
406       }
407       Lesson lesson = course.getLesson(parent.getName());
408       if (lesson != null) {
409         Task task = lesson.getTask(virtualFile.getName());
410         if (task != null) {
411           return false;
412         }
413       }
414     }
415     return true;
416   }
417
418   public static boolean canRenameOrMove(DataContext dataContext) {
419     Project project = CommonDataKeys.PROJECT.getData(dataContext);
420     PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(dataContext);
421     if (element == null || project == null) {
422       return false;
423     }
424     Course course = StudyTaskManager.getInstance(project).getCourse();
425     if (course == null || !EduNames.STUDY.equals(course.getCourseMode())) {
426       return false;
427     }
428
429     if (!isRenameableOrMoveable(project, course, element)) {
430       return true;
431     }
432     return false;
433   }
434
435   @Nullable
436   public static String getTaskTextFromTask(@Nullable final Task task, @Nullable final VirtualFile taskDirectory) {
437     if (task == null) {
438       return null;
439     }
440     String text = task.getText();
441     if (text != null) {
442       return text;
443     }
444     if (taskDirectory != null) {
445       VirtualFile taskTextFile = taskDirectory.findChild(EduNames.TASK_HTML);
446       if (taskTextFile == null) {
447         VirtualFile srcDir = taskDirectory.findChild("src");
448         if (srcDir != null) {
449            taskTextFile = srcDir.findChild(EduNames.TASK_HTML);
450         }
451       }
452       if (taskTextFile != null) {
453         try {
454           return FileUtil.loadTextAndClose(taskTextFile.getInputStream());
455         }
456         catch (IOException e) {
457           LOG.info(e);
458         }
459       }
460     }
461     return null;
462   }
463   
464   @Nullable
465   public static StudyPluginConfigurator getConfigurator(@NotNull final Project project) {
466     StudyPluginConfigurator[] extensions = StudyPluginConfigurator.EP_NAME.getExtensions();
467     for (StudyPluginConfigurator extension: extensions) {
468       if (extension.accept(project)) {
469         return extension;
470       }
471     }
472     return null;
473   }
474
475   @Nullable
476   public static StudyTwitterPluginConfigurator getTwitterConfigurator(@NotNull final Project project) {
477     StudyTwitterPluginConfigurator[] extensions = StudyTwitterPluginConfigurator.EP_NAME.getExtensions();
478     for (StudyTwitterPluginConfigurator extension: extensions) {
479       if (extension.accept(project)) {
480         return extension;
481       }
482     }
483     return null;
484   }
485
486   @Nullable
487   public static String getTaskText(@NotNull final Project project) {
488     TaskFile taskFile = getSelectedTaskFile(project);
489     if (taskFile == null) {
490       return EMPTY_TASK_TEXT;
491     }
492     final Task task = taskFile.getTask();
493     if (task != null) {
494       return getTaskTextFromTask(task, task.getTaskDir(project));
495     }
496     return null;
497   }
498   @Nullable
499   public static TaskFile getSelectedTaskFile(@NotNull Project project) {
500     VirtualFile[] files = FileEditorManager.getInstance(project).getSelectedFiles();
501     TaskFile taskFile = null;
502     for (VirtualFile file : files) {
503       taskFile = getTaskFile(project, file);
504       if (taskFile != null) {
505         break;
506       }
507     }
508     return taskFile;
509   }
510   
511   @Nullable
512   public static Task getCurrentTask(@NotNull final Project project) {
513     VirtualFile[] files = FileEditorManager.getInstance(project).getSelectedFiles();
514     TaskFile taskFile = null;
515     for (VirtualFile file : files) {
516       taskFile = getTaskFile(project, file);
517       if (taskFile != null) {
518         break;
519       }
520     }
521     if (taskFile != null) {
522       return taskFile.getTask();
523     }
524     return null;
525   }
526
527   public static void updateStudyToolWindow(Project project) {
528     final StudyToolWindow studyToolWindow = getStudyToolWindow(project);
529     if (studyToolWindow != null) {
530       String taskText = getTaskText(project);
531       if (taskText != null) {
532         studyToolWindow.setTaskText(taskText, null, project);
533       }
534       else {
535         LOG.warn("Task text is null");
536       }
537     }
538   }
539
540   public static boolean isStudyProject(@NotNull Project project) {
541     return StudyTaskManager.getInstance(project).getCourse() != null;
542   }
543
544   @Nullable
545   public static Project getStudyProject() {
546     Project studyProject = null;
547     Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
548     for (Project project : openProjects) {
549       if (StudyTaskManager.getInstance(project).getCourse() != null) {
550          studyProject = project;
551       }
552     }
553     return studyProject;
554   }
555
556   @NotNull
557   public static File getCourseDirectory(@NotNull Project project, Course course) {
558     final File courseDirectory;
559     if (course.isAdaptive()) {
560       courseDirectory = new File(StudyProjectGenerator.OUR_COURSES_DIR,
561                                  StudyProjectGenerator.ADAPTIVE_COURSE_PREFIX + course.getName()
562                                  + "_" + StudyTaskManager.getInstance(project).getUser().getEmail());
563     }
564     else {
565       courseDirectory = new File(StudyProjectGenerator.OUR_COURSES_DIR, course.getName());
566     }
567     return courseDirectory;
568   }
569
570   public static boolean hasJavaFx() {
571     try {
572       Class.forName("javafx.application.Platform");
573       return true;
574     }
575     catch (ClassNotFoundException e) {
576       return false;
577     }
578   }
579
580   @Nullable
581   public static Task getTask(@NotNull Project project, @NotNull VirtualFile taskVF) {
582     Course course = StudyTaskManager.getInstance(project).getCourse();
583     if (course == null) {
584       return null;
585     }
586     VirtualFile lessonVF = taskVF.getParent();
587     if (lessonVF == null) {
588       return null;
589     }
590     Lesson lesson = course.getLesson(lessonVF.getName());
591     if (lesson == null) {
592       return null;
593     }
594     return lesson.getTask(taskVF.getName());
595   }
596
597   @Nullable
598   public static VirtualFile getTaskDir(@NotNull VirtualFile taskFile) {
599     VirtualFile parent = taskFile.getParent();
600     if (parent == null) {
601       return null;
602     }
603     String name = parent.getName();
604     if (name.contains(EduNames.TASK)) {
605       return parent;
606     }
607     if (EduNames.SRC.equals(name)) {
608       return parent.getParent();
609     }
610     return null;
611   }
612 }