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