Navigation to a target task

This commit is contained in:
meanmail
2017-04-08 09:12:32 +07:00
parent a2aecb5daf
commit 3aff09380d
7 changed files with 125 additions and 46 deletions
@@ -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<EduProjectCreator> EP_NAME = ExtensionPointName.create("Edu.eduProjectCreator");
public static boolean createProject(@NotNull Course course) {
public static boolean createProject(@NotNull Course course, @Nullable Consumer<Project> 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<Project> callback) {
return false;
}
@@ -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<String> 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;
@@ -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<String> 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<Project> 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<Lesson> lessons = course.getLessons();
for (Lesson lesson : lessons) {
Optional<Task> optionalTask = lesson.getTaskList().stream()
.filter(task -> task.getStepId() == stepId)
.findFirst();
if (optionalTask.isPresent()) {
return optionalTask.get();
}
}
return null;
}
}
@@ -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<TreePath> paths = TreeUtil.collectExpandedPaths(tree);
List<TreePath> toCollapse = new ArrayList<>();
@@ -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<PyN
private static final Logger LOG = Logger.getInstance(PyStudyDirectoryProjectGenerator.class.getName());
private final StudyProjectGenerator myGenerator;
private static final String NO_PYTHON_INTERPRETER = "<html><u>Add</u> python interpreter.</html>";
private final Consumer<Project> 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<Project> callback) {
myCallback = callback;
myGenerator = new StudyProjectGenerator();
myGenerator.addSettingsStateListener(new StudyProjectGenerator.SettingsListener() {
@Override
@@ -111,6 +114,10 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator<PyN
});
}
public PyStudyDirectoryProjectGenerator(boolean isLocal) {
this(isLocal, null);
}
@Nls
@NotNull
@Override
@@ -142,6 +149,10 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator<PyN
catch (Exception exception) {
LOG.error("Can't copy test_helper.py " + exception.getMessage());
}
if (myCallback != null) {
myCallback.consume(project);
}
}
@NotNull
@@ -15,12 +15,15 @@
*/
package com.jetbrains.edu.learning.builtInServer;
import com.intellij.openapi.project.Project;
import com.intellij.platform.DirectoryProjectGenerator;
import com.intellij.util.Consumer;
import com.jetbrains.edu.learning.PyStudyDirectoryProjectGenerator;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
import com.jetbrains.python.newProject.steps.PyCharmNewProjectStep;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
@@ -28,17 +31,17 @@ import java.util.ArrayList;
* @author meanmail
*/
public class BuiltInServerNewProjectStep extends PyCharmNewProjectStep {
public BuiltInServerNewProjectStep(@NotNull Course course) {
super(new BuiltInServerNewProjectStep.MyCustomization(course));
public BuiltInServerNewProjectStep(@NotNull Course course, @Nullable Consumer<Project> 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<Project> callback) {
myCourse = course;
myGenerator = new PyStudyDirectoryProjectGenerator(true);
myGenerator = new PyStudyDirectoryProjectGenerator(true, callback);
}
@NotNull
@@ -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<Project> callback) {
ApplicationManager.getApplication().invokeAndWait(() -> {
AbstractNewProjectDialog dlg = new AbstractNewProjectDialog() {
@Override
protected DefaultActionGroup createRootStep() {
return new BuiltInServerNewProjectStep(course);
return new BuiltInServerNewProjectStep(course, callback);
}
};
dlg.show();