diff --git a/python/educational-core/educational-core.iml b/python/educational-core/educational-core.iml
index 058f88e071e4..811dc90aff11 100644
--- a/python/educational-core/educational-core.iml
+++ b/python/educational-core/educational-core.iml
@@ -33,5 +33,6 @@
+
\ No newline at end of file
diff --git a/python/educational-core/resources/META-INF/plugin.xml b/python/educational-core/resources/META-INF/plugin.xml
index 370643525c1d..5ebd039efd0c 100644
--- a/python/educational-core/resources/META-INF/plugin.xml
+++ b/python/educational-core/resources/META-INF/plugin.xml
@@ -173,6 +173,7 @@
+
diff --git a/python/educational-core/src/com/jetbrains/edu/learning/EduPluginConfigurator.java b/python/educational-core/src/com/jetbrains/edu/learning/EduPluginConfigurator.java
index 2dc554e97479..d29ff113de48 100644
--- a/python/educational-core/src/com/jetbrains/edu/learning/EduPluginConfigurator.java
+++ b/python/educational-core/src/com/jetbrains/edu/learning/EduPluginConfigurator.java
@@ -14,6 +14,7 @@ import com.intellij.openapi.module.StdModuleTypes;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
+import com.jetbrains.edu.learning.newproject.EduCourseProjectGenerator;
import com.jetbrains.edu.learning.actions.*;
import com.jetbrains.edu.learning.checker.StudyTaskChecker;
import com.jetbrains.edu.learning.core.EduNames;
@@ -138,5 +139,7 @@ public interface EduPluginConfigurator {
return Collections.emptyList();
}
+ EduCourseProjectGenerator getEduCourseProjectGenerator();
+
default ModuleType getModuleType() {return StdModuleTypes.JAVA;}
}
diff --git a/python/educational-core/src/com/jetbrains/edu/learning/StudyProjectComponent.java b/python/educational-core/src/com/jetbrains/edu/learning/StudyProjectComponent.java
index e2e84ae3ea67..16aa194a1ba4 100644
--- a/python/educational-core/src/com/jetbrains/edu/learning/StudyProjectComponent.java
+++ b/python/educational-core/src/com/jetbrains/edu/learning/StudyProjectComponent.java
@@ -21,6 +21,7 @@ import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.DumbAwareRunnable;
import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
@@ -56,13 +57,14 @@ import java.util.List;
import java.util.Map;
import static com.jetbrains.edu.learning.StudyUtils.execCancelable;
+import static com.jetbrains.edu.learning.StudyUtils.navigateToStep;
public class StudyProjectComponent implements ProjectComponent {
private static final Logger LOG = Logger.getInstance(StudyProjectComponent.class.getName());
private final Project myProject;
private FileCreatedByUserListener myListener;
- private Map>> myDeletedShortcuts = new HashMap<>();
+ private final Map>> myDeletedShortcuts = new HashMap<>();
private MessageBusConnection myBusConnection;
private StudyProjectComponent(@NotNull final Project project) {
@@ -103,6 +105,29 @@ public class StudyProjectComponent implements ProjectComponent {
}
}
});
+
+ selectStep();
+ }
+
+ private void selectStep() {
+ StartupManager.getInstance(myProject).runWhenProjectIsInitialized(() -> {
+ Project defaultProject = ProjectManager.getInstance().getDefaultProject();
+ if (myProject == defaultProject) {
+ return;
+ }
+ StudyTaskManager defaultTaskManager = StudyTaskManager.getInstance(defaultProject);
+ int stepId = defaultTaskManager.getStepId();
+
+ if (stepId != 0) {
+ StudyTaskManager taskManager = StudyTaskManager.getInstance(myProject);
+ Course course = taskManager.getCourse();
+ if (course != null) {
+
+ navigateToStep(myProject, course, stepId);
+ defaultTaskManager.setStepId(0);
+ }
+ }
+ });
}
private void updateAvailable(Course course) {
@@ -122,7 +147,6 @@ public class StudyProjectComponent implements ProjectComponent {
}, "Updating Course", true, myProject);
EduUtils.synchronize();
course.setUpdated();
-
}
});
notification.notify(myProject);
@@ -272,7 +296,7 @@ public class StudyProjectComponent implements ProjectComponent {
AnAction[] newGroupActions = ((ActionGroup)ActionManager.getInstance().getAction("NewGroup")).getChildren(null);
for (AnAction newAction : newGroupActions) {
if (newAction == action) {
- myListener = new FileCreatedByUserListener();
+ myListener = new FileCreatedByUserListener();
VirtualFileManager.getInstance().addVirtualFileListener(myListener);
break;
}
diff --git a/python/educational-core/src/com/jetbrains/edu/learning/StudyTaskManager.java b/python/educational-core/src/com/jetbrains/edu/learning/StudyTaskManager.java
index d64896ca4bb8..210dd11c6a87 100644
--- a/python/educational-core/src/com/jetbrains/edu/learning/StudyTaskManager.java
+++ b/python/educational-core/src/com/jetbrains/edu/learning/StudyTaskManager.java
@@ -46,15 +46,16 @@ public class StudyTaskManager implements PersistentStateComponent, Dumb
private static final Logger LOG = Logger.getInstance(StudyTaskManager.class);
public static final int CURRENT_VERSION = 5;
private Course myCourse;
- public int VERSION = 5;
+ public int VERSION = CURRENT_VERSION;
- public Map> myUserTests = new HashMap<>();
+ public final Map> myUserTests = new HashMap<>();
public boolean myShouldUseJavaFx = StudyUtils.hasJavaFx();
private StudyToolWindow.StudyToolWindowMode myToolWindowMode = StudyToolWindow.StudyToolWindowMode.TEXT;
private boolean myTurnEditingMode = false;
@Transient private final Project myProject;
+ @Transient private int myStepId;
public StudyTaskManager(Project project) {
myProject = project;
@@ -191,14 +192,18 @@ public class StudyTaskManager implements PersistentStateComponent, Dumb
private void updateTestHelper() {
StartupManager.getInstance(myProject).runWhenProjectIsInitialized(() -> ApplicationManager.getApplication().runWriteAction(() -> {
- final VirtualFile testHelper = myProject.getBaseDir().findChild(EduNames.TEST_HELPER);
+ final VirtualFile baseDir = myProject.getBaseDir();
+ if (baseDir == null) {
+ return;
+ }
+ final VirtualFile testHelper = baseDir.findChild(EduNames.TEST_HELPER);
if (testHelper != null) {
StudyUtils.deleteFile(testHelper);
}
final FileTemplate template =
FileTemplateManager.getInstance(myProject).getInternalTemplate(FileUtil.getNameWithoutExtension(EduNames.TEST_HELPER));
try {
- final PsiDirectory projectDir = PsiManager.getInstance(myProject).findDirectory(myProject.getBaseDir());
+ final PsiDirectory projectDir = PsiManager.getInstance(myProject).findDirectory(baseDir);
if (projectDir != null) {
FileTemplateUtil.createFromTemplate(template, EduNames.TEST_HELPER, null, projectDir);
}
@@ -211,8 +216,9 @@ public class StudyTaskManager implements PersistentStateComponent, Dumb
private void deserialize(Element state) throws StudySerializationUtils.StudyUnrecognizedFormatException {
final Element taskManagerElement = state.getChild(StudySerializationUtils.Xml.MAIN_ELEMENT);
- if (taskManagerElement == null)
+ if (taskManagerElement == null) {
throw new StudySerializationUtils.StudyUnrecognizedFormatException();
+ }
XmlSerializer.deserializeInto(this, taskManagerElement);
final Element xmlCourse = StudySerializationUtils.Xml.getChildWithName(taskManagerElement, StudySerializationUtils.COURSE);
final Element remoteCourseElement = xmlCourse.getChild(REMOTE_COURSE);
@@ -251,4 +257,11 @@ public class StudyTaskManager implements PersistentStateComponent, Dumb
myTurnEditingMode = turnEditingMode;
}
+ public void setStepId(int stepId) {
+ myStepId = stepId;
+ }
+
+ public int getStepId() {
+ return myStepId;
+ }
}
diff --git a/python/educational-core/src/com/jetbrains/edu/learning/StudyUtils.java b/python/educational-core/src/com/jetbrains/edu/learning/StudyUtils.java
index 46d3ca1b1fc2..0b1b6bcf557e 100644
--- a/python/educational-core/src/com/jetbrains/edu/learning/StudyUtils.java
+++ b/python/educational-core/src/com/jetbrains/edu/learning/StudyUtils.java
@@ -84,6 +84,8 @@ import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
+import static com.jetbrains.edu.learning.navigation.StudyNavigator.navigateToTask;
+
public class StudyUtils {
private StudyUtils() {
}
@@ -821,4 +823,25 @@ public class StudyUtils {
}
}
}
+
+ public 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) {
+ for (Lesson lesson : course.getLessons()) {
+ Task task = lesson.getTask(stepId);
+ if (task != null) {
+ return task;
+ }
+ }
+ return null;
+ }
}
diff --git a/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/EduBuiltInServerUtils.java b/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/EduBuiltInServerUtils.java
new file mode 100644
index 000000000000..32408c5262ae
--- /dev/null
+++ b/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/EduBuiltInServerUtils.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2000-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.edu.learning.builtInServer;
+
+import com.intellij.ide.RecentProjectsManagerBase;
+import com.intellij.ide.impl.ProjectUtil;
+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.util.xmlb.XmlSerializationException;
+import com.jetbrains.edu.learning.StudyTaskManager;
+import com.jetbrains.edu.learning.courseFormat.Course;
+import com.jetbrains.edu.learning.courseFormat.RemoteCourse;
+import com.jetbrains.edu.learning.newproject.ui.EduCreateNewProjectDialog;
+import com.jetbrains.edu.learning.newproject.ui.EduCreateNewStepikProjectDialog;
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jdom.JDOMException;
+import org.jdom.input.SAXBuilder;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import static com.jetbrains.edu.learning.StudyUtils.navigateToStep;
+import static com.jetbrains.edu.learning.core.EduNames.STUDY_PROJECT_XML_PATH;
+
+public class EduBuiltInServerUtils {
+ public static boolean focusOpenProject(int courseId, int stepId) {
+ Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
+ for (Project project : openProjects) {
+ 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;
+ }
+
+ @Nullable
+ private static Project openProject(@NotNull String projectPath) {
+ final Project[] project = {null};
+ ApplicationManager.getApplication().invokeAndWait(() -> {
+ TransactionGuard.getInstance().submitTransactionAndWait(() ->
+ project[0] = ProjectUtil.openProject(projectPath, null, true));
+ requestFocus(project[0]);
+ });
+ return project[0];
+ }
+
+ private static void requestFocus(@NotNull Project project) {
+ ProjectUtil.focusProjectWindow(project, false);
+ }
+
+ public static boolean openRecentProject(int targetCourseId, int stepId) {
+ RecentProjectsManagerBase recentProjectsManager = RecentProjectsManagerBase.getInstanceEx();
+
+ if (recentProjectsManager == null) {
+ return false;
+ }
+
+ RecentProjectsManagerBase.State state = recentProjectsManager.getState();
+
+ if (state == null) {
+ return false;
+ }
+
+ List recentPaths = state.recentPaths;
+
+ SAXBuilder parser = new SAXBuilder();
+
+ for (String projectPath : recentPaths) {
+ Element component = readComponent(parser, projectPath);
+ if (component == null) {
+ continue;
+ }
+ StudyTaskManager taskManager = getDefaultTaskManager();
+ int courseId = getCourseId(taskManager, component);
+
+ if (courseId == targetCourseId) {
+ taskManager.setStepId(stepId);
+ Project project = openProject(projectPath);
+ if (project != null) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+
+ @Nullable
+ private static Element readComponent(@NotNull SAXBuilder parser, @NotNull String projectPath) {
+ Element component = null;
+ try {
+ String studyProjectXML = projectPath + STUDY_PROJECT_XML_PATH;
+ Document xmlDoc = parser.build(new File(studyProjectXML));
+ Element root = xmlDoc.getRootElement();
+ component = root.getChild("component");
+ }
+ catch (JDOMException | IOException ignored) {
+ }
+
+ return component;
+ }
+
+ private static int getCourseId(@NotNull StudyTaskManager taskManager, @NotNull Element component) {
+ try {
+ taskManager.loadState(component);
+ Course course = taskManager.getCourse();
+
+ if (course instanceof RemoteCourse) {
+ return ((RemoteCourse)course).getId();
+ }
+ }
+ catch (IllegalStateException | XmlSerializationException ignored) {
+ }
+ return 0;
+ }
+
+ public static boolean createProject(int courseId, int stepId) {
+ ApplicationManager.getApplication().invokeLater(() -> {
+ getDefaultTaskManager().setStepId(stepId);
+ EduCreateNewProjectDialog createNewProjectDlg = new EduCreateNewStepikProjectDialog(courseId);
+ createNewProjectDlg.show();
+ });
+
+ return true;
+ }
+ @NotNull
+ private static StudyTaskManager getDefaultTaskManager() {
+ Project defaultProject = ProjectManager.getInstance().getDefaultProject();
+ return StudyTaskManager.getInstance(defaultProject);
+ }
+}
diff --git a/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/EduStepikRestService.java b/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/EduStepikRestService.java
new file mode 100644
index 000000000000..245797f796aa
--- /dev/null
+++ b/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/EduStepikRestService.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2000-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.edu.learning.builtInServer;
+
+import com.intellij.openapi.diagnostic.Logger;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http.QueryStringDecoder;
+import org.jetbrains.annotations.NotNull;
+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;
+
+import static com.jetbrains.edu.learning.builtInServer.EduBuiltInServerUtils.*;
+import static com.jetbrains.edu.learning.stepic.EduStepicNames.EDU_STEPIK_SERVICE_NAME;
+import static com.jetbrains.edu.learning.stepic.EduStepicNames.STEP_ID;
+
+public class EduStepikRestService extends RestService {
+ private static final Logger LOG = Logger.getInstance(EduStepikRestService.class.getName());
+ private static final Pattern OPEN_COURSE_PATTERN = Pattern.compile("/" + EDU_STEPIK_SERVICE_NAME + "/course/(\\d+)");
+
+ @NotNull
+ @Override
+ protected String getServiceName() {
+ return EDU_STEPIK_SERVICE_NAME;
+ }
+
+ @Override
+ protected boolean isMethodSupported(@NotNull HttpMethod method) {
+ return method == HttpMethod.GET;
+ }
+
+ @Override
+ protected boolean isPrefixlessAllowed() {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public String execute(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context)
+ throws IOException {
+ LOG.info("Request: " + urlDecoder.uri());
+
+ String path = urlDecoder.path();
+ Matcher matcher = OPEN_COURSE_PATTERN.matcher(path);
+ if (matcher.matches()) {
+ int courseId = Integer.parseInt(matcher.group(1));
+ List stepIds = urlDecoder.parameters().get(STEP_ID);
+ int stepId = 0;
+ if (stepIds != null && !stepIds.isEmpty()) {
+ String firstStepId = "";
+ try {
+ firstStepId = stepIds.get(0);
+ stepId = Integer.parseInt(firstStepId);
+ } catch (NumberFormatException e) {
+ LOG.warn("Wrong a request parameter: step_id=" + firstStepId, e);
+ }
+ }
+ LOG.info(String.format("Try to open a course: courseId=%s, stepId=%s", courseId, stepId));
+
+ if (focusOpenProject(courseId, stepId) || openRecentProject(courseId, stepId) || createProject(courseId, stepId)) {
+ RestService.sendOk(request, context);
+ LOG.info("Course opened: " + courseId);
+ return null;
+ }
+
+ RestService.sendStatus(HttpResponseStatus.NOT_FOUND, false, context.channel());
+ String message = "A project didn't found or created";
+ LOG.info(message);
+ return message;
+ }
+
+ RestService.sendStatus(HttpResponseStatus.BAD_REQUEST, false, context.channel());
+ String message = "Unknown command: " + path;
+ LOG.info(message);
+ return message;
+ }
+}
diff --git a/python/educational-core/src/com/jetbrains/edu/learning/core/EduNames.java b/python/educational-core/src/com/jetbrains/edu/learning/core/EduNames.java
index c2c35a63b217..db66e50205b5 100644
--- a/python/educational-core/src/com/jetbrains/edu/learning/core/EduNames.java
+++ b/python/educational-core/src/com/jetbrains/edu/learning/core/EduNames.java
@@ -50,6 +50,8 @@ public class EduNames {
public static final String SUBTASK_MARKER = "_subtask";
public static final String SUBTASK = "subtask";
+ public static final String STUDY_PROJECT_XML_PATH = "/.idea/study_project.xml";
+
private EduNames() {
}
diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseGeneration/StudyGenerator.java b/python/educational-core/src/com/jetbrains/edu/learning/courseGeneration/StudyGenerator.java
index 1fed74118ab7..d26e77110c00 100644
--- a/python/educational-core/src/com/jetbrains/edu/learning/courseGeneration/StudyGenerator.java
+++ b/python/educational-core/src/com/jetbrains/edu/learning/courseGeneration/StudyGenerator.java
@@ -41,7 +41,7 @@ public class StudyGenerator {
}
else {
String lessonDirName = EduNames.LESSON + Integer.toString(lesson.getIndex());
- VirtualFile lessonDir = courseDir.createChildDirectory(courseDir, lessonDirName);
+ VirtualFile lessonDir = VfsUtil.createDirectoryIfMissing(courseDir, lessonDirName);
final List taskList = lesson.getTaskList();
for (int i = 1; i <= taskList.size(); i++) {
Task task = taskList.get(i - 1);
@@ -52,11 +52,12 @@ public class StudyGenerator {
}
public static void createTask(@NotNull final Task task, @NotNull final VirtualFile lessonDir) throws IOException {
- VirtualFile taskDir = lessonDir.createChildDirectory(lessonDir, EduNames.TASK + Integer.toString(task.getIndex()));
+ String name = EduNames.TASK + Integer.toString(task.getIndex());
+ VirtualFile taskDir = VfsUtil.createDirectoryIfMissing(lessonDir, name);
createTaskContent(task, taskDir);
}
- public static void createTaskContent(@NotNull Task task, VirtualFile taskDir) throws IOException {
+ public static void createTaskContent(@NotNull Task task, @NotNull VirtualFile taskDir) throws IOException {
int i = 0;
for (Map.Entry taskFile : task.getTaskFiles().entrySet()) {
TaskFile taskFileContent = taskFile.getValue();
@@ -73,25 +74,28 @@ public class StudyGenerator {
createChildFile(taskDir, name, taskFile.text);
}
- private static void createDescriptions(VirtualFile taskDir, Task task) throws IOException {
+ private static void createDescriptions(@NotNull VirtualFile taskDir, @NotNull Task task) throws IOException {
final Map texts = task.getTaskTexts();
+ createFiles(taskDir, texts);
+ }
+
+ private static void createTestFiles(@NotNull VirtualFile taskDir, @NotNull Task task) throws IOException {
+ final Map tests = task.getTestsText();
+ createFiles(taskDir, tests);
+ }
+
+ private static void createFiles(@NotNull VirtualFile taskDir, @NotNull Map texts) throws IOException {
for (Map.Entry entry : texts.entrySet()) {
final String name = entry.getKey();
- final VirtualFile virtualTaskFile = taskDir.createChildData(taskDir, name);
+ VirtualFile virtualTaskFile = taskDir.findChild(name);
+ if (virtualTaskFile == null) {
+ virtualTaskFile = taskDir.createChildData(taskDir, name);
+ }
VfsUtil.saveText(virtualTaskFile, entry.getValue());
}
}
- private static void createTestFiles(VirtualFile taskDir, Task task) throws IOException {
- final Map tests = task.getTestsText();
- for (Map.Entry entry : tests.entrySet()) {
- final String name = entry.getKey();
- final VirtualFile virtualTaskFile = taskDir.createChildData(taskDir, name);
- VfsUtil.saveText(virtualTaskFile, entry.getValue());
- }
- }
-
- private static void createAdditionalFiles(Lesson lesson, VirtualFile courseDir) throws IOException {
+ private static void createAdditionalFiles(@NotNull Lesson lesson, @NotNull VirtualFile courseDir) throws IOException {
final List taskList = lesson.getTaskList();
if (taskList.size() != 1) return;
final Task task = taskList.get(0);
@@ -100,8 +104,7 @@ public class StudyGenerator {
}
}
-
- public static void createChildFile(@NotNull VirtualFile taskDir, String name, String text) throws IOException {
+ public static void createChildFile(@NotNull VirtualFile taskDir, @NotNull String name, @NotNull String text) throws IOException {
String newDirectories = null;
String fileName = name;
VirtualFile dir = taskDir;
@@ -114,7 +117,10 @@ public class StudyGenerator {
dir = VfsUtil.createDirectoryIfMissing(taskDir, newDirectories);
}
if (dir != null) {
- final VirtualFile virtualTaskFile = dir.createChildData(taskDir, fileName);
+ VirtualFile virtualTaskFile = dir.findChild(fileName);
+ if (virtualTaskFile == null) {
+ virtualTaskFile = dir.createChildData(taskDir, fileName);
+ }
if (EduUtils.isImage(name)) {
virtualTaskFile.setBinaryContent(Base64.decodeBase64(text));
}
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-core/src/com/jetbrains/edu/learning/newproject/EduCourseProjectGenerator.java b/python/educational-core/src/com/jetbrains/edu/learning/newproject/EduCourseProjectGenerator.java
new file mode 100644
index 000000000000..0a0b04f5c09d
--- /dev/null
+++ b/python/educational-core/src/com/jetbrains/edu/learning/newproject/EduCourseProjectGenerator.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2000-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.edu.learning.newproject;
+
+import com.intellij.facet.ui.ValidationResult;
+import com.intellij.openapi.project.Project;
+import com.intellij.platform.DirectoryProjectGenerator;
+import com.jetbrains.edu.learning.courseFormat.Course;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public interface EduCourseProjectGenerator {
+ @NotNull
+ DirectoryProjectGenerator getDirectoryProjectGenerator();
+
+ @Nullable
+ Object getProjectSettings();
+
+ void setCourse(@NotNull Course course);
+
+ ValidationResult validate();
+
+ boolean beforeProjectGenerated();
+
+ void afterProjectGenerated(@NotNull Project project);
+}
diff --git a/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectDialog.java b/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectDialog.java
new file mode 100644
index 000000000000..9d7c42ca66fe
--- /dev/null
+++ b/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectDialog.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2000-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.edu.learning.newproject.ui;
+
+import com.intellij.facet.ui.ValidationResult;
+import com.intellij.ide.RecentProjectsManager;
+import com.intellij.internal.statistic.UsageTrigger;
+import com.intellij.internal.statistic.beans.ConvertUsagesUtil;
+import com.intellij.lang.Language;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManager;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.platform.DirectoryProjectGenerator;
+import com.intellij.platform.PlatformProjectOpenProcessor;
+import com.intellij.platform.templates.TemplateProjectDirectoryGenerator;
+import com.intellij.projectImport.ProjectOpenedCallback;
+import com.jetbrains.edu.learning.EduPluginConfigurator;
+import com.jetbrains.edu.learning.courseFormat.Course;
+import com.jetbrains.edu.learning.newproject.EduCourseProjectGenerator;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.io.File;
+import java.util.EnumSet;
+
+public class EduCreateNewProjectDialog extends DialogWrapper {
+ private static final Logger LOG = Logger.getInstance(EduCreateNewProjectDialog.class);
+ protected Project myProject;
+ protected Course myCourse;
+ private final EduCreateNewProjectPanel myPanel;
+
+ public EduCreateNewProjectDialog() {
+ super(false);
+ setTitle("New Project");
+ Project defaultProject = ProjectManager.getInstance().getDefaultProject();
+ myPanel = new EduCreateNewProjectPanel(defaultProject, this);
+ setOKButtonText("Create");
+ init();
+ }
+
+ @Nullable
+ @Override
+ protected JComponent createCenterPanel() {
+ return myPanel;
+ }
+
+ public void setCourse(@Nullable Course course) {
+ myCourse = course;
+ String description = course != null ? course.getDescription() : "";
+ myPanel.setDescription(description);
+ }
+
+ @Override
+ protected void doOKAction() {
+ if (myCourse == null) {
+ myPanel.setError("Selected course is null");
+ return;
+ }
+ Language language = myCourse.getLanguageById();
+ if (language == null) {
+ String message = "Selected course don't have language";
+ myPanel.setError(message);
+ LOG.warn(message);
+ return;
+ }
+ EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(language);
+ if (configurator == null) {
+ String message = "A configurator for the selected course not found";
+ myPanel.setError(message);
+ LOG.warn(message + ": " + language);
+ return;
+ }
+ EduCourseProjectGenerator projectGenerator = configurator.getEduCourseProjectGenerator();
+ String errorMessage = createProject(projectGenerator);
+ if (errorMessage != null) {
+ myPanel.setError(errorMessage);
+ return;
+ }
+ if (myProject == null) {
+ myPanel.setError("Project did't created");
+ return;
+ }
+ super.doOKAction();
+ }
+
+ /**
+ * @param projectGenerator
+ * @return error message if didn't create project else return null
+ */
+ @Nullable
+ private String createProject(@NotNull final EduCourseProjectGenerator projectGenerator) {
+ String location = FileUtil.toSystemDependentName(myPanel.getLocationPath());
+
+ ValidationResult result = projectGenerator.validate();
+ if (!result.isOk()) {
+ return result.getErrorMessage();
+ }
+
+ final File directory = new File(location);
+ if (!FileUtil.createDirectory(directory)) {
+ String message = "Can't create a project directory";
+ LOG.error(message + ": " + location);
+ return message;
+ }
+
+ projectGenerator.setCourse(myCourse);
+
+ final VirtualFile baseDir = ApplicationManager.getApplication()
+ .runWriteAction((Computable)() ->
+ LocalFileSystem.getInstance().refreshAndFindFileByIoFile(directory)
+ );
+
+ if (baseDir == null) {
+ LOG.error("Couldn't find '" + directory + "' in VFS");
+ return "Couldn't find in VFS";
+ }
+ VfsUtil.markDirtyAndRefresh(false, true, true, baseDir);
+
+ if (baseDir.getChildren().length > 0) {
+ String message =
+ String.format("Directory '%s' is not empty.\nFiles and directories will remove.\nDo you want continue?",
+ directory.getAbsolutePath());
+ int rc = Messages.showYesNoDialog((Project)null, message, "New Project", Messages.getQuestionIcon());
+ if (rc != Messages.YES) {
+ myPanel.resetError();
+ return "Canceled by user";
+ }
+ }
+
+ DirectoryProjectGenerator generator = projectGenerator.getDirectoryProjectGenerator();
+
+ String generatorName = ConvertUsagesUtil.ensureProperKey(generator.getName());
+ UsageTrigger.trigger("AbstractNewProjectStep." + generatorName);
+
+ RecentProjectsManager.getInstance().setLastProjectCreationLocation(directory.getParent());
+
+ ProjectOpenedCallback callback = null;
+ if(generator instanceof TemplateProjectDirectoryGenerator){
+ ((TemplateProjectDirectoryGenerator)generator).generateProject(baseDir.getName(), location);
+ } else {
+ callback = (project, module) -> {
+ if (projectGenerator.beforeProjectGenerated()) {
+ Object settings = projectGenerator.getProjectSettings();
+ //noinspection unchecked
+ generator.generateProject(project, baseDir, settings, module);
+ projectGenerator.afterProjectGenerated(project);
+ }
+ };
+ }
+ EnumSet options = EnumSet.noneOf(PlatformProjectOpenProcessor.Option.class);
+ myProject = PlatformProjectOpenProcessor.doOpenProject(baseDir, null, -1, callback, options);
+ return null;
+ }
+}
diff --git a/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectPanel.form b/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectPanel.form
new file mode 100644
index 000000000000..7a771f682f0f
--- /dev/null
+++ b/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectPanel.form
@@ -0,0 +1,71 @@
+
+
diff --git a/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectPanel.java b/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectPanel.java
new file mode 100644
index 000000000000..d9e71daa53d3
--- /dev/null
+++ b/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectPanel.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2000-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.edu.learning.newproject.ui;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.ide.impl.ProjectUtil;
+import com.intellij.openapi.fileChooser.FileChooserDescriptor;
+import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapperPeer;
+import com.intellij.openapi.ui.TextFieldWithBrowseButton;
+import com.intellij.openapi.util.io.FileUtil;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+import java.io.File;
+
+public class EduCreateNewProjectPanel extends JPanel {
+ private JPanel myPanel;
+ private TextFieldWithBrowseButton myLocationField;
+ private JLabel myErrorIcon;
+ private JLabel myErrorLabel;
+ private JTextPane myDescription;
+
+ public EduCreateNewProjectPanel(@NotNull final Project project, @NotNull EduCreateNewProjectDialog dialog) {
+ setLayout(new BorderLayout());
+ add(myPanel, BorderLayout.CENTER);
+ myErrorIcon.setIcon(AllIcons.Actions.Lightning);
+ resetError();
+ String location = findSequentNonExistingUntitled().toString();
+ myLocationField.setText(location);
+ final int index = location.lastIndexOf(File.separator);
+ if (index > 0) {
+ JTextField textField = myLocationField.getTextField();
+ textField.select(index + 1, location.length());
+ textField.putClientProperty(DialogWrapperPeer.HAVE_INITIAL_SELECTION, true);
+ }
+
+ final FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor();
+ myLocationField.addBrowseFolderListener("Select Base Directory",
+ "Select base directory for the project",
+ project,
+ descriptor);
+
+ myLocationField.addFocusListener(new FocusAdapter() {
+ @Override
+ public void focusLost(FocusEvent e) {
+ String location = FileUtil.toSystemDependentName(myLocationField.getText());
+ File file = new File(location);
+ if (!FileUtil.ensureCanCreateFile(file)) {
+ dialog.setOKActionEnabled(false);
+ setError("Invalid location");
+ } else {
+ dialog.setOKActionEnabled(true);
+ resetError();
+ }
+ }
+ });
+ }
+
+ @NotNull
+ protected File findSequentNonExistingUntitled() {
+ return FileUtil.findSequentNonexistentFile(new File(ProjectUtil.getBaseDir()), "course", "");
+ }
+
+ private void setState(boolean isVisible) {
+ myErrorIcon.setVisible(isVisible);
+ myErrorLabel.setVisible(isVisible);
+ }
+
+ void setError(@NotNull String message) {
+ myErrorLabel.setText(message);
+ setState(true);
+ }
+
+ public String getLocationPath() {
+ return myLocationField.getText();
+ }
+
+ public void resetError() {
+ setState(false);
+ }
+
+ public void setDescription(@NotNull String description) {
+ myDescription.setText(description);
+ }
+}
diff --git a/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewStepikProjectDialog.java b/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewStepikProjectDialog.java
new file mode 100644
index 000000000000..7db74ffde68d
--- /dev/null
+++ b/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewStepikProjectDialog.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2000-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.edu.learning.newproject.ui;
+
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManager;
+import com.jetbrains.edu.learning.courseFormat.Course;
+import com.jetbrains.edu.learning.stepic.EduStepicAuthorizedClient;
+import com.jetbrains.edu.learning.stepic.EduStepicConnector;
+import com.jetbrains.edu.learning.stepic.StepicUser;
+
+import java.io.IOException;
+
+import static com.jetbrains.edu.learning.StudyUtils.execCancelable;
+
+public class EduCreateNewStepikProjectDialog extends EduCreateNewProjectDialog {
+ private static final Logger LOG = Logger.getInstance(EduCreateNewStepikProjectDialog.class);
+
+ public EduCreateNewStepikProjectDialog() {
+ super();
+ }
+
+ public EduCreateNewStepikProjectDialog(int courseId) {
+ this();
+
+ StepicUser user = EduStepicAuthorizedClient.getCurrentUser();
+ Project defaultProject = ProjectManager.getInstance().getDefaultProject();
+ ApplicationManager.getApplication().invokeAndWait(() ->
+ ProgressManager.getInstance()
+ .runProcessWithProgressSynchronously(() -> {
+ ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true);
+ execCancelable(() -> {
+ try {
+ Course course = EduStepicConnector.getCourseFromStepik(user, courseId);
+ if (course != null) {
+ setTitle("New Project - " + course.getName());
+ }
+
+ setCourse(course);
+ }
+ catch (IOException e) {
+ LOG.warn("Tried to create a project for course with id=" + courseId, e);
+ }
+ return null;
+ });
+ }, "Getting Available Courses", true, defaultProject)
+ );
+ }
+
+ @Override
+ public void show() {
+ if (myCourse != null) {
+ super.show();
+ } else {
+ doCancelAction();
+ }
+ }
+}
diff --git a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java b/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java
index 5e7726077452..d9765b2b045a 100644
--- a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java
+++ b/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java
@@ -127,6 +127,17 @@ public class EduStepicConnector {
return null;
}
+ private static StepicWrappers.CoursesContainer getCoursesFromStepik(@Nullable StepicUser user, URI url) throws IOException {
+ final StepicWrappers.CoursesContainer coursesContainer;
+ if (user != null) {
+ coursesContainer = EduStepicAuthorizedClient.getFromStepic(url.toString(), StepicWrappers.CoursesContainer.class, user);
+ }
+ else {
+ coursesContainer = EduStepicClient.getFromStepic(url.toString(), StepicWrappers.CoursesContainer.class);
+ }
+ return coursesContainer;
+ }
+
private static boolean addCoursesFromStepic(@Nullable StepicUser user, List result, int pageNumber) throws IOException {
final URI url;
try {
@@ -137,17 +148,31 @@ public class EduStepicConnector {
LOG.error(e.getMessage());
return false;
}
- final StepicWrappers.CoursesContainer coursesContainer;
- if (user != null) {
- coursesContainer = EduStepicAuthorizedClient.getFromStepic(url.toString(), StepicWrappers.CoursesContainer.class, user);
- }
- else {
- coursesContainer = EduStepicClient.getFromStepic(url.toString(), StepicWrappers.CoursesContainer.class);
- }
+ final StepicWrappers.CoursesContainer coursesContainer = getCoursesFromStepik(user, url);
addAvailableCourses(result, coursesContainer);
return coursesContainer.meta.containsKey("has_next") && coursesContainer.meta.get("has_next") == Boolean.TRUE;
}
+ @Nullable
+ public static Course getCourseFromStepik(@Nullable StepicUser user, int courseId) throws IOException {
+ final URI url;
+ try {
+ url = new URIBuilder(EduStepicNames.COURSES + "/" + courseId).addParameter("is_idea_compatible", "true")
+ .build();
+ }
+ catch (URISyntaxException e) {
+ LOG.error(e.getMessage());
+ return null;
+ }
+ final StepicWrappers.CoursesContainer coursesContainer = getCoursesFromStepik(user, url);
+
+ if (coursesContainer!= null && !coursesContainer.courses.isEmpty()) {
+ return coursesContainer.courses.get(0);
+ } else {
+ return null;
+ }
+ }
+
static void addAvailableCourses(List result, StepicWrappers.CoursesContainer coursesContainer) throws IOException {
final List courses = coursesContainer.courses;
for (RemoteCourse info : courses) {
diff --git a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicNames.java b/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicNames.java
index 5ee99dc6cde8..e2e8ef92ec1f 100644
--- a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicNames.java
+++ b/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicNames.java
@@ -23,4 +23,7 @@ public class EduStepicNames {
public static final String STEPIC_REGISTRATION_LINK = "https://stepic.org/registration?next=%2Fexplore%2Fcourses";
public static final String PYCHARM_PREFIX = "pycharm";
+
+ public static final String EDU_STEPIK_SERVICE_NAME = "edu/stepik";
+ public static final String STEP_ID = "step_id";
}
diff --git a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java b/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java
index f7eb72f4b607..6945d806c7c3 100644
--- a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java
+++ b/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java
@@ -333,10 +333,6 @@ public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor {
myAuthorLabel.setText(!StringUtil.isEmptyOrSpaces(authorsString) ? "Author: " + authorsString : "");
}
- public JComboBox getCoursesComboBox() {
- return myCoursesComboBox;
- }
-
public JPanel getInfoPanel() {
return myInfoPanel;
}
diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyEduPluginConfigurator.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyEduPluginConfigurator.java
index bd224e42f31c..fad33f310523 100644
--- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyEduPluginConfigurator.java
+++ b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyEduPluginConfigurator.java
@@ -25,6 +25,7 @@ import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.tasks.PyCharmTask;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks;
+import com.jetbrains.edu.learning.newproject.EduCourseProjectGenerator;
import com.jetbrains.python.PythonModuleTypeBase;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -151,6 +152,11 @@ public class PyEduPluginConfigurator implements EduPluginConfigurator {
return Collections.singletonList(new File(new File(jarPath, "courses"), COURSE_NAME).getPath());
}
+ @Override
+ public EduCourseProjectGenerator getEduCourseProjectGenerator() {
+ return new PyStudyDirectoryProjectGenerator();
+ }
+
public ModuleType getModuleType() {
return PythonModuleTypeBase.getInstance();
}
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..ce401b6100eb 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
@@ -18,8 +18,10 @@ import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.SdkAdditionalData;
import com.intellij.openapi.projectRoots.impl.ProjectJdkImpl;
+import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil;
import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel;
import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.platform.DirectoryProjectGenerator;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiManager;
import com.intellij.util.BooleanFunction;
@@ -27,6 +29,7 @@ import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.RemoteCourse;
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
+import com.jetbrains.edu.learning.newproject.EduCourseProjectGenerator;
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
import com.jetbrains.edu.learning.ui.StudyNewProjectPanel;
import com.jetbrains.python.configuration.PyConfigurableInterpreterList;
@@ -38,6 +41,7 @@ import com.jetbrains.python.remote.PyProjectSynchronizer;
import com.jetbrains.python.sdk.AbstractCreateVirtualEnvDialog;
import com.jetbrains.python.sdk.PyDetectedSdk;
import com.jetbrains.python.sdk.PythonSdkAdditionalData;
+import com.jetbrains.python.sdk.PythonSdkType;
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
import icons.InteractiveLearningPythonIcons;
import org.jetbrains.annotations.Nls;
@@ -51,13 +55,13 @@ import java.awt.event.MouseEvent;
import java.util.Collection;
import java.util.List;
-
-public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator {
+public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator
+ implements EduCourseProjectGenerator {
private static final Logger LOG = Logger.getInstance(PyStudyDirectoryProjectGenerator.class.getName());
private final StudyProjectGenerator myGenerator;
private static final String NO_PYTHON_INTERPRETER = "Add python interpreter.";
+ private final boolean isLocal;
public ValidationResult myValidationResult = new ValidationResult("selected course is not valid");
- private StudyNewProjectPanel mySettingsPanel;
@SuppressWarnings("unused") // used on startup
public PyStudyDirectoryProjectGenerator() {
@@ -65,6 +69,7 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator fireStateChanged());
- }
- });
-
- addErrorLabelMouseListener(new MouseAdapter() {
- @Override
- public void mouseClicked(MouseEvent e) {
- final Object selectedItem = mySettingsPanel.getCoursesComboBox().getSelectedItem();
- if (selectedItem != null && ((Course)selectedItem).isAdaptive() && !myGenerator.isLoggedIn()) {
- mySettingsPanel.showLoginDialog(false, "Signing In");
- }
- }
-
- @Override
- public void mouseEntered(MouseEvent e) {
- final Object selectedItem = mySettingsPanel.getCoursesComboBox().getSelectedItem();
- if (selectedItem != null && ((Course)selectedItem).isAdaptive() && !myGenerator.isLoggedIn()) {
- e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
- }
- }
-
- @Override
- public void mouseExited(MouseEvent e) {
- final Course selectedItem = (Course)mySettingsPanel.getCoursesComboBox().getSelectedItem();
- if (selectedItem != null && selectedItem.isAdaptive() && !myGenerator.isLoggedIn()) {
- e.getComponent().setCursor(Cursor.getDefaultCursor());
- }
- }
- });
}
@Nls
@@ -131,6 +99,10 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator sdks = PyConfigurableInterpreterList.getInstance(project).getAllPythonSdks();
+
+ ValidationResult validationResult;
if (sdks.isEmpty()) {
- myValidationResult = new ValidationResult(NO_PYTHON_INTERPRETER);
+ validationResult = new ValidationResult(NO_PYTHON_INTERPRETER);
+ } else {
+ validationResult = ValidationResult.OK;
+ }
+
+ return validationResult;
+ }
+
+ @NotNull
+ @Override
+ public ValidationResult validate(@NotNull String s) {
+ ValidationResult validationResult = validate();
+ if (!validationResult.isOk()) {
+ myValidationResult = validationResult;
}
return myValidationResult;
}
+ @Override
+ public boolean beforeProjectGenerated() {
+ BooleanFunction function = beforeProjectGenerated(null);
+ return function != null && function.fun(this);
+ }
+
+ @Override
+ public void afterProjectGenerated(@NotNull Project project) {
+ PyNewProjectSettings settings = (PyNewProjectSettings)getProjectSettings();
+ Sdk sdk = settings.getSdk();
+
+ if (sdk == null) {
+ createAndAddVirtualEnv(project, settings);
+ sdk = settings.getSdk();
+ }
+
+ SdkConfigurationUtil.setDirectoryProjectSdk(project, sdk);
+ final List sdks = PythonSdkType.getAllSdks();
+ for (Sdk s : sdks) {
+ final SdkAdditionalData additionalData = s.getSdkAdditionalData();
+ if (additionalData instanceof PythonSdkAdditionalData) {
+ ((PythonSdkAdditionalData)additionalData).reassociateWithCreatedProject(project);
+ }
+ }
+ }
+
public void setValidationResult(ValidationResult validationResult) {
myValidationResult = validationResult;
}
@@ -163,10 +180,49 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator fireStateChanged());
+ }
+ });
+
+ addErrorLabelMouseListener(new MouseAdapter() {
+ private boolean isCourseAdaptiveAndNotLogged() {
+ Course course = myGenerator.getSelectedCourse();
+ return course != null && course.isAdaptive() && !myGenerator.isLoggedIn();
+ }
+
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (isCourseAdaptiveAndNotLogged()) {
+ mySettingsPanel.showLoginDialog(false, "Signing In");
+ }
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ if (isCourseAdaptiveAndNotLogged()) {
+ e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ }
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ if (isCourseAdaptiveAndNotLogged()) {
+ e.getComponent().setCursor(Cursor.getDefaultCursor());
+ }
+ }
+ });
+
return mySettingsPanel;
}
- public void setSelectedCourse(Course course) {
+ public void setCourse(@NotNull Course course) {
myGenerator.setSelectedCourse(course);
}
@@ -184,7 +240,7 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator beforeProjectGenerated(@Nullable Sdk sdk) {
return generator -> {
final List enrolledCoursesIds = myGenerator.getEnrolledCoursesIds();
- final Course course = (Course)mySettingsPanel.getCoursesComboBox().getSelectedItem();
+ final Course course = myGenerator.getSelectedCourse();
if (course == null || !(course instanceof RemoteCourse)) return true;
if (((RemoteCourse)course).getId() > 0 && !enrolledCoursesIds.contains(((RemoteCourse)course).getId())) {
ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
@@ -192,7 +248,6 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator EduStepicConnector.enrollToCourse(((RemoteCourse)course).getId(),
StudySettings.getInstance().getUser()));
}, "Creating Course", true, ProjectManager.getInstance().getDefaultProject());
-
}
return true;
};
diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/actions/PyStudyIntroductionCourseAction.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/actions/PyStudyIntroductionCourseAction.java
index d2564a08b358..35ca0b58ad70 100644
--- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/actions/PyStudyIntroductionCourseAction.java
+++ b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/actions/PyStudyIntroductionCourseAction.java
@@ -76,7 +76,7 @@ public class PyStudyIntroductionCourseAction extends AnAction {
final ProjectSpecificSettingsStep step = new ProjectSpecificSettingsStep(generator, callback);
step.createPanel(); // initialize panel to set location
step.setLocation(projectDir.toString());
- generator.setSelectedCourse(introCourse);
+ generator.setCourse(introCourse);
callback.consume(step);
}