From 3aff09380ddbd2da9187bb373eb36863e9d53dcb Mon Sep 17 00:00:00 2001 From: meanmail Date: Sat, 8 Apr 2017 09:12:32 +0700 Subject: [PATCH] Navigation to a target task --- .../builtInServer/EduProjectCreator.java | 9 +- .../builtInServer/StepikRestService.java | 18 ++- .../edu/learning/builtInServer/Utils.java | 104 +++++++++++++----- .../learning/navigation/StudyNavigator.java | 7 +- .../PyStudyDirectoryProjectGenerator.java | 15 ++- .../BuiltInServerNewProjectStep.java | 11 +- .../EduPythonProjectCreator.java | 7 +- 7 files changed, 125 insertions(+), 46 deletions(-) diff --git a/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/EduProjectCreator.java b/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/EduProjectCreator.java index 224ff03ef6f2..3e71263cc13f 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/EduProjectCreator.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/EduProjectCreator.java @@ -17,8 +17,11 @@ package com.jetbrains.edu.learning.builtInServer; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.project.Project; +import com.intellij.util.Consumer; import com.jetbrains.edu.learning.courseFormat.Course; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * @author meanmail @@ -26,19 +29,19 @@ import org.jetbrains.annotations.NotNull; public class EduProjectCreator { public static final ExtensionPointName EP_NAME = ExtensionPointName.create("Edu.eduProjectCreator"); - public static boolean createProject(@NotNull Course course) { + public static boolean createProject(@NotNull Course course, @Nullable Consumer callback) { EduProjectCreator[] extensions = Extensions.getExtensions(EP_NAME); for (EduProjectCreator projectCreator : extensions) { if (projectCreator.canCreateProject(course)) { - return projectCreator.createCourseProject(course); + return projectCreator.createCourseProject(course, callback); } } return false; } - public boolean createCourseProject(@NotNull Course course) { + public boolean createCourseProject(@NotNull Course course, @Nullable Consumer callback) { return false; } diff --git a/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/StepikRestService.java b/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/StepikRestService.java index 50c51dd0de1e..f07ec33d9163 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/StepikRestService.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/StepikRestService.java @@ -26,6 +26,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.ide.RestService; import java.io.IOException; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -37,7 +38,8 @@ import static com.jetbrains.edu.learning.builtInServer.Utils.*; public class StepikRestService extends RestService { private static final Logger LOG = Logger.getInstance(StepikRestService.class.getName()); private static final String SERVICE_NAME = "edu/stepik"; - private static final Pattern OPEN_COURSE = Pattern.compile("/" + SERVICE_NAME + "/course/[^/]*-(\\d+)(?:$|\\?|/.*)"); + public static final String STEP_ID = "step_id"; + private static final Pattern OPEN_COURSE = Pattern.compile("/" + SERVICE_NAME + "/course/(\\d+)"); @NotNull @Override @@ -59,15 +61,23 @@ public class StepikRestService extends RestService { @Override public String execute(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) throws IOException { - LOG.info("Request: " + urlDecoder.path()); + LOG.info("Request: " + urlDecoder.uri()); String path = urlDecoder.path(); Matcher matcher = OPEN_COURSE.matcher(path); if (matcher.matches()) { int courseId = Integer.parseInt(matcher.group(1)); - LOG.info("Open course: " + courseId); + List stepIds = urlDecoder.parameters().get(STEP_ID); + int stepId = 0; + if (stepIds != null && !stepIds.isEmpty()) { + try { + stepId = Integer.parseInt(stepIds.get(0)); + } catch (NumberFormatException ignored) { + } + } + LOG.info(String.format("Try to open a course: courseId=%s, stepId=%s", courseId, stepId)); - if (findOpenProjectAndFocus(courseId) || findRecentProjectAndOpen(courseId) || createProjectAndOpen(courseId)) { + if (focusOpenProject(courseId, stepId) || openRecentProject(courseId, stepId) || createProject(courseId, stepId)) { RestService.sendOk(request, context); LOG.info("Course opened: " + courseId); return null; diff --git a/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/Utils.java b/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/Utils.java index 00fb0156c556..40f1f319827d 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/Utils.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/Utils.java @@ -22,11 +22,12 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.TransactionGuard; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; -import com.intellij.openapi.wm.IdeFrame; -import com.intellij.openapi.wm.WindowManager; +import com.intellij.util.Consumer; import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.courseFormat.Course; +import com.jetbrains.edu.learning.courseFormat.Lesson; import com.jetbrains.edu.learning.courseFormat.RemoteCourse; +import com.jetbrains.edu.learning.courseFormat.tasks.Task; import com.jetbrains.edu.learning.intellij.generation.EduProjectGenerator; import org.jdom.Document; import org.jdom.Element; @@ -35,11 +36,13 @@ import org.jdom.input.SAXBuilder; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.awt.*; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Optional; + +import static com.jetbrains.edu.learning.navigation.StudyNavigator.navigateToTask; /** * @author meanmail @@ -47,43 +50,43 @@ import java.util.List; public class Utils { public static final String STUDY_PROJECT_XML_PATH = "/.idea/study_project.xml"; - public static boolean findOpenProjectAndFocus(int courseId) { + public static boolean focusOpenProject(int courseId, int stepId) { Project[] openProjects = ProjectManager.getInstance().getOpenProjects(); for (Project project : openProjects) { - StudyTaskManager taskManager = StudyTaskManager.getInstance(project); - if (taskManager != null) { - Course course = taskManager.getCourse(); - RemoteCourse remoteCourse = course instanceof RemoteCourse ? (RemoteCourse)course : null; - if (remoteCourse != null && remoteCourse.getId() == courseId) { - ApplicationManager.getApplication().invokeLater(() -> requestFocus(project)); - return true; + if (!project.isDefault()) { + StudyTaskManager taskManager = StudyTaskManager.getInstance(project); + if (taskManager != null) { + Course course = taskManager.getCourse(); + RemoteCourse remoteCourse = course instanceof RemoteCourse ? (RemoteCourse)course : null; + if (remoteCourse != null && remoteCourse.getId() == courseId) { + ApplicationManager.getApplication().invokeLater(() -> { + requestFocus(project); + navigateToStep(project, course, stepId); + }); + return true; + } } } } return false; } - private static boolean openProject(@NotNull String projectPath) { - final boolean[] opened = {false}; + @Nullable + private static Project openProject(@NotNull String projectPath) { + final Project[] project = {null}; ApplicationManager.getApplication().invokeAndWait(() -> { - final Project[] project = new Project[1]; - TransactionGuard.getInstance().submitTransactionAndWait(() -> { - project[0] = ProjectUtil.openProject(projectPath, null, true); - opened[0] = project[0] != null; - }); + TransactionGuard.getInstance().submitTransactionAndWait(() -> + project[0] = ProjectUtil.openProject(projectPath, null, true)); requestFocus(project[0]); }); - return opened[0]; + return project[0]; } private static void requestFocus(@NotNull Project project) { - IdeFrame frame = WindowManager.getInstance().getIdeFrame(project); - if (frame instanceof Window) { - ((Window)frame).toFront(); - } + ProjectUtil.focusProjectWindow(project, false); } - public static boolean findRecentProjectAndOpen(int targetCourseId) { + public static boolean openRecentProject(int targetCourseId, int stepId) { RecentProjectsManagerBase recentProjectsManager; recentProjectsManager = (RecentProjectsManagerBase)RecentProjectsManager.getInstance(); @@ -99,8 +102,8 @@ public class Utils { List recentPaths = state.recentPaths; - Project project = ProjectManager.getInstance().getDefaultProject(); - StudyTaskManager taskManager = new StudyTaskManager(project); + Project defaultProject = ProjectManager.getInstance().getDefaultProject(); + StudyTaskManager taskManager = new StudyTaskManager(defaultProject); SAXBuilder parser = new SAXBuilder(); for (String projectPath : recentPaths) { @@ -110,8 +113,17 @@ public class Utils { } int courseId = getCourseId(taskManager, component); - if (courseId == targetCourseId && openProject(projectPath)) { - return true; + if (courseId == targetCourseId) { + Project project = openProject(projectPath); + if (project != null) { + Course course = taskManager.getCourse(); + if (course != null) { + ApplicationManager.getApplication().invokeLater(() -> + navigateToStep(project, course, stepId) + ); + } + return true; + } } } return false; @@ -146,7 +158,7 @@ public class Utils { return 0; } - public static boolean createProjectAndOpen(int courseId) { + public static boolean createProject(int courseId, int stepId) { EduProjectGenerator generator = new EduProjectGenerator(); Project defaultProject = ProjectManager.getInstance().getDefaultProject(); String title = "Getting Available Courses"; @@ -157,9 +169,41 @@ public class Utils { }); for (Course course : availableCourses) { if (course instanceof RemoteCourse && ((RemoteCourse)course).getId() == courseId) { - return EduProjectCreator.createProject(course); + Consumer callback = project -> + ApplicationManager.getApplication().invokeLater(() -> { + StudyTaskManager taskManager = StudyTaskManager.getInstance(project); + Course targetCourse = taskManager.getCourse(); + if (targetCourse != null) { + navigateToStep(project, targetCourse, stepId); + } + }); + return EduProjectCreator.createProject(course, callback); } } return false; } + + private static void navigateToStep(@NotNull Project project, @NotNull Course course, int stepId) { + if (stepId == 0 || course.isAdaptive()) { + return; + } + Task task = getTask(course, stepId); + if (task != null) { + navigateToTask(project, task); + } + } + + @Nullable + private static Task getTask(@NotNull Course course, int stepId) { + List lessons = course.getLessons(); + for (Lesson lesson : lessons) { + Optional optionalTask = lesson.getTaskList().stream() + .filter(task -> task.getStepId() == stepId) + .findFirst(); + if (optionalTask.isPresent()) { + return optionalTask.get(); + } + } + return null; + } } diff --git a/python/educational-core/src/com/jetbrains/edu/learning/navigation/StudyNavigator.java b/python/educational-core/src/com/jetbrains/edu/learning/navigation/StudyNavigator.java index 85b709a9dc63..d5b42cd248eb 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/navigation/StudyNavigator.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/navigation/StudyNavigator.java @@ -1,6 +1,7 @@ package com.jetbrains.edu.learning.navigation; import com.intellij.ide.projectView.ProjectView; +import com.intellij.ide.projectView.impl.AbstractProjectViewPane; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.FileEditorManager; @@ -190,7 +191,11 @@ public class StudyNavigator { } private static void updateProjectView(@NotNull Project project, @NotNull VirtualFile fileToActivate) { - JTree tree = ProjectView.getInstance(project).getCurrentProjectViewPane().getTree(); + AbstractProjectViewPane viewPane = ProjectView.getInstance(project).getCurrentProjectViewPane(); + if (viewPane == null) { + return; + } + JTree tree = viewPane.getTree(); ProjectView.getInstance(project).selectCB(fileToActivate, fileToActivate, false).doWhenDone(() -> { List paths = TreeUtil.collectExpandedPaths(tree); List toCollapse = new ArrayList<>(); diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java index 7b4b40317bea..297984784aa8 100644 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java +++ b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java @@ -23,6 +23,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiManager; import com.intellij.util.BooleanFunction; +import com.intellij.util.Consumer; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseFormat.RemoteCourse; @@ -56,15 +57,17 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator myCallback; public ValidationResult myValidationResult = new ValidationResult("selected course is not valid"); - private StudyNewProjectPanel mySettingsPanel; + private final StudyNewProjectPanel mySettingsPanel; @SuppressWarnings("unused") // used on startup public PyStudyDirectoryProjectGenerator() { this(false); } - public PyStudyDirectoryProjectGenerator(boolean isLocal) { + public PyStudyDirectoryProjectGenerator(boolean isLocal, @Nullable Consumer callback) { + myCallback = callback; myGenerator = new StudyProjectGenerator(); myGenerator.addSettingsStateListener(new StudyProjectGenerator.SettingsListener() { @Override @@ -111,6 +114,10 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator callback) { + super(new BuiltInServerNewProjectStep.MyCustomization(course, callback)); } protected static class MyCustomization extends PyCharmNewProjectStep.Customization { private final Course myCourse; private final PyStudyDirectoryProjectGenerator myGenerator; - public MyCustomization(@NotNull Course course) { + public MyCustomization(@NotNull Course course, @Nullable Consumer callback) { myCourse = course; - myGenerator = new PyStudyDirectoryProjectGenerator(true); + myGenerator = new PyStudyDirectoryProjectGenerator(true, callback); } @NotNull diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/builtInServer/EduPythonProjectCreator.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/builtInServer/EduPythonProjectCreator.java index cac9d2333c32..405f23416948 100644 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/builtInServer/EduPythonProjectCreator.java +++ b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/builtInServer/EduPythonProjectCreator.java @@ -18,9 +18,12 @@ package com.jetbrains.edu.learning.builtInServer; import com.intellij.ide.util.projectWizard.AbstractNewProjectDialog; import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.util.Consumer; import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.python.PythonLanguage; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * @author meanmail @@ -32,12 +35,12 @@ public class EduPythonProjectCreator extends EduProjectCreator { } @Override - public boolean createCourseProject(@NotNull Course course) { + public boolean createCourseProject(@NotNull Course course, @Nullable Consumer callback) { ApplicationManager.getApplication().invokeAndWait(() -> { AbstractNewProjectDialog dlg = new AbstractNewProjectDialog() { @Override protected DefaultActionGroup createRootStep() { - return new BuiltInServerNewProjectStep(course); + return new BuiltInServerNewProjectStep(course, callback); } }; dlg.show();