EDU-452 Save task status when converting to new course format
[idea/community.git] / python / educational / interactive-learning / src / com / jetbrains / edu / learning / actions / StudyCheckAction.java
1 package com.jetbrains.edu.learning.actions;
2
3 import com.intellij.execution.ExecutionException;
4 import com.intellij.execution.process.CapturingProcessHandler;
5 import com.intellij.execution.process.ProcessOutput;
6 import com.intellij.ide.projectView.ProjectView;
7 import com.intellij.openapi.actionSystem.ActionManager;
8 import com.intellij.openapi.actionSystem.AnActionEvent;
9 import com.intellij.openapi.actionSystem.KeyboardShortcut;
10 import com.intellij.openapi.actionSystem.Presentation;
11 import com.intellij.openapi.application.ApplicationManager;
12 import com.intellij.openapi.command.CommandProcessor;
13 import com.intellij.openapi.diagnostic.Logger;
14 import com.intellij.openapi.editor.Document;
15 import com.intellij.openapi.editor.Editor;
16 import com.intellij.openapi.fileEditor.FileDocumentManager;
17 import com.intellij.openapi.fileEditor.FileEditor;
18 import com.intellij.openapi.fileEditor.FileEditorManager;
19 import com.intellij.openapi.keymap.KeymapUtil;
20 import com.intellij.openapi.progress.ProgressIndicator;
21 import com.intellij.openapi.progress.ProgressManager;
22 import com.intellij.openapi.progress.TaskInfo;
23 import com.intellij.openapi.project.DumbAwareAction;
24 import com.intellij.openapi.project.DumbService;
25 import com.intellij.openapi.project.Project;
26 import com.intellij.openapi.ui.MessageType;
27 import com.intellij.openapi.ui.popup.Balloon;
28 import com.intellij.openapi.ui.popup.BalloonBuilder;
29 import com.intellij.openapi.ui.popup.JBPopupFactory;
30 import com.intellij.openapi.util.Pair;
31 import com.intellij.openapi.util.text.StringUtil;
32 import com.intellij.openapi.vfs.VirtualFile;
33 import com.intellij.openapi.wm.IdeFocusManager;
34 import com.intellij.openapi.wm.IdeFrame;
35 import com.intellij.openapi.wm.WindowManager;
36 import com.intellij.openapi.wm.ex.StatusBarEx;
37 import com.intellij.openapi.wm.ex.WindowManagerEx;
38 import com.jetbrains.edu.EduDocumentListener;
39 import com.jetbrains.edu.EduUtils;
40 import com.jetbrains.edu.courseFormat.AnswerPlaceholder;
41 import com.jetbrains.edu.courseFormat.Task;
42 import com.jetbrains.edu.courseFormat.TaskFile;
43 import com.jetbrains.edu.learning.StudyState;
44 import com.jetbrains.edu.learning.StudyTaskManager;
45 import com.jetbrains.edu.learning.StudyUtils;
46 import com.jetbrains.edu.courseFormat.StudyStatus;
47 import com.jetbrains.edu.learning.editor.StudyEditor;
48 import com.jetbrains.edu.learning.navigation.StudyNavigator;
49 import com.jetbrains.edu.learning.run.StudySmartChecker;
50 import com.jetbrains.edu.learning.run.StudyTestRunner;
51 import com.jetbrains.edu.stepic.StudySettings;
52 import com.jetbrains.edu.stepic.EduStepicConnector;
53 import icons.InteractiveLearningIcons;
54 import org.jetbrains.annotations.NotNull;
55 import org.jetbrains.annotations.Nullable;
56
57 import javax.swing.*;
58 import java.awt.*;
59 import java.io.IOException;
60 import java.util.List;
61 import java.util.Map;
62
63 public class StudyCheckAction extends DumbAwareAction {
64
65   private static final Logger LOG = Logger.getInstance(StudyCheckAction.class.getName());
66   private static final String ANSWERS_POSTFIX = "_answers";
67   public static final String ACTION_ID = "CheckAction";
68   public static final String SHORTCUT = "ctrl alt pressed ENTER";
69
70   boolean checkInProgress = false;
71
72   public StudyCheckAction() {
73     super("Check Task (" + KeymapUtil.getShortcutText(new KeyboardShortcut(KeyStroke.getKeyStroke(SHORTCUT), null)) + ")", "Check current task", InteractiveLearningIcons.Resolve);
74   }
75
76   private static void flushWindows(@NotNull final Task task, @NotNull final VirtualFile taskDir) {
77     for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
78       String name = entry.getKey();
79       TaskFile taskFile = entry.getValue();
80       VirtualFile virtualFile = taskDir.findChild(name);
81       if (virtualFile == null) {
82         continue;
83       }
84       EduUtils.flushWindows(taskFile, virtualFile, true);
85     }
86   }
87
88   private static void drawAllPlaceholders(@NotNull final Project project, @NotNull final Task task, @NotNull final VirtualFile taskDir) {
89     for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
90       String name = entry.getKey();
91       TaskFile taskFile = entry.getValue();
92       VirtualFile virtualFile = taskDir.findChild(name);
93       if (virtualFile == null) {
94         continue;
95       }
96       FileEditor fileEditor = FileEditorManager.getInstance(project).getSelectedEditor(virtualFile);
97       if (fileEditor instanceof StudyEditor) {
98         StudyEditor studyEditor = (StudyEditor)fileEditor;
99         StudyUtils.drawAllWindows(studyEditor.getEditor(), taskFile);
100       }
101     }
102   }
103
104
105   public void check(@NotNull final Project project) {
106     if (DumbService.isDumb(project)) {
107       DumbService.getInstance(project).showDumbModeNotification("Check Action is not available while indexing is in progress");
108       return;
109     }
110     ApplicationManager.getApplication().runWriteAction(new Runnable() {
111       @Override
112       public void run() {
113         CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() {
114           @Override
115           public void run() {
116             final StudyEditor selectedEditor = StudyUtils.getSelectedStudyEditor(project);
117             if (selectedEditor == null) return;
118             final StudyState studyState = new StudyState(selectedEditor);
119             if (!studyState.isValid()) {
120               LOG.error("StudyCheckAction was invoked outside study editor");
121               return;
122             }
123             final IdeFrame frame = ((WindowManagerEx)WindowManager.getInstance()).findFrameFor(project);
124             final StatusBarEx statusBar = frame == null ? null : (StatusBarEx)frame.getStatusBar();
125             if (statusBar != null) {
126               final List<Pair<TaskInfo, ProgressIndicator>> processes = statusBar.getBackgroundProcesses();
127               if (!processes.isEmpty()) return;
128             }
129
130             final Task task = studyState.getTask();
131             final VirtualFile taskDir = studyState.getTaskDir();
132             flushWindows(task, taskDir);
133             final StudyRunAction runAction = (StudyRunAction)ActionManager.getInstance().getAction(StudyRunAction.ACTION_ID);
134             if (runAction == null) {
135               return;
136             }
137             runAction.run(project);
138             ApplicationManager.getApplication().invokeLater(new Runnable() {
139               @Override
140               public void run() {
141                 IdeFocusManager.getInstance(project).requestFocus(studyState.getEditor().getComponent(), true);
142               }
143             });
144
145             final StudyTestRunner testRunner = StudyUtils.getTestRunner(task, taskDir);
146             Process testProcess = null;
147             try {
148               final VirtualFile executablePath = getTaskVirtualFile(studyState, task, taskDir);
149               if (executablePath != null) {
150                 testProcess = testRunner.createCheckProcess(project, executablePath.getPath());
151               }
152             }
153             catch (ExecutionException e) {
154               LOG.error(e);
155             }
156             if (testProcess == null) {
157               return;
158             }
159             checkInProgress = true;
160             ProgressManager.getInstance().run(getCheckTask(studyState, testRunner, testProcess, project, selectedEditor));
161           }
162         });
163       }
164
165       @Nullable
166       private VirtualFile getTaskVirtualFile(@NotNull final StudyState studyState,
167                                              @NotNull final Task task,
168                                              @NotNull final VirtualFile taskDir) {
169         VirtualFile taskVirtualFile = studyState.getVirtualFile();
170         for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
171           String name = entry.getKey();
172           TaskFile taskFile = entry.getValue();
173           VirtualFile virtualFile = taskDir.findChild(name);
174           if (virtualFile != null) {
175             if (!taskFile.getAnswerPlaceholders().isEmpty()) {
176               taskVirtualFile = virtualFile;
177             }
178           }
179         }
180         return taskVirtualFile;
181       }
182     });
183   }
184
185   @NotNull
186   private com.intellij.openapi.progress.Task.Backgroundable getCheckTask(final StudyState studyState,
187                                                                          final StudyTestRunner testRunner,
188                                                                          final Process testProcess,
189                                                                          @NotNull final Project project,
190                                                                          final StudyEditor selectedEditor) {
191     final Task task = studyState.getTask();
192     final VirtualFile taskDir = studyState.getTaskDir();
193
194     final StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
195     final StudyStatus statusBeforeCheck = taskManager.getStatus(task);
196     return new com.intellij.openapi.progress.Task.Backgroundable(project, "Checking Task", true) {
197       @Override
198       public void onSuccess() {
199         StudyUtils.updateToolWindows(project);
200         drawAllPlaceholders(project, task, taskDir);
201         ProjectView.getInstance(project).refresh();
202         EduUtils.deleteWindowDescriptions(task, taskDir);
203         checkInProgress = false;
204       }
205
206       @Override
207       public void onCancel() {
208         taskManager.setStatus(task, statusBeforeCheck);
209         EduUtils.deleteWindowDescriptions(task, taskDir);
210         checkInProgress = false;
211       }
212
213       @Override
214       public void run(@NotNull ProgressIndicator indicator) {
215         final Map<String, TaskFile> taskFiles = task.getTaskFiles();
216         final CapturingProcessHandler handler = new CapturingProcessHandler(testProcess);
217         final ProcessOutput output = handler.runProcessWithProgressIndicator(indicator);
218         if (indicator.isCanceled()) {
219           ApplicationManager.getApplication().invokeLater(new Runnable() {
220             @Override
221             public void run() {
222               showTestResultPopUp("Tests check cancelled.", MessageType.WARNING.getPopupBackground(), project);
223             }
224           });
225           return;
226         }
227         final StudyTestRunner.TestsOutput testsOutput = testRunner.getTestsOutput(output);
228         final StudySettings studySettings = StudySettings.getInstance();
229
230         final String login = studySettings.getLogin();
231         final String password = StringUtil.isEmptyOrSpaces(login) ? "" : studySettings.getPassword();
232         if (testsOutput.isSuccess()) {
233           taskManager.setStatus(task, StudyStatus.Solved);
234           EduStepicConnector.postAttempt(task, true, login, password);
235           ApplicationManager.getApplication().invokeLater(new Runnable() {
236             @Override
237             public void run() {
238               showTestResultPopUp(testsOutput.getMessage(), MessageType.INFO.getPopupBackground(), project);
239             }
240           });
241         }
242         else {
243           ApplicationManager.getApplication().invokeLater(new Runnable() {
244             @Override
245             public void run() {
246               if (taskDir == null) return;
247               EduStepicConnector.postAttempt(task, false, login, password);
248               taskManager.setStatus(task, StudyStatus.Failed);
249               for (Map.Entry<String, TaskFile> entry : taskFiles.entrySet()) {
250                 final String name = entry.getKey();
251                 final TaskFile taskFile = entry.getValue();
252                 if (taskFile.getAnswerPlaceholders().size() < 2) {
253                   taskManager.setStatus(taskFile, StudyStatus.Failed);
254                   continue;
255                 }
256                 CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() {
257                   @Override
258                   public void run() {
259                     ApplicationManager.getApplication().runWriteAction(new Runnable() {
260                       @Override
261                       public void run() {
262                         runSmartTestProcess(taskDir, testRunner, name, taskFile, project);
263                       }
264                     });
265                   }
266                 });
267               }
268               showTestResultPopUp(testsOutput.getMessage(), MessageType.ERROR.getPopupBackground(), project);
269               navigateToFailedPlaceholder(studyState, task, taskDir, project);
270             }
271           });
272         }
273       }
274     };
275   }
276
277   private static void navigateToFailedPlaceholder(@NotNull final StudyState studyState,
278                                                   @NotNull final Task task,
279                                                   @NotNull final VirtualFile taskDir,
280                                                   @NotNull final Project project) {
281     TaskFile selectedTaskFile = studyState.getTaskFile();
282     Editor editor = studyState.getEditor();
283     TaskFile taskFileToNavigate = selectedTaskFile;
284     VirtualFile fileToNavigate = studyState.getVirtualFile();
285     final StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
286     if (!taskManager.hasFailedAnswerPlaceholders(selectedTaskFile)) {
287       for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
288         String name = entry.getKey();
289         TaskFile taskFile = entry.getValue();
290         if (taskManager.hasFailedAnswerPlaceholders(taskFile)) {
291           taskFileToNavigate = taskFile;
292           VirtualFile virtualFile = taskDir.findChild(name);
293           if (virtualFile == null) {
294             continue;
295           }
296           FileEditor fileEditor = FileEditorManager.getInstance(project).getSelectedEditor(virtualFile);
297           if (fileEditor instanceof StudyEditor) {
298             StudyEditor studyEditor = (StudyEditor)fileEditor;
299             editor = studyEditor.getEditor();
300           }
301           fileToNavigate = virtualFile;
302           break;
303         }
304       }
305     }
306     if (fileToNavigate != null) {
307       FileEditorManager.getInstance(project).openFile(fileToNavigate, true);
308     }
309     final Editor editorToNavigate = editor;
310     ApplicationManager.getApplication().invokeLater(new Runnable() {
311       @Override
312       public void run() {
313         IdeFocusManager.getInstance(project).requestFocus(editorToNavigate.getContentComponent(), true);
314       }
315     });
316
317     StudyNavigator.navigateToFirstFailedAnswerPlaceholder(editor, taskFileToNavigate);
318   }
319
320   private void runSmartTestProcess(@NotNull final VirtualFile taskDir,
321                                    @NotNull final StudyTestRunner testRunner,
322                                    final String taskFileName,
323                                    @NotNull final TaskFile taskFile,
324                                    @NotNull final Project project) {
325     final TaskFile answerTaskFile = new TaskFile();
326     answerTaskFile.name = taskFileName;
327     final VirtualFile virtualFile = taskDir.findChild(taskFileName);
328     if (virtualFile == null) {
329       return;
330     }
331     final VirtualFile answerFile = getCopyWithAnswers(taskDir, virtualFile, taskFile, answerTaskFile);
332     for (final AnswerPlaceholder answerPlaceholder : answerTaskFile.getAnswerPlaceholders()) {
333       final Document document = FileDocumentManager.getInstance().getDocument(virtualFile);
334       if (document == null) {
335         continue;
336       }
337       if (!answerPlaceholder.isValid(document)) {
338         continue;
339       }
340       StudySmartChecker.smartCheck(answerPlaceholder, project, answerFile, answerTaskFile, taskFile, testRunner,
341                                    virtualFile, document);
342     }
343     StudyUtils.deleteFile(answerFile);
344   }
345
346   private VirtualFile getCopyWithAnswers(@NotNull final VirtualFile taskDir,
347                                          @NotNull final VirtualFile file,
348                                          @NotNull final TaskFile source,
349                                          @NotNull final TaskFile target) {
350     VirtualFile copy = null;
351     try {
352
353       copy = file.copy(this, taskDir, file.getNameWithoutExtension() + ANSWERS_POSTFIX + "." + file.getExtension());
354       final FileDocumentManager documentManager = FileDocumentManager.getInstance();
355       final Document document = documentManager.getDocument(copy);
356       if (document != null) {
357         TaskFile.copy(source, target);
358         EduDocumentListener listener = new EduDocumentListener(target);
359         document.addDocumentListener(listener);
360         for (AnswerPlaceholder answerPlaceholder : target.getAnswerPlaceholders()) {
361           if (!answerPlaceholder.isValid(document)) {
362             continue;
363           }
364           final int start = answerPlaceholder.getRealStartOffset(document);
365           final int end = start + answerPlaceholder.getLength();
366           final String text = answerPlaceholder.getPossibleAnswer();
367           document.replaceString(start, end, text);
368         }
369         ApplicationManager.getApplication().runWriteAction(new Runnable() {
370           @Override
371           public void run() {
372             documentManager.saveDocument(document);
373           }
374         });
375       }
376     }
377     catch (IOException e) {
378       LOG.error(e);
379     }
380     return copy;
381   }
382
383   private static void showTestResultPopUp(final String text, Color color, @NotNull final Project project) {
384     BalloonBuilder balloonBuilder =
385       JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(text, null, color, null);
386     final Balloon balloon = balloonBuilder.createBalloon();
387     StudyUtils.showCheckPopUp(project, balloon);
388   }
389
390   @Override
391   public void actionPerformed(@NotNull AnActionEvent e) {
392     Project project = e.getProject();
393     if (project != null) {
394       check(project);
395     }
396   }
397
398   @Override
399   public void update(AnActionEvent e) {
400     final Presentation presentation = e.getPresentation();
401     StudyUtils.updateAction(e);
402     if (presentation.isEnabled()) {
403       presentation.setEnabled(!checkInProgress);
404     }
405   }
406 }