1 package com.jetbrains.edu.learning;
3 import com.intellij.ide.ui.UISettings;
4 import com.intellij.notification.Notification;
5 import com.intellij.notification.NotificationListener;
6 import com.intellij.notification.NotificationType;
7 import com.intellij.openapi.actionSystem.*;
8 import com.intellij.openapi.actionSystem.ex.AnActionListener;
9 import com.intellij.openapi.application.ApplicationManager;
10 import com.intellij.openapi.components.ProjectComponent;
11 import com.intellij.openapi.diagnostic.Logger;
12 import com.intellij.openapi.editor.EditorFactory;
13 import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
14 import com.intellij.openapi.keymap.Keymap;
15 import com.intellij.openapi.keymap.ex.KeymapManagerEx;
16 import com.intellij.openapi.module.Module;
17 import com.intellij.openapi.module.ModuleManager;
18 import com.intellij.openapi.progress.ProgressManager;
19 import com.intellij.openapi.project.DumbAwareRunnable;
20 import com.intellij.openapi.project.Project;
21 import com.intellij.openapi.util.Pair;
22 import com.intellij.openapi.util.io.FileUtil;
23 import com.intellij.openapi.vfs.VirtualFile;
24 import com.intellij.openapi.vfs.VirtualFileAdapter;
25 import com.intellij.openapi.vfs.VirtualFileEvent;
26 import com.intellij.openapi.vfs.VirtualFileManager;
27 import com.intellij.openapi.wm.ToolWindow;
28 import com.intellij.openapi.wm.ToolWindowAnchor;
29 import com.intellij.openapi.wm.ToolWindowManager;
30 import com.intellij.util.containers.hash.HashMap;
31 import com.jetbrains.edu.learning.actions.StudyActionWithShortcut;
32 import com.jetbrains.edu.learning.actions.StudyNextWindowAction;
33 import com.jetbrains.edu.learning.actions.StudyPrevWindowAction;
34 import com.jetbrains.edu.learning.core.EduNames;
35 import com.jetbrains.edu.learning.core.EduUtils;
36 import com.jetbrains.edu.learning.courseFormat.*;
37 import com.jetbrains.edu.learning.editor.StudyEditorFactoryListener;
38 import com.jetbrains.edu.learning.statistics.EduUsagesCollector;
39 import com.jetbrains.edu.learning.stepic.CourseInfo;
40 import com.jetbrains.edu.learning.stepic.EduStepicConnector;
41 import com.jetbrains.edu.learning.ui.StudyToolWindow;
42 import com.jetbrains.edu.learning.ui.StudyToolWindowFactory;
43 import javafx.application.Platform;
44 import org.jetbrains.annotations.NotNull;
45 import org.jetbrains.annotations.Nullable;
48 import javax.swing.event.HyperlinkEvent;
50 import java.io.IOException;
51 import java.util.ArrayList;
52 import java.util.List;
55 import static com.jetbrains.edu.learning.StudyUtils.execCancelable;
56 import static com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator.flushCourse;
59 public class StudyProjectComponent implements ProjectComponent {
60 private static final Logger LOG = Logger.getInstance(StudyProjectComponent.class.getName());
61 private final Project myProject;
62 private FileCreatedByUserListener myListener;
63 private Map<Keymap, List<Pair<String, String>>> myDeletedShortcuts = new HashMap<Keymap, List<Pair<String, String>>>();
64 private StudyProjectComponent(@NotNull final Project project) {
69 public void projectOpened() {
70 final Course course = StudyTaskManager.getInstance(myProject).getCourse();
71 // Check if user has javafx lib in his JDK. Now bundled JDK doesn't have this lib inside.
72 if (StudyUtils.hasJavaFx()) {
73 Platform.setImplicitExit(false);
76 if (course != null && !course.isUpToDate()) {
77 final Notification notification =
78 new Notification("Update.course", "Course Updates", "Course is ready to <a href=\"update\">update</a>", NotificationType.INFORMATION,
79 new NotificationListener() {
81 public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
82 FileEditorManagerEx.getInstanceEx(myProject).closeAllFiles();
84 ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
85 ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true);
86 return execCancelable(() -> {
90 }, "Updating Course", true, myProject);
91 EduUtils.synchronize();
96 notification.notify(myProject);
100 registerStudyToolWindow(course);
101 ApplicationManager.getApplication().invokeLater(new DumbAwareRunnable() {
104 ApplicationManager.getApplication().runWriteAction(new DumbAwareRunnable() {
107 if (course != null) {
108 final UISettings instance = UISettings.getInstance();
109 if (instance != null) {
110 instance.HIDE_TOOL_STRIPES = false;
111 instance.fireUISettingsChanged();
114 EduUsagesCollector.projectTypeOpened(course.isAdaptive() ? EduNames.ADAPTIVE : EduNames.STUDY);
122 public void registerStudyToolWindow(@Nullable final Course course) {
123 if (course != null && "PyCharm".equals(course.getCourseType())) {
124 final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject);
125 registerToolWindows(toolWindowManager);
126 final ToolWindow studyToolWindow = toolWindowManager.getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW);
127 if (studyToolWindow != null) {
128 studyToolWindow.show(null);
129 StudyUtils.initToolWindows(myProject);
134 private void registerShortcuts() {
135 StudyToolWindow window = StudyUtils.getStudyToolWindow(myProject);
136 if (window != null) {
137 List<AnAction> actionsOnToolbar = window.getActions(true);
138 if (actionsOnToolbar != null) {
139 for (AnAction action : actionsOnToolbar) {
140 if (action instanceof StudyActionWithShortcut) {
141 String id = ((StudyActionWithShortcut)action).getActionId();
142 String[] shortcuts = ((StudyActionWithShortcut)action).getShortcuts();
143 if (shortcuts != null) {
144 addShortcut(id, shortcuts);
148 addShortcut(StudyNextWindowAction.ACTION_ID, new String[]{StudyNextWindowAction.SHORTCUT, StudyNextWindowAction.SHORTCUT2});
149 addShortcut(StudyPrevWindowAction.ACTION_ID, new String[]{StudyPrevWindowAction.SHORTCUT});
152 LOG.warn("Actions on toolbar are nulls");
157 private void registerToolWindows(@NotNull final ToolWindowManager toolWindowManager) {
158 final ToolWindow toolWindow = toolWindowManager.getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW);
159 if (toolWindow == null) {
160 toolWindowManager.registerToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW, true, ToolWindowAnchor.RIGHT, myProject, true);
164 private void updateCourse() {
165 final Course currentCourse = StudyTaskManager.getInstance(myProject).getCourse();
166 final CourseInfo info = CourseInfo.fromCourse(currentCourse);
167 if (info == null) return;
169 final File resourceDirectory = new File(currentCourse.getCourseDirectory());
170 if (resourceDirectory.exists()) {
171 FileUtil.delete(resourceDirectory);
174 final Course course = EduStepicConnector.getCourse(myProject, info);
176 if (course == null) return;
177 flushCourse(myProject, course);
178 course.initCourse(false);
180 StudyLanguageManager manager = StudyUtils.getLanguageManager(course);
181 if (manager == null) {
182 LOG.info("Study Language Manager is null for " + course.getLanguageById().getDisplayName());
186 final ArrayList<Lesson> updatedLessons = new ArrayList<>();
189 for (Lesson lesson : course.getLessons()) {
191 Lesson studentLesson = currentCourse.getLesson(lesson.getId());
192 final String lessonDirName = EduNames.LESSON + String.valueOf(lessonIndex);
194 final File lessonDir = new File(myProject.getBasePath(), lessonDirName);
195 if (!lessonDir.exists()){
196 final File fromLesson = new File(resourceDirectory, lessonDirName);
198 FileUtil.copyDir(fromLesson, lessonDir);
200 catch (IOException e) {
201 LOG.warn("Failed to copy lesson " + fromLesson.getPath());
203 lesson.setIndex(lessonIndex);
204 lesson.initLesson(currentCourse, false);
205 for (int i = 1; i <= lesson.getTaskList().size(); i++) {
206 Task task = lesson.getTaskList().get(i - 1);
209 updatedLessons.add(lesson);
212 studentLesson.setIndex(lessonIndex);
213 updatedLessons.add(studentLesson);
216 final ArrayList<Task> tasks = new ArrayList<>();
217 for (Task task : lesson.getTaskList()) {
219 final Task studentTask = studentLesson.getTask(task.getStepicId());
220 if (studentTask != null && StudyStatus.Solved.equals(studentTask.getStatus())) {
221 studentTask.setIndex(index);
222 tasks.add(studentTask);
225 task.initTask(studentLesson, false);
226 task.setIndex(index);
228 final String taskDirName = EduNames.TASK + String.valueOf(index);
229 final File toTask = new File(lessonDir, taskDirName);
231 final String taskPath = FileUtil.join(resourceDirectory.getPath(), lessonDirName, taskDirName);
232 final File taskDir = new File(taskPath);
233 if (!taskDir.exists()) return;
234 final File[] taskFiles = taskDir.listFiles();
235 if (taskFiles == null) continue;
236 for (File fromFile : taskFiles) {
237 copyFile(fromFile, new File(toTask, fromFile.getName()));
241 studentLesson.updateTaskList(tasks);
243 currentCourse.setLessons(updatedLessons);
245 final Notification notification =
246 new Notification("Update.course", "Course update", "Current course is synchronized", NotificationType.INFORMATION);
247 notification.notify(myProject);
250 private static void copyFile(@NotNull final File from, @NotNull final File to) {
253 FileUtil.copy(from, to);
255 catch (IOException e) {
256 LOG.warn("Failed to copy " + from.getName());
261 private void addShortcut(@NotNull final String actionIdString, @NotNull final String[] shortcuts) {
262 KeymapManagerEx keymapManager = KeymapManagerEx.getInstanceEx();
263 for (Keymap keymap : keymapManager.getAllKeymaps()) {
264 List<Pair<String, String>> pairs = myDeletedShortcuts.get(keymap);
266 pairs = new ArrayList<Pair<String, String>>();
267 myDeletedShortcuts.put(keymap, pairs);
269 for (String shortcutString : shortcuts) {
270 Shortcut studyActionShortcut = new KeyboardShortcut(KeyStroke.getKeyStroke(shortcutString), null);
271 String[] actionsIds = keymap.getActionIds(studyActionShortcut);
272 for (String actionId : actionsIds) {
273 pairs.add(Pair.create(actionId, shortcutString));
274 keymap.removeShortcut(actionId, studyActionShortcut);
276 keymap.addShortcut(actionIdString, studyActionShortcut);
282 public void projectClosed() {
283 final Course course = StudyTaskManager.getInstance(myProject).getCourse();
284 if (course != null) {
285 final ToolWindow toolWindow = ToolWindowManager.getInstance(myProject).getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW);
286 if (toolWindow != null) {
287 toolWindow.getContentManager().removeAllContents(false);
289 KeymapManagerEx keymapManager = KeymapManagerEx.getInstanceEx();
290 for (Keymap keymap : keymapManager.getAllKeymaps()) {
291 List<Pair<String, String>> pairs = myDeletedShortcuts.get(keymap);
292 if (pairs != null && !pairs.isEmpty()) {
293 for (Pair<String, String> actionShortcut : pairs) {
294 keymap.addShortcut(actionShortcut.first, new KeyboardShortcut(KeyStroke.getKeyStroke(actionShortcut.second), null));
303 public void initComponent() {
304 EditorFactory.getInstance().addEditorFactoryListener(new StudyEditorFactoryListener(), myProject);
305 ActionManager.getInstance().addAnActionListener(new AnActionListener() {
307 public void beforeActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
308 AnAction[] newGroupActions = ((ActionGroup)ActionManager.getInstance().getAction("NewGroup")).getChildren(null);
309 for (AnAction newAction : newGroupActions) {
310 if (newAction == action) {
311 myListener = new FileCreatedByUserListener();
312 VirtualFileManager.getInstance().addVirtualFileListener(myListener);
319 public void afterActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
320 AnAction[] newGroupActions = ((ActionGroup)ActionManager.getInstance().getAction("NewGroup")).getChildren(null);
321 for (AnAction newAction : newGroupActions) {
322 if (newAction == action) {
323 VirtualFileManager.getInstance().removeVirtualFileListener(myListener);
329 public void beforeEditorTyping(char c, DataContext dataContext) {
336 public void disposeComponent() {
341 public String getComponentName() {
342 return "StudyTaskManager";
345 public static StudyProjectComponent getInstance(@NotNull final Project project) {
346 final Module module = ModuleManager.getInstance(project).getModules()[0];
347 return module.getComponent(StudyProjectComponent.class);
350 private class FileCreatedByUserListener extends VirtualFileAdapter {
352 public void fileCreated(@NotNull VirtualFileEvent event) {
353 if (myProject.isDisposed()) return;
354 final VirtualFile createdFile = event.getFile();
355 final VirtualFile taskDir = createdFile.getParent();
356 final Course course = StudyTaskManager.getInstance(myProject).getCourse();
357 if (course == null || !EduNames.STUDY.equals(course.getCourseMode())) {
360 if (taskDir != null && taskDir.getName().contains(EduNames.TASK)) {
361 int taskIndex = EduUtils.getIndex(taskDir.getName(), EduNames.TASK);
362 final VirtualFile lessonDir = taskDir.getParent();
363 if (lessonDir != null && lessonDir.getName().contains(EduNames.LESSON)) {
364 int lessonIndex = EduUtils.getIndex(lessonDir.getName(), EduNames.LESSON);
365 List<Lesson> lessons = course.getLessons();
366 if (StudyUtils.indexIsValid(lessonIndex, lessons)) {
367 final Lesson lesson = lessons.get(lessonIndex);
368 final List<Task> tasks = lesson.getTaskList();
369 if (StudyUtils.indexIsValid(taskIndex, tasks)) {
370 final Task task = tasks.get(taskIndex);
371 final TaskFile taskFile = new TaskFile();
372 taskFile.initTaskFile(task, false);
373 taskFile.setUserCreated(true);
374 final String name = createdFile.getName();
375 taskFile.name = name;
376 //TODO: put to other steps as well
377 task.getTaskFiles().put(name, taskFile);