Merge remote-tracking branch 'origin/master' into meanmail/EDU-839

This commit is contained in:
meanmail
2017-04-07 00:11:46 +07:00
603 changed files with 16376 additions and 4233 deletions
+1 -1
View File
@@ -38,7 +38,7 @@
</target>
<!--
<target name="test" depends="init">
<call_gant script="${project.home}/build/scripts/tests.gant"/>
<call_gant script="${project.home}/build/scripts/tests_in_community.gant"/>
</target>
-->
@@ -122,9 +122,9 @@
<reference id="CC.NewStep"/>
<separator/>
<action id="PackCourse" class="com.jetbrains.edu.coursecreator.actions.CCCreateCourseArchive"/>
<action id="PushTask" class="com.jetbrains.edu.coursecreator.actions.CCPushTask"/>
<action id="PushLesson" class="com.jetbrains.edu.coursecreator.actions.CCPushLesson"/>
<action id="PushCourse" class="com.jetbrains.edu.coursecreator.actions.CCPushCourse"/>
<action id="PushTask" class="com.jetbrains.edu.coursecreator.actions.stepik.CCPushTask"/>
<action id="PushLesson" class="com.jetbrains.edu.coursecreator.actions.stepik.CCPushLesson"/>
<action id="PushCourse" class="com.jetbrains.edu.coursecreator.actions.stepik.CCPushCourse"/>
<reference id="ChangeCourseInfo"/>
<add-to-group group-id="ProjectViewPopupMenu" anchor="before" relative-to-action="CutCopyPasteGroup"/>
</group>
@@ -154,7 +154,7 @@
</group>
<action id="UnpackCourse" class="com.jetbrains.edu.coursecreator.actions.CCFromCourseArchive"/>
<action id="GetCourse" class="com.jetbrains.edu.coursecreator.actions.CCGetCourseFromStepic"/>
<action id="GetCourse" class="com.jetbrains.edu.coursecreator.actions.stepik.CCGetCourseFromStepic"/>
</actions>
@@ -204,7 +204,6 @@
<checkListener implementation="com.jetbrains.edu.learning.twitter.StudyTwitterAction"/>
<!--course creator-->
<checkListener implementation="com.jetbrains.edu.coursecreator.CCCheckListener"/>
<optionsProvider instance="com.jetbrains.edu.coursecreator.settings.CCOptions"/>
</extensions>
</idea-plugin>
@@ -1,22 +0,0 @@
package com.jetbrains.edu.coursecreator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.edu.learning.StudyCheckListener;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import org.jetbrains.annotations.NotNull;
public class CCCheckListener implements StudyCheckListener {
@Override
public void beforeCheck(@NotNull Project project, @NotNull Task task) {
if (!CCUtils.isCourseCreator(project)) {
return;
}
VirtualFile taskDir = task.getTaskDir(project);
if (taskDir == null) {
return;
}
CCUtils.updateResources(project, task, taskDir);
}
}
@@ -1,7 +1,6 @@
package com.jetbrains.edu.coursecreator;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.components.AbstractProjectComponent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
@@ -15,12 +14,11 @@ import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.Lesson;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import com.jetbrains.edu.learning.statistics.EduUsagesCollector;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -47,9 +45,6 @@ public class CCProjectComponent extends AbstractProjectComponent {
CCProjectService.getInstance(myProject).setCourse(null);
oldCourse.initCourse(true);
oldCourse.setCourseMode(CCUtils.COURSE_MODE);
File coursesDir = new File(PathManager.getConfigPath(), "courses");
File courseDir = new File(coursesDir, oldCourse.getName() + "-" + myProject.getName());
oldCourse.setCourseDirectory(courseDir.getPath());
StudyUtils.registerStudyToolWindow(oldCourse, myProject);
transformFiles(oldCourse, myProject);
}
@@ -15,10 +15,7 @@
*/
package com.jetbrains.edu.coursecreator;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
@@ -30,16 +27,14 @@ import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.Lesson;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.Map;
public class CCRefactoringElementListenerProvider implements RefactoringElementListenerProvider {
private static final Logger LOG = Logger.getInstance(CCRefactoringElementListenerProvider.class);
@Nullable
@Override
@@ -94,21 +89,8 @@ public class CCRefactoringElementListenerProvider implements RefactoringElementL
if (taskFile == null) {
return;
}
ApplicationManager.getApplication().runWriteAction(() -> {
VirtualFile patternFile = StudyUtils.getPatternFile(taskFile, oldName);
if (patternFile != null) {
try {
patternFile.delete(CCRefactoringElementListenerProvider.class);
}
catch (IOException e) {
LOG.info(e);
}
}
});
taskFiles.remove(oldName);
taskFiles.put(StudyUtils.pathRelativeToTask(file.getVirtualFile()), taskFile);
CCUtils.createResourceFile(file.getVirtualFile(), course, taskDir.getVirtualFile());
}
@Override
@@ -8,34 +8,37 @@ import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.DumbModePermission;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ModuleRootModificationUtil;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileVisitor;
import com.intellij.psi.PsiDirectory;
import com.intellij.util.Function;
import com.intellij.util.containers.hash.HashMap;
import com.jetbrains.edu.learning.EduPluginConfigurator;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.core.EduUtils;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.Lesson;
import com.jetbrains.edu.learning.courseFormat.StudyItem;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks;
import org.apache.commons.codec.binary.Base64;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
public class CCUtils {
public static final String ANSWER_EXTENSION_DOTTED = ".answer.";
@@ -137,27 +140,22 @@ public class CCUtils {
return folder;
}
final Ref<VirtualFile> generatedRoot = new Ref<>();
DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, new Runnable() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
try {
generatedRoot.set(baseDir.createChildDirectory(this, GENERATED_FILES_FOLDER));
VirtualFile contentRootForFile =
ProjectRootManager.getInstance(module.getProject()).getFileIndex().getContentRootForFile(generatedRoot.get());
if (contentRootForFile == null) {
return;
}
ModuleRootModificationUtil.updateExcludedFolders(module, contentRootForFile, Collections.emptyList(),
Collections.singletonList(generatedRoot.get().getUrl()));
}
catch (IOException e) {
LOG.info("Failed to create folder for generated files", e);
}
try {
generatedRoot.set(baseDir.createChildDirectory(this, GENERATED_FILES_FOLDER));
VirtualFile contentRootForFile =
ProjectRootManager.getInstance(module.getProject()).getFileIndex().getContentRootForFile(generatedRoot.get());
if (contentRootForFile == null) {
return;
}
});
ModuleRootModificationUtil.updateExcludedFolders(module, contentRootForFile, Collections.emptyList(),
Collections.singletonList(generatedRoot.get().getUrl()));
}
catch (IOException e) {
LOG.info("Failed to create folder for generated files", e);
}
}
});
return generatedRoot.get();
@@ -211,63 +209,6 @@ public class CCUtils {
return configurator.isTestFile(file);
}
public static void createResourceFile(VirtualFile createdFile, Course course, VirtualFile taskVF) {
VirtualFile lessonVF = taskVF.getParent();
if (lessonVF == null) {
return;
}
String taskResourcesPath = FileUtil.join(course.getCourseDirectory(), lessonVF.getName(), taskVF.getName());
File taskResourceFile = new File(taskResourcesPath);
if (!taskResourceFile.exists()) {
if (!taskResourceFile.mkdirs()) {
LOG.info("Failed to create resources for task " + taskResourcesPath);
}
}
try {
File toFile = new File(taskResourceFile, createdFile.getName());
FileUtil.copy(new File(createdFile.getPath()), toFile);
}
catch (IOException e) {
LOG.info("Failed to copy created task file to resources " + createdFile.getPath());
}
}
public static void updateResources(Project project, Task task, VirtualFile taskDir) {
Course course = StudyTaskManager.getInstance(project).getCourse();
if (course == null) {
return;
}
VirtualFile lessonVF = taskDir.getParent();
if (lessonVF == null) {
return;
}
String taskResourcesPath = FileUtil.join(course.getCourseDirectory(), lessonVF.getName(), taskDir.getName());
File taskResourceFile = new File(taskResourcesPath);
if (!taskResourceFile.exists()) {
if (!taskResourceFile.mkdirs()) {
LOG.info("Failed to create resources for task " + taskResourcesPath);
}
}
VirtualFile studentDir = LocalFileSystem.getInstance().findFileByIoFile(taskResourceFile);
if (studentDir == null) {
return;
}
for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
String name = entry.getKey();
VirtualFile answerFile = taskDir.findFileByRelativePath(name);
if (answerFile == null) {
continue;
}
ApplicationManager.getApplication().runWriteAction(() -> {
EduUtils.createStudentFile(CCUtils.class, project, answerFile, studentDir, null,
task instanceof TaskWithSubtasks ? ((TaskWithSubtasks)task).getActiveSubtaskIndex() : 0);
});
}
}
public static void updateActionGroup(AnActionEvent e) {
Presentation presentation = e.getPresentation();
Project project = e.getProject();
@@ -315,4 +256,58 @@ public class CCUtils {
}
});
}
@Nullable
public static Lesson createAdditionalLesson(Course course, Project project) {
final VirtualFile baseDir = project.getBaseDir();
EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(course.getLanguageById());
final Lesson lesson = new Lesson();
lesson.setName(EduNames.PYCHARM_ADDITIONAL);
final Task task = new Task();
task.setLesson(lesson);
task.setName(EduNames.PYCHARM_ADDITIONAL);
task.setIndex(1);
VfsUtilCore.visitChildrenRecursively(baseDir, new VirtualFileVisitor(VirtualFileVisitor.NO_FOLLOW_SYMLINKS) {
@Override
public boolean visitFile(@NotNull VirtualFile file) {
final String name = file.getName();
if (name.equals(EduNames.COURSE_META_FILE) || name.equals(EduNames.HINTS) || name.startsWith(".")) return false;
String sanitizedName = FileUtil.sanitizeFileName(course.getName());
final String archiveName = sanitizedName.startsWith("_") ? EduNames.COURSE : sanitizedName;
if (name.equals(archiveName + ".zip")) return false;
if (GENERATED_FILES_FOLDER.equals(name) || Project.DIRECTORY_STORE_FOLDER.equals(name)) {
return false;
}
if (file.isDirectory()) return true;
if (StudyUtils.isTaskDescriptionFile(name) || StudyUtils.isTestsFile(project, name)) return true;
if (name.contains(".iml") || (configurator != null && configurator.excludeFromArchive(file.getPath()))) {
return false;
}
final TaskFile taskFile = StudyUtils.getTaskFile(project, file);
if (taskFile == null) {
final String path = VfsUtilCore.getRelativePath(file, baseDir);
try {
if (EduUtils.isImage(file.getName())) {
task.addTestsTexts(path, Base64.encodeBase64URLSafeString(FileUtil.loadBytes(file.getInputStream())));
}
else {
task.addTestsTexts(path, FileUtil.loadTextAndClose(file.getInputStream()));
}
}
catch (IOException e) {
LOG.error("Can't find file " + path);
}
}
return true;
}
});
if (task.getTestsText().isEmpty()) return null;
lesson.addTask(task);
lesson.setIndex(course.getLessons().size());
return lesson;
}
}
@@ -4,21 +4,21 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileAdapter;
import com.intellij.openapi.vfs.VirtualFileEvent;
import com.intellij.openapi.vfs.VirtualFileListener;
import com.jetbrains.edu.learning.EduPluginConfigurator;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.Lesson;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import org.jetbrains.annotations.NotNull;
import java.io.File;
public class CCVirtualFileListener extends VirtualFileAdapter {
public class CCVirtualFileListener implements VirtualFileListener {
@Override
public void fileCreated(@NotNull VirtualFileEvent event) {
@@ -48,7 +48,7 @@ public class CCVirtualFileListener extends VirtualFileAdapter {
String taskRelativePath = StudyUtils.pathRelativeToTask(createdFile);
EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(course.getLanguageById());
if (configurator != null && configurator.excludeFromArchive(new File(createdFile.getPath()))) {
if (configurator != null && configurator.excludeFromArchive(createdFile.getPath())) {
return;
}
@@ -68,9 +68,7 @@ public class CCVirtualFileListener extends VirtualFileAdapter {
return;
}
CCUtils.createResourceFile(createdFile, course, taskVF);
task.addTaskFile(taskRelativePath, 1);
task.addTaskFile(taskRelativePath, task.getTaskFiles().size()+1);
}
@Override
@@ -89,7 +87,7 @@ public class CCVirtualFileListener extends VirtualFileAdapter {
return;
}
Course course = StudyTaskManager.getInstance(project).getCourse();
if (course == null || path.contains(FileUtil.toSystemIndependentName(course.getCourseDirectory()))) {
if (course == null) {
return;
}
final TaskFile taskFile = StudyUtils.getTaskFile(project, removedFile);
@@ -112,7 +110,7 @@ public class CCVirtualFileListener extends VirtualFileAdapter {
}
VirtualFile courseDir = project.getBaseDir();
CCUtils.updateHigherElements(courseDir.getChildren(), file -> course.getLesson(file.getName()), removedLesson.getIndex(), EduNames.LESSON, -1);
course.getLessons().remove(removedLesson);
course.removeLesson(removedLesson);
}
private static void deleteTask(@NotNull final Course course, @NotNull final VirtualFile removedTask) {
@@ -9,8 +9,8 @@ import com.jetbrains.edu.coursecreator.CCUtils;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.core.EduUtils;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
public class CCAddAsTaskFile extends CCTaskFileActionBase {
public static final String ACTION_NAME = "Make Visible to Student";
@@ -21,7 +21,7 @@ public class CCAddAsTaskFile extends CCTaskFileActionBase {
protected void performAction(VirtualFile file, Task task, Course course, Project project) {
EduUtils.runUndoableAction(project, ACTION_NAME, new AddTaskFile(file, null, course, project, task));
EduUtils.runUndoableAction(project, ACTION_NAME, new AddTaskFile(file, null, project, task));
}
protected boolean isAvailable(Project project, VirtualFile file) {
@@ -31,21 +31,20 @@ public class CCAddAsTaskFile extends CCTaskFileActionBase {
private static class AddTaskFile extends BasicUndoableAction {
private final VirtualFile myFile;
private TaskFile myTaskFile;
private final Course myCourse;
private final Project myProject;
private final Task myTask;
public AddTaskFile(VirtualFile file, TaskFile taskFile, Course course, Project project, Task task) {
public AddTaskFile(VirtualFile file, TaskFile taskFile, Project project, Task task) {
super(file);
myFile = file;
myTaskFile = taskFile;
myCourse = course;
myProject = project;
myTask = task;
}
@Override
public void undo() throws UnexpectedUndoException {
if (myTaskFile == null) return;
CCHideFromStudent.hideFromStudent(myFile, myProject, myTask.getTaskFiles(), myTaskFile);
ProjectView.getInstance(myProject).refresh();
}
@@ -59,7 +58,6 @@ public class CCAddAsTaskFile extends CCTaskFileActionBase {
myTask.addTaskFile(taskRelativePath, myTask.getTaskFiles().size());
myTaskFile = myTask.getTaskFile(taskRelativePath);
}
CCUtils.createResourceFile(myFile, myCourse, StudyUtils.getTaskDir(myFile));
ProjectView.getInstance(myProject).refresh();
}
@@ -14,30 +14,31 @@ import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.hash.HashMap;
import com.intellij.util.io.ZipUtil;
import com.jetbrains.edu.coursecreator.CCUtils;
import com.jetbrains.edu.coursecreator.ui.CreateCourseArchiveDialog;
import com.jetbrains.edu.learning.StudySerializationUtils;
import com.jetbrains.edu.learning.EduPluginConfigurator;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.core.EduUtils;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.Lesson;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks;
import com.jetbrains.edu.learning.statistics.EduUsagesCollector;
import org.jetbrains.annotations.NotNull;
import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.zip.ZipOutputStream;
public class CCCreateCourseArchive extends DumbAwareAction {
@@ -90,34 +91,27 @@ public class CCCreateCourseArchive extends DumbAwareAction {
return;
}
EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(course.getLanguageById());
if (configurator == null) {
return;
}
FileFilter filter = pathname -> !configurator.excludeFromArchive(pathname);
for (VirtualFile child : baseDir.getChildren()) {
String name = child.getName();
File fromFile = new File(child.getPath());
if (CCUtils.GENERATED_FILES_FOLDER.equals(name) || Project.DIRECTORY_STORE_FOLDER.equals(name)
|| name.contains("iml") || configurator.excludeFromArchive(fromFile)) {
continue;
}
copyChild(archiveFolder, filter, child, fromFile);
}
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
archiveFolder.refresh(false, true);
Course courseCopy = course.copy();
replaceAnswerFilesWithTaskFiles(courseCopy);
courseCopy.sortLessons();
createAdditionalFiles(courseCopy);
generateJson(archiveFolder, courseCopy);
VirtualFileManager.getInstance().refreshWithoutFileWatcher(false);
packCourse(archiveFolder, locationDir, zipName, showMessage);
synchronize(project);
}
private void createAdditionalFiles(Course course) {
final Lesson lesson = CCUtils.createAdditionalLesson(course, project);
if (lesson != null) {
course.addLesson(lesson);
}
}
private void replaceAnswerFilesWithTaskFiles(Course courseCopy) {
for (Lesson lesson : courseCopy.getLessons()) {
String lessonDirName = EduNames.LESSON + String.valueOf(lesson.getIndex());
@@ -126,61 +120,76 @@ public class CCCreateCourseArchive extends DumbAwareAction {
for (Task task : lesson.getTaskList()) {
final VirtualFile taskDir = task.getTaskDir(project);
if (taskDir == null) continue;
String taskDirName = EduNames.TASK + String.valueOf(task.getIndex());
VirtualFile studentFileDir = VfsUtil.findRelativeFile(archiveFolder, lessonDirName, taskDirName);
if (studentFileDir == null) {
continue;
}
VirtualFile srcDir = studentFileDir.findChild(EduNames.SRC);
if (srcDir != null) {
studentFileDir = srcDir;
}
if (task instanceof TaskWithSubtasks) {
transformSubtaskTestsToTextFiles(studentFileDir);
}
for (String taskFile : task.getTaskFiles().keySet()) {
VirtualFile answerFile = taskDir.findFileByRelativePath(taskFile);
if (answerFile == null) {
continue;
}
EduUtils.createStudentFile(this, project, answerFile, studentFileDir, task, 0);
}
convertToStudentTaskFiles(task, taskDir);
addTestsToTask(task);
addDescriptions(task);
}
}
}
private void transformSubtaskTestsToTextFiles(VirtualFile studentFileDir) {
Condition<VirtualFile> isSubtaskTestFile =
file -> CCUtils.isTestsFile(project, file) && file.getName().contains(EduNames.SUBTASK_MARKER);
List<VirtualFile> subtaskTests = ContainerUtil.filter(Arrays.asList(studentFileDir.getChildren()), isSubtaskTestFile);
for (VirtualFile subtaskTest : subtaskTests) {
private void convertToStudentTaskFiles(Task task, VirtualFile taskDir) {
final HashMap<String, TaskFile> studentTaskFiles = new HashMap<>();
for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
VirtualFile answerFile = taskDir.findFileByRelativePath(entry.getKey());
if (answerFile == null) {
continue;
}
final TaskFile studentFile = EduUtils.createStudentFile(project, answerFile, task, 0);
if (studentFile != null) {
studentTaskFiles.put(entry.getKey(), studentFile);
}
}
task.taskFiles = studentTaskFiles;
}
private void addDescriptions(@NotNull final Task task) {
final List<VirtualFile> descriptions = getDescriptionFiles(task, project);
for (VirtualFile file : descriptions) {
try {
subtaskTest.rename(this, subtaskTest.getNameWithoutExtension() + ".txt");
task.addTaskText(file.getName(), VfsUtilCore.loadText(file));
}
catch (IOException e) {
LOG.error(e);
LOG.warn("Failed to load text " + file.getName());
}
}
}
});
}
private static void copyChild(VirtualFile archiveFolder, FileFilter filter, VirtualFile child, File fromFile) {
File toFile = new File(archiveFolder.getPath(), child.getName());
try {
if (child.isDirectory()) {
FileUtil.copyDir(fromFile, toFile, filter);
}
else {
if (filter.accept(fromFile)) {
FileUtil.copy(fromFile, toFile);
private void addTestsToTask(Task task) {
final List<VirtualFile> testFiles = getTestFiles(task, project);
for (VirtualFile file : testFiles) {
try {
task.addTestsTexts(file.getName(), VfsUtilCore.loadText(file));
}
catch (IOException e) {
LOG.warn("Failed to load text " + file.getName());
}
}
}
}
catch (IOException e) {
LOG.info("Failed to copy" + fromFile.getPath(), e);
}
private List<VirtualFile> getTestFiles(@NotNull Task task, @NotNull Project project) {
List<VirtualFile> testFiles = new ArrayList<>();
VirtualFile taskDir = task.getTaskDir(project);
if (taskDir == null) {
return testFiles;
}
testFiles.addAll(Arrays.stream(taskDir.getChildren())
.filter(file -> StudyUtils.isTestsFile(project, file.getName()))
.collect(Collectors.toList()));
return testFiles;
}
private List<VirtualFile> getDescriptionFiles(@NotNull Task task, @NotNull Project project) {
List<VirtualFile> testFiles = new ArrayList<>();
VirtualFile taskDir = task.getTaskDir(project);
if (taskDir == null) {
return testFiles;
}
testFiles.addAll(Arrays.stream(taskDir.getChildren())
.filter(file -> StudyUtils.isTaskDescriptionFile(file.getName()))
.collect(Collectors.toList()));
return testFiles;
}
});
}
private static void synchronize(@NotNull final Project project) {
@@ -212,7 +221,7 @@ public class CCCreateCourseArchive extends DumbAwareAction {
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
private static void generateJson(VirtualFile parentDir, Course course) {
final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().
registerTypeAdapter(Task.class, new StudySerializationUtils.Json.TaskSerializer()).create();
registerTypeAdapter(Task.class, new StudySerializationUtils.Json.TaskAdapter()).create();
final String json = gson.toJson(course);
final File courseJson = new File(parentDir.getPath(), EduNames.COURSE_META_FILE);
OutputStreamWriter outputStreamWriter = null;
@@ -83,7 +83,7 @@ public class CCCreateLesson extends CCCreateStudyItemActionBase {
@Override
protected List<? extends StudyItem> getSiblings(@NotNull Course course, @Nullable StudyItem parentItem) {
return course.getLessons();
return course.getLessons(true);
}
@Override
@@ -1,16 +1,12 @@
package com.jetbrains.edu.coursecreator.actions;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import com.intellij.icons.AllIcons;
import com.intellij.ide.projectView.ProjectView;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileChooser.FileChooser;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
@@ -21,25 +17,23 @@ import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.platform.templates.github.ZipUtil;
import com.jetbrains.edu.coursecreator.CCUtils;
import com.jetbrains.edu.learning.StudySerializationUtils;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.core.EduDocumentListener;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.courseFormat.*;
import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.Lesson;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import com.jetbrains.edu.learning.courseGeneration.StudyGenerator;
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
import org.jetbrains.annotations.NotNull;
import java.io.*;
import java.util.ArrayList;
import java.util.Map;
import static com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator.OUR_COURSES_DIR;
public class CCFromCourseArchive extends DumbAwareAction {
private static final Logger LOG = Logger.getInstance(CCFromCourseArchive.class.getName());
public CCFromCourseArchive() {
super("Unpack Course Archive", "Unpack Course Archive", AllIcons.FileTypes.Archive);
}
@@ -54,7 +48,8 @@ public class CCFromCourseArchive extends DumbAwareAction {
}
private static void unpackCourseArchive(final Project project) {
FileChooserDescriptor descriptor = new FileChooserDescriptor(true, true, true, true, true, false);
FileChooserDescriptor descriptor =
new FileChooserDescriptor(true, true, true, true, true, false);
final VirtualFile virtualFile = FileChooser.chooseFile(descriptor, project, null);
if (virtualFile == null) {
@@ -62,62 +57,51 @@ public class CCFromCourseArchive extends DumbAwareAction {
}
final String basePath = project.getBasePath();
if (basePath == null) return;
Reader reader = null;
try {
ZipUtil.unzip(null, new File(basePath), new File(virtualFile.getPath()), null, null, true);
File courseMetaFile = new File(basePath, EduNames.COURSE_META_FILE);
reader = new InputStreamReader(new FileInputStream(courseMetaFile));
Gson gson = new GsonBuilder()
.registerTypeAdapter(Course.class, new StudySerializationUtils.Json.CourseTypeAdapter(courseMetaFile))
.registerTypeAdapter(Task.class, new StudySerializationUtils.Json.TaskDeserializer())
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create();
Course course = gson.fromJson(reader, Course.class);
if (course == null) {
Messages.showErrorDialog("This course is incompatible with current version", "Failed to Unpack Course");
return;
}
Course course = StudyProjectGenerator.getCourse(virtualFile.getPath());
if (course == null) {
Messages.showErrorDialog("This course is incompatible with current version", "Failed to Unpack Course");
return;
}
generateFromStudentCourse(project, course);
}
StudyTaskManager.getInstance(project).setCourse(course);
File courseDir = new File(OUR_COURSES_DIR, course.getName() + "-" + project.getName());
course.setCourseDirectory(courseDir.getPath());
course.setCourseMode(CCUtils.COURSE_MODE);
project.getBaseDir().refresh(false, true);
int index = 1;
int taskIndex = 1;
for (Lesson lesson : course.getLessons()) {
final VirtualFile lessonDir = project.getBaseDir().findChild(EduNames.LESSON + String.valueOf(index));
lesson.setIndex(index);
if (lessonDir == null) continue;
for (Task task : lesson.getTaskList()) {
final VirtualFile taskDir = lessonDir.findChild(EduNames.TASK + String.valueOf(taskIndex));
task.setIndex(taskIndex);
task.setLesson(lesson);
if (taskDir == null) continue;
for (final Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
ApplicationManager.getApplication().runWriteAction(() -> createAnswerFile(project, taskDir, entry));
}
taskIndex += 1;
}
index += 1;
taskIndex = 1;
public static void generateFromStudentCourse(Project project, Course course) {
StudyTaskManager.getInstance(project).setCourse(course);
course.setCourseMode(CCUtils.COURSE_MODE);
final VirtualFile baseDir = project.getBaseDir();
final Application application = ApplicationManager.getApplication();
application.invokeAndWait(() -> application.runWriteAction(() -> {
final VirtualFile[] children = baseDir.getChildren();
for (VirtualFile child : children) {
StudyUtils.deleteFile(child);
}
course.initCourse(true);
}
catch (JsonSyntaxException | IOException e) {
LOG.error(e.getMessage());
}
finally {
if (reader != null) {
try {
reader.close();
}
catch (IOException e) {
LOG.error(e.getMessage());
StudyGenerator.createCourse(course, baseDir);
}));
baseDir.refresh(false, true);
int index = 1;
int taskIndex = 1;
for (Lesson lesson : course.getLessons()) {
final VirtualFile lessonDir = project.getBaseDir().findChild(EduNames.LESSON + String.valueOf(index));
lesson.setIndex(index);
if (lessonDir == null) continue;
for (Task task : lesson.getTaskList()) {
final VirtualFile taskDir = lessonDir.findChild(EduNames.TASK + String.valueOf(taskIndex));
task.setIndex(taskIndex);
task.setLesson(lesson);
if (taskDir == null) continue;
for (final Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
application.invokeAndWait(() -> application.runWriteAction(() -> createAnswerFile(project, taskDir, entry)));
}
taskIndex += 1;
}
index += 1;
taskIndex = 1;
}
course.initCourse(true);
application.invokeAndWait(() -> StudyUtils.registerStudyToolWindow(course, project));
synchronize(project);
}
@@ -128,57 +112,44 @@ public class CCFromCourseArchive extends DumbAwareAction {
final TaskFile taskFile = taskFileEntry.getValue();
VirtualFile file = userFileDir.findFileByRelativePath(name);
assert file != null;
final Document originDocument = FileDocumentManager.getInstance().getDocument(file);
if (originDocument == null) {
return;
}
final Document document = FileDocumentManager.getInstance().getDocument(file);
if (document == null) return;
CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> document.replaceString(0, document.getTextLength(), originDocument.getCharsSequence())), "Create answer document", "Create answer document");
CommandProcessor.getInstance().executeCommand(project,() -> ApplicationManager.getApplication().runWriteAction(
() -> document.replaceString(0, document.getTextLength(), document.getCharsSequence())),
"Create answer document", "Create answer document");
EduDocumentListener listener = new EduDocumentListener(taskFile, false);
document.addDocumentListener(listener);
taskFile.sortAnswerPlaceholders();
for (int i = taskFile.getActivePlaceholders().size() - 1; i >= 0; i--) {
final AnswerPlaceholder answerPlaceholder = taskFile.getAnswerPlaceholders().get(i);
replaceAnswerPlaceholder(project, document, answerPlaceholder);
for (AnswerPlaceholder placeholder : taskFile.getActivePlaceholders()) {
replaceAnswerPlaceholder(document, placeholder);
}
CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> FileDocumentManager.getInstance().saveDocument(document)), "x", "qwe");
for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) {
placeholder.setUseLength(false);
}
CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(
() -> FileDocumentManager.getInstance().saveDocument(document)),"Create answer document", "Create answer document");
document.removeDocumentListener(listener);
}
private static void replaceAnswerPlaceholder(@NotNull final Project project,
@NotNull final Document document,
@NotNull final AnswerPlaceholder answerPlaceholder) {
final int offset = answerPlaceholder.getOffset();
CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> {
final String text = document.getText(TextRange.create(offset, offset + answerPlaceholder.getRealLength()));
answerPlaceholder.setTaskText(text);
answerPlaceholder.init();
final VirtualFile hints = project.getBaseDir().findChild(EduNames.HINTS);
if (hints != null) {
final ArrayList<String> result = new ArrayList<>();
for (String hint : answerPlaceholder.getHints()) {
final VirtualFile virtualFile = hints.findChild(hint);
if (virtualFile != null) {
final Document hintDocument = FileDocumentManager.getInstance().getDocument(virtualFile);
if (hintDocument != null) {
final String hintText = hintDocument.getText();
result.add(hintText);
}
}
}
answerPlaceholder.setHints(result);
}
document.replaceString(offset, offset + answerPlaceholder.getRealLength(), answerPlaceholder.getPossibleAnswer());
answerPlaceholder.setUseLength(false);
private static void replaceAnswerPlaceholder(@NotNull final Document document,
@NotNull final AnswerPlaceholder placeholder) {
final int offset = placeholder.getOffset();
final String text = document.getText(TextRange.create(offset, offset + placeholder.getRealLength()));
placeholder.setTaskText(text);
placeholder.init();
String replacementText = placeholder.getPossibleAnswer();
CommandProcessor.getInstance().runUndoTransparentAction(() -> ApplicationManager.getApplication().runWriteAction(() -> {
document.replaceString(offset, offset + placeholder.getRealLength(), replacementText);
FileDocumentManager.getInstance().saveDocument(document);
}), "Replace answer placeholder", "From Course Archive");
}));
}
private static void synchronize(@NotNull final Project project) {
VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
ProjectView.getInstance(project).refresh();
}
}
@@ -1,113 +0,0 @@
package com.jetbrains.edu.coursecreator.actions;
import com.intellij.ide.IdeView;
import com.intellij.ide.projectView.ProjectView;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.LangDataKeys;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.jetbrains.edu.coursecreator.CCUtils;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.Lesson;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import com.jetbrains.edu.learning.courseGeneration.StudyGenerator;
import com.jetbrains.edu.coursecreator.stepik.CCStepicConnector;
import com.jetbrains.edu.learning.courseFormat.CourseInfo;
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.Map;
import static com.jetbrains.edu.coursecreator.actions.CCFromCourseArchive.createAnswerFile;
import static com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator.OUR_COURSES_DIR;
import static com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator.flushCourse;
public class CCGetCourseFromStepic extends DumbAwareAction {
public CCGetCourseFromStepic() {
super("Get Course From Stepik", "Get Course From Stepik", null);
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
final IdeView view = e.getData(LangDataKeys.IDE_VIEW);
final Project project = e.getData(CommonDataKeys.PROJECT);
if (view == null || project == null) {
return;
}
final String courseId = Messages.showInputDialog("Please, enter course id", "Get Course From Stepik", null);
if (StringUtil.isNotEmpty(courseId)) {
ProgressManager.getInstance().run(new com.intellij.openapi.progress.Task.Modal(project, "Creating Course", true) {
@Override
public void run(@NotNull final ProgressIndicator indicator) {
createCourse(project, courseId);
}
});
}
}
private static void createCourse(Project project, String courseId) {
final VirtualFile baseDir = project.getBaseDir();
final CourseInfo info = CCStepicConnector.getCourseInfo(courseId);
if (info == null) return;
final Course course = EduStepicConnector.getCourse(project, info);
if (course != null) {
flushCourse(course);
final File courseDirectory = StudyUtils.getCourseDirectory(course);
ApplicationManager.getApplication().invokeAndWait(() -> ApplicationManager.getApplication().runWriteAction(() -> {
final VirtualFile[] children = baseDir.getChildren();
for (VirtualFile child : children) {
StudyUtils.deleteFile(child);
}
StudyGenerator.createCourse(course, baseDir, courseDirectory, project);
}));
StudyTaskManager.getInstance(project).setCourse(course);
File courseDir = new File(OUR_COURSES_DIR, course.getName() + "-" + project.getName());
course.setCourseDirectory(courseDir.getPath());
course.setCourseMode(CCUtils.COURSE_MODE);
project.getBaseDir().refresh(false, true);
int index = 1;
int taskIndex = 1;
for (Lesson lesson : course.getLessons()) {
final VirtualFile lessonDir = project.getBaseDir().findChild(EduNames.LESSON + String.valueOf(index));
lesson.setIndex(index);
if (lessonDir == null) continue;
for (Task task : lesson.getTaskList()) {
final VirtualFile taskDir = lessonDir.findChild(EduNames.TASK + String.valueOf(taskIndex));
task.setIndex(taskIndex);
task.setLesson(lesson);
if (taskDir == null) continue;
for (final Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
ApplicationManager.getApplication()
.invokeAndWait(() -> ApplicationManager.getApplication().runWriteAction(() -> createAnswerFile(project, taskDir, entry)));
}
taskIndex += 1;
}
index += 1;
taskIndex = 1;
}
course.initCourse(true);
ApplicationManager.getApplication()
.invokeAndWait(() -> StudyUtils.registerStudyToolWindow(course, project));
}
VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
ProjectView.getInstance(project).refresh();
}
}
@@ -1,29 +1,24 @@
package com.jetbrains.edu.coursecreator.actions;
import com.intellij.ide.projectView.ProjectView;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.undo.BasicUndoableAction;
import com.intellij.openapi.command.undo.UnexpectedUndoException;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.TextEditor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.edu.coursecreator.CCUtils;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.core.EduUtils;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.Map;
public class CCHideFromStudent extends CCTaskFileActionBase {
private static final Logger LOG = Logger.getInstance(CCHideFromStudent.class);
private static final String ACTION_NAME = "Hide from Student";
public CCHideFromStudent() {
@@ -36,21 +31,19 @@ public class CCHideFromStudent extends CCTaskFileActionBase {
if (taskFile == null) {
return;
}
EduUtils.runUndoableAction(project, ACTION_NAME, new HideTaskFile(project, course, file, task, taskFile));
EduUtils.runUndoableAction(project, ACTION_NAME, new HideTaskFile(project, file, task, taskFile));
}
private static class HideTaskFile extends BasicUndoableAction {
private final Project myProject;
private final Course myCourse;
private final VirtualFile myFile;
private final Task myTask;
private final TaskFile myTaskFile;
public HideTaskFile(Project project, Course course, VirtualFile file, Task task, TaskFile taskFile) {
public HideTaskFile(Project project, VirtualFile file, Task task, TaskFile taskFile) {
super(file);
myProject = project;
myCourse = course;
myFile = file;
myTask = task;
myTaskFile = taskFile;
@@ -59,7 +52,6 @@ public class CCHideFromStudent extends CCTaskFileActionBase {
@Override
public void undo() throws UnexpectedUndoException {
myTask.getTaskFiles().put(StudyUtils.pathRelativeToTask(myFile), myTaskFile);
CCUtils.createResourceFile(myFile, myCourse, StudyUtils.getTaskDir(myFile));
if (!myTaskFile.getAnswerPlaceholders().isEmpty() && FileEditorManager.getInstance(myProject).isFileOpen(myFile)) {
for (FileEditor fileEditor : FileEditorManager.getInstance(myProject).getEditors(myFile)) {
if (fileEditor instanceof TextEditor) {
@@ -83,7 +75,7 @@ public class CCHideFromStudent extends CCTaskFileActionBase {
}
}
public static void hideFromStudent(VirtualFile file, Project project, Map<String, TaskFile> taskFiles, TaskFile taskFile) {
public static void hideFromStudent(VirtualFile file, Project project, Map<String, TaskFile> taskFiles, @NotNull final TaskFile taskFile) {
if (!taskFile.getAnswerPlaceholders().isEmpty() && FileEditorManager.getInstance(project).isFileOpen(file)) {
for (FileEditor fileEditor : FileEditorManager.getInstance(project).getEditors(file)) {
if (fileEditor instanceof TextEditor) {
@@ -93,17 +85,6 @@ public class CCHideFromStudent extends CCTaskFileActionBase {
}
}
String taskRelativePath = StudyUtils.pathRelativeToTask(file);
VirtualFile patternFile = StudyUtils.getPatternFile(taskFile, taskRelativePath);
ApplicationManager.getApplication().runWriteAction(() -> {
if (patternFile != null) {
try {
patternFile.delete(CCHideFromStudent.class);
}
catch (IOException e) {
LOG.info(e);
}
}
});
taskFiles.remove(taskRelativePath);
}
@@ -31,10 +31,10 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.FrameWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiFile;
import com.intellij.testFramework.LightVirtualFile;
import com.intellij.ui.JBColor;
import com.jetbrains.edu.coursecreator.CCUtils;
import com.jetbrains.edu.learning.StudyTaskManager;
@@ -109,33 +109,24 @@ public class CCShowPreview extends DumbAwareAction {
return;
}
if (taskFile.getActivePlaceholders().isEmpty()) {
Messages.showInfoMessage("Preview is available for task files with answer placeholders only", "No Preview for This File");
return;
}
VirtualFile generatedFilesFolder = CCUtils.getGeneratedFilesFolder(project, module);
if (generatedFilesFolder == null) {
return;
}
final Task task = taskFile.getTask();
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
Pair<VirtualFile, TaskFile> pair =
EduUtils.createStudentFile(this, project, virtualFile, generatedFilesFolder, null,
task instanceof TaskWithSubtasks ? ((TaskWithSubtasks)task).getActiveSubtaskIndex() : 0);
if (pair != null) {
showPreviewDialog(project, pair.getFirst(), pair.getSecond());
}
ApplicationManager.getApplication().runWriteAction(() -> {
TaskFile studentTaskFile = EduUtils.createStudentFile(project, virtualFile, task.copy(),
task instanceof TaskWithSubtasks ? ((TaskWithSubtasks)task).getActiveSubtaskIndex() : 0);
if (studentTaskFile != null) {
showPreviewDialog(project, studentTaskFile);
}
});
}
private static void showPreviewDialog(@NotNull Project project, @NotNull VirtualFile userFile, @NotNull TaskFile taskFile) {
private static void showPreviewDialog(@NotNull Project project, @NotNull TaskFile taskFile) {
final FrameWrapper showPreviewFrame = new FrameWrapper(project);
final LightVirtualFile userFile = new LightVirtualFile(taskFile.name, taskFile.text);
showPreviewFrame.setTitle(userFile.getName());
LabeledEditor labeledEditor = new LabeledEditor(null);
final EditorFactory factory = EditorFactory.getInstance();
@@ -0,0 +1,54 @@
package com.jetbrains.edu.coursecreator.actions.stepik;
import com.intellij.ide.IdeView;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.LangDataKeys;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.text.StringUtil;
import com.jetbrains.edu.coursecreator.actions.CCFromCourseArchive;
import com.jetbrains.edu.coursecreator.stepik.CCStepicConnector;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.RemoteCourse;
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
import org.jetbrains.annotations.NotNull;
public class CCGetCourseFromStepic extends DumbAwareAction {
public CCGetCourseFromStepic() {
super("Get Course From Stepik", "Get Course From Stepik", null);
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
final IdeView view = e.getData(LangDataKeys.IDE_VIEW);
final Project project = e.getData(CommonDataKeys.PROJECT);
if (view == null || project == null) {
return;
}
final String courseId = Messages.showInputDialog("Please, enter course id", "Get Course From Stepik", null);
if (StringUtil.isNotEmpty(courseId)) {
ProgressManager.getInstance().run(new Task.Modal(project, "Creating Course", true) {
@Override
public void run(@NotNull final ProgressIndicator indicator) {
createCourse(project, courseId);
}
});
}
}
private static void createCourse(Project project, String courseId) {
final RemoteCourse info = CCStepicConnector.getCourseInfo(courseId);
if (info == null) return;
final Course course = EduStepicConnector.getCourse(project, info);
if (course == null) return;
CCFromCourseArchive.generateFromStudentCourse(project, course);
}
}
@@ -1,4 +1,4 @@
package com.jetbrains.edu.coursecreator.actions;
package com.jetbrains.edu.coursecreator.actions.stepik;
import com.intellij.ide.IdeView;
import com.intellij.openapi.actionSystem.AnActionEvent;
@@ -11,12 +11,12 @@ import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.jetbrains.edu.coursecreator.CCUtils;
import com.jetbrains.edu.coursecreator.stepik.CCStepicConnector;
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.statistics.EduUsagesCollector;
import com.jetbrains.edu.coursecreator.stepik.CCStepicConnector;
import com.jetbrains.edu.learning.courseFormat.CourseInfo;
import org.jetbrains.annotations.NotNull;
import java.util.List;
@@ -33,11 +33,8 @@ public class CCPushCourse extends DumbAwareAction {
presentation.setEnabledAndVisible(project != null && CCUtils.isCourseCreator(project));
if (project != null) {
final Course course = StudyTaskManager.getInstance(project).getCourse();
if (course != null) {
final int id = course.getId();
if (id > 0) {
presentation.setText("Update Course on Stepik");
}
if (course instanceof RemoteCourse) {
presentation.setText("Update Course on Stepik");
}
}
}
@@ -53,19 +50,18 @@ public class CCPushCourse extends DumbAwareAction {
if (course == null) {
return;
}
if (course.getId() > 0) {
if (course instanceof RemoteCourse) {
ProgressManager.getInstance().run(new Task.Modal(project, "Updating Course", true) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
for (Lesson lesson : course.getLessons()) {
if (lesson.getId() > 0) {
CCStepicConnector.updateLesson(project, lesson, indicator);
CCStepicConnector.updateLesson(project, lesson);
}
else {
final CourseInfo info = CourseInfo.fromCourse(course);
final int lessonId = CCStepicConnector.postLesson(project, lesson, indicator);
final int lessonId = CCStepicConnector.postLesson(project, lesson);
if (lessonId != -1) {
final List<Integer> sections = info.getSections();
final List<Integer> sections = ((RemoteCourse)course).getSections();
final Integer sectionId = sections.get(sections.size() - 1);
CCStepicConnector.postUnit(lessonId, lesson.getIndex(), sectionId);
}
@@ -1,4 +1,4 @@
package com.jetbrains.edu.coursecreator.actions;
package com.jetbrains.edu.coursecreator.actions.stepik;
import com.intellij.ide.IdeView;
import com.intellij.ide.util.DirectoryChooserUtil;
@@ -12,11 +12,11 @@ import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiDirectory;
import com.jetbrains.edu.coursecreator.CCUtils;
import com.jetbrains.edu.coursecreator.stepik.CCStepicConnector;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.Lesson;
import com.jetbrains.edu.coursecreator.stepik.CCStepicConnector;
import com.jetbrains.edu.learning.courseFormat.CourseInfo;
import com.jetbrains.edu.learning.courseFormat.RemoteCourse;
import com.jetbrains.edu.learning.stepic.EduStepicNames;
import org.jetbrains.annotations.NotNull;
@@ -36,7 +36,7 @@ public class CCPushLesson extends DumbAwareAction {
return;
}
final Course course = StudyTaskManager.getInstance(project).getCourse();
if (course == null) {
if (course == null || !(course instanceof RemoteCourse)) {
return;
}
if (!course.getCourseMode().equals(CCUtils.COURSE_MODE)) return;
@@ -45,7 +45,7 @@ public class CCPushLesson extends DumbAwareAction {
return;
}
final Lesson lesson = course.getLesson(lessonDir.getName());
if (lesson != null && course.getId() > 0) {
if (lesson != null && ((RemoteCourse)course).getId() > 0) {
e.getPresentation().setEnabledAndVisible(true);
if (lesson.getId() <= 0) {
e.getPresentation().setText("Upload Lesson to Stepik");
@@ -61,7 +61,7 @@ public class CCPushLesson extends DumbAwareAction {
return;
}
final Course course = StudyTaskManager.getInstance(project).getCourse();
if (course == null) {
if (course == null || !(course instanceof RemoteCourse)) {
return;
}
PsiDirectory lessonDir = DirectoryChooserUtil.getOrChooseDirectory(view);
@@ -77,12 +77,11 @@ public class CCPushLesson extends DumbAwareAction {
public void run(@NotNull ProgressIndicator indicator) {
indicator.setText("Uploading lesson to " + EduStepicNames.STEPIC_URL);
if (lesson.getId() > 0) {
CCStepicConnector.updateLesson(project, lesson, indicator);
CCStepicConnector.updateLesson(project, lesson);
}
else {
final CourseInfo info = CourseInfo.fromCourse(course);
final int lessonId = CCStepicConnector.postLesson(project, lesson, indicator);
final List<Integer> sections = info.getSections();
final int lessonId = CCStepicConnector.postLesson(project, lesson);
final List<Integer> sections = ((RemoteCourse)course).getSections();
final Integer sectionId = sections.get(sections.size()-1);
CCStepicConnector.postUnit(lessonId, lesson.getIndex(), sectionId);
}
@@ -1,4 +1,4 @@
package com.jetbrains.edu.coursecreator.actions;
package com.jetbrains.edu.coursecreator.actions.stepik;
import com.intellij.ide.IdeView;
import com.intellij.ide.util.DirectoryChooserUtil;
@@ -12,11 +12,12 @@ import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiDirectory;
import com.jetbrains.edu.coursecreator.CCUtils;
import com.jetbrains.edu.coursecreator.stepik.CCStepicConnector;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.Lesson;
import com.jetbrains.edu.coursecreator.stepik.CCStepicConnector;
import com.jetbrains.edu.learning.courseFormat.RemoteCourse;
import com.jetbrains.edu.learning.stepic.EduStepicNames;
import org.jetbrains.annotations.NotNull;
@@ -34,7 +35,7 @@ public class CCPushTask extends DumbAwareAction {
return;
}
final Course course = StudyTaskManager.getInstance(project).getCourse();
if (course == null) {
if (course == null || !(course instanceof RemoteCourse)) {
return;
}
if (!course.getCourseMode().equals(CCUtils.COURSE_MODE)) return;
@@ -45,7 +46,7 @@ public class CCPushTask extends DumbAwareAction {
final PsiDirectory lessonDir = taskDir.getParentDirectory();
if (lessonDir == null) return;
final Lesson lesson = course.getLesson(lessonDir.getName());
if (lesson != null && lesson.getId() > 0 && course.getId() > 0) {
if (lesson != null && lesson.getId() > 0 && ((RemoteCourse)course).getId() > 0) {
e.getPresentation().setEnabledAndVisible(true);
final com.jetbrains.edu.learning.courseFormat.tasks.Task task = lesson.getTask(taskDir.getName());
if (task.getStepId() <= 0) {
@@ -109,7 +109,7 @@ public class CCLessonMoveHandlerDelegate extends MoveHandlerDelegate {
newItemIndex - 1, EduNames.LESSON, 1);
sourceLesson.setIndex(newItemIndex);
Collections.sort(course.getLessons(), EduUtils.INDEX_COMPARATOR);
Collections.sort(course.getLessons(true), EduUtils.INDEX_COMPARATOR);
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
@@ -28,7 +28,6 @@ import com.jetbrains.edu.coursecreator.ui.CCNewProjectPanel;
import com.jetbrains.edu.learning.EduPluginConfigurator;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
import com.jetbrains.edu.learning.intellij.generation.EduCourseModuleBuilder;
import org.jdom.JDOMException;
import org.jetbrains.annotations.NotNull;
@@ -36,7 +35,6 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.text.JTextComponent;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.function.Function;
@@ -93,8 +91,6 @@ class EduCCModuleBuilder extends EduCourseModuleBuilder {
Language language = wrapper.getLanguage();
course.setLanguage(language.getID());
course.setCourseMode(CCUtils.COURSE_MODE);
File courseDir = new File(StudyProjectGenerator.OUR_COURSES_DIR, myPanel.getName() + "-" + project.getName());
course.setCourseDirectory(courseDir.getPath());
StudyTaskManager.getInstance(project).setCourse(course);
EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(language);
String languageName = language.getDisplayName();
@@ -19,15 +19,13 @@ public class CCOptions implements StudyOptionsProvider {
if (!StudySettings.getInstance().isCourseCreatorEnabled()) return null;
if (CCSettings.getInstance().useHtmlAsDefaultTaskFormat()) {
myHtmlRadioButton.setSelected(true);
IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> {
IdeFocusManager.getGlobalInstance().requestFocus(myHtmlRadioButton, true);
});
IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(
() -> IdeFocusManager.getGlobalInstance().requestFocus(myHtmlRadioButton, true));
}
else {
myMarkdownRadioButton.setSelected(true);
IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> {
IdeFocusManager.getGlobalInstance().requestFocus(myMarkdownRadioButton, true);
});
IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(
() -> IdeFocusManager.getGlobalInstance().requestFocus(myMarkdownRadioButton, true));
}
return myPanel;
}
@@ -8,23 +8,19 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileFilter;
import com.jetbrains.edu.coursecreator.CCUtils;
import com.jetbrains.edu.learning.StudySerializationUtils;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.core.EduUtils;
import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.CourseInfo;
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.stepic.EduStepicAuthorizedClient;
import com.jetbrains.edu.learning.stepic.EduStepicNames;
import com.jetbrains.edu.learning.stepic.StepicUser;
import com.jetbrains.edu.learning.stepic.StepicWrappers;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
@@ -48,7 +44,7 @@ public class CCStepicConnector {
private CCStepicConnector() {
}
public static CourseInfo getCourseInfo(String courseId) {
public static RemoteCourse getCourseInfo(String courseId) {
final String url = EduStepicNames.COURSES + "/" + courseId;
try {
final StepicWrappers.CoursesContainer coursesContainer =
@@ -65,13 +61,16 @@ public class CCStepicConnector {
ProgressManager.getInstance().run(new com.intellij.openapi.progress.Task.Modal(project, "Uploading Course", true) {
@Override
public void run(@NotNull final ProgressIndicator indicator) {
postCourse(project, course, indicator);
postCourse(project, course);
}
});
}
private static void postCourse(final Project project, @NotNull Course course, @NotNull final ProgressIndicator indicator) {
indicator.setText("Uploading course to " + EduStepicNames.STEPIC_URL);
private static void postCourse(final Project project, @NotNull Course course) {
final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
if (indicator != null) {
indicator.setText("Uploading course to " + EduStepicNames.STEPIC_URL);
}
final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + "/courses");
final StepicUser currentUser = EduStepicAuthorizedClient.getCurrentUser();
@@ -105,60 +104,33 @@ public class CCStepicConnector {
LOG.error("Failed to push " + responseString);
return;
}
final CourseInfo postedCourse = new Gson().fromJson(responseString, StepicWrappers.CoursesContainer.class).courses.get(0);
course.setId(postedCourse.getId());
final RemoteCourse postedCourse = new Gson().fromJson(responseString, StepicWrappers.CoursesContainer.class).courses.get(0);
postedCourse.setLessons(course.getLessons(true));
postedCourse.setAuthors(course.getAuthors());
postedCourse.setCourseMode(CCUtils.COURSE_MODE);
final int sectionId = postModule(postedCourse.getId(), 1, String.valueOf(postedCourse.getName()));
int position = 1;
for (Lesson lesson : course.getLessons()) {
indicator.checkCanceled();
final int lessonId = postLesson(project, lesson, indicator);
if (indicator != null) {
indicator.checkCanceled();
}
final int lessonId = postLesson(project, lesson);
postUnit(lessonId, position, sectionId);
position += 1;
}
ApplicationManager.getApplication().runReadAction(() -> postAdditionalFiles(project, postedCourse.getId(), indicator));
ApplicationManager.getApplication().runReadAction(() -> postAdditionalFiles(course, project, postedCourse.getId()));
StudyTaskManager.getInstance(project).setCourse(postedCourse);
}
catch (IOException e) {
LOG.error(e.getMessage());
}
}
private static void postAdditionalFiles(@NotNull final Project project, int id, ProgressIndicator indicator) {
final VirtualFile baseDir = project.getBaseDir();
final List<VirtualFile> files = VfsUtil.getChildren(baseDir, new VirtualFileFilter() {
@Override
public boolean accept(VirtualFile file) {
final String name = file.getName();
return !name.contains(EduNames.LESSON) && !name.equals(EduNames.COURSE_META_FILE) && !name.equals(EduNames.HINTS) &&
!"pyc".equals(file.getExtension()) && !file.isDirectory() && !name.equals(EduNames.TEST_HELPER) && !name.startsWith(".");
}
});
if (!files.isEmpty()) {
private static void postAdditionalFiles(Course course, @NotNull final Project project, int id) {
final Lesson lesson = CCUtils.createAdditionalLesson(course, project);
if (lesson != null) {
final int sectionId = postModule(id, 2, EduNames.PYCHARM_ADDITIONAL);
final Lesson lesson = new Lesson();
lesson.setName(EduNames.PYCHARM_ADDITIONAL);
final Task task = new Task();
task.setLesson(lesson);
task.setName(EduNames.PYCHARM_ADDITIONAL);
task.setIndex(1);
for (VirtualFile file : files) {
try {
if (file != null) {
if (EduUtils.isImage(file.getName())) {
task.addTestsTexts(file.getName(), Base64.encodeBase64URLSafeString(FileUtil.loadBytes(file.getInputStream())));
}
else {
task.addTestsTexts(file.getName(), FileUtil.loadTextAndClose(file.getInputStream()));
}
}
}
catch (IOException e) {
LOG.error("Can't find file " + file.getPath());
}
}
lesson.addTask(task);
lesson.setIndex(1);
final int lessonId = postLesson(project, lesson, indicator);
final int lessonId = postLesson(project, lesson);
postUnit(lessonId, 1, sectionId);
}
}
@@ -255,7 +227,7 @@ public class CCStepicConnector {
return -1;
}
public static int updateLesson(@NotNull final Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
public static int updateLesson(@NotNull final Project project, @NotNull final Lesson lesson) {
final HttpPut request = new HttpPut(EduStepicNames.STEPIC_API_URL + EduStepicNames.LESSONS + String.valueOf(lesson.getId()));
String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson));
@@ -273,13 +245,16 @@ public class CCStepicConnector {
LOG.error("Failed to push " + responseString);
return -1;
}
final Lesson postedLesson = new Gson().fromJson(responseString, Course.class).getLessons().get(0);
final Lesson postedLesson = new Gson().fromJson(responseString, RemoteCourse.class).getLessons().get(0);
for (Integer step : postedLesson.steps) {
deleteTask(step);
}
for (Task task : lesson.getTaskList()) {
indicator.checkCanceled();
final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
if (indicator != null) {
indicator.checkCanceled();
}
postTask(project, task, lesson.getId());
}
return lesson.getId();
@@ -290,7 +265,7 @@ public class CCStepicConnector {
return -1;
}
public static int postLesson(@NotNull final Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
public static int postLesson(@NotNull final Project project, @NotNull final Lesson lesson) {
final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + "/lessons");
String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson));
@@ -308,10 +283,13 @@ public class CCStepicConnector {
LOG.error("Failed to push " + responseString);
return 0;
}
final Lesson postedLesson = new Gson().fromJson(responseString, Course.class).getLessons().get(0);
final Lesson postedLesson = new Gson().fromJson(responseString, RemoteCourse.class).getLessons(true).get(0);
lesson.setId(postedLesson.getId());
for (Task task : lesson.getTaskList()) {
indicator.checkCanceled();
final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
if (indicator != null) {
indicator.checkCanceled();
}
postTask(project, task, postedLesson.getId());
}
return postedLesson.getId();
@@ -21,7 +21,8 @@ import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.Collections;
import java.util.List;
public interface EduPluginConfigurator {
String EP_NAME = "Edu.pluginConfigurator";
@@ -64,7 +65,7 @@ public interface EduPluginConfigurator {
/**
* Used in educator plugin to filter files to be packed into course archive
*/
boolean excludeFromArchive(@NotNull File pathname);
boolean excludeFromArchive(@NotNull String name);
/**
* @return true for all the test files including tests for subtasks
@@ -128,4 +129,7 @@ public interface EduPluginConfigurator {
@Nullable String moduleDir) {
}
default List<String> getBundledCoursePaths() {
return Collections.emptyList();
}
}
@@ -25,8 +25,8 @@ import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileAdapter;
import com.intellij.openapi.vfs.VirtualFileEvent;
import com.intellij.openapi.vfs.VirtualFileListener;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowManager;
@@ -39,9 +39,9 @@ import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.core.EduUtils;
import com.jetbrains.edu.learning.courseFormat.*;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import com.jetbrains.edu.learning.courseGeneration.StudyGenerator;
import com.jetbrains.edu.learning.editor.StudyEditorFactoryListener;
import com.jetbrains.edu.learning.statistics.EduUsagesCollector;
import com.jetbrains.edu.learning.courseFormat.CourseInfo;
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
import com.jetbrains.edu.learning.ui.StudyToolWindow;
import com.jetbrains.edu.learning.ui.StudyToolWindowFactory;
@@ -50,14 +50,12 @@ import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static com.jetbrains.edu.learning.StudyUtils.execCancelable;
import static com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator.flushCourse;
public class StudyProjectComponent implements ProjectComponent {
@@ -80,39 +78,18 @@ public class StudyProjectComponent implements ProjectComponent {
}
if (course != null && !course.isAdaptive() && !course.isUpToDate()) {
final Notification notification =
new Notification("Update.course", "Course Updates", "Course is ready to <a href=\"update\">update</a>", NotificationType.INFORMATION,
new NotificationListener() {
@Override
public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
FileEditorManagerEx.getInstanceEx(myProject).closeAllFiles();
ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true);
return execCancelable(() -> {
updateCourse();
return true;
});
}, "Updating Course", true, myProject);
EduUtils.synchronize();
course.setUpdated();
}
});
notification.notify(myProject);
updateAvailable(course);
}
StudyUtils.registerStudyToolWindow(course, myProject);
StartupManager.getInstance(myProject).runWhenProjectIsInitialized(() -> ApplicationManager.getApplication().invokeLater(
(DumbAwareRunnable)() -> ApplicationManager.getApplication().runWriteAction((DumbAwareRunnable)() -> {
Course course1 = StudyTaskManager.getInstance(myProject).getCourse();
if (course1 != null) {
if (course != null) {
UISettings instance = UISettings.getInstance();
instance.setHideToolStripes(false);
instance.fireUISettingsChanged();
registerShortcuts();
EduUsagesCollector.projectTypeOpened(course1.isAdaptive() ? EduNames.ADAPTIVE : EduNames.STUDY);
EduUsagesCollector.projectTypeOpened(course.isAdaptive() ? EduNames.ADAPTIVE : EduNames.STUDY);
}
})));
@@ -128,6 +105,29 @@ public class StudyProjectComponent implements ProjectComponent {
});
}
private void updateAvailable(Course course) {
final Notification notification =
new Notification("Update.course", "Course Updates", "Course is ready to <a href=\"update\">update</a>", NotificationType.INFORMATION,
new NotificationListener() {
@Override
public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
FileEditorManagerEx.getInstanceEx(myProject).closeAllFiles();
ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true);
return execCancelable(() -> {
updateCourse();
return true;
});
}, "Updating Course", true, myProject);
EduUtils.synchronize();
course.setUpdated();
}
});
notification.notify(myProject);
}
private void registerShortcuts() {
StudyToolWindow window = StudyUtils.getStudyToolWindow(myProject);
if (window != null) {
@@ -148,18 +148,9 @@ public class StudyProjectComponent implements ProjectComponent {
private void updateCourse() {
final Course currentCourse = StudyTaskManager.getInstance(myProject).getCourse();
final CourseInfo info = CourseInfo.fromCourse(currentCourse);
if (info == null) return;
final File resourceDirectory = new File(currentCourse.getCourseDirectory());
if (resourceDirectory.exists()) {
FileUtil.delete(resourceDirectory);
}
final Course course = EduStepicConnector.getCourse(myProject, info);
if (currentCourse == null || !(currentCourse instanceof RemoteCourse)) return;
final Course course = EduStepicConnector.getCourse(myProject, (RemoteCourse)currentCourse);
if (course == null) return;
flushCourse(course);
course.initCourse(false);
EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(course.getLanguageById());
@@ -171,19 +162,19 @@ public class StudyProjectComponent implements ProjectComponent {
final ArrayList<Lesson> updatedLessons = new ArrayList<>();
int lessonIndex = 0;
for (Lesson lesson : course.getLessons()) {
for (Lesson lesson : course.getLessons(true)) {
lessonIndex += 1;
Lesson studentLesson = currentCourse.getLesson(lesson.getId());
final String lessonDirName = EduNames.LESSON + String.valueOf(lessonIndex);
final File lessonDir = new File(myProject.getBasePath(), lessonDirName);
if (!lessonDir.exists()){
final File fromLesson = new File(resourceDirectory, lessonDirName);
final VirtualFile baseDir = myProject.getBaseDir();
final VirtualFile lessonDir = baseDir.findChild(lessonDirName);
if (lessonDir == null) {
try {
FileUtil.copyDir(fromLesson, lessonDir);
StudyGenerator.createLesson(lesson, baseDir);
}
catch (IOException e) {
LOG.warn("Failed to copy lesson " + fromLesson.getPath());
LOG.error("Failed to create lesson");
}
lesson.setIndex(lessonIndex);
lesson.initLesson(currentCourse, false);
@@ -211,15 +202,14 @@ public class StudyProjectComponent implements ProjectComponent {
task.setIndex(index);
final String taskDirName = EduNames.TASK + String.valueOf(index);
final File toTask = new File(lessonDir, taskDirName);
final VirtualFile taskDir = lessonDir.findChild(taskDirName);
final String taskPath = FileUtil.join(resourceDirectory.getPath(), lessonDirName, taskDirName);
final File taskDir = new File(taskPath);
if (!taskDir.exists()) return;
final File[] taskFiles = taskDir.listFiles();
if (taskFiles == null) continue;
for (File fromFile : taskFiles) {
copyFile(fromFile, new File(toTask, fromFile.getName()));
if (taskDir != null) return;
try {
StudyGenerator.createTask(task, lessonDir);
}
catch (IOException e) {
LOG.error("Failed to create task");
}
tasks.add(task);
}
@@ -232,17 +222,6 @@ public class StudyProjectComponent implements ProjectComponent {
notification.notify(myProject);
}
private static void copyFile(@NotNull final File from, @NotNull final File to) {
if (from.exists()) {
try {
FileUtil.copyFileOrDir(from, to);
}
catch (IOException e) {
LOG.warn("Failed to copy " + from.getName());
}
}
}
private void addShortcut(@NotNull final String actionIdString, @NotNull final String[] shortcuts) {
KeymapManagerEx keymapManager = KeymapManagerEx.getInstanceEx();
for (Keymap keymap : keymapManager.getAllKeymaps()) {
@@ -333,7 +312,7 @@ public class StudyProjectComponent implements ProjectComponent {
return module.getComponent(StudyProjectComponent.class);
}
private class FileCreatedByUserListener extends VirtualFileAdapter {
private class FileCreatedByUserListener implements VirtualFileListener {
@Override
public void fileCreated(@NotNull VirtualFileEvent event) {
if (myProject.isDisposed()) return;
@@ -5,17 +5,13 @@ import com.google.gson.reflect.TypeToken;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.hash.HashMap;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.StudyStatus;
import com.jetbrains.edu.learning.courseFormat.tasks.*;
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
@@ -26,7 +22,6 @@ import org.jdom.Element;
import org.jdom.output.XMLOutputter;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
@@ -62,6 +57,7 @@ public class StudySerializationUtils {
public static class Xml {
public final static String COURSE_ELEMENT = "courseElement";
public final static String MAIN_ELEMENT = "StudyTaskManager";
public final static String REMOTE_COURSE = "RemoteCourse";
public static final String MAP = "map";
public static final String KEY = "key";
public static final String VALUE = "value";
@@ -262,14 +258,17 @@ public class StudySerializationUtils {
final Element adaptive = getChildWithName(courseElement, ADAPTIVE);
for (Element lesson : getChildList(courseElement, LESSONS)) {
for (Element task : getChildList(lesson, TASK_LIST)) {
final Element lastSubtaskIndex = getChildWithName(task, LAST_SUBTASK_INDEX);
final Element theoryTask = getChildWithName(task, THEORY_TAG);
final Element adaptiveParams = getChildWithName(task, ADAPTIVE_TASK_PARAMETERS);
final boolean hasAdaptiveParams = !adaptiveParams.getChildren().isEmpty();
if (Integer.valueOf(lastSubtaskIndex.getAttributeValue(VALUE)) != 0) {
final Element lastSubtaskIndex = getChildWithName(task, LAST_SUBTASK_INDEX, true); //could be broken by 3->4 migration
final Element adaptiveParams = getChildWithName(task, ADAPTIVE_TASK_PARAMETERS, true);
Element theoryTask = getChildWithName(task, THEORY_TAG, true);
if (theoryTask == null && adaptiveParams != null) {
theoryTask = getChildWithName(adaptiveParams, THEORY_TAG, true);
}
final boolean hasAdaptiveParams = adaptiveParams != null && !adaptiveParams.getChildren().isEmpty();
if (lastSubtaskIndex != null && Integer.valueOf(lastSubtaskIndex.getAttributeValue(VALUE)) != 0) {
task.setName(TASK_WITH_SUBTASKS);
}
else if (Boolean.valueOf(theoryTask.getAttributeValue(VALUE))) {
else if (theoryTask != null && Boolean.valueOf(theoryTask.getAttributeValue(VALUE))) {
task.setName(THEORY_TASK);
}
else if (hasAdaptiveParams) {
@@ -455,61 +454,6 @@ public class StudySerializationUtils {
private Json() {
}
public static class CourseTypeAdapter implements JsonDeserializer<Course> {
private final File myCourseFile;
public CourseTypeAdapter(File courseFile) {
myCourseFile = courseFile;
}
@Override
public Course deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject courseObject = json.getAsJsonObject();
JsonArray lessons = courseObject.getAsJsonArray(LESSONS);
for (int lessonIndex = 1; lessonIndex <= lessons.size(); lessonIndex++) {
JsonObject lessonObject = lessons.get(lessonIndex - 1).getAsJsonObject();
JsonArray tasks = lessonObject.getAsJsonArray(TASK_LIST);
for (int taskIndex = 1; taskIndex <= tasks.size(); taskIndex++) {
JsonObject taskObject = tasks.get(taskIndex - 1).getAsJsonObject();
for (Map.Entry<String, JsonElement> taskFile : taskObject.getAsJsonObject(TASK_FILES).entrySet()) {
String name = taskFile.getKey();
String filePath = FileUtil.join(myCourseFile.getParent(), EduNames.LESSON + lessonIndex, EduNames.TASK + taskIndex, name);
VirtualFile resourceFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(filePath));
if (resourceFile == null) {
continue;
}
Document document = FileDocumentManager.getInstance().getDocument(resourceFile);
if (document == null) {
continue;
}
JsonObject taskFileObject = taskFile.getValue().getAsJsonObject();
JsonArray placeholders = taskFileObject.getAsJsonArray(PLACEHOLDERS);
for (JsonElement placeholder : placeholders) {
convertToAbsoluteOffset(document, placeholder);
if (placeholder.getAsJsonObject().getAsJsonObject(SUBTASK_INFOS) == null) {
convertToSubtaskInfo(placeholder.getAsJsonObject());
removeIndexFromSubtaskInfos(placeholder.getAsJsonObject());
}
}
}
}
}
return new GsonBuilder().registerTypeAdapter(Task.class, new TaskDeserializer()).create().fromJson(json, Course.class);
}
private static void convertToAbsoluteOffset(Document document, JsonElement placeholder) {
JsonObject placeholderObject = placeholder.getAsJsonObject();
if (placeholderObject.getAsJsonPrimitive(OFFSET) != null) {
return;
}
int line = placeholderObject.getAsJsonPrimitive(LINE).getAsInt();
int start = placeholderObject.getAsJsonPrimitive(START).getAsInt();
int offset = document.getLineStartOffset(line) + start;
placeholderObject.addProperty(OFFSET, offset);
}
}
public static class StepicStepOptionsAdapter implements JsonDeserializer<StepicWrappers.StepOptions> {
@Override
public StepicWrappers.StepOptions deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
@@ -537,7 +481,7 @@ public class StudySerializationUtils {
return stepOptions;
}
private JsonObject convertToThirdVersion(JsonObject stepOptionsJson) {
private static JsonObject convertToThirdVersion(JsonObject stepOptionsJson) {
if (!stepOptionsJson.has(LAST_SUBTASK)) return stepOptionsJson;
final int lastSubtaskIndex = stepOptionsJson.get(LAST_SUBTASK).getAsInt();
if (lastSubtaskIndex == 0) return stepOptionsJson;
@@ -549,7 +493,7 @@ public class StudySerializationUtils {
}
}
final JsonArray descriptions = stepOptionsJson.getAsJsonArray(TEXTS);
if (descriptions.size() > 0) {
if (descriptions != null && descriptions.size() > 0) {
final JsonObject fileWrapper = descriptions.get(0).getAsJsonObject();
if (fileWrapper.has(NAME)) {
replaceWithSubtask(fileWrapper);
@@ -558,7 +502,7 @@ public class StudySerializationUtils {
return stepOptionsJson;
}
private void replaceWithSubtask(JsonObject fileWrapper) {
private static void replaceWithSubtask(JsonObject fileWrapper) {
final String file = fileWrapper.get(NAME).getAsString();
final String extension = FileUtilRt.getExtension(file);
final String name = FileUtil.getNameWithoutExtension(file);
@@ -673,7 +617,7 @@ public class StudySerializationUtils {
subtaskInfo.addProperty(POSSIBLE_ANSWER, placeholderObject.getAsJsonPrimitive(POSSIBLE_ANSWER).getAsString());
}
public static class TaskSerializer implements JsonSerializer<Task> {
public static class TaskAdapter implements JsonSerializer<Task>, JsonDeserializer<Task> {
@Override
public JsonElement serialize(Task src, Type typeOfSrc, JsonSerializationContext context) {
@@ -683,9 +627,6 @@ public class StudySerializationUtils {
task.add(TASK_TYPE, new JsonPrimitive(src.getTaskType()));
return task;
}
}
public static class TaskDeserializer implements JsonDeserializer<Task> {
@Override
public Task deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
@@ -705,7 +646,7 @@ public class StudySerializationUtils {
}
}
}
LOG.warn("No task type found in json " + json.getAsString());
LOG.warn("No task type found in json " + json.toString());
return null;
}
}
@@ -1,5 +1,9 @@
package com.jetbrains.edu.learning;
import com.intellij.ide.fileTemplates.FileTemplate;
import com.intellij.ide.fileTemplates.FileTemplateManager;
import com.intellij.ide.fileTemplates.FileTemplateUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.components.State;
@@ -7,25 +11,31 @@ import com.intellij.openapi.components.Storage;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiManager;
import com.intellij.ui.JBColor;
import com.intellij.util.containers.hash.HashMap;
import com.intellij.util.xmlb.XmlSerializer;
import com.intellij.util.xmlb.annotations.Transient;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.courseFormat.*;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
import com.jetbrains.edu.learning.ui.StudyToolWindow;
import org.jdom.Element;
import org.jdom.output.XMLOutputter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static com.jetbrains.edu.learning.StudySerializationUtils.Xml.REMOTE_COURSE;
/**
* Implementation of class which contains all the information
* about study in context of current project
@@ -39,7 +49,6 @@ public class StudyTaskManager implements PersistentStateComponent<Element>, Dumb
public int VERSION = 5;
public Map<Task, List<UserTest>> myUserTests = new HashMap<>();
public List<String> myInvisibleFiles = new ArrayList<>();
public boolean myShouldUseJavaFx = StudyUtils.hasJavaFx();
private StudyToolWindow.StudyToolWindowMode myToolWindowMode = StudyToolWindow.StudyToolWindowMode.TEXT;
@@ -118,10 +127,32 @@ public class StudyTaskManager implements PersistentStateComponent<Element>, Dumb
if (myCourse == null) {
return null;
}
return serialize();
}
@NotNull
private Element serialize() {
Element el = new Element("taskManager");
Element courseElement = new Element(StudySerializationUtils.Xml.MAIN_ELEMENT);
XmlSerializer.serializeInto(this, courseElement);
el.addContent(courseElement);
Element taskManagerElement = new Element(StudySerializationUtils.Xml.MAIN_ELEMENT);
XmlSerializer.serializeInto(this, taskManagerElement);
if (myCourse instanceof RemoteCourse) {
try {
Element course = new Element(REMOTE_COURSE);
//noinspection RedundantCast
XmlSerializer.serializeInto((RemoteCourse)myCourse, course);
final Element xmlCourse = StudySerializationUtils.Xml.getChildWithName(taskManagerElement, StudySerializationUtils.COURSE);
xmlCourse.removeContent();
xmlCourse.addContent(course);
}
catch (StudySerializationUtils.StudyUnrecognizedFormatException e) {
LOG.error("Failed to serialize remote course");
}
}
el.addContent(taskManagerElement);
return el;
}
@@ -142,20 +173,15 @@ public class StudyTaskManager implements PersistentStateComponent<Element>, Dumb
state = StudySerializationUtils.Xml.convertToForthVersion(state);
case 4:
state = StudySerializationUtils.Xml.convertToFifthVersion(state);
updateTestHelper();
//uncomment for future versions
//case 5:
// state = StudySerializationUtils.Xml.convertToSixthVersion(state, myProject);
}
XmlSerializer.deserializeInto(this, state.getChild(StudySerializationUtils.Xml.MAIN_ELEMENT));
deserialize(state);
VERSION = CURRENT_VERSION;
if (myCourse != null) {
myCourse.initCourse(true);
if (version != VERSION) {
final File updatedCourse = new File(StudyProjectGenerator.OUR_COURSES_DIR, myCourse.getName());
if (updatedCourse.exists()) {
myCourse.setCourseDirectory(updatedCourse.getAbsolutePath());
}
}
}
}
catch (StudySerializationUtils.StudyUnrecognizedFormatException e) {
@@ -163,18 +189,44 @@ public class StudyTaskManager implements PersistentStateComponent<Element>, Dumb
}
}
private void updateTestHelper() {
StartupManager.getInstance(myProject).runWhenProjectIsInitialized(() -> ApplicationManager.getApplication().runWriteAction(() -> {
final VirtualFile testHelper = myProject.getBaseDir().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());
if (projectDir != null) {
FileTemplateUtil.createFromTemplate(template, EduNames.TEST_HELPER, null, projectDir);
}
}
catch (Exception e) {
LOG.warn("Failed to create new test helper");
}
}));
}
private void deserialize(Element state) throws StudySerializationUtils.StudyUnrecognizedFormatException {
final Element taskManagerElement = state.getChild(StudySerializationUtils.Xml.MAIN_ELEMENT);
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);
if (remoteCourseElement != null) {
final RemoteCourse remoteCourse = new RemoteCourse();
XmlSerializer.deserializeInto(remoteCourse, remoteCourseElement);
myCourse = remoteCourse;
}
}
public static StudyTaskManager getInstance(@NotNull final Project project) {
return ServiceManager.getService(project, StudyTaskManager.class);
}
public void addInvisibleFiles(String filePath) {
myInvisibleFiles.add(filePath);
}
public boolean isInvisibleFile(String path) {
return myInvisibleFiles.contains(path);
}
public boolean shouldUseJavaFx() {
return myShouldUseJavaFx;
}
@@ -7,6 +7,7 @@ import com.intellij.ide.IdeView;
import com.intellij.ide.fileTemplates.FileTemplate;
import com.intellij.ide.fileTemplates.FileTemplateManager;
import com.intellij.ide.fileTemplates.FileTemplateUtil;
import com.intellij.ide.projectView.ProjectView;
import com.intellij.ide.util.EditorHelper;
import com.intellij.lang.Language;
import com.intellij.openapi.actionSystem.AnActionEvent;
@@ -40,8 +41,9 @@ import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
import com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowAnchor;
@@ -49,6 +51,7 @@ import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.ui.JBColor;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.ui.content.Content;
@@ -72,9 +75,7 @@ import com.jetbrains.edu.learning.courseFormat.tasks.ChoiceTask;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks;
import com.jetbrains.edu.learning.courseFormat.tasks.TheoryTask;
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
import com.jetbrains.edu.learning.editor.StudyEditor;
import com.jetbrains.edu.learning.stepic.StepicUser;
import com.jetbrains.edu.learning.ui.StudyToolWindow;
import com.jetbrains.edu.learning.ui.StudyToolWindowFactory;
import com.petebevin.markdown.MarkdownProcessor;
@@ -84,9 +85,7 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.*;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
@@ -238,20 +237,6 @@ public class StudyUtils {
}
}
public static File copyResourceFile(@NotNull final String sourceName, @NotNull final String copyName, @NotNull final Project project,
@NotNull final Task task)
throws IOException {
final StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
final Course course = taskManager.getCourse();
int taskNum = task.getIndex();
int lessonNum = task.getLesson().getIndex();
assert course != null;
final String pathToResource = FileUtil.join(course.getCourseDirectory(), EduNames.LESSON + lessonNum, EduNames.TASK + taskNum);
final File resourceFile = new File(pathToResource, copyName);
FileUtil.copy(new File(pathToResource, sourceName), resourceFile);
return resourceFile;
}
@Nullable
public static Sdk findSdk(@NotNull final Task task, @NotNull final Project project) {
final Language language = task.getLesson().getCourse().getLanguageById();
@@ -413,34 +398,6 @@ public class StudyUtils {
}
}
@Nullable
public static VirtualFile getPatternFile(@NotNull TaskFile taskFile, String name) {
Task task = taskFile.getTask();
String lessonDir = EduNames.LESSON + String.valueOf(task.getLesson().getIndex());
String taskDir = EduNames.TASK + String.valueOf(task.getIndex());
Course course = task.getLesson().getCourse();
File resourceFile = new File(course.getCourseDirectory());
if (!resourceFile.exists()) {
return null;
}
String patternPath = FileUtil.join(resourceFile.getPath(), lessonDir, taskDir, name);
VirtualFile patternFile = VfsUtil.findFileByIoFile(new File(patternPath), true);
if (patternFile == null) {
return null;
}
return patternFile;
}
@Nullable
public static Document getPatternDocument(@NotNull final TaskFile taskFile, String name) {
VirtualFile patternFile = getPatternFile(taskFile, name);
if (patternFile == null) {
return null;
}
return FileDocumentManager.getInstance().getDocument(patternFile);
}
public static boolean isRenameableOrMoveable(@NotNull final Project project, @NotNull final Course course, @NotNull final PsiElement element) {
if (element instanceof PsiFile) {
VirtualFile virtualFile = ((PsiFile)element).getVirtualFile();
@@ -497,7 +454,6 @@ public class StudyUtils {
String text = task.getTaskDescription() != null ? task.getTaskDescription() : getTaskTextByTaskName(task, taskDirectory);
if (text == null) return null;
text = convertToHtml(text);
if (course.isAdaptive()) text = wrapAdaptiveCourseText(task, text);
return wrapTextToDisplayLatex(text);
@@ -537,20 +493,33 @@ public class StudyUtils {
@Nullable
private static String getTaskTextByTaskName(@NotNull Task task, @Nullable VirtualFile taskDirectory) {
if (taskDirectory == null) return null;
final String taskFileNameMd = constructTaskTextFilename(task, EduNames.TASK_MD);
final String taskFileNameHtml = constructTaskTextFilename(task, EduNames.TASK_HTML);
VirtualFile taskTextFile = ObjectUtils.chooseNotNull(taskDirectory.findChild(taskFileNameMd), taskDirectory.findChild(taskFileNameHtml));
if (taskTextFile == null) {
VirtualFile srcDir = taskDirectory.findChild(EduNames.SRC);
if (srcDir != null) {
taskTextFile = ObjectUtils.chooseNotNull(srcDir.findChild(taskFileNameHtml), srcDir.findChild(taskFileNameMd));
}
String textFromHtmlFile = getTextByTaskFileFormat(task, taskDirectory, EduNames.TASK_HTML);
if (textFromHtmlFile != null) {
return textFromHtmlFile;
}
String taskTextFromMd = getTextByTaskFileFormat(task, taskDirectory, EduNames.TASK_HTML);
return convertToHtml(taskTextFromMd);
}
@Nullable
private static String getTextByTaskFileFormat(@NotNull Task task, @NotNull VirtualFile taskDirectory, @NotNull String taskTextFileName) {
String textFilename = constructTaskTextFilename(task, taskTextFileName);
VirtualFile taskTextFile = taskDirectory.findChild(taskTextFileName);
if (taskTextFile != null) {
return String.valueOf(LoadTextUtil.loadText(taskTextFile));
}
VirtualFile srcDir = taskDirectory.findChild(EduNames.SRC);
if (srcDir != null) {
VirtualFile taskTextSrcFile = srcDir.findChild(textFilename);
if (taskTextSrcFile != null) {
return String.valueOf(LoadTextUtil.loadText(taskTextSrcFile));
}
}
return null;
}
@@ -611,23 +580,6 @@ public class StudyUtils {
return course != null && EduNames.STUDY.equals(course.getCourseMode());
}
@NotNull
public static File getCourseDirectory(Course course) {
final File courseDirectory;
if (course.isAdaptive()) {
StepicUser user = StudySettings.getInstance().getUser();
String email = user == null ? "" : user.getEmail();
courseDirectory = new File(StudyProjectGenerator.OUR_COURSES_DIR,
StudyProjectGenerator.ADAPTIVE_COURSE_PREFIX + course.getName()
+ "_" + email);
}
else {
courseDirectory = new File(StudyProjectGenerator.OUR_COURSES_DIR, course.getName());
}
return courseDirectory;
}
public static boolean hasJavaFx() {
try {
Class.forName("javafx.application.Platform");
@@ -713,7 +665,9 @@ public class StudyUtils {
return task;
}
private static String convertToHtml(@NotNull final String content) {
@Nullable
private static String convertToHtml(@Nullable final String content) {
if (content == null) return null;
ArrayList<String> lines = ContainerUtil.newArrayList(content.split("\n|\r|\r\n"));
MarkdownUtil.replaceHeaders(lines);
MarkdownUtil.replaceCodeBlock(lines);
@@ -866,4 +820,48 @@ public class StudyUtils {
LOG.error(e);
}
}
public static void openFirstTask(@NotNull final Course course, @NotNull final Project project) {
LocalFileSystem.getInstance().refresh(false);
final Lesson firstLesson = getFirst(course.getLessons());
if (firstLesson == null) return;
final Task firstTask = getFirst(firstLesson.getTaskList());
if (firstTask == null) return;
final VirtualFile taskDir = firstTask.getTaskDir(project);
if (taskDir == null) return;
final Map<String, TaskFile> taskFiles = firstTask.getTaskFiles();
VirtualFile activeVirtualFile = null;
for (Map.Entry<String, TaskFile> entry : taskFiles.entrySet()) {
final String relativePath = entry.getKey();
final TaskFile taskFile = entry.getValue();
taskDir.refresh(false, true);
final VirtualFile virtualFile = taskDir.findFileByRelativePath(relativePath);
if (virtualFile != null) {
if (!taskFile.getActivePlaceholders().isEmpty()) {
activeVirtualFile = virtualFile;
}
}
}
if (activeVirtualFile != null) {
final PsiFile file = PsiManager.getInstance(project).findFile(activeVirtualFile);
ProjectView.getInstance(project).select(file, activeVirtualFile, false);
final FileEditor[] editors = FileEditorManager.getInstance(project).openFile(activeVirtualFile, true);
if (editors.length == 0) {
return;
}
final FileEditor studyEditor = editors[0];
if (studyEditor instanceof StudyEditor) {
selectFirstAnswerPlaceholder((StudyEditor)studyEditor, project);
}
FileEditorManager.getInstance(project).openFile(activeVirtualFile, true);
}
else {
String first = getFirst(taskFiles.keySet());
if (first != null) {
NewVirtualFile firstFile = ((VirtualDirectoryImpl)taskDir).refreshAndFindChild(first);
if (firstFile != null) {
FileEditorManager.getInstance(project).openFile(firstFile, true);
}
}
}
}
}
@@ -3,13 +3,9 @@ package com.jetbrains.edu.learning.actions;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.jetbrains.edu.learning.StudyState;
import com.jetbrains.edu.learning.StudySubtaskUtils;
import com.jetbrains.edu.learning.StudyTaskManager;
@@ -17,8 +13,8 @@ import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.StudyStatus;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks;
import com.jetbrains.edu.learning.editor.StudyEditor;
import org.jetbrains.annotations.Nullable;
@@ -41,24 +37,12 @@ public class StudyRefreshAnswerPlaceholder extends DumbAwareAction {
return;
}
StudyEditor studyEditor = StudyUtils.getSelectedStudyEditor(project);
final StudyState studyState = new StudyState(studyEditor);
if (answerPlaceholder.getTaskFile().getTask() instanceof TaskWithSubtasks) {
StudySubtaskUtils.refreshPlaceholder(studyState.getEditor(), answerPlaceholder);
return;
if (studyEditor != null) {
StudySubtaskUtils.refreshPlaceholder(studyEditor.getEditor(), answerPlaceholder);
final StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
answerPlaceholder.reset();
taskManager.setStatus(answerPlaceholder, StudyStatus.Unchecked);
}
Document patternDocument = StudyUtils.getPatternDocument(answerPlaceholder.getTaskFile(), studyState.getVirtualFile().getName());
if (patternDocument == null) {
return;
}
AnswerPlaceholder.MyInitialState initialState = answerPlaceholder.getInitialState();
int startOffset = initialState.getOffset();
final String text = patternDocument.getText(new TextRange(startOffset, startOffset + initialState.getLength()));
CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> {
Document document = studyState.getEditor().getDocument();
int offset = answerPlaceholder.getOffset();
document.deleteString(offset, offset + answerPlaceholder.getRealLength());
document.insertString(offset, text);
}), NAME, null);
}
@Override
@@ -76,7 +76,7 @@ public class StudyRefreshTaskFileAction extends StudyActionWithShortcut {
}
}
else {
if (!resetTaskFile(editor.getDocument(), project, taskFile, studyState.getVirtualFile().getName())) {
if (!resetTaskFile(editor.getDocument(), project, taskFile)) {
Messages.showInfoMessage("The initial text of task file is unavailable", "Failed to Refresh Task File");
return;
}
@@ -95,16 +95,13 @@ public class StudyRefreshTaskFileAction extends StudyActionWithShortcut {
() -> IdeFocusManager.getInstance(project).requestFocus(editor.getContentComponent(), true));
StudyNavigator.navigateToFirstAnswerPlaceholder(editor, taskFile);
showBalloon(project, "You can start again now", MessageType.INFO);
showBalloon(project, MessageType.INFO);
}
private static boolean resetTaskFile(@NotNull final Document document,
@NotNull final Project project,
TaskFile taskFile,
String name) {
if (!resetDocument(document, taskFile, name)) {
return false;
}
TaskFile taskFile) {
resetDocument(document, taskFile);
final Task task = taskFile.getTask();
task.setStatus(StudyStatus.Unchecked);
if (task instanceof ChoiceTask) {
@@ -116,9 +113,9 @@ public class StudyRefreshTaskFileAction extends StudyActionWithShortcut {
return true;
}
private static void showBalloon(@NotNull final Project project, String text, @NotNull final MessageType messageType) {
private static void showBalloon(@NotNull final Project project, @NotNull final MessageType messageType) {
BalloonBuilder balloonBuilder =
JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(text, messageType, null);
JBPopupFactory.getInstance().createHtmlTextBalloonBuilder("You can start again now", messageType, null);
final Balloon balloon = balloonBuilder.createBalloon();
StudyEditor selectedStudyEditor = StudyUtils.getSelectedStudyEditor(project);
assert selectedStudyEditor != null;
@@ -135,19 +132,14 @@ public class StudyRefreshTaskFileAction extends StudyActionWithShortcut {
}
private static boolean resetDocument(@NotNull final Document document,
@NotNull final TaskFile taskFile,
String fileName) {
final Document patternDocument = StudyUtils.getPatternDocument(taskFile, fileName);
if (patternDocument == null) {
return false;
}
private static void resetDocument(@NotNull final Document document,
@NotNull final TaskFile taskFile) {
StudyUtils.deleteGuardedBlocks(document);
taskFile.setTrackChanges(false);
clearDocument(document);
document.setText(patternDocument.getCharsSequence());
document.setText(taskFile.text);
taskFile.setTrackChanges(true);
return true;
}
private static void clearDocument(@NotNull final Document document) {
@@ -21,7 +21,6 @@ import com.jetbrains.edu.learning.courseFormat.StudyStatus;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
public class StudySmartChecker {
@@ -40,7 +39,6 @@ public class StudySmartChecker {
@NotNull final VirtualFile virtualFile,
@NotNull final Document usersDocument) {
VirtualFile fileWindows = null;
File resourceFile = null;
VirtualFile windowCopy = null;
try {
final int index = placeholder.getIndex();
@@ -49,7 +47,6 @@ public class StudySmartChecker {
final FileDocumentManager documentManager = FileDocumentManager.getInstance();
final Document windowDocument = documentManager.getDocument(windowCopy);
if (windowDocument != null) {
resourceFile = StudyUtils.copyResourceFile(virtualFile.getName(), windowCopy.getName(), project, usersTaskFile.getTask());
TaskFile windowTaskFile = answerTaskFile.getTask().copy().getTaskFile(StudyUtils.pathRelativeToTask(virtualFile));
if (windowTaskFile == null) {
return;
@@ -81,9 +78,6 @@ public class StudySmartChecker {
finally {
StudyUtils.deleteFile(windowCopy);
StudyUtils.deleteFile(fileWindows);
if (resourceFile != null && resourceFile.exists() && !resourceFile.delete()) {
LOG.error("failed to delete", resourceFile.getPath());
}
}
}
}
@@ -13,18 +13,20 @@ import com.intellij.openapi.command.undo.UndoableAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileTypes.PlainTextFileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.psi.PsiDirectory;
import com.intellij.testFramework.LightVirtualFile;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.courseFormat.*;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks;
import org.apache.commons.codec.binary.Base64;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -120,68 +122,52 @@ public class EduUtils {
VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
}
public static VirtualFile copyFile(Object requestor, VirtualFile toDir, VirtualFile file) {
Document document = FileDocumentManager.getInstance().getDocument(file);
if (document != null) {
FileDocumentManager.getInstance().saveDocument(document);
}
String taskRelativePath = StudyUtils.pathRelativeToTask(file);
try {
VirtualFile userFile = toDir.findFileByRelativePath(taskRelativePath);
if (userFile != null) {
userFile.delete(requestor);
}
return VfsUtil.copyFileRelative(requestor, file, toDir, taskRelativePath);
}
catch (IOException e) {
LOG.info("Failed to create file " + taskRelativePath + " in folder " + toDir.getPath(), e);
}
return null;
}
@Nullable
public static Pair<VirtualFile, TaskFile> createStudentFile(Object requestor,
Project project,
VirtualFile answerFile,
VirtualFile parentDir,
@Nullable Task task,
int targetSubtaskIndex) {
VirtualFile studentFile = copyFile(requestor, parentDir, answerFile);
if (studentFile == null) {
return null;
}
Document studentDocument = FileDocumentManager.getInstance().getDocument(studentFile);
if (studentDocument == null) {
return null;
}
if (task == null) {
task = StudyUtils.getTaskForFile(project, answerFile);
public static TaskFile createStudentFile(Project project, VirtualFile answerFile, @Nullable Task task, int targetSubtaskIndex) {
try {
if (task == null) {
task = StudyUtils.getTaskForFile(project, answerFile);
if (task == null) {
return null;
}
task = task.copy();
}
TaskFile taskFile = task.getTaskFile(StudyUtils.pathRelativeToTask(answerFile));
if (taskFile == null) {
return null;
}
task = task.copy();
}
TaskFile taskFile = task.getTaskFile(StudyUtils.pathRelativeToTask(answerFile));
if (taskFile == null) {
return null;
}
EduDocumentListener listener = new EduDocumentListener(taskFile, false);
studentDocument.addDocumentListener(listener);
taskFile.setTrackLengths(false);
for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) {
if (task instanceof TaskWithSubtasks) {
int fromSubtask = ((TaskWithSubtasks)task).getActiveSubtaskIndex();
placeholder.switchSubtask(studentDocument, fromSubtask, targetSubtaskIndex);
if (isImage(taskFile.name)) {
taskFile.text = Base64.encodeBase64String(answerFile.contentsToByteArray());
return taskFile;
}
final LightVirtualFile studentFile = new LightVirtualFile("student_task", PlainTextFileType.INSTANCE,
VfsUtilCore.loadText(answerFile));
Document studentDocument = FileDocumentManager.getInstance().getDocument(studentFile);
if (studentDocument == null) {
return null;
}
EduDocumentListener listener = new EduDocumentListener(taskFile, false);
studentDocument.addDocumentListener(listener);
taskFile.setTrackLengths(false);
for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) {
if (task instanceof TaskWithSubtasks) {
int fromSubtask = ((TaskWithSubtasks)task).getActiveSubtaskIndex();
placeholder.switchSubtask(studentDocument, fromSubtask, targetSubtaskIndex);
}
}
for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) {
replaceWithTaskText(studentDocument, placeholder, targetSubtaskIndex);
}
taskFile.setTrackChanges(true);
studentDocument.removeDocumentListener(listener);
taskFile.text = studentDocument.getImmutableCharSequence().toString();
return taskFile;
}
for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) {
replaceWithTaskText(studentDocument, placeholder, targetSubtaskIndex);
catch (IOException e) {
LOG.error("Failed to convert answer file to student one");
}
taskFile.setTrackChanges(true);
studentDocument.removeDocumentListener(listener);
return Pair.create(studentFile, taskFile);
return null;
}
private static void replaceWithTaskText(Document studentDocument, AnswerPlaceholder placeholder, int toSubtaskIndex) {
@@ -8,35 +8,32 @@ import com.intellij.util.xmlb.XmlSerializer;
import com.intellij.util.xmlb.annotations.Transient;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.core.EduUtils;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
import com.jetbrains.edu.learning.stepic.StepicUser;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Date;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class Course {
@Expose private List<Lesson> lessons = new ArrayList<>();
@Expose private List<StepicUser> authors = new ArrayList<>();
@Expose private String description;
@Expose private String name;
private String myCourseDirectory = "";
@Expose private int id;
@Expose @SerializedName("update_date") private Date myUpdateDate;
@Expose private boolean isAdaptive = false;
@Expose @SerializedName("language") private String myLanguage = "Python";
public static final Course INVALID_COURSE = new Course();
@Expose protected List<Lesson> lessons = new ArrayList<>();
transient private List<StepicUser> authors = new ArrayList<>();
@Expose @SerializedName("summary") private String description;
@Expose @SerializedName("title") private String name;
transient private String myLanguage = "Python";
//this field is used to distinguish ordinary and CheckIO projects,
//"PyCharm" is used here for historical reasons
private String courseType = EduNames.PYCHARM;
private String courseMode = EduNames.STUDY; //this field is used to distinguish study and course creator modes
protected String courseMode = EduNames.STUDY; //this field is used to distinguish study and course creator modes
public Course() {
}
public Course() {}
/**
* Initializes state of course
@@ -48,12 +45,18 @@ public class Course {
}
public List<Lesson> getLessons() {
return lessons;
return getLessons(false);
}
public List<Lesson> getLessons(boolean withAdditional) {
return withAdditional ? lessons
: lessons.stream().filter(lesson -> !EduNames.PYCHARM_ADDITIONAL.equals(lesson.getName())).collect(Collectors.toList());
}
public void setLessons(List<Lesson> lessons) {
this.lessons = lessons;
}
public void addLessons(List<Lesson> lessons) {
this.lessons.addAll(lessons);
}
@@ -62,9 +65,12 @@ public class Course {
lessons.add(lesson);
}
public void removeLesson(Lesson lesson) {
lessons.remove(lesson);
}
public Lesson getLesson(@NotNull final String name) {
int lessonIndex = EduUtils.getIndex(name, EduNames.LESSON);
List<Lesson> lessons = getLessons();
if (!EduUtils.indexIsValid(lessonIndex, lessons)) {
return null;
}
@@ -118,14 +124,6 @@ public class Course {
this.name = name;
}
public String getCourseDirectory() {
return myCourseDirectory;
}
public void setCourseDirectory(@NotNull final String courseDirectory) {
myCourseDirectory = courseDirectory;
}
public String getDescription() {
return description;
}
@@ -135,35 +133,10 @@ public class Course {
}
public boolean isUpToDate() {
if (id == 0) return true;
if (!EduNames.STUDY.equals(courseMode)) return true;
final Date date = EduStepicConnector.getCourseUpdateDate(id);
if (date == null) return true;
if (myUpdateDate == null) return true;
if (date.after(myUpdateDate)) return false;
for (Lesson lesson : lessons) {
if (!lesson.isUpToDate()) return false;
}
return true;
}
public void setUpdated() {
setUpdateDate(EduStepicConnector.getCourseUpdateDate(id));
for (Lesson lesson : lessons) {
lesson.setUpdateDate(EduStepicConnector.getLessonUpdateDate(lesson.getId()));
for (Task task : lesson.getTaskList()) {
task.setUpdateDate(EduStepicConnector.getTaskUpdateDate(task.getStepId()));
}
}
}
public void setUpdateDate(Date date) {
myUpdateDate = date;
}
public Date getUpdateDate() {
return myUpdateDate;
}
public void setUpdated() {}
public Language getLanguageById() {
return Language.findLanguageByID(getLanguageID());
@@ -208,22 +181,6 @@ public class Course {
this.courseType = courseType;
}
public boolean isAdaptive() {
return isAdaptive;
}
public void setAdaptive(boolean adaptive) {
isAdaptive = adaptive;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCourseMode() {
return courseMode;
}
@@ -234,8 +191,21 @@ public class Course {
public Course copy() {
Element element = XmlSerializer.serialize(this);
Course copy = XmlSerializer.deserialize(element, Course.class);
Course copy = XmlSerializer.deserialize(element, getClass());
copy.initCourse(true);
return copy;
}
public boolean isAdaptive() {
return false;
}
public void sortLessons() {
Collections.sort(lessons, EduUtils.INDEX_COMPARATOR);
}
@Override
public String toString() {
return getName();
}
}
@@ -1,176 +0,0 @@
package com.jetbrains.edu.learning.courseFormat;
import com.google.gson.annotations.SerializedName;
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
import com.jetbrains.edu.learning.stepic.StepicUser;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
/**
* Implementation of class which contains information to be shawn in course description in tool window
* and when project is being created
*/
public class CourseInfo {
public static final CourseInfo INVALID_COURSE = new CourseInfo();
@SerializedName("title") private String myName;
int id;
boolean isAdaptive;
@SerializedName("is_public") boolean isPublic;
List<Integer> sections;
List<Integer> instructors = new ArrayList<>();
List<StepicUser> myAuthors = new ArrayList<>();
@SerializedName("summary") private String myDescription;
@SerializedName("course_format") private String myType = "pycharm" + EduStepicConnector.CURRENT_VERSION + " Python"; //course type in format "pycharm<version> <language>"
@Nullable private String username;
@SerializedName("update_date") private Date updateDate;
@SerializedName("is_idea_compatible") private boolean isCompatible = true;
public String getName() {
return myName;
}
@NotNull
public List<StepicUser> getAuthors() {
return myAuthors;
}
public String getDescription() {
return myDescription;
}
public String getType() {
return myType;
}
@Override
public String toString() {
return getName();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CourseInfo that = (CourseInfo)o;
final String thatName = that.getName();
final String thatDescription = that.getDescription();
if (thatName == null && getName() == null && thatDescription == null &&
getDescription() == null) {
return true;
}
if (thatName == null || thatDescription == null) {
return false;
}
return thatName.equals(getName())
&& thatDescription.equals(myDescription);
}
@Override
public int hashCode() {
int result = getName() != null ? getName().hashCode() : 0;
result = 31 * result + (myDescription != null ? myDescription.hashCode() : 0);
return result;
}
@Nullable
public String getUsername() {
return username;
}
public void setUsername(@Nullable String username) {
this.username = username;
}
public void setName(String name) {
myName = name;
}
public void setAuthors(List<StepicUser> authors) {
myAuthors = authors;
for (StepicUser author : authors) {
if (author.getId() > 0) {
instructors.add(author.getId());
}
}
}
public void addAuthor(StepicUser author) {
if (myAuthors == null) {
myAuthors = new ArrayList<>();
}
myAuthors.add(author);
}
public void setDescription(String description) {
myDescription = description;
}
public void setType(String type) {
myType = type;
}
public boolean isAdaptive() {
return isAdaptive;
}
public void setAdaptive(boolean adaptive) {
isAdaptive = adaptive;
}
public boolean isPublic() {
return isPublic;
}
public void setPublic(boolean aPublic) {
isPublic = aPublic;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Date getUpdateDate() {
return updateDate;
}
public void setUpdateDate(Date updateDate) {
this.updateDate = updateDate;
}
public static CourseInfo fromCourse(@Nullable final Course course) {
if (course == null) return null;
final List<CourseInfo> infos = StudyProjectGenerator.getCoursesFromCache().stream().
filter(info -> info.id == course.getId()).collect(Collectors.toList());
if (infos.isEmpty()) return null;
return infos.get(0);
}
public List<Integer> getSections() {
return sections;
}
public boolean isCompatible() {
return isCompatible;
}
public void setCompatible(boolean compatible) {
isCompatible = compatible;
}
public List<Integer> getInstructors() {
return instructors;
}
}
@@ -0,0 +1,98 @@
package com.jetbrains.edu.learning.courseFormat;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class RemoteCourse extends Course {
//course type in format "pycharm<version> <language>"
@SerializedName("course_format") private String myType = "pycharm" + EduStepicConnector.CURRENT_VERSION + " Python";
@SerializedName("is_idea_compatible") private boolean isCompatible = true;
List<Integer> sections;
List<Integer> instructors = new ArrayList<>();
@Expose private int id;
@Expose @SerializedName("update_date") private Date myUpdateDate;
@Expose private boolean isAdaptive = false;
@SerializedName("is_public") boolean isPublic;
public String getType() {
return myType;
}
public List<Integer> getSections() {
return sections;
}
public void setSections(List<Integer> sections) {
this.sections = sections;
}
public void setInstructors(List<Integer> instructors) {
this.instructors = instructors;
}
public List<Integer> getInstructors() {
return instructors;
}
public boolean isUpToDate() {
if (id == 0) return true;
if (!EduNames.STUDY.equals(courseMode)) return true;
final Date date = EduStepicConnector.getCourseUpdateDate(id);
if (date == null) return true;
if (myUpdateDate == null) return true;
if (date.after(myUpdateDate)) return false;
for (Lesson lesson : lessons) {
if (!lesson.isUpToDate()) return false;
}
return true;
}
public void setUpdated() {
setUpdateDate(EduStepicConnector.getCourseUpdateDate(id));
for (Lesson lesson : lessons) {
lesson.setUpdateDate(EduStepicConnector.getLessonUpdateDate(lesson.getId()));
for (Task task : lesson.getTaskList()) {
task.setUpdateDate(EduStepicConnector.getTaskUpdateDate(task.getStepId()));
}
}
}
public void setUpdateDate(Date date) {
myUpdateDate = date;
}
public Date getUpdateDate() {
return myUpdateDate;
}
public boolean isAdaptive() {
return isAdaptive;
}
public void setAdaptive(boolean adaptive) {
isAdaptive = adaptive;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public void copyCourseParameters(RemoteCourse course) {
setName(course.getName());
setUpdateDate(course.getUpdateDate());
}
}
@@ -21,6 +21,11 @@ import java.util.Map;
/**
* Implementation of task which contains task files, tests, input file for tests
*
* To implement new task there are 3 steps to be done:
* - extend Task class
* - go to Lesson and update elementTypes in taskList AbstractCollection. Needed for proper xml serialization
* - Update TaskSerializer and TaskDeserializer in StudySerializationUtil to handle json serialization
*/
public class Task implements StudyItem {
@Expose private String name;
@@ -35,8 +40,10 @@ public class Task implements StudyItem {
@SerializedName("task_files")
@Expose public Map<String, TaskFile> taskFiles = new HashMap<>();
protected Map<String, String> testsText = new HashMap<>();
protected Map<String, String> taskTexts = new HashMap<>();
@SerializedName("test_files")
@Expose protected Map<String, String> testsText = new HashMap<>();
@SerializedName("task_texts")
@Expose protected Map<String, String> taskTexts = new HashMap<>();
@Transient private Lesson myLesson;
@Expose @SerializedName("update_date") private Date myUpdateDate;
@@ -105,10 +112,6 @@ public class Task implements StudyItem {
return name != null ? taskFiles.get(name) : null;
}
public boolean isTaskFile(@NotNull final String fileName) {
return taskFiles.get(fileName) != null;
}
public void addTaskFile(@NotNull final String name, int index) {
TaskFile taskFile = new TaskFile();
taskFile.setIndex(index);
@@ -226,7 +229,7 @@ public class Task implements StudyItem {
public Task copy() {
Element element = XmlSerializer.serialize(this);
Task copy = XmlSerializer.deserialize(element, Task.class);
Task copy = XmlSerializer.deserialize(element, getClass());
copy.initTask(null, true);
return copy;
}
@@ -1,125 +1,33 @@
package com.jetbrains.edu.learning.courseGeneration;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.core.EduUtils;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.Lesson;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import org.apache.commons.codec.binary.Base64;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
public class StudyGenerator {
private StudyGenerator() {
}
private static final Logger LOG = Logger.getInstance(StudyGenerator.class.getName());
/**
* Creates task files in its task folder in project user created
*
* @param taskDir project directory of task which task file belongs to
* @param resourceRoot directory where original task file stored
* @throws IOException
*/
public static void createTaskFile(@NotNull final VirtualFile taskDir, @NotNull final File resourceRoot,
@NotNull final String name) throws IOException {
String systemIndependentName = FileUtil.toSystemIndependentName(name);
File resourceFile = new File(resourceRoot, name);
File fileInProject = new File(taskDir.getPath(), systemIndependentName);
FileUtil.copyFileOrDir(resourceFile, fileInProject);
}
/**
* Creates task directory in its lesson folder in project user created
*
* @param lessonDir project directory of lesson which task belongs to
* @param resourceRoot directory where original task file stored
* @throws IOException
*/
public static void createTask(@NotNull final Task task, @NotNull final VirtualFile lessonDir, @NotNull final File resourceRoot,
@NotNull final Project project) throws IOException {
VirtualFile taskDir = lessonDir.createChildDirectory(project, EduNames.TASK + Integer.toString(task.getIndex()));
File newResourceRoot = new File(resourceRoot, taskDir.getName());
int i = 0;
for (Map.Entry<String, TaskFile> taskFile : task.getTaskFiles().entrySet()) {
TaskFile taskFileContent = taskFile.getValue();
taskFileContent.setIndex(i);
i++;
createTaskFile(taskDir, newResourceRoot, taskFile.getKey());
}
File[] filesInTask = newResourceRoot.listFiles();
if (filesInTask != null) {
for (File file : filesInTask) {
String fileName = file.getName();
if (!task.isTaskFile(fileName)) {
File resourceFile = new File(newResourceRoot, fileName);
File fileInProject = new File(taskDir.getCanonicalPath(), fileName);
FileUtil.copyFileOrDir(resourceFile, fileInProject);
if (!StudyUtils.isTestsFile(project, fileName) && !StudyUtils.isTaskDescriptionFile(fileName)) {
StudyTaskManager.getInstance(project).addInvisibleFiles(FileUtil.toSystemIndependentName(fileInProject.getPath()));
}
}
}
}
}
/**
* Creates lesson directory in its course folder in project user created
*
* @param courseDir project directory of course
* @param resourceRoot directory where original lesson stored
* @throws IOException
*/
public static void createLesson(@NotNull final Lesson lesson, @NotNull final VirtualFile courseDir, @NotNull final File resourceRoot,
@NotNull final Project project) throws IOException {
if (EduNames.PYCHARM_ADDITIONAL.equals(lesson.getName())) return;
String lessonDirName = EduNames.LESSON + Integer.toString(lesson.getIndex());
VirtualFile lessonDir = courseDir.createChildDirectory(project, lessonDirName);
final List<Task> taskList = lesson.getTaskList();
for (int i = 1; i <= taskList.size(); i++) {
Task task = taskList.get(i - 1);
task.setIndex(i);
createTask(task, lessonDir, new File(resourceRoot, lessonDir.getName()), project);
}
}
/**
* Creates course directory in project user created
*
* @param baseDir project directory
* @param resourceRoot directory where original course is stored
*/
public static void createCourse(@NotNull final Course course, @NotNull final VirtualFile baseDir, @NotNull final File resourceRoot,
@NotNull final Project project) {
private StudyGenerator() {}
public static void createCourse(@NotNull final Course course, @NotNull final VirtualFile baseDir) {
try {
final List<Lesson> lessons = course.getLessons();
final List<Lesson> lessons = course.getLessons(true);
for (int i = 1; i <= lessons.size(); i++) {
Lesson lesson = lessons.get(i - 1);
lesson.setIndex(i);
createLesson(lesson, baseDir, resourceRoot, project);
}
File[] files = resourceRoot.listFiles(
(dir, name) -> !name.contains(EduNames.LESSON) && !name.equals(EduNames.COURSE_META_FILE) && !name.equals(EduNames.HINTS));
if (files != null) {
for (File file : files) {
File dir = new File(baseDir.getPath(), file.getName());
if (file.isDirectory()) {
FileUtil.copyDir(file, dir);
continue;
}
FileUtil.copy(file, dir);
}
createLesson(lesson, baseDir);
}
}
catch (IOException e) {
@@ -127,4 +35,92 @@ public class StudyGenerator {
}
}
public static void createLesson(@NotNull final Lesson lesson, @NotNull final VirtualFile courseDir) throws IOException {
if (EduNames.PYCHARM_ADDITIONAL.equals(lesson.getName())) {
createAdditionalFiles(lesson, courseDir);
}
else {
String lessonDirName = EduNames.LESSON + Integer.toString(lesson.getIndex());
VirtualFile lessonDir = courseDir.createChildDirectory(courseDir, lessonDirName);
final List<Task> taskList = lesson.getTaskList();
for (int i = 1; i <= taskList.size(); i++) {
Task task = taskList.get(i - 1);
task.setIndex(i);
createTask(task, lessonDir);
}
}
}
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()));
createTaskContent(task, taskDir);
}
public static void createTaskContent(@NotNull Task task, VirtualFile taskDir) throws IOException {
int i = 0;
for (Map.Entry<String, TaskFile> taskFile : task.getTaskFiles().entrySet()) {
TaskFile taskFileContent = taskFile.getValue();
taskFileContent.setIndex(i);
i++;
createTaskFile(taskDir, taskFile.getValue());
}
createTestFiles(taskDir, task);
createDescriptions(taskDir, task);
}
public static void createTaskFile(@NotNull final VirtualFile taskDir, @NotNull final TaskFile taskFile) throws IOException {
final String name = taskFile.name;
createChildFile(taskDir, name, taskFile.text);
}
public static void createDescriptions(VirtualFile taskDir, Task task) throws IOException {
final Map<String, String> texts = task.getTaskTexts();
for (Map.Entry<String, String> entry : texts.entrySet()) {
final String name = entry.getKey();
final VirtualFile virtualTaskFile = taskDir.createChildData(taskDir, name);
VfsUtil.saveText(virtualTaskFile, entry.getValue());
}
}
public static void createTestFiles(VirtualFile taskDir, Task task) throws IOException {
final Map<String, String> tests = task.getTestsText();
for (Map.Entry<String, String> 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 {
final List<Task> taskList = lesson.getTaskList();
if (taskList.size() != 1) return;
final Task task = taskList.get(0);
for (Map.Entry<String, String> entry : task.getTestsText().entrySet()) {
createChildFile(courseDir, entry.getKey(), entry.getValue());
}
}
public static void createChildFile(@NotNull VirtualFile taskDir, String name, String text) throws IOException {
String newDirectories = null;
String fileName = name;
VirtualFile dir = taskDir;
if (name.contains("/")) {
int pos = name.lastIndexOf("/");
fileName = name.substring(pos + 1);
newDirectories = name.substring(0, pos);
}
if (newDirectories != null) {
dir = VfsUtil.createDirectoryIfMissing(taskDir, newDirectories);
}
if (dir != null) {
final VirtualFile virtualTaskFile = dir.createChildData(taskDir, fileName);
if (EduUtils.isImage(name)) {
virtualTaskFile.setBinaryContent(Base64.decodeBase64(text));
}
else {
VfsUtil.saveText(virtualTaskFile, text);
}
}
}
}
@@ -1,66 +1,51 @@
package com.jetbrains.edu.learning.courseGeneration;
import com.google.gson.*;
import com.google.gson.stream.JsonReader;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.intellij.facet.ui.ValidationResult;
import com.intellij.ide.projectView.ProjectView;
import com.intellij.lang.LanguageExtensionPoint;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
import com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl;
import com.intellij.platform.templates.github.ZipUtil;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.io.zip.JBZipEntry;
import com.intellij.util.io.zip.JBZipFile;
import com.jetbrains.edu.learning.EduPluginConfigurator;
import com.jetbrains.edu.learning.StudySerializationUtils;
import com.jetbrains.edu.learning.StudySettings;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.core.EduUtils;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.CourseInfo;
import com.jetbrains.edu.learning.courseFormat.Lesson;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import com.jetbrains.edu.learning.courseFormat.RemoteCourse;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import com.jetbrains.edu.learning.editor.StudyEditor;
import com.jetbrains.edu.learning.statistics.EduUsagesCollector;
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
import com.jetbrains.edu.learning.stepic.StepicUser;
import org.apache.commons.codec.binary.Base64;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.*;
import java.util.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.jetbrains.edu.learning.StudyUtils.execCancelable;
public class StudyProjectGenerator {
public static final String AUTHOR_ATTRIBUTE = "authors";
public static final String LANGUAGE_ATTRIBUTE = "language";
public static final String ADAPTIVE_COURSE_PREFIX = "__AdaptivePyCharmPython__";
public static final File OUR_COURSES_DIR = new File(PathManager.getConfigPath(), "courses");
private static final Logger LOG = Logger.getInstance(StudyProjectGenerator.class.getName());
private static final String COURSE_NAME_ATTRIBUTE = "name";
private static final String COURSE_DESCRIPTION = "description";
private static final String CACHE_NAME = "courseNames.txt";
private final List<SettingsListener> myListeners = ContainerUtil.newArrayList();
private List<CourseInfo> myCourses = new ArrayList<>();
private List<Course> myCourses = new ArrayList<>();
private List<Integer> myEnrolledCoursesIds = new ArrayList<>();
protected CourseInfo mySelectedCourseInfo;
protected Course mySelectedCourse;
public void setCourses(List<CourseInfo> courses) {
public void setCourses(List<Course> courses) {
myCourses = courses;
}
@@ -78,12 +63,12 @@ public class StudyProjectGenerator {
return myEnrolledCoursesIds;
}
public void setSelectedCourse(@NotNull final CourseInfo courseName) {
mySelectedCourseInfo = courseName;
public void setSelectedCourse(@NotNull final Course course) {
mySelectedCourse = course;
}
public CourseInfo getSelectedCourseInfo() {
return mySelectedCourseInfo;
public Course getSelectedCourse() {
return mySelectedCourse;
}
public void generateProject(@NotNull final Project project, @NotNull final VirtualFile baseDir) {
@@ -97,50 +82,30 @@ public class StudyProjectGenerator {
Messages.showWarningDialog("There is no recommended tasks for this adaptive course", "Error in Course Creation");
return;
}
final File courseDirectory = StudyUtils.getCourseDirectory(course);
StudyTaskManager.getInstance(project).setCourse(course);
ApplicationManager.getApplication().runWriteAction(() -> {
StudyGenerator.createCourse(course, baseDir, courseDirectory, project);
course.setCourseDirectory(courseDirectory.getAbsolutePath());
VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
StudyGenerator.createCourse(course, baseDir);
StudyUtils.registerStudyToolWindow(course, project);
openFirstTask(course, project);
StudyUtils.openFirstTask(course, project);
EduUsagesCollector.projectTypeCreated(course.isAdaptive() ? EduNames.ADAPTIVE : EduNames.STUDY);
});
}
@Nullable
public Course getCourse(@NotNull final Project project) {
final File courseFile = new File(new File(OUR_COURSES_DIR, mySelectedCourseInfo.getName()), EduNames.COURSE_META_FILE);
if (courseFile.exists()) {
final Course course = readCourseFromCache(courseFile, false);
if (course != null && course.isUpToDate()) {
return course;
}
return getCourseFromStepic(project);
if (mySelectedCourse instanceof RemoteCourse) {
return getCourseFromStepic(project, (RemoteCourse)mySelectedCourse);
}
else {
final StepicUser user = StudySettings.getInstance().getUser();
if (user != null) {
final File adaptiveCourseFile = new File(new File(OUR_COURSES_DIR, ADAPTIVE_COURSE_PREFIX +
mySelectedCourseInfo.getName() + "_" +
user.getEmail()), EduNames.COURSE_META_FILE);
if (adaptiveCourseFile.exists()) {
return readCourseFromCache(adaptiveCourseFile, true);
}
}
}
return getCourseFromStepic(project);
mySelectedCourse.initCourse(false);
return mySelectedCourse;
}
private Course getCourseFromStepic(@NotNull Project project) {
private static RemoteCourse getCourseFromStepic(@NotNull Project project, RemoteCourse selectedCourse) {
return ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true);
return execCancelable(() -> {
final Course course = EduStepicConnector.getCourse(project, mySelectedCourseInfo);
final RemoteCourse course = EduStepicConnector.getCourse(project, selectedCourse);
if (StudyUtils.isCourseValid(course)) {
flushCourse(course);
course.initCourse(false);
}
return course;
@@ -148,281 +113,26 @@ public class StudyProjectGenerator {
}, "Creating Course", true, project);
}
@Nullable
private static Course readCourseFromCache(@NotNull File courseFile, boolean isAdaptive) {
Reader reader = null;
try {
reader = new InputStreamReader(new FileInputStream(courseFile), "UTF-8");
Gson gson =
new GsonBuilder().registerTypeAdapter(Course.class, new StudySerializationUtils.Json.CourseTypeAdapter(courseFile))
.registerTypeAdapter(Task.class, new StudySerializationUtils.Json.TaskDeserializer()).create();
final Course course = gson.fromJson(reader, Course.class);
course.initCourse(isAdaptive);
return course;
}
catch (UnsupportedEncodingException | FileNotFoundException e) {
LOG.warn(e.getMessage());
}
finally {
StudyUtils.closeSilently(reader);
}
return null;
}
public static void openFirstTask(@NotNull final Course course, @NotNull final Project project) {
LocalFileSystem.getInstance().refresh(false);
final Lesson firstLesson = StudyUtils.getFirst(course.getLessons());
if (firstLesson == null) return;
final Task firstTask = StudyUtils.getFirst(firstLesson.getTaskList());
if (firstTask == null) return;
final VirtualFile taskDir = firstTask.getTaskDir(project);
if (taskDir == null) return;
final Map<String, TaskFile> taskFiles = firstTask.getTaskFiles();
VirtualFile activeVirtualFile = null;
for (Map.Entry<String, TaskFile> entry : taskFiles.entrySet()) {
final String relativePath = entry.getKey();
final TaskFile taskFile = entry.getValue();
taskDir.refresh(false, true);
final VirtualFile virtualFile = taskDir.findFileByRelativePath(relativePath);
if (virtualFile != null) {
if (!taskFile.getActivePlaceholders().isEmpty()) {
activeVirtualFile = virtualFile;
}
}
}
if (activeVirtualFile != null) {
final PsiFile file = PsiManager.getInstance(project).findFile(activeVirtualFile);
ProjectView.getInstance(project).select(file, activeVirtualFile, false);
final FileEditor[] editors = FileEditorManager.getInstance(project).openFile(activeVirtualFile, true);
if (editors.length == 0) {
return;
}
final FileEditor studyEditor = editors[0];
if (studyEditor instanceof StudyEditor) {
StudyUtils.selectFirstAnswerPlaceholder((StudyEditor)studyEditor, project);
}
FileEditorManager.getInstance(project).openFile(activeVirtualFile, true);
}
else {
String first = StudyUtils.getFirst(taskFiles.keySet());
if (first != null) {
NewVirtualFile firstFile = ((VirtualDirectoryImpl)taskDir).refreshAndFindChild(first);
if (firstFile != null) {
FileEditorManager.getInstance(project).openFile(firstFile, true);
}
}
}
}
public static void flushCourse(@NotNull final Course course) {
final File courseDirectory = StudyUtils.getCourseDirectory(course);
FileUtil.createDirectory(courseDirectory);
flushCourseJson(course, courseDirectory);
int lessonIndex = 1;
List<Lesson> additionalLessons = new ArrayList<>();
for (Lesson lesson : course.getLessons()) {
if (lesson.getName().equals(EduNames.PYCHARM_ADDITIONAL)) {
flushAdditionalFiles(courseDirectory, lesson);
additionalLessons.add(lesson);
}
else {
final File lessonDirectory = new File(courseDirectory, EduNames.LESSON + String.valueOf(lessonIndex));
flushLesson(lessonDirectory, lesson);
lessonIndex += 1;
}
}
for (Lesson lesson : additionalLessons) {
course.getLessons().remove(lesson);
}
}
private static void flushAdditionalFiles(File courseDirectory, Lesson lesson) {
final List<Task> taskList = lesson.getTaskList();
if (taskList.size() != 1) return;
final Task task = taskList.get(0);
for (Map.Entry<String, String> entry : task.getTestsText().entrySet()) {
final String name = entry.getKey();
final String text = entry.getValue();
final File file = new File(courseDirectory, name);
FileUtil.createIfDoesntExist(file);
try {
if (EduUtils.isImage(name)) {
FileUtil.writeToFile(file, Base64.decodeBase64(text));
}
else {
FileUtil.writeToFile(file, text);
}
}
catch (IOException e) {
LOG.error("ERROR copying file " + name);
}
}
}
public static void flushLesson(@NotNull final File lessonDirectory, @NotNull final Lesson lesson) {
FileUtil.createDirectory(lessonDirectory);
int taskIndex = 1;
for (Task task : lesson.taskList) {
final File taskDirectory = new File(lessonDirectory, EduNames.TASK + String.valueOf(taskIndex));
flushTask(task, taskDirectory);
taskIndex += 1;
}
}
public static void flushTask(@NotNull final Task task, @NotNull final File taskDirectory) {
FileUtil.createDirectory(taskDirectory);
for (Map.Entry<String, TaskFile> taskFileEntry : task.taskFiles.entrySet()) {
final String name = FileUtil.toSystemDependentName(taskFileEntry.getKey());
final TaskFile taskFile = taskFileEntry.getValue();
final File file = new File(taskDirectory, name);
FileUtil.createIfDoesntExist(file);
try {
if (EduUtils.isImage(taskFile.name)) {
FileUtil.writeToFile(file, Base64.decodeBase64(taskFile.text));
}
else {
FileUtil.writeToFile(file, taskFile.text);
}
}
catch (IOException e) {
LOG.error(e);
}
}
createFiles(taskDirectory, task.getTestsText());
createFiles(taskDirectory, task.getTaskTexts());
}
private static void createFiles(@NotNull File taskDirectory, Map<String, String> files) {
for (Map.Entry<String, String> entry : files.entrySet()) {
final File file = new File(taskDirectory, entry.getKey());
if (file.exists()) {
FileUtil.delete(file);
}
FileUtil.createIfDoesntExist(file);
try {
FileUtil.writeToFile(file, entry.getValue());
}
catch (IOException e) {
LOG.error(e);
}
}
}
public static void flushCourseJson(@NotNull final Course course, @NotNull final File courseDirectory) {
final Gson gson = new GsonBuilder().setPrettyPrinting().
registerTypeAdapter(Task.class, new StudySerializationUtils.Json.TaskSerializer()).
excludeFieldsWithoutExposeAnnotation().create();
final String json = gson.toJson(course);
final File courseJson = new File(courseDirectory, EduNames.COURSE_META_FILE);
final FileOutputStream fileOutputStream;
try {
fileOutputStream = new FileOutputStream(courseJson);
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, "UTF-8");
try {
outputStreamWriter.write(json);
}
catch (IOException e) {
Messages.showErrorDialog(e.getMessage(), "Failed to Generate Json");
LOG.info(e);
}
finally {
try {
outputStreamWriter.close();
}
catch (IOException e) {
LOG.info(e);
}
}
}
catch (FileNotFoundException | UnsupportedEncodingException e) {
LOG.info(e);
}
}
/**
* Writes courses to cache file {@link StudyProjectGenerator#CACHE_NAME}
*/
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
public static void flushCache(List<CourseInfo> courses) {
flushCache(courses, true);
}
public static void flushCache(List<CourseInfo> courses, boolean preserveOld) {
File cacheFile = new File(OUR_COURSES_DIR, CACHE_NAME);
PrintWriter writer = null;
try {
if (!createCacheFile(cacheFile)) return;
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
final Set<CourseInfo> courseInfos = new HashSet<>();
courseInfos.addAll(courses);
if (preserveOld) {
courseInfos.addAll(getCoursesFromCache());
}
writer = new PrintWriter(cacheFile, "UTF-8");
try {
for (CourseInfo courseInfo : courseInfos) {
final String json = gson.toJson(courseInfo);
writer.println(json);
}
}
finally {
StudyUtils.closeSilently(writer);
}
}
catch (IOException e) {
LOG.error(e);
}
finally {
StudyUtils.closeSilently(writer);
}
}
private static boolean createCacheFile(File cacheFile) throws IOException {
if (!OUR_COURSES_DIR.exists()) {
final boolean created = OUR_COURSES_DIR.mkdirs();
if (!created) {
LOG.error("Cannot flush courses cache. Can't create courses directory");
return false;
}
}
if (!cacheFile.exists()) {
final boolean created = cacheFile.createNewFile();
if (!created) {
LOG.error("Cannot flush courses cache. Can't create " + CACHE_NAME + " file");
return false;
}
}
return true;
}
// Supposed to be called under progress
public List<CourseInfo> getCourses(boolean force) {
if (OUR_COURSES_DIR.exists() && !force) {
myCourses = getCoursesFromCache();
}
if (force || myCourses.isEmpty()) {
public List<Course> getCourses(boolean force) {
if (force) {
myCourses = execCancelable(() -> EduStepicConnector.getCourses(StudySettings.getInstance().getUser()));
flushCache(myCourses);
}
if (myCourses.isEmpty() || (myCourses.size() == 1 && myCourses.contains(CourseInfo.INVALID_COURSE))) {
myCourses = Collections.singletonList(getBundledIntro());
if (myCourses == null || myCourses.isEmpty() || (myCourses.size() == 1 && myCourses.contains(Course.INVALID_COURSE))) {
myCourses = getBundledCourses();
}
sortCourses(myCourses);
return myCourses;
}
public void sortCourses(List<CourseInfo> result) {
public void sortCourses(List<Course> result) {
// sort courses so as to have non-adaptive courses in the beginning of the list
Collections.sort(result, (c1, c2) -> {
if (mySelectedCourseInfo != null) {
if (mySelectedCourseInfo.equals(c1)) {
if (mySelectedCourse != null) {
if (mySelectedCourse.equals(c1)) {
return -1;
}
if (mySelectedCourseInfo.equals(c2)) {
if (mySelectedCourse.equals(c2)) {
return 1;
}
}
@@ -434,7 +144,7 @@ public class StudyProjectGenerator {
}
@NotNull
public List<CourseInfo> getCoursesUnderProgress(boolean force, @NotNull final String progressTitle, @NotNull final Project project) {
public List<Course> getCoursesUnderProgress(boolean force, @NotNull final String progressTitle, @NotNull final Project project) {
try {
return ProgressManager.getInstance()
.runProcessWithProgressSynchronously(() -> {
@@ -443,7 +153,7 @@ public class StudyProjectGenerator {
}, progressTitle, true, project);
}
catch (RuntimeException e) {
return Collections.singletonList(CourseInfo.INVALID_COURSE);
return Collections.singletonList(Course.INVALID_COURSE);
}
}
@@ -461,78 +171,41 @@ public class StudyProjectGenerator {
}
}
public static CourseInfo getBundledIntro() {
final File introCourse = new File(OUR_COURSES_DIR, "Introduction to Python");
if (introCourse.exists()) {
return getCourseInfo(introCourse);
}
return null;
}
public static List<CourseInfo> getCoursesFromCache() {
List<CourseInfo> courses = new ArrayList<>();
final File cacheFile = new File(OUR_COURSES_DIR, CACHE_NAME);
if (!cacheFile.exists()) {
return courses;
}
try {
final FileInputStream inputStream = new FileInputStream(cacheFile);
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
try {
String line;
while ((line = reader.readLine()) != null) {
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
final CourseInfo courseInfo = gson.fromJson(line, CourseInfo.class);
courses.add(courseInfo);
}
}
catch (IOException | JsonSyntaxException e) {
LOG.error(e.getMessage());
}
finally {
StudyUtils.closeSilently(reader);
}
@Nullable
public List<Course> getBundledCourses() {
final ArrayList<Course> courses = new ArrayList<>();
final LanguageExtensionPoint<EduPluginConfigurator>[] extensions = Extensions.getExtensions(EduPluginConfigurator.EP_NAME, null);
for (LanguageExtensionPoint<EduPluginConfigurator> extension : extensions) {
final EduPluginConfigurator configurator = extension.getInstance();
final List<String> paths = configurator.getBundledCoursePaths();
for (String path : paths) {
courses.add(getCourse(path));
}
catch (UnsupportedEncodingException e) {
LOG.error(e.getMessage());
}
finally {
StudyUtils.closeSilently(inputStream);
}
}
catch (FileNotFoundException e) {
LOG.error(e.getMessage());
}
return courses;
}
/**
* Adds course from zip archive to courses
*
* @return added course name or null if course is invalid
*/
@Nullable
public CourseInfo addLocalCourse(String zipFilePath) {
File file = new File(zipFilePath);
public Course addLocalCourse(String zipFilePath) {
final Course courseInfo = getCourse(zipFilePath);
if (courseInfo != null) {
myCourses.add(0, courseInfo);
}
return courseInfo;
}
@Nullable
public static Course getCourse(String zipFilePath) {
try {
String fileName = file.getName();
String unzippedName = fileName.substring(0, fileName.indexOf("."));
File courseDir = new File(OUR_COURSES_DIR, unzippedName);
ZipUtil.unzip(null, courseDir, file, null, null, true);
CourseInfo courseName = addCourse(myCourses, courseDir);
flushCache(myCourses);
if (courseName != null && !courseName.getName().equals(unzippedName)) {
//noinspection ResultOfMethodCallIgnored
File dest = new File(OUR_COURSES_DIR, courseName.getName());
if (dest.exists()) {
FileUtil.delete(dest);
}
courseDir.renameTo(dest);
//noinspection ResultOfMethodCallIgnored
courseDir.delete();
}
return courseName;
final JBZipFile zipFile = new JBZipFile(zipFilePath);
final JBZipEntry entry = zipFile.getEntry(EduNames.COURSE_META_FILE);
byte[] bytes = entry.getData();
final String jsonText = new String(bytes, CharsetToolkit.UTF8_CHARSET);
Gson gson = new GsonBuilder()
.registerTypeAdapter(Task.class, new StudySerializationUtils.Json.TaskAdapter())
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create();
return gson.fromJson(jsonText, Course.class);
}
catch (IOException e) {
LOG.error("Failed to unzip course archive");
@@ -540,80 +213,4 @@ public class StudyProjectGenerator {
}
return null;
}
/**
* Adds course to courses specified in params
*
* @param courses
* @param courseDir must be directory containing course file
* @return added course name or null if course is invalid
*/
@Nullable
private static CourseInfo addCourse(List<CourseInfo> courses, File courseDir) {
if (courseDir.isDirectory()) {
File[] courseFiles = courseDir.listFiles((dir, name) -> name.equals(EduNames.COURSE_META_FILE));
if (courseFiles == null || courseFiles.length != 1) {
LOG.info("User tried to add course with more than one or without course files");
return null;
}
File courseFile = courseFiles[0];
CourseInfo courseInfo = getCourseInfo(courseFile);
if (courseInfo != null) {
courses.add(0, courseInfo);
}
return courseInfo;
}
return null;
}
/**
* Parses course json meta file and finds course name
*
* @return information about course or null if course file is invalid
*/
@Nullable
private static CourseInfo getCourseInfo(File courseFile) {
if (courseFile.isDirectory()) {
File[] courseFiles = courseFile.listFiles((dir, name) -> name.equals(EduNames.COURSE_META_FILE));
if (courseFiles == null || courseFiles.length != 1) {
LOG.info("More than one or without course files");
return null;
}
courseFile = courseFiles[0];
}
CourseInfo courseInfo = null;
BufferedReader reader = null;
try {
if (courseFile.getName().equals(EduNames.COURSE_META_FILE)) {
reader = new BufferedReader(new InputStreamReader(new FileInputStream(courseFile), "UTF-8"));
JsonReader r = new JsonReader(reader);
JsonParser parser = new JsonParser();
JsonElement el = parser.parse(r);
String courseName = el.getAsJsonObject().get(COURSE_NAME_ATTRIBUTE).getAsString();
String courseDescription = el.getAsJsonObject().get(COURSE_DESCRIPTION).getAsString();
JsonArray courseAuthors = el.getAsJsonObject().get(AUTHOR_ATTRIBUTE).getAsJsonArray();
String language = el.getAsJsonObject().get(LANGUAGE_ATTRIBUTE).getAsString();
courseInfo = new CourseInfo();
courseInfo.setName(courseName);
courseInfo.setDescription(courseDescription);
courseInfo.setType("pycharm " + language);
final ArrayList<StepicUser> authors = new ArrayList<>();
for (JsonElement author : courseAuthors) {
final JsonObject authorAsJsonObject = author.getAsJsonObject();
final StepicUser stepicUser = StepicUser.createEmptyUser();
stepicUser.setFirstName(authorAsJsonObject.get("first_name").getAsString());
stepicUser.setLastName(authorAsJsonObject.get("last_name").getAsString());
authors.add(stepicUser);
}
courseInfo.setAuthors(authors);
}
}
catch (Exception e) {
//error will be shown in UI
}
finally {
StudyUtils.closeSilently(reader);
}
return courseInfo;
}
}
}
@@ -8,8 +8,6 @@ import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
import org.jetbrains.annotations.NotNull;
import java.io.File;
public class EduProjectGenerator extends StudyProjectGenerator {
private static final Logger LOG = Logger.getInstance(EduProjectGenerator.class);
@@ -21,6 +19,5 @@ public class EduProjectGenerator extends StudyProjectGenerator {
return;
}
StudyTaskManager.getInstance(project).setCourse(course);
course.setCourseDirectory(new File(OUR_COURSES_DIR, mySelectedCourseInfo.getName()).getAbsolutePath());
}
}
@@ -14,7 +14,6 @@ import com.intellij.openapi.util.InvalidDataException;
import com.jetbrains.edu.learning.EduPluginConfigurator;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.CourseInfo;
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
import com.jetbrains.edu.learning.intellij.generation.EduProjectGenerator;
import org.jdom.JDOMException;
@@ -27,7 +26,7 @@ import java.io.IOException;
class EduCustomCourseModuleBuilder extends JavaModuleBuilder {
private static final Logger LOG = Logger.getInstance(EduCustomCourseModuleBuilder.class);
private EduProjectGenerator myGenerator = new EduProjectGenerator();
private CourseInfo mySelectedCourse;
private Course mySelectedCourse;
@Nullable
@Override
@@ -36,8 +35,7 @@ class EduCustomCourseModuleBuilder extends JavaModuleBuilder {
if (module == null) {
return null;
}
String type = mySelectedCourse.getType();
String languageName = type.substring("pycharm ".length());
String languageName = mySelectedCourse.getLanguageID();
Language language = Language.findLanguageByID(languageName);
if (language == null) {
return module;
@@ -87,7 +85,7 @@ class EduCustomCourseModuleBuilder extends JavaModuleBuilder {
return ProjectWizardStepFactory.getInstance().createJavaSettingsStep(settingsStep, this, Conditions.alwaysTrue());
}
void setSelectedCourse(CourseInfo selectedCourse) {
void setSelectedCourse(Course selectedCourse) {
mySelectedCourse = selectedCourse;
}
@@ -97,8 +95,7 @@ class EduCustomCourseModuleBuilder extends JavaModuleBuilder {
throws InvalidDataException, IOException, ModuleWithNameAlreadyExists, JDOMException, ConfigurationException {
Module baseModule = super.createModule(moduleModel);
if (mySelectedCourse != null) {
String type = mySelectedCourse.getType();
String languageName = type.substring("pycharm ".length());
String languageName = mySelectedCourse.getLanguageID();
Language language = Language.findLanguageByID(languageName);
if (language != null) {
EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(language);
@@ -2,8 +2,8 @@ package com.jetbrains.edu.learning.intellij.localCourses;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
import com.jetbrains.edu.learning.courseFormat.CourseInfo;
import com.jetbrains.edu.learning.ui.StudyNewProjectPanel;
import javax.swing.*;
@@ -25,7 +25,7 @@ public class EduLocalCoursePanel {
@Override
public void actionPerformed(ActionEvent actionEvent) {
String coursePath = myCourseArchivePath.getText();
CourseInfo courseInfo = generator.addLocalCourse(coursePath);
Course courseInfo = generator.addLocalCourse(coursePath);
if (courseInfo != null) {
myBuilder.setSelectedCourse(courseInfo);
myGenerator.setSelectedCourse(courseInfo);
@@ -45,7 +45,7 @@ public class EduLocalCoursePanel {
}
private void createUIComponents() {
myStudyPanel = new StudyNewProjectPanel(myGenerator);
myStudyPanel = new StudyNewProjectPanel(myGenerator, true);
myInfoPanel = myStudyPanel.getInfoPanel();
}
}
@@ -41,7 +41,15 @@ public class StudyNavigator {
if (nextLesson == null) {
return null;
}
return StudyUtils.getFirst(nextLesson.getTaskList());
List<Task> nextLessonTaskList = nextLesson.getTaskList();
while (nextLessonTaskList.isEmpty()) {
nextLesson = nextLesson(nextLesson);
if (nextLesson == null) {
return null;
}
nextLessonTaskList = nextLesson.getTaskList();
}
return StudyUtils.getFirst(nextLessonTaskList);
}
public static Task previousTask(@NotNull final Task task) {
@@ -55,7 +63,15 @@ public class StudyNavigator {
return null;
}
//getting last task in previous lesson
return prevLesson.getTaskList().get(prevLesson.getTaskList().size() - 1);
List<Task> prevLessonTaskList = prevLesson.getTaskList();
while (prevLessonTaskList.isEmpty()) {
prevLesson = previousLesson(prevLesson);
if (prevLesson == null) {
return null;
}
prevLessonTaskList = prevLesson.getTaskList();
}
return prevLessonTaskList.get(prevLessonTaskList.size() - 1);
}
public static Lesson nextLesson(@NotNull final Lesson lesson) {
@@ -64,11 +80,7 @@ public class StudyNavigator {
if (nextLessonIndex >= lessons.size()) {
return null;
}
final Lesson nextLesson = lessons.get(nextLessonIndex);
if (EduNames.PYCHARM_ADDITIONAL.equals(nextLesson.getName())) {
return null;
}
return nextLesson;
return lessons.get(nextLessonIndex);
}
public static Lesson previousLesson(@NotNull final Lesson lesson) {
@@ -23,7 +23,6 @@ import com.intellij.openapi.components.ServiceManager;
import com.intellij.util.containers.FactoryMap;
import com.intellij.util.containers.hash.HashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Set;
@@ -31,7 +30,6 @@ public class EduUsagesCollector extends UsagesCollector {
private static final String GROUP_ID = "educational";
private final FactoryMap<String, UsageDescriptor> myUsageDescriptors = new FactoryMap<String, UsageDescriptor>() {
@Nullable
@Override
protected UsageDescriptor create(String key) {
return new UsageDescriptor(key, 0);
@@ -7,15 +7,12 @@ import com.intellij.ide.projectView.ProjectView;
import com.intellij.lang.Language;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
@@ -31,7 +28,6 @@ import com.jetbrains.edu.learning.courseFormat.tasks.CodeTask;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import com.jetbrains.edu.learning.courseFormat.tasks.TheoryTask;
import com.jetbrains.edu.learning.courseGeneration.StudyGenerator;
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
import com.jetbrains.edu.learning.editor.StudyEditor;
import com.jetbrains.edu.learning.navigation.StudyNavigator;
import com.jetbrains.edu.learning.ui.StudyToolWindow;
@@ -49,15 +45,11 @@ import org.apache.http.util.EntityUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.*;
import java.util.concurrent.TimeUnit;
import static com.jetbrains.edu.learning.stepic.EduStepicConnector.getStep;
@@ -72,11 +64,12 @@ public class EduAdaptiveStepicConnector {
private static final Logger LOG = Logger.getInstance(EduAdaptiveStepicConnector.class);
private static final int CONNECTION_TIMEOUT = 60 * 1000;
private static final String CODE_TASK_TYPE = "code";
private static final String CODE_TASK_NAME = "code.py";
private static final String CHOICE_TYPE_TEXT = "choice";
private static final String TEXT_STEP_TYPE = "text";
@Nullable
public static Task getNextRecommendation(@NotNull Project project, @NotNull Course course) {
public static Task getNextRecommendation(@NotNull Project project, @NotNull RemoteCourse course) {
try {
final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient();
if (client == null) {
@@ -134,16 +127,8 @@ public class EduAdaptiveStepicConnector {
}
catch (IOException e) {
LOG.warn(e.getMessage());
final String connectionMessages = "Connection problems, Please, try again";
final Balloon balloon =
JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(connectionMessages, MessageType.ERROR, null)
.createBalloon();
ApplicationManager.getApplication().invokeLater(() -> {
if (StudyUtils.getSelectedEditor(project) != null) {
StudyUtils.showCheckPopUp(project, balloon);
}
});
ApplicationManager.getApplication()
.invokeLater(() -> StudyUtils.showErrorPopupOnToolbar(project, "Connection problems, Please, try again"));
}
catch (URISyntaxException e) {
LOG.warn(e.getMessage());
@@ -209,8 +194,8 @@ public class EduAdaptiveStepicConnector {
private static void createMockTaskFile(@NotNull Task task, String editorText) {
final TaskFile taskFile = new TaskFile();
taskFile.text = editorText;
taskFile.name = "code";
task.taskFiles.put("code.py", taskFile);
taskFile.name = CODE_TASK_NAME;
task.taskFiles.put(CODE_TASK_NAME, taskFile);
}
@Nullable
@@ -267,9 +252,7 @@ public class EduAdaptiveStepicConnector {
request.setConfig(requestConfig);
}
public static boolean postRecommendationReaction(@NotNull String lessonId,
@NotNull String user,
int reaction) {
public static boolean postRecommendationReaction(@NotNull String lessonId, @NotNull String user, int reaction) {
final HttpPost post = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.RECOMMENDATION_REACTIONS_URL);
final String json = new Gson()
.toJson(new StepicWrappers.RecommendationReactionWrapper(new StepicWrappers.RecommendationReaction(reaction, user, lessonId)));
@@ -293,133 +276,171 @@ public class EduAdaptiveStepicConnector {
}
catch (IOException e) {
LOG.warn(e.getMessage());
return false;
}
return false;
}
public static void addNextRecommendedTask(@NotNull Project project,
@NotNull ProgressIndicator indicator,
int reaction) {
public static void addNextRecommendedTask(@NotNull Project project, @NotNull ProgressIndicator indicator, int reactionToPost) {
final StudyEditor editor = StudyUtils.getSelectedStudyEditor(project);
final Course course = StudyTaskManager.getInstance(project).getCourse();
if (course != null && editor != null && editor.getTaskFile() != null) {
indicator.checkCanceled();
final StepicUser user = StudySettings.getInstance().getUser();
if (user == null) {
LOG.warn("Can't get next recommendation: user is null");
ApplicationManager.getApplication().invokeLater(() -> StudyUtils.showErrorPopupOnToolbar(project,
"Can't get next recommendation: you're not authorized"));
return;
}
final boolean recommendationReaction = postRecommendationReaction(String.valueOf(editor.getTaskFile().getTask().getLesson().getId()),
String.valueOf(user.getId()), reaction);
if (recommendationReaction) {
indicator.checkCanceled();
final Task task = getNextRecommendation(project, course);
if (course == null || editor == null || editor.getTaskFile() == null || !(course instanceof RemoteCourse)) {
return;
}
if (task != null) {
final Lesson adaptive = course.getLessons().get(0);
task.initTask(adaptive, false);
final Task unsolvedTask = adaptive.getTaskList().get(adaptive.getTaskList().size() - 1);
final String lessonName = EduNames.LESSON + String.valueOf(adaptive.getIndex());
if (reaction == TOO_HARD_RECOMMENDATION_REACTION || reaction == TOO_BORING_RECOMMENDATION_REACTION) {
task.setLesson(unsolvedTask.getLesson());
task.setIndex(unsolvedTask.getIndex());
adaptive.getTaskList().set(adaptive.getTaskList().size() - 1, task);
ApplicationManager.getApplication().invokeLater(()->StudyNavigator.navigateToTask(project, task));
indicator.checkCanceled();
final StepicUser user = StudySettings.getInstance().getUser();
if (user == null) {
LOG.warn("Can't get next recommendation: user is null");
ApplicationManager.getApplication().invokeLater(() -> StudyUtils.showErrorPopupOnToolbar(project,
"Can't get next recommendation: you're not authorized"));
return;
}
final File lessonDirectory = new File(course.getCourseDirectory(), lessonName);
final String taskName = EduNames.TASK + String.valueOf(adaptive.getTaskList().size());
final File taskDirectory = new File(lessonDirectory, taskName);
StudyProjectGenerator.flushTask(task, taskDirectory);
StudyProjectGenerator.flushCourseJson(course, new File(course.getCourseDirectory()));
final VirtualFile lessonDir = project.getBaseDir().findChild(lessonName);
final Lesson lesson = editor.getTaskFile().getTask().getLesson();
final boolean reactionPosted = postRecommendationReaction(String.valueOf(lesson.getId()), String.valueOf(user.getId()), reactionToPost);
if (!reactionPosted) {
LOG.warn("Recommendation reaction wasn't posted");
ApplicationManager.getApplication().invokeLater(() -> StudyUtils.showErrorPopupOnToolbar(project, "Couldn't post your reactionToPost"));
return;
}
if (lessonDir != null) {
createTestFiles(course, project, unsolvedTask, lessonDir);
}
final StudyToolWindow window = StudyUtils.getStudyToolWindow(project);
if (window != null) {
window.setTaskText(StudyUtils.wrapTextToDisplayLatex(unsolvedTask.getTaskDescription()), unsolvedTask.getTaskDir(project), project);
}
StudyNavigator.navigateToTask(project, lessonName, taskName);
}
else {
adaptive.addTask(task);
task.setIndex(adaptive.getTaskList().size());
final VirtualFile lessonDir = project.getBaseDir().findChild(lessonName);
indicator.checkCanceled();
final Task task = getNextRecommendation(project, (RemoteCourse)course);
if (task == null) {
ApplicationManager.getApplication().invokeLater(() -> StudyUtils.showErrorPopupOnToolbar(project,
"Couldn't load a new recommendation"));
return;
}
if (lessonDir != null) {
ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> {
try {
final File lessonDirectory = new File(course.getCourseDirectory(), lessonName);
final String taskName = EduNames.TASK + String.valueOf(task.getIndex());
final File taskDir = new File(lessonDirectory, taskName);
StudyProjectGenerator.flushTask(task, taskDir);
StudyProjectGenerator.flushCourseJson(course, new File(course.getCourseDirectory()));
StudyGenerator.createTask(task, lessonDir, new File(course.getCourseDirectory(), lessonDir.getName()), project);
adaptive.initLesson(course, true);
StudyNavigator.navigateToTask(project, lessonName, taskName);
}
catch (IOException e) {
LOG.warn(e.getMessage());
}
}));
}
}
task.initTask(lesson, false);
boolean replaceCurrentTask = reactionToPost == TOO_HARD_RECOMMENDATION_REACTION || reactionToPost == TOO_BORING_RECOMMENDATION_REACTION;
if (replaceCurrentTask) {
replaceCurrentTask(project, editor, task);
}
else {
addAsNextTask(project, editor, task);
}
ApplicationManager.getApplication().invokeLater(() -> {
VirtualFileManager.getInstance().refreshWithoutFileWatcher(false);
ProjectView.getInstance(project).refresh();
StudyNavigator.navigateToTask(project, task);
});
}
private static void addAsNextTask(@NotNull Project project, @NotNull StudyEditor editor, @NotNull Task task) {
Course course = StudyTaskManager.getInstance(project).getCourse();
assert course != null;
final Lesson lesson = editor.getTaskFile().getTask().getLesson();
lesson.addTask(task);
task.setIndex(lesson.getTaskList().size());
lesson.initLesson(course, true);
final String lessonName = EduNames.LESSON + lesson.getIndex();
createFilesForNewTask(project, task, lessonName);
}
private static void createFilesForNewTask(@NotNull Project project,
@NotNull Task task,
@NotNull String lessonName) {
final VirtualFile lessonDir = project.getBaseDir().findChild(lessonName);
if (lessonDir != null) {
ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> {
try {
StudyGenerator.createTask(task, lessonDir);
}
else {
ApplicationManager.getApplication().invokeLater(() -> {
final Balloon balloon =
JBPopupFactory.getInstance().createHtmlTextBalloonBuilder("Couldn't load a new recommendation", MessageType.ERROR, null)
.createBalloon();
StudyUtils.showCheckPopUp(project, balloon);
});
catch (IOException e) {
LOG.warn(e.getMessage());
}
ApplicationManager.getApplication().invokeLater(() -> {
VirtualFileManager.getInstance().refreshWithoutFileWatcher(false);
ProjectView.getInstance(project).refresh();
});
}
else {
LOG.warn("Recommendation reactions weren't posted");
ApplicationManager.getApplication().invokeLater(() -> StudyUtils.showErrorPopupOnToolbar(project, "Couldn't post your reaction"));
}));
}
}
private static void replaceCurrentTask(@NotNull Project project, @NotNull StudyEditor editor, @NotNull Task task) {
Course course = StudyTaskManager.getInstance(project).getCourse();
assert course != null;
final Lesson lesson = editor.getTaskFile().getTask().getLesson();
int taskIndex = lesson.getTaskList().size();
task.setLesson(lesson);
task.setIndex(taskIndex);
lesson.getTaskList().set(taskIndex - 1, task);
copyTaskFileParameters(editor, task);
final String lessonName = EduNames.LESSON + lesson.getIndex();
updateProjectFiles(project, task, lessonName);
setToolWindowText(project, task);
}
private static void updateProjectFiles(@NotNull Project project, @NotNull Task task, @NotNull String lessonName) {
final VirtualFile lessonDir = project.getBaseDir().findChild(lessonName);
if (lessonDir != null) {
final VirtualFile taskResourceRoot = lessonDir.findChild(EduNames.TASK + task.getIndex());
ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> {
try {
removeOldProjectFiles(project, taskResourceRoot);
StudyGenerator.createTestFiles(taskResourceRoot, task);
StudyGenerator.createDescriptions(taskResourceRoot, task);
}
catch (IOException e) {
LOG.warn(e.getMessage());
}
}));
}
}
private static void removeOldProjectFiles(@NotNull Project project, @Nullable VirtualFile taskRecourseRoot) throws IOException {
if (taskRecourseRoot == null) {
LOG.warn("Failed to update files for a new recommendation: task directory is null");
return;
}
VirtualFile[] children = VfsUtil.getChildren(taskRecourseRoot);
for (VirtualFile file : children) {
String name = file.getName();
if (StudyUtils.isTestsFile(project, name) || StudyUtils.isTaskDescriptionFile(name)) {
file.delete(taskRecourseRoot);
}
}
}
private static void createTestFiles(@NotNull Course course, @NotNull Project project,
@NotNull Task unsolvedTask, @NotNull VirtualFile lessonDir) {
private static void copyTaskFileParameters(@NotNull StudyEditor editor, @NotNull Task task) {
final Map<String, TaskFile> taskFiles = task.getTaskFiles();
if (taskFiles.size() == 1) {
TaskFile newTaskFile = (TaskFile)taskFiles.values().toArray()[0];
setTaskFileParameters(editor, task, newTaskFile);
updateEditorText(editor, newTaskFile);
}
else {
LOG.warn("Got task without unexpected number of task files: " + taskFiles.size());
}
}
private static void updateEditorText(@NotNull StudyEditor editor, @NotNull TaskFile newTaskFile) {
ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> {
try {
final VirtualFile taskDir = VfsUtil
.findFileByIoFile(new File(lessonDir.getCanonicalPath(), EduNames.TASK + unsolvedTask.getIndex()), true);
final File resourceRoot = new File(course.getCourseDirectory(), lessonDir.getName());
if (taskDir != null) {
final File newResourceRoot = new File(resourceRoot, taskDir.getName());
File[] filesInTask = newResourceRoot.listFiles();
if (filesInTask != null) {
for (File file : filesInTask) {
final String taskRelativePath = FileUtil.getRelativePath(taskDir.getPath(), file.getPath(), '/');
if (taskRelativePath != null && StudyUtils.isTestsFile(project, taskDir.getName())) {
final File resourceFile = new File(newResourceRoot, taskRelativePath);
final File fileInProject = new File(taskDir.getCanonicalPath(), taskRelativePath);
FileUtil.copy(resourceFile, fileInProject);
}
}
}
}
else {
LOG.warn("Task directory is null");
}
}
catch (IOException e) {
LOG.warn(e.getMessage());
}
final Document document = editor.getEditor().getDocument();
document.setText(newTaskFile.text);
}));
}
private static void setTaskFileParameters(@NotNull StudyEditor editor, @NotNull Task task, @NotNull TaskFile newTaskFile) {
TaskFile currentTaskFile = editor.getTaskFile();
currentTaskFile.text = newTaskFile.text;
currentTaskFile.name = newTaskFile.name;
currentTaskFile.setTask(task);
task.getTaskFiles().clear();
task.taskFiles.put(CODE_TASK_NAME, currentTaskFile);
}
private static void setToolWindowText(@NotNull Project project, @NotNull Task task) {
final StudyToolWindow window = StudyUtils.getStudyToolWindow(project);
if (window != null) {
window.setTaskText(StudyUtils.wrapTextToDisplayLatex(task.getTaskDescription()), task.getTaskDir(project), project);
}
}
@NotNull
private static Task getCodeTaskFromStep(@NotNull Project project,
@NotNull StepicWrappers.Step step,
@@ -468,11 +489,9 @@ public class EduAdaptiveStepicConnector {
}
}
else {
final TaskFile taskFile = new TaskFile();
taskFile.name = CODE_TASK_TYPE;
final String templateForTask = getCodeTemplateForTask(project, task, step.options.codeTemplates);
taskFile.text = templateForTask == null ? "# write your answer here \n" : templateForTask;
task.taskFiles.put("code.py", taskFile);
String text = templateForTask == null ? "# write your answer here \n" : templateForTask;
createMockTaskFile(task, text);
}
return task;
}
@@ -533,7 +552,6 @@ public class EduAdaptiveStepicConnector {
return answer;
}
@Nullable
public static Pair<Boolean, String> checkCodeTask(@NotNull Project project, @NotNull Task task, @NotNull StepicUser user) {
int attemptId = -1;
try {
@@ -693,11 +711,11 @@ public class EduAdaptiveStepicConnector {
public static List<Integer> getEnrolledCoursesIds(@NotNull StepicUser stepicUser) {
try {
final URI enrolledCoursesUri = new URIBuilder(EduStepicNames.COURSES).addParameter("enrolled", "true").build();
final List<CourseInfo> courses = EduStepicAuthorizedClient.getFromStepic(enrolledCoursesUri.toString(),
final List<RemoteCourse> courses = EduStepicAuthorizedClient.getFromStepic(enrolledCoursesUri.toString(),
StepicWrappers.CoursesContainer.class,
stepicUser).courses;
final ArrayList<Integer> ids = new ArrayList<>();
for (CourseInfo course : courses) {
for (RemoteCourse course : courses) {
ids.add(course.getId());
}
return ids;
@@ -25,6 +25,7 @@ import org.jetbrains.annotations.NotNull;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URI;
@@ -70,6 +71,7 @@ public class EduStepicClient {
static <T> T deserializeStepicResponse(Class<T> container, String responseString) {
Gson gson = new GsonBuilder().registerTypeAdapter(StepicWrappers.StepOptions.class, new StudySerializationUtils.Json.StepicStepOptionsAdapter()).
excludeFieldsWithModifiers(Modifier.TRANSIENT).
setDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").
setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
return gson.fromJson(responseString, container);
@@ -11,10 +11,7 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.edu.learning.StudySettings;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.CourseInfo;
import com.jetbrains.edu.learning.courseFormat.Lesson;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import com.jetbrains.edu.learning.courseFormat.*;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks;
import org.apache.http.HttpEntity;
@@ -65,9 +62,9 @@ public class EduStepicConnector {
}
@NotNull
public static List<CourseInfo> getCourses(@Nullable StepicUser user) {
public static List<Course> getCourses(@Nullable StepicUser user) {
try {
List<CourseInfo> result = new ArrayList<>();
List<Course> result = new ArrayList<>();
int pageNumber = 1;
while (addCoursesFromStepic(user, result, pageNumber)) {
pageNumber += 1;
@@ -77,13 +74,13 @@ public class EduStepicConnector {
catch (IOException e) {
LOG.warn("Cannot load course list " + e.getMessage());
}
return Collections.singletonList(CourseInfo.INVALID_COURSE);
return Collections.emptyList();
}
public static Date getCourseUpdateDate(final int courseId) {
final String url = EduStepicNames.COURSES + "/" + courseId;
try {
final List<CourseInfo> courses = EduStepicClient.getFromStepic(url, StepicWrappers.CoursesContainer.class).courses;
final List<RemoteCourse> courses = EduStepicClient.getFromStepic(url, StepicWrappers.CoursesContainer.class).courses;
if (!courses.isEmpty()) {
return courses.get(0).getUpdateDate();
}
@@ -125,7 +122,7 @@ public class EduStepicConnector {
return null;
}
private static boolean addCoursesFromStepic(@Nullable StepicUser user, List<CourseInfo> result, int pageNumber) throws IOException {
private static boolean addCoursesFromStepic(@Nullable StepicUser user, List<Course> result, int pageNumber) throws IOException {
final URI url;
try {
url = new URIBuilder(EduStepicNames.COURSES).addParameter("is_idea_compatible", "true").
@@ -146,27 +143,28 @@ public class EduStepicConnector {
return coursesContainer.meta.containsKey("has_next") && coursesContainer.meta.get("has_next") == Boolean.TRUE;
}
static void addAvailableCourses(List<CourseInfo> result, StepicWrappers.CoursesContainer coursesContainer) throws IOException {
final List<CourseInfo> courseInfos = coursesContainer.courses;
for (CourseInfo info : courseInfos) {
static void addAvailableCourses(List<Course> result, StepicWrappers.CoursesContainer coursesContainer) throws IOException {
final List<RemoteCourse> courses = coursesContainer.courses;
for (RemoteCourse info : courses) {
if (!info.isAdaptive() && StringUtil.isEmptyOrSpaces(info.getType())) continue;
if (canBeOpened(info)) {
final ArrayList<StepicUser> authors = new ArrayList<>();
for (Integer instructor : info.getInstructors()) {
final StepicUser author = EduStepicClient.getFromStepic(EduStepicNames.USERS + String.valueOf(instructor),
StepicWrappers.AuthorWrapper.class).users.get(0);
info.addAuthor(author);
authors.add(author);
}
info.setAuthors(authors);
if (info.isAdaptive()) {
info.setDescription("This is a Stepik Adaptive course.\n\n" + info.getDescription() + ADAPTIVE_NOTE);
}
result.add(info);
}
}
}
static boolean canBeOpened(CourseInfo courseInfo) {
static boolean canBeOpened(RemoteCourse courseInfo) {
if (courseInfo.isAdaptive()) {
return true;
}
@@ -190,26 +188,19 @@ public class EduStepicConnector {
}
}
public static Course getCourse(@NotNull final Project project, @NotNull final CourseInfo info) {
final Course course = new Course();
course.setAuthors(info.getAuthors());
course.setDescription(info.getDescription());
course.setAdaptive(info.isAdaptive());
course.setId(info.getId());
course.setUpdateDate(getCourseUpdateDate(info.getId()));
if (!course.isAdaptive()) {
String courseType = info.getType();
course.setName(info.getName());
public static RemoteCourse getCourse(@NotNull final Project project, @NotNull final RemoteCourse course) {
final RemoteCourse remoteCourse = (RemoteCourse)course.copy();
if (!remoteCourse.isAdaptive()) {
String courseType = remoteCourse.getType();
final int separator = courseType.indexOf(" ");
assert separator != -1;
final String language = courseType.substring(separator + 1);
course.setLanguage(language);
remoteCourse.setLanguage(language);
try {
for (Integer section : info.getSections()) {
course.addLessons(getLessons(section));
for (Integer section : remoteCourse.getSections()) {
remoteCourse.addLessons(getLessons(section));
}
return course;
return remoteCourse;
}
catch (IOException e) {
LOG.error("IOException " + e.getMessage());
@@ -218,14 +209,13 @@ public class EduStepicConnector {
else {
final Lesson lesson = new Lesson();
lesson.setName("Adaptive");
course.addLesson(lesson);
course.setName(info.getName());
remoteCourse.addLesson(lesson);
//TODO: more specific name?
final Task recommendation = EduAdaptiveStepicConnector.getNextRecommendation(project, course);
final Task recommendation = EduAdaptiveStepicConnector.getNextRecommendation(project, remoteCourse);
if (recommendation != null) {
lesson.addTask(recommendation);
}
return course;
return remoteCourse;
}
return null;
}
@@ -299,12 +289,26 @@ public class EduStepicConnector {
task.taskFiles = new HashMap<>(); // TODO: it looks like we don't need taskFiles as map anymore
if (block.options.files != null) {
for (TaskFile taskFile : block.options.files) {
addPlaceholdersTexts(taskFile);
task.taskFiles.put(taskFile.name, taskFile);
}
}
return task;
}
private static void addPlaceholdersTexts(TaskFile file) {
final String fileText = file.text;
final List<AnswerPlaceholder> placeholders = file.getAnswerPlaceholders();
for (AnswerPlaceholder placeholder : placeholders) {
final AnswerPlaceholderSubtaskInfo info = placeholder.getActiveSubtaskInfo();
final int offset = placeholder.getOffset();
final int length = placeholder.getLength();
if (fileText.length() > offset + length) {
info.setPlaceholderText(fileText.substring(offset, offset+length));
}
}
}
@NotNull
private static Task createTaskWithSubtasks(int lastSubtaskIndex) {
TaskWithSubtasks task = new TaskWithSubtasks();
@@ -11,10 +11,12 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.Alarm;
import com.intellij.util.text.DateFormatUtil;
import com.jetbrains.edu.learning.StudySettings;
import com.jetbrains.edu.learning.courseFormat.CourseInfo;
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.RemoteCourse;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class EduStepicUpdater {
@@ -49,25 +51,29 @@ public class EduStepicUpdater {
private static ActionCallback updateCourseList() {
ActionCallback callback = new ActionCallback();
ApplicationManager.getApplication().executeOnPooledThread(() -> {
final List<CourseInfo> courses = EduStepicConnector.getCourses(null);
final List<CourseInfo> cachedCourses = StudyProjectGenerator.getCoursesFromCache();
StudyProjectGenerator.flushCache(courses);
final List<Course> courses = EduStepicConnector.getCourses(null);
StudySettings.getInstance().setLastTimeChecked(System.currentTimeMillis());
courses.removeAll(cachedCourses);
if (!courses.isEmpty() && !cachedCourses.isEmpty()) {
if (!courses.isEmpty()) {
List<Course> updated = new ArrayList<>();
for (Course course : courses) {
if (course instanceof RemoteCourse && ((RemoteCourse)course).getUpdateDate().
after(new Date(StudySettings.getInstance().getLastTimeChecked()))) {
updated.add(course);
}
}
if (updated.isEmpty()) return;
final String message;
final String title;
if (courses.size() == 1) {
message = courses.get(0).getName();
if (updated.size() == 1) {
message = updated.get(0).getName();
title = "New course available";
}
else {
title = "New courses available";
message = StringUtil.join(courses, CourseInfo::getName, ", ");
message = StringUtil.join(updated, Course::getName, ", ");
}
final Notification notification =
new Notification("New.course", title, message, NotificationType.INFORMATION);
final Notification notification = new Notification("New.course", title, message, NotificationType.INFORMATION);
notification.notify(null);
}
});
@@ -79,8 +85,7 @@ public class EduStepicUpdater {
}
private static boolean checkNeeded() {
final List<CourseInfo> courses = StudyProjectGenerator.getCoursesFromCache();
long timeToNextCheck = StudySettings.getInstance().getLastTimeChecked() + CHECK_INTERVAL - System.currentTimeMillis();
return courses.isEmpty() || timeToNextCheck <= 0;
return timeToNextCheck <= 0;
}
}
@@ -87,6 +87,10 @@ public class StepicUser {
@NotNull
public String getAccessToken() {
// for old project where authors were created with null tokens
if (myAccessToken == null) {
return "";
}
return myAccessToken;
}
@@ -96,6 +100,10 @@ public class StepicUser {
@NotNull
public String getRefreshToken() {
// for old project where authors were created with null tokens
if (myRefreshToken == null) {
return "";
}
return myRefreshToken;
}
@@ -5,25 +5,21 @@ import com.google.gson.annotations.SerializedName;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.core.EduUtils;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.CourseInfo;
import com.jetbrains.edu.learning.courseFormat.Lesson;
import com.jetbrains.edu.learning.courseFormat.RemoteCourse;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks;
import org.apache.commons.codec.binary.Base64;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;
@@ -89,21 +85,11 @@ public class StepicWrappers {
}
String name = entry.getKey();
VirtualFile answerFile = taskDir.findFileByRelativePath(name);
Pair<VirtualFile, TaskFile> pair = EduUtils.createStudentFile(StepicWrappers.class, project, answerFile, stepicDir, null, 0);
if (pair == null) {
TaskFile studentTaskFile = EduUtils.createStudentFile(project, answerFile, null, 0);
if (studentTaskFile == null) {
return;
}
VirtualFile virtualFile = pair.getFirst();
TaskFile taskFile = pair.getSecond();
try {
InputStream stream = virtualFile.getInputStream();
taskFile.text =
EduUtils.isImage(name) ? Base64.encodeBase64URLSafeString(FileUtil.loadBytes(stream)) : FileUtil.loadTextAndClose(stream);
}
catch (IOException e) {
LOG.error("Can't find file " + virtualFile.getPath());
}
source.files.add(taskFile);
source.files.add(studentTaskFile);
});
}
return source;
@@ -185,7 +171,7 @@ public class StepicWrappers {
}
public static class CoursesContainer {
public List<CourseInfo> courses;
public List<RemoteCourse> courses;
public Map meta;
}
@@ -199,10 +185,10 @@ public class StepicWrappers {
}
public static class CourseWrapper {
CourseInfo course;
RemoteCourse course;
public CourseWrapper(Course course) {
this.course = new CourseInfo();
this.course = new RemoteCourse();
this.course.setName(course.getName());
this.course.setDescription(course.getDescription());
this.course.setAuthors(course.getAuthors());
@@ -29,7 +29,6 @@ import com.intellij.util.ui.UIUtil;
import com.jetbrains.edu.learning.StudySettings;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.CourseInfo;
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
import com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector;
import com.jetbrains.edu.learning.stepic.EduStepicAuthorizedClient;
@@ -52,15 +51,16 @@ import java.util.List;
* data: 7/31/14.
*/
public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor {
private List<CourseInfo> myAvailableCourses = new ArrayList<>();
private List<Course> myAvailableCourses = new ArrayList<>();
private JButton myBrowseButton;
private ComboBox<CourseInfo> myCoursesComboBox;
private ComboBox<Course> myCoursesComboBox;
private JButton myRefreshButton;
private JLabel myAuthorLabel;
private JPanel myInfoPanel;
private JTextPane myDescriptionPane;
private JComponent myAnchor;
private final StudyProjectGenerator myGenerator;
private boolean isLocal = false;
private static final String CONNECTION_ERROR = "<html>Failed to download courses.<br>Check your Internet connection.</html>";
private static final String INVALID_COURSE = "Selected course is invalid";
private FacetValidatorsManager myValidationManager;
@@ -68,10 +68,10 @@ public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor {
private static final String LOGIN_TO_STEPIC_MESSAGE = "<html><u>Login to Stepik</u> to open the adaptive course </html>";
private static final String LOGIN_TO_STEPIC = "Login to Stepik";
public StudyNewProjectPanel(@NotNull final StudyProjectGenerator generator) {
public StudyNewProjectPanel(@NotNull final StudyProjectGenerator generator, boolean isLocal) {
super(new VerticalFlowLayout(true, true));
myGenerator = generator;
this.isLocal = isLocal;
layoutPanel();
initListeners();
}
@@ -120,21 +120,21 @@ public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor {
private void initCoursesCombobox() {
myAvailableCourses =
myGenerator.getCoursesUnderProgress(false, "Getting Available Courses", ProjectManager.getInstance().getDefaultProject());
if (myAvailableCourses.contains(CourseInfo.INVALID_COURSE)) {
myGenerator.getCoursesUnderProgress(!isLocal, "Getting Available Courses", ProjectManager.getInstance().getDefaultProject());
if (myAvailableCourses.contains(Course.INVALID_COURSE)) {
setError(CONNECTION_ERROR);
}
else {
addCoursesToCombobox(myAvailableCourses);
final CourseInfo selectedCourse = StudyUtils.getFirst(myAvailableCourses);
final Course selectedCourse = StudyUtils.getFirst(myAvailableCourses);
if (selectedCourse == null) return;
setAuthors(selectedCourse);
myDescriptionPane.setText(selectedCourse.getDescription());
myDescriptionPane.setEditable(false);
//setting the first course in list as selected
myGenerator.setSelectedCourse(selectedCourse);
if (myGenerator.getSelectedCourseInfo() != null) {
myCoursesComboBox.setSelectedItem(myGenerator.getSelectedCourseInfo());
if (myGenerator.getSelectedCourse() != null) {
myCoursesComboBox.setSelectedItem(myGenerator.getSelectedCourse());
}
if (selectedCourse.isAdaptive() && !myGenerator.isLoggedIn()) {
setError(LOGIN_TO_STEPIC_MESSAGE);
@@ -173,7 +173,7 @@ public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor {
file -> {
String fileName = file.getPath();
int oldSize = myAvailableCourses.size();
CourseInfo courseInfo = myGenerator.addLocalCourse(fileName);
Course courseInfo = myGenerator.addLocalCourse(fileName);
if (courseInfo != null) {
if (oldSize != myAvailableCourses.size()) {
myCoursesComboBox.addItem(courseInfo);
@@ -184,11 +184,11 @@ public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor {
else {
setError(INVALID_COURSE);
myCoursesComboBox.removeAllItems();
myCoursesComboBox.addItem(CourseInfo.INVALID_COURSE);
for (CourseInfo course : myAvailableCourses) {
myCoursesComboBox.addItem(Course.INVALID_COURSE);
for (Course course : myAvailableCourses) {
myCoursesComboBox.addItem(course);
}
myCoursesComboBox.setSelectedItem(CourseInfo.INVALID_COURSE);
myCoursesComboBox.setSelectedItem(Course.INVALID_COURSE);
}
});
}
@@ -220,8 +220,8 @@ public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor {
isComboboxInitialized = true;
initCoursesCombobox();
}
CourseInfo selectedCourse = (CourseInfo)myCoursesComboBox.getSelectedItem();
if (selectedCourse == null || selectedCourse.equals(CourseInfo.INVALID_COURSE)) {
Course selectedCourse = (Course)myCoursesComboBox.getSelectedItem();
if (selectedCourse == null || selectedCourse.equals(Course.INVALID_COURSE)) {
setError(CONNECTION_ERROR);
}
}
@@ -255,9 +255,9 @@ public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor {
myAnchor = anchor;
}
public void updateInfoPanel(@NotNull CourseInfo selectedCourseInfo) {
setAuthors(selectedCourseInfo);
myDescriptionPane.setText(selectedCourseInfo.getDescription());
public void updateInfoPanel(@NotNull Course selectedCourse) {
setAuthors(selectedCourse);
myDescriptionPane.setText(selectedCourse.getDescription());
}
/**
@@ -268,15 +268,15 @@ public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor {
private class RefreshActionListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
final List<CourseInfo> courses =
final List<Course> courses =
myGenerator.getCoursesUnderProgress(true, "Refreshing Course List", DefaultProjectFactory.getInstance().getDefaultProject());
if (!courses.contains(CourseInfo.INVALID_COURSE)) {
if (!courses.contains(Course.INVALID_COURSE)) {
refreshCoursesList(courses);
}
}
}
private void refreshCoursesList(@NotNull final List<CourseInfo> courses) {
private void refreshCoursesList(@NotNull final List<Course> courses) {
if (courses.isEmpty()) {
setError(CONNECTION_ERROR);
return;
@@ -284,17 +284,16 @@ public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor {
myCoursesComboBox.removeAllItems();
addCoursesToCombobox(courses);
final CourseInfo selectedCourse = StudyUtils.getFirst(courses);
final Course selectedCourse = StudyUtils.getFirst(courses);
if (selectedCourse == null) return;
myGenerator.setSelectedCourse(selectedCourse);
myGenerator.setCourses(courses);
myAvailableCourses = courses;
StudyProjectGenerator.flushCache(myAvailableCourses, false);
}
private void addCoursesToCombobox(@NotNull List<CourseInfo> courses) {
for (CourseInfo courseInfo : courses) {
private void addCoursesToCombobox(@NotNull List<Course> courses) {
for (Course courseInfo : courses) {
myCoursesComboBox.addItem(courseInfo);
}
}
@@ -309,15 +308,15 @@ public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor {
@Override
public void actionPerformed(ActionEvent e) {
JComboBox cb = (JComboBox)e.getSource();
CourseInfo selectedCourse = (CourseInfo)cb.getSelectedItem();
if (selectedCourse == null || selectedCourse.equals(CourseInfo.INVALID_COURSE)) {
Course selectedCourse = (Course)cb.getSelectedItem();
if (selectedCourse == null || selectedCourse.equals(Course.INVALID_COURSE)) {
myAuthorLabel.setText("");
myDescriptionPane.setText("");
setError(INVALID_COURSE);
return;
}
updateInfoPanel(selectedCourse);
myCoursesComboBox.removeItem(CourseInfo.INVALID_COURSE);
myCoursesComboBox.removeItem(Course.INVALID_COURSE);
myGenerator.setSelectedCourse(selectedCourse);
setOK();
@@ -329,12 +328,12 @@ public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor {
}
}
private void setAuthors(CourseInfo selectedCourse) {
private void setAuthors(Course selectedCourse) {
final String authorsString = Course.getAuthorsString(selectedCourse.getAuthors());
myAuthorLabel.setText(!StringUtil.isEmptyOrSpaces(authorsString) ? "Author: " + authorsString : "");
}
public JComboBox<CourseInfo> getCoursesComboBox() {
public JComboBox<Course> getCoursesComboBox() {
return myCoursesComboBox;
}
@@ -367,7 +366,7 @@ public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor {
stepicUser.setPassword(myLoginPanel.getPassword());
StudySettings.getInstance().setUser(stepicUser);
myGenerator.setEnrolledCoursesIds(EduAdaptiveStepicConnector.getEnrolledCoursesIds(stepicUser));
final List<CourseInfo> courses = myGenerator.getCourses(true);
final List<Course> courses = myGenerator.getCourses(true);
if (courses != null && myRefreshCourseList) {
ApplicationManager.getApplication().invokeLater(() -> refreshCoursesList(courses));
}
@@ -324,7 +324,6 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D
int taskNum = 0;
int taskSolved = 0;
for (Lesson lesson : lessons) {
if (lesson.getName().equals(EduNames.PYCHARM_ADDITIONAL)) continue;
taskNum += lesson.getTaskList().size();
taskSolved += getSolvedTasks(lesson);
}
@@ -0,0 +1,115 @@
<component name="StudySettings">
<StudyTaskManager>
<option name="course">
<Course>
<option name="adaptive" value="false" />
<option name="authors">
<list>
<StepicUser>
<option name="accessToken" />
<option name="email" value="" />
<option name="firstName" value="ktisha" />
<option name="id" value="-1" />
<option name="lastName" value="" />
<option name="refreshToken" />
</StepicUser>
</list>
</option>
<option name="courseDirectory" value="$APPLICATION_CONFIG_DIR$/courses/test-untitled31" />
<option name="courseMode" value="Course Creator" />
<option name="courseType" value="PyCharm" />
<option name="description" value="test project" />
<option name="id" value="0" />
<option name="language" value="Python" />
<option name="lessons">
<list>
<Lesson>
<option name="id" value="0" />
<option name="index" value="1" />
<option name="name" value="lesson1" />
<option name="updateDate" />
<option name="taskList">
<list>
<Task>
<option name="activeSubtaskIndex" value="0" />
<option name="index" value="1" />
<option name="lastSubtaskIndex" value="0" />
<option name="name" value="task1" />
<option name="status" value="Unchecked" />
<option name="stepId" value="0" />
<option name="text" />
<option name="updateDate" />
<option name="taskFiles">
<map>
<entry key="task.py">
<value>
<TaskFile>
<option name="answerPlaceholders">
<list>
<AnswerPlaceholder>
<option name="index" value="0" />
<option name="initialState" />
<option name="length" value="4" />
<option name="offset" value="42" />
<option name="selected" value="false" />
<option name="subtaskInfos">
<map>
<entry key="0">
<value>
<AnswerPlaceholderSubtaskInfo>
<option name="answer" value="" />
<option name="hasFrame" value="true" />
<option name="hints">
<list>
<option value="hint" />
</list>
</option>
<option name="needInsertText" value="false" />
<option name="placeholderText" value="type" />
<option name="possibleAnswer" value="pass" />
<option name="selected" value="false" />
<option name="status" value="Unchecked" />
</AnswerPlaceholderSubtaskInfo>
</value>
</entry>
</map>
</option>
<option name="useLength" value="false" />
</AnswerPlaceholder>
</list>
</option>
<option name="highlightErrors" value="true" />
<option name="index" value="1" />
<option name="trackChanges" value="true" />
<option name="trackLengths" value="true" />
<option name="userCreated" value="false" />
<option name="name" value="task.py" />
<option name="text" />
</TaskFile>
</value>
</entry>
</map>
</option>
</Task>
</list>
</option>
</Lesson>
</list>
</option>
<option name="name" value="test" />
<option name="updateDate" />
</Course>
</option>
<option name="enableTestingFromSamples" value="false" />
<option name="toolWindowMode" value="TEXT" />
<option name="turnEditingMode" value="false" />
<option name="VERSION" value="4" />
<option name="myUserTests">
<map />
</option>
<option name="myInvisibleFiles">
<list />
</option>
<option name="myShouldUseJavaFx" value="true" />
</StudyTaskManager>
</component>
@@ -0,0 +1,117 @@
<component name="StudySettings">
<StudyTaskManager>
<option name="course">
<Course>
<option name="adaptive" value="false" />
<option name="authors">
<list>
<StepicUser>
<option name="accessToken" />
<option name="email" value="" />
<option name="firstName" value="ktisha" />
<option name="id" value="-1" />
<option name="lastName" value="" />
<option name="refreshToken" />
</StepicUser>
</list>
</option>
<option name="courseDirectory" value="$APPLICATION_CONFIG_DIR$/courses/test-untitled31" />
<option name="courseMode" value="Course Creator" />
<option name="courseType" value="PyCharm" />
<option name="description" value="test project" />
<option name="id" value="0" />
<option name="language" value="Python" />
<option name="lessons">
<list>
<Lesson>
<option name="id" value="0" />
<option name="index" value="1" />
<option name="name" value="lesson1" />
<option name="updateDate" />
<option name="taskList">
<list>
<Task>
<option name="activeSubtaskIndex" value="0" />
<option name="adaptiveTaskParameters" />
<option name="index" value="1" />
<option name="lastSubtaskIndex" value="0" />
<option name="name" value="task1" />
<option name="status" value="Unchecked" />
<option name="stepId" value="0" />
<option name="text" />
<option name="theoryTask" value="false" />
<option name="updateDate" />
<option name="taskFiles">
<map>
<entry key="task.py">
<value>
<TaskFile>
<option name="answerPlaceholders">
<list>
<AnswerPlaceholder>
<option name="index" value="0" />
<option name="initialState" />
<option name="length" value="4" />
<option name="offset" value="42" />
<option name="selected" value="false" />
<option name="subtaskInfos">
<map>
<entry key="0">
<value>
<AnswerPlaceholderSubtaskInfo>
<option name="answer" value="" />
<option name="hasFrame" value="true" />
<option name="hints">
<list>
<option value="hint" />
</list>
</option>
<option name="needInsertText" value="false" />
<option name="placeholderText" value="type" />
<option name="possibleAnswer" value="pass" />
<option name="selected" value="false" />
<option name="status" value="Unchecked" />
</AnswerPlaceholderSubtaskInfo>
</value>
</entry>
</map>
</option>
<option name="useLength" value="false" />
</AnswerPlaceholder>
</list>
</option>
<option name="highlightErrors" value="true" />
<option name="index" value="1" />
<option name="trackChanges" value="true" />
<option name="trackLengths" value="true" />
<option name="userCreated" value="false" />
<option name="name" value="task.py" />
<option name="text" />
</TaskFile>
</value>
</entry>
</map>
</option>
</Task>
</list>
</option>
</Lesson>
</list>
</option>
<option name="name" value="test" />
<option name="updateDate" />
</Course>
</option>
<option name="enableTestingFromSamples" value="false" />
<option name="toolWindowMode" value="TEXT" />
<option name="turnEditingMode" value="false" />
<option name="VERSION" value="4" />
<option name="myUserTests">
<map />
</option>
<option name="myInvisibleFiles">
<list />
</option>
<option name="myShouldUseJavaFx" value="true" />
</StudyTaskManager>
</component>
@@ -0,0 +1,115 @@
<component name="StudySettings">
<StudyTaskManager>
<option name="course">
<Course>
<option name="adaptive" value="false" />
<option name="authors">
<list>
<StepicUser>
<option name="accessToken" />
<option name="email" value="" />
<option name="firstName" value="ktisha" />
<option name="id" value="-1" />
<option name="lastName" value="" />
<option name="refreshToken" />
</StepicUser>
</list>
</option>
<option name="courseDirectory" value="$APPLICATION_CONFIG_DIR$/courses/test-untitled30" />
<option name="courseMode" value="Course Creator" />
<option name="courseType" value="PyCharm" />
<option name="description" value="test project" />
<option name="id" value="0" />
<option name="language" value="Python" />
<option name="lessons">
<list>
<Lesson>
<option name="id" value="0" />
<option name="index" value="1" />
<option name="name" value="lesson1" />
<option name="updateDate" />
<option name="taskList">
<list>
<Task>
<option name="activeSubtaskIndex" value="0" />
<option name="index" value="1" />
<option name="lastSubtaskIndex" value="0" />
<option name="name" value="task1" />
<option name="status" value="Unchecked" />
<option name="stepId" value="0" />
<option name="text" />
<option name="updateDate" />
<option name="taskFiles">
<map>
<entry key="task.py">
<value>
<TaskFile>
<option name="answerPlaceholders">
<list>
<AnswerPlaceholder>
<option name="index" value="0" />
<option name="initialState" />
<option name="length" value="5" />
<option name="offset" value="42" />
<option name="selected" value="false" />
<option name="subtaskInfos">
<map>
<entry key="0">
<value>
<AnswerPlaceholderSubtaskInfo>
<option name="answer" value="" />
<option name="hasFrame" value="true" />
<option name="hints">
<list>
<option value="hint1" />
</list>
</option>
<option name="needInsertText" value="false" />
<option name="placeholderText" value="place" />
<option name="possibleAnswer" value="pass" />
<option name="selected" value="false" />
<option name="status" value="Unchecked" />
</AnswerPlaceholderSubtaskInfo>
</value>
</entry>
</map>
</option>
<option name="useLength" value="false" />
</AnswerPlaceholder>
</list>
</option>
<option name="highlightErrors" value="true" />
<option name="index" value="1" />
<option name="trackChanges" value="true" />
<option name="trackLengths" value="true" />
<option name="userCreated" value="false" />
<option name="name" value="task.py" />
<option name="text" />
</TaskFile>
</value>
</entry>
</map>
</option>
</Task>
</list>
</option>
</Lesson>
</list>
</option>
<option name="name" value="test" />
<option name="updateDate" />
</Course>
</option>
<option name="enableTestingFromSamples" value="false" />
<option name="toolWindowMode" value="TEXT" />
<option name="turnEditingMode" value="false" />
<option name="VERSION" value="4" />
<option name="myUserTests">
<map />
</option>
<option name="myInvisibleFiles">
<list />
</option>
<option name="myShouldUseJavaFx" value="true" />
</StudyTaskManager>
</component>
@@ -0,0 +1,117 @@
<component name="StudySettings">
<StudyTaskManager>
<option name="course">
<Course>
<option name="adaptive" value="false" />
<option name="authors">
<list>
<StepicUser>
<option name="accessToken" />
<option name="email" value="" />
<option name="firstName" value="ktisha" />
<option name="id" value="-1" />
<option name="lastName" value="" />
<option name="refreshToken" />
</StepicUser>
</list>
</option>
<option name="courseDirectory" value="$APPLICATION_CONFIG_DIR$/courses/test-untitled30" />
<option name="courseMode" value="Course Creator" />
<option name="courseType" value="PyCharm" />
<option name="description" value="test project" />
<option name="id" value="0" />
<option name="language" value="Python" />
<option name="lessons">
<list>
<Lesson>
<option name="id" value="0" />
<option name="index" value="1" />
<option name="name" value="lesson1" />
<option name="updateDate" />
<option name="taskList">
<list>
<Task>
<option name="activeSubtaskIndex" value="0" />
<option name="adaptiveTaskParameters" />
<option name="index" value="1" />
<option name="lastSubtaskIndex" value="0" />
<option name="name" value="task1" />
<option name="status" value="Unchecked" />
<option name="stepId" value="0" />
<option name="text" />
<option name="theoryTask" value="false" />
<option name="updateDate" />
<option name="taskFiles">
<map>
<entry key="task.py">
<value>
<TaskFile>
<option name="answerPlaceholders">
<list>
<AnswerPlaceholder>
<option name="index" value="0" />
<option name="initialState" />
<option name="length" value="5" />
<option name="offset" value="42" />
<option name="selected" value="false" />
<option name="subtaskInfos">
<map>
<entry key="0">
<value>
<AnswerPlaceholderSubtaskInfo>
<option name="answer" value="" />
<option name="hasFrame" value="true" />
<option name="hints">
<list>
<option value="hint1" />
</list>
</option>
<option name="needInsertText" value="false" />
<option name="placeholderText" value="place" />
<option name="possibleAnswer" value="pass" />
<option name="selected" value="false" />
<option name="status" value="Unchecked" />
</AnswerPlaceholderSubtaskInfo>
</value>
</entry>
</map>
</option>
<option name="useLength" value="false" />
</AnswerPlaceholder>
</list>
</option>
<option name="highlightErrors" value="true" />
<option name="index" value="1" />
<option name="trackChanges" value="true" />
<option name="trackLengths" value="true" />
<option name="userCreated" value="false" />
<option name="name" value="task.py" />
<option name="text" />
</TaskFile>
</value>
</entry>
</map>
</option>
</Task>
</list>
</option>
</Lesson>
</list>
</option>
<option name="name" value="test" />
<option name="updateDate" />
</Course>
</option>
<option name="enableTestingFromSamples" value="false" />
<option name="toolWindowMode" value="TEXT" />
<option name="turnEditingMode" value="false" />
<option name="VERSION" value="4" />
<option name="myUserTests">
<map />
</option>
<option name="myInvisibleFiles">
<list />
</option>
<option name="myShouldUseJavaFx" value="true" />
</StudyTaskManager>
</component>
@@ -81,7 +81,6 @@ public abstract class CCTestCase extends CodeInsightFixtureTestCase {
super.setUp();
Course course = new Course();
course.setName("test course");
course.setCourseDirectory(getProject().getBasePath());
StudyTaskManager.getInstance(getProject()).setCourse(course);
Lesson lesson = new Lesson();
@@ -91,7 +90,7 @@ public abstract class CCTestCase extends CodeInsightFixtureTestCase {
task.setIndex(1);
lesson.addTask(task);
lesson.setIndex(1);
course.getLessons().add(lesson);
course.addLesson(lesson);
course.setCourseMode(CCUtils.COURSE_MODE);
course.initCourse(false);
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@@ -118,6 +117,8 @@ public abstract class CCTestCase extends CodeInsightFixtureTestCase {
taskFile.setTask(task);
task.getTaskFiles().put(name, taskFile);
VirtualFile file = copyFileToTask(name);
taskFile.name = name;
myFixture.configureFromExistingVirtualFile(file);
Document document = FileDocumentManager.getInstance().getDocument(file);
for (AnswerPlaceholder placeholder : getPlaceholders(document, false)) {
@@ -125,7 +126,6 @@ public abstract class CCTestCase extends CodeInsightFixtureTestCase {
}
taskFile.sortAnswerPlaceholders();
StudyUtils.drawAllAnswerPlaceholders(myFixture.getEditor(), taskFile);
CCUtils.createResourceFile(file, StudyTaskManager.getInstance(getProject()).getCourse(), file.getParent());
return file;
}
@@ -171,6 +171,7 @@ public abstract class CCTestCase extends CodeInsightFixtureTestCase {
}
document.deleteString(closingMatcher.start(), closingMatcher.end());
document.deleteString(openingMatcher.start(), openingMatcher.end());
FileDocumentManager.getInstance().saveDocument(document);
placeholders.add(answerPlaceholder);
pos = answerPlaceholder.getOffset() + answerPlaceholder.getRealLength();
}
@@ -51,7 +51,7 @@ public class CCShowPreviewTest extends CCTestCase {
action.actionPerformed(e);
Editor editor = EditorFactory.getInstance().getAllEditors()[1];
Pair<Document, List<AnswerPlaceholder>> pair = getPlaceholders(name + CCTestsUtil.AFTER_POSTFIX);
assertEquals("Files don't match", editor.getDocument().getText(), pair.getFirst().getText());
assertEquals("Files don't match", pair.getFirst().getText(), editor.getDocument().getText());
for (AnswerPlaceholder placeholder : pair.getSecond()) {
assertNotNull("No highlighter for placeholder:" + CCTestsUtil.getPlaceholderPresentation(placeholder), getHighlighter(editor.getMarkupModel(), placeholder));
}
@@ -5,7 +5,9 @@ import com.intellij.util.JdomKt;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jetbrains.annotations.NotNull;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import java.io.IOException;
import java.nio.file.Path;
@@ -14,22 +16,51 @@ import java.nio.file.Paths;
import static com.intellij.testFramework.assertions.Assertions.assertThat;
public class StudyMigrationTest {
@Rule public TestName name = new TestName();
@Test
public void testFromThirdToForth() throws JDOMException, IOException, StudySerializationUtils.StudyUnrecognizedFormatException {
Element element = JdomKt.loadElement(getTestDataPath().resolve("3.xml"));
assertThat(StudySerializationUtils.Xml.convertToForthVersion(element)).isEqualTo(getTestDataPath().resolve("4.xml"));
doTest(3);
}
@Test
public void testAdaptive45() throws JDOMException, IOException, StudySerializationUtils.StudyUnrecognizedFormatException {
Element element = JdomKt.loadElement(getTestDataPath().resolve("adaptive4.xml"));
assertThat(StudySerializationUtils.Xml.convertToFifthVersion(element)).isEqualTo(getTestDataPath().resolve("adaptive5.xml"));
doTest(4);
}
@Test
public void testSubtasks45() throws JDOMException, IOException, StudySerializationUtils.StudyUnrecognizedFormatException {
Element element = JdomKt.loadElement(getTestDataPath().resolve("subtasks4.xml"));
assertThat(StudySerializationUtils.Xml.convertToFifthVersion(element)).isEqualTo(getTestDataPath().resolve("subtasks5.xml"));
doTest(4);
}
@Test
public void testTheory35To4() throws JDOMException, IOException, StudySerializationUtils.StudyUnrecognizedFormatException {
doTest(4);
}
@Test
public void testTheory351To4() throws JDOMException, IOException, StudySerializationUtils.StudyUnrecognizedFormatException {
doTest(4);
}
private void doTest(int version) throws IOException, JDOMException, StudySerializationUtils.StudyUnrecognizedFormatException {
final String name = PlatformTestUtil.getTestName(this.name.getMethodName(), true);
final Path before = getTestDataPath().resolve(name + ".xml");
final Path after = getTestDataPath().resolve(name + ".after.xml");
Element element = JdomKt.loadElement(before);
Element converted = element;
switch (version) {
case 1:
converted = StudySerializationUtils.Xml.convertToSecondVersion(element);
break;
case 3:
converted = StudySerializationUtils.Xml.convertToForthVersion(element);
break;
case 4:
converted = StudySerializationUtils.Xml.convertToFifthVersion(element);
break;
}
assertThat(converted).isEqualTo(after);
}
@NotNull
@@ -8,7 +8,7 @@ import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.edu.learning.StudySerializationUtils;
import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholderSubtaskInfo;
import com.jetbrains.edu.learning.courseFormat.CourseInfo;
import com.jetbrains.edu.learning.courseFormat.RemoteCourse;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
@@ -65,9 +65,9 @@ public class StudyStepicFormatTest {
EduStepicClient.deserializeStepicResponse(StepicWrappers.CoursesContainer.class, responseString);
assertNotNull(container.courses);
assertTrue("Incorrect number of courses", container.courses.size() == 4);
List<CourseInfo> filtered = ContainerUtil.filter(container.courses, info -> EduStepicConnector.canBeOpened(info));
List<RemoteCourse> filtered = ContainerUtil.filter(container.courses, info -> EduStepicConnector.canBeOpened(info));
assertEquals(ContainerUtil.newArrayList("Adaptive Python", "Introduction to Python", "format2"),
ContainerUtil.map(filtered, CourseInfo::getName));
ContainerUtil.map(filtered, RemoteCourse::getName));
}
@Test
@@ -11,7 +11,6 @@ import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
import com.jetbrains.edu.learning.courseFormat.CourseInfo;
import com.jetbrains.python.newProject.steps.PyCharmNewProjectStep;
import org.jetbrains.annotations.NotNull;
@@ -25,7 +24,7 @@ class CreateFromArchiveProjectStep extends PyCharmNewProjectStep {
private final Project myProject;
private final Module myModule;
private PyStudyDirectoryProjectGenerator myGenerator = new PyStudyDirectoryProjectGenerator();
private PyStudyDirectoryProjectGenerator myGenerator = new PyStudyDirectoryProjectGenerator(true);
public MyCustomization(Project project,
Module module) {
@@ -44,10 +43,10 @@ class CreateFromArchiveProjectStep extends PyCharmNewProjectStep {
@NotNull
@Override
protected DirectoryProjectGenerator createEmptyProjectGenerator() {
Course course = StudyTaskManager.getInstance(myProject).getCourse();
if (course != null) {
Course currentCourse = StudyTaskManager.getInstance(myProject).getCourse();
if (currentCourse != null) {
VirtualFile folder = CCUtils.getGeneratedFilesFolder(myProject, myModule);
String zipName = FileUtil.sanitizeFileName(course.getName());
String zipName = FileUtil.sanitizeFileName(currentCourse.getName());
if (zipName.isEmpty()) {
zipName = EduNames.COURSE;
}
@@ -55,9 +54,9 @@ class CreateFromArchiveProjectStep extends PyCharmNewProjectStep {
CCCreateCourseArchive.createCourseArchive(myProject, myModule, zipName, locationDir, false);
String path = FileUtil.join(FileUtil.toSystemDependentName(folder.getPath()), zipName + ".zip");
StudyProjectGenerator generator = myGenerator.getGenerator();
CourseInfo info = generator.addLocalCourse(path);
assert info != null;
generator.setSelectedCourse(info);
Course course = generator.addLocalCourse(path);
assert course != null;
generator.setSelectedCourse(course);
}
return myGenerator;
}
@@ -33,10 +33,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.io.File;
import static com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator.OUR_COURSES_DIR;
public class PyCCProjectGenerator extends PythonProjectGenerator<PyNewProjectSettings> {
private static final Logger LOG = Logger.getInstance(PyCCProjectGenerator.class);
@@ -60,11 +56,7 @@ public class PyCCProjectGenerator extends PythonProjectGenerator<PyNewProjectSet
@NotNull PyNewProjectSettings settings,
@NotNull Module module,
@Nullable final PyProjectSynchronizer synchronizer) {
generateProject(project, baseDir, mySettingsPanel);
}
public static void generateProject(@NotNull final Project project, @NotNull final VirtualFile baseDir, CCNewProjectPanel settingsPanel) {
final Course course = getCourse(project, settingsPanel);
final Course course = getCourse(project, mySettingsPanel);
EduUsagesCollector.projectTypeCreated(CCUtils.COURSE_MODE);
final PsiDirectory projectDir = PsiManager.getInstance(project).findDirectory(baseDir);
@@ -107,9 +99,6 @@ public class PyCCProjectGenerator extends PythonProjectGenerator<PyNewProjectSet
course.setLanguage(language);
course.setCourseMode(CCUtils.COURSE_MODE);
File courseDir = new File(OUR_COURSES_DIR, name + "-" + project.getName());
course.setCourseDirectory(courseDir.getPath());
StudyTaskManager.getInstance(project).setCourse(course);
StudyUtils.registerStudyToolWindow(course, project);
return course;
@@ -15,20 +15,18 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.edu.coursecreator.CCUtils;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.checker.StudyCheckUtils;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.core.EduUtils;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import com.jetbrains.python.run.CommandLinePatcher;
import com.jetbrains.python.run.PythonCommandLineState;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.Map;
public class PyCCCommandLineState extends PythonCommandLineState {
@@ -60,7 +58,6 @@ public class PyCCCommandLineState extends PythonCommandLineState {
assert course != null;
group.addParameter(myRunConfiguration.getPathToTest());
group.addParameter(new File(course.getCourseDirectory()).getPath());
String path = getCurrentTaskFilePath();
if (path != null) {
@@ -97,7 +94,6 @@ public class PyCCCommandLineState extends PythonCommandLineState {
@Override
public ExecutionResult execute(Executor executor, PythonProcessStarter processStarter, CommandLinePatcher... patchers) throws ExecutionException {
CCUtils.updateResources(myRunConfiguration.getProject(), myTask, myTaskDir);
ApplicationManager.getApplication().runWriteAction(() -> StudyCheckUtils.flushWindows(myTask, myTaskDir));
return super.execute(executor, processStarter, patchers);
@@ -14,22 +14,27 @@ import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiManager;
import com.intellij.util.PathUtil;
import com.jetbrains.edu.coursecreator.settings.CCSettings;
import com.jetbrains.edu.learning.actions.StudyCheckAction;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.Collections;
import java.util.List;
public class PyEduPluginConfigurator implements EduPluginConfigurator {
public static final String PYTHON_3 = "3.x";
public static final String PYTHON_2 = "2.x";
private static final String TESTS_PY = "tests.py";
private static final Logger LOG = Logger.getInstance(PyEduPluginConfigurator.class);
private static final String COURSE_NAME = "Introduction to Python.zip";
@NotNull
@Override
@@ -65,9 +70,8 @@ public class PyEduPluginConfigurator implements EduPluginConfigurator {
}
@Override
public boolean excludeFromArchive(@NotNull File pathname) {
String name = pathname.getName();
return name.contains("__pycache__") || name.contains(".pyc");
public boolean excludeFromArchive(@NotNull String path) {
return path.contains("__pycache__") || path.endsWith(".pyc");
}
@Override
@@ -126,4 +130,18 @@ public class PyEduPluginConfigurator implements EduPluginConfigurator {
public StudyCheckAction getCheckAction() {
return new PyStudyCheckAction();
}
@Override
public List<String> getBundledCoursePaths() {
@NonNls String jarPath = PathUtil.getJarPathForClass(PyEduPluginConfigurator.class);
if (jarPath.endsWith(".jar")) {
final File jarFile = new File(jarPath);
File pluginBaseDir = jarFile.getParentFile();
return Collections.singletonList(new File(new File(pluginBaseDir, "courses"), COURSE_NAME).getPath());
}
return Collections.singletonList(new File(new File(jarPath, "courses"), COURSE_NAME).getPath());
}
}
@@ -94,9 +94,9 @@ public class PyStudyCheckAction extends StudyCheckAction {
}
final Course course = myTaskManger.getCourse();
if (course != null && EduNames.STUDY.equals(course.getCourseMode())) {
CommandProcessor.getInstance().runUndoTransparentAction(() -> ApplicationManager.getApplication().runWriteAction(() -> {
StudyCheckUtils.runSmartTestProcess(myTaskDir, testRunner, name, taskFile, project);
}));
CommandProcessor.getInstance().runUndoTransparentAction(
() -> ApplicationManager.getApplication().runWriteAction(
() -> StudyCheckUtils.runSmartTestProcess(myTaskDir, testRunner, name, taskFile, project)));
}
}
final StudyToolWindow toolWindow = StudyUtils.getStudyToolWindow(project);
@@ -23,8 +23,9 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiManager;
import com.intellij.util.BooleanFunction;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.CourseInfo;
import com.jetbrains.edu.learning.courseFormat.RemoteCourse;
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
import com.jetbrains.edu.learning.ui.StudyNewProjectPanel;
@@ -58,7 +59,12 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator<PyN
public ValidationResult myValidationResult = new ValidationResult("selected course is not valid");
private StudyNewProjectPanel mySettingsPanel;
@SuppressWarnings("unused") // used on startup
public PyStudyDirectoryProjectGenerator() {
this(false);
}
public PyStudyDirectoryProjectGenerator(boolean isLocal) {
myGenerator = new StudyProjectGenerator();
myGenerator.addSettingsStateListener(new StudyProjectGenerator.SettingsListener() {
@Override
@@ -67,7 +73,7 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator<PyN
}
});
mySettingsPanel = new StudyNewProjectPanel(myGenerator);
mySettingsPanel = new StudyNewProjectPanel(myGenerator, isLocal);
mySettingsPanel.registerValidators(new FacetValidatorsManager() {
public void registerValidator(FacetEditorValidator validator, JComponent... componentsToWatch) {
throw new UnsupportedOperationException();
@@ -82,7 +88,7 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator<PyN
@Override
public void mouseClicked(MouseEvent e) {
final Object selectedItem = mySettingsPanel.getCoursesComboBox().getSelectedItem();
if (selectedItem != null && ((CourseInfo)selectedItem).isAdaptive() && !myGenerator.isLoggedIn()) {
if (selectedItem != null && ((Course)selectedItem).isAdaptive() && !myGenerator.isLoggedIn()) {
mySettingsPanel.showLoginDialog(false, "Signing In");
}
}
@@ -90,14 +96,14 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator<PyN
@Override
public void mouseEntered(MouseEvent e) {
final Object selectedItem = mySettingsPanel.getCoursesComboBox().getSelectedItem();
if (selectedItem != null && ((CourseInfo)selectedItem).isAdaptive() && !myGenerator.isLoggedIn()) {
if (selectedItem != null && ((Course)selectedItem).isAdaptive() && !myGenerator.isLoggedIn()) {
e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
}
@Override
public void mouseExited(MouseEvent e) {
final CourseInfo selectedItem = (CourseInfo)mySettingsPanel.getCoursesComboBox().getSelectedItem();
final Course selectedItem = (Course)mySettingsPanel.getCoursesComboBox().getSelectedItem();
if (selectedItem != null && selectedItem.isAdaptive() && !myGenerator.isLoggedIn()) {
e.getComponent().setCursor(Cursor.getDefaultCursor());
}
@@ -125,7 +131,7 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator<PyN
@NotNull Module module,
@Nullable PyProjectSynchronizer synchronizer) {
myGenerator.generateProject(project, baseDir);
final String testHelper = "test_helper.py";
final String testHelper = EduNames.TEST_HELPER;
if (baseDir.findChild(testHelper) != null) return;
final FileTemplate template = FileTemplateManager.getInstance(project).getInternalTemplate("test_helper");
final PsiDirectory projectDir = PsiManager.getInstance(project).findDirectory(baseDir);
@@ -160,12 +166,7 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator<PyN
return mySettingsPanel;
}
@NotNull
public List<CourseInfo> getCourses() {
return myGenerator.getCoursesUnderProgress(false, "Getting Courses", ProjectManager.getInstance().getDefaultProject());
}
public void setSelectedCourse(CourseInfo course) {
public void setSelectedCourse(Course course) {
myGenerator.setSelectedCourse(course);
}
@@ -183,12 +184,12 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator<PyN
public BooleanFunction<PythonProjectGenerator> beforeProjectGenerated(@Nullable Sdk sdk) {
return generator -> {
final List<Integer> enrolledCoursesIds = myGenerator.getEnrolledCoursesIds();
final CourseInfo course = (CourseInfo)mySettingsPanel.getCoursesComboBox().getSelectedItem();
if (course == null) return true;
if (course.getId() > 0 && !enrolledCoursesIds.contains(course.getId())) {
final Course course = (Course)mySettingsPanel.getCoursesComboBox().getSelectedItem();
if (course == null || !(course instanceof RemoteCourse)) return true;
if (((RemoteCourse)course).getId() > 0 && !enrolledCoursesIds.contains(((RemoteCourse)course).getId())) {
ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true);
return StudyUtils.execCancelable(() -> EduStepicConnector.enrollToCourse(course.getId(),
return StudyUtils.execCancelable(() -> EduStepicConnector.enrollToCourse(((RemoteCourse)course).getId(),
StudySettings.getInstance().getUser()));
}, "Creating Course", true, ProjectManager.getInstance().getDefaultProject());
@@ -2,24 +2,18 @@ package com.jetbrains.edu.learning;
import com.intellij.codeInsight.CodeInsightSettings;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.project.ex.ProjectManagerEx;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.platform.templates.github.ZipUtil;
import com.intellij.util.PathUtil;
import com.intellij.util.messages.MessageBus;
import com.jetbrains.edu.learning.actions.PyStudyIntroductionCourseAction;
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
import org.jetbrains.annotations.NonNls;
import java.io.File;
import java.io.IOException;
@SuppressWarnings({"UtilityClassWithoutPrivateConstructor", "UtilityClassWithPublicConstructor"})
public class PyStudyInitialConfigurator {
private static final Logger LOG = Logger.getInstance(PyStudyInitialConfigurator.class.getName());
@NonNls private static final String CONFIGURED_V35 = "StudyPyCharm.InitialConfiguration35";
@NonNls private static final String CONFIGURED_V40 = "StudyPyCharm.InitialConfiguration40";
/**
* @noinspection UnusedParameters
@@ -29,42 +23,10 @@ public class PyStudyInitialConfigurator {
final PropertiesComponent propertiesComponent,
FileTypeManager fileTypeManager,
final ProjectManagerEx projectManager) {
final File file = new File(getCoursesRoot(), "Introduction to Python.zip");
if (!propertiesComponent.getBoolean(CONFIGURED_V35) && file.exists()) {
try {
File[] children = StudyProjectGenerator.OUR_COURSES_DIR.listFiles(
(dir, name) -> name.equals(PyStudyIntroductionCourseAction.INTRODUCTION_TO_PYTHON));
if (children != null) {
for (File child : children) {
FileUtil.delete(child);
}
}
copyCourse(file, StudyProjectGenerator.OUR_COURSES_DIR);
propertiesComponent.setValue(CONFIGURED_V35, "true");
}
catch (IOException e) {
LOG.warn("Couldn't copy bundled courses " + e);
}
if (!propertiesComponent.getBoolean(CONFIGURED_V40)) {
final File courses = new File(PathManager.getConfigPath(), "courses");
FileUtil.delete(courses);
propertiesComponent.setValue(CONFIGURED_V40, "true");
}
}
private static void copyCourse(File bundledCourse, File userCourseDir) throws IOException {
String fileName = bundledCourse.getName();
String unzippedName = fileName.substring(0, fileName.indexOf("."));
File courseDir = new File(userCourseDir, unzippedName);
ZipUtil.unzip(null, courseDir, bundledCourse, null, null, true);
}
private static File getCoursesRoot() {
@NonNls String jarPath = PathUtil.getJarPathForClass(PyStudyInitialConfigurator.class);
if (jarPath.endsWith(".jar")) {
final File jarFile = new File(jarPath);
File pluginBaseDir = jarFile.getParentFile();
return new File(pluginBaseDir, "courses");
}
return new File(jarPath, "courses");
}
}
@@ -23,32 +23,25 @@ public class PyStudyShowTutorial extends AbstractProjectComponent {
@Override
public void projectOpened() {
ApplicationManager.getApplication().invokeLater(new DumbAwareRunnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new DumbAwareRunnable() {
@Override
public void run() {
if (PropertiesComponent.getInstance().getBoolean(ourShowPopup, true)) {
final String content = "<html>If you'd like to learn more about PyCharm Edu, " +
"click <a href=\"https://www.jetbrains.com/pycharm-edu/quickstart/\">here</a> to watch a tutorial</html>";
final Notification notification = new Notification("Watch Tutorials!", "", content, NotificationType.INFORMATION,
new NotificationListener.UrlOpeningListener(true));
Notifications.Bus.notify(notification);
Balloon balloon = notification.getBalloon();
if (balloon != null) {
balloon.addListener(new JBPopupAdapter() {
@Override
public void onClosed(LightweightWindowEvent event) {
notification.expire();
}
});
ApplicationManager.getApplication().invokeLater((DumbAwareRunnable)() -> ApplicationManager.getApplication().runWriteAction(
(DumbAwareRunnable)() -> {
if (PropertiesComponent.getInstance().getBoolean(ourShowPopup, true)) {
final String content = "<html>If you'd like to learn more about PyCharm Edu, " +
"click <a href=\"https://www.jetbrains.com/pycharm-edu/quickstart/\">here</a> to watch a tutorial</html>";
final Notification notification = new Notification("Watch Tutorials!", "", content, NotificationType.INFORMATION,
new NotificationListener.UrlOpeningListener(true));
Notifications.Bus.notify(notification);
Balloon balloon = notification.getBalloon();
if (balloon != null) {
balloon.addListener(new JBPopupAdapter() {
@Override
public void onClosed(LightweightWindowEvent event) {
notification.expire();
}
notification.whenExpired(() -> PropertiesComponent.getInstance().setValue(ourShowPopup, false, true));
}
});
}
});
}
});
notification.whenExpired(() -> PropertiesComponent.getInstance().setValue(ourShowPopup, false, true));
}
}));
}
}
@@ -10,7 +10,6 @@ import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.edu.learning.checker.StudyTestRunner;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks;
import com.jetbrains.python.sdk.PythonSdkType;
@@ -28,7 +27,6 @@ public class PyStudyTestRunner extends StudyTestRunner {
public Process createCheckProcess(@NotNull final Project project, @NotNull final String executablePath) throws ExecutionException {
final Sdk sdk = PythonSdkType.findPythonSdk(ModuleManager.getInstance(project).getModules()[0]);
Course course = myTask.getLesson().getCourse();
PyEduPluginConfigurator configurator = new PyEduPluginConfigurator();
String testsFileName = configurator.getTestFileName();
if (myTask instanceof TaskWithSubtasks) {
@@ -50,8 +48,6 @@ public class PyStudyTestRunner extends StudyTestRunner {
if (pythonPath != null) {
commandLine.setExePath(pythonPath);
commandLine.addParameter(testRunner.getPath());
File resourceFile = new File(course.getCourseDirectory());
commandLine.addParameter(resourceFile.getPath());
commandLine.addParameter(FileUtil.toSystemDependentName(executablePath));
return commandLine.createProcess();
}
@@ -19,15 +19,19 @@ import com.intellij.ide.impl.ProjectUtil;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.Presentation;
import com.jetbrains.edu.learning.EduPluginConfigurator;
import com.jetbrains.edu.learning.PyEduPluginConfigurator;
import com.jetbrains.edu.learning.PyStudyDirectoryProjectGenerator;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
import com.jetbrains.edu.learning.courseFormat.CourseInfo;
import com.jetbrains.python.PythonLanguage;
import com.jetbrains.python.newProject.steps.ProjectSpecificSettingsStep;
import com.jetbrains.python.newProject.steps.PythonGenerateProjectCallback;
import icons.InteractiveLearningPythonIcons;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.List;
public class PyStudyIntroductionCourseAction extends AnAction {
@@ -44,7 +48,9 @@ public class PyStudyIntroductionCourseAction extends AnAction {
if (projectDir.exists()) {
return;
}
if (StudyProjectGenerator.getBundledIntro() != null) {
final EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(PythonLanguage.getInstance());
final List<String> paths = configurator.getBundledCoursePaths();
if (paths.isEmpty()) {
return;
}
Presentation presentation = e.getPresentation();
@@ -59,8 +65,10 @@ public class PyStudyIntroductionCourseAction extends AnAction {
ProjectUtil.openProject(projectDir.getPath(), null, false);
}
else {
final PyStudyDirectoryProjectGenerator generator = new PyStudyDirectoryProjectGenerator();
CourseInfo introCourse = StudyProjectGenerator.getBundledIntro();
final PyStudyDirectoryProjectGenerator generator = new PyStudyDirectoryProjectGenerator(true);
final EduPluginConfigurator configurator = new PyEduPluginConfigurator();
final String bundledCoursePath = configurator.getBundledCoursePaths().get(0);
Course introCourse = StudyProjectGenerator.getCourse(bundledCoursePath);
if (introCourse == null) {
return;
}
@@ -120,39 +120,6 @@ def test_is_not_empty():
failed("The file is empty. Please, reload the task and try again.")
def test_is_initial_text(error_text="You should modify the file"):
"""
Checks that file was modified
"""
if not get_answer_placeholders():
passed()
return
path = sys.argv[-1]
text = get_initial_text(path)
file_text = get_file_text(path)
if file_text.strip() == text.strip():
failed(error_text)
else:
passed()
def get_initial_text(path):
"""
Returns the initial task text
"""
course_lib = sys.argv[-2]
test_dir = sys.argv[-3]
import os
lesson_task = os.sep.join(test_dir.split(os.sep)[-3:-1])
task_dir = os.sep.join(test_dir.split(os.sep)[:-1])
relative_path = lesson_task + os.sep + path[len(task_dir):]
# path format is "project_root/lessonX/taskY/.../file.py"
initial_file_path = os.path.join(course_lib, relative_path)
return get_file_text(initial_file_path)
def test_text_equals(text, error_text):
"""
Checks that answer equals text.
@@ -259,7 +226,6 @@ def do_not_run_on_check():
def run_common_tests(error_text="Please, reload file and try again"):
test_is_initial_text()
test_is_not_empty()
test_answer_placeholders_text_deleted()
test_file_importable()
@@ -71,7 +71,7 @@ public class PyExecuteFileLineMarkerProvider implements LineMarkerProvider {
return PlatformUtils.isPyCharmEducational() ? text : actions.get(0).getTemplatePresentation().getText();
}, null,
GutterIconRenderer.Alignment.RIGHT) {
@Nullable
@Override
public GutterIconRenderer createGutterRenderer() {
return new LineMarkerGutterIconRenderer<PsiElement>(this) {
@@ -7,7 +7,6 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.jetbrains.python.debugger.PyRunCythonExtensionsFilter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
@@ -17,7 +16,6 @@ public class PyEduConsoleInputFilterProvider implements ConsoleInputFilterProvid
@Override
public InputFilter[] getDefaultFilters(@NotNull Project project) {
return new InputFilter[]{new InputFilter() {
@Nullable
@Override
public List<Pair<String, ConsoleViewContentType>> applyFilter(String text, ConsoleViewContentType outputType) {
if (outputType.equals(ConsoleViewContentType.SYSTEM_OUTPUT) && !text.contains("exit code")) {
@@ -332,18 +332,15 @@ def _is_django_exception_break_context(frame):
#=======================================================================================================================
def can_not_skip(plugin, main_debugger, pydb_frame, frame):
if main_debugger.django_breakpoints and _is_django_render_call(frame):
filename = _get_template_file_name(frame)
django_breakpoints_for_file = main_debugger.django_breakpoints.get(filename)
if django_breakpoints_for_file:
return True
return False
return main_debugger.django_breakpoints and _is_django_render_call(frame)
def has_exception_breaks(plugin):
if len(plugin.main_debugger.django_exception_break) > 0:
return True
return False
def has_line_breaks(plugin):
for file, breakpoints in dict_iter_items(plugin.main_debugger.django_breakpoints):
if len(breakpoints) > 0:
@@ -2,6 +2,7 @@
<!-- Components and extensions declared in this file work ONLY in the Python plugin for minor IDEs -->
<xi:include href="/META-INF/python-community-configure-common.xml" xpointer="xpointer(/idea-plugin/*)"/>
<xi:include href="/META-INF/python-community-plugin-core.xml" xpointer="xpointer(/idea-plugin/*)"/>
<extensions defaultExtensionNs="com.intellij">
<projectConfigurable groupId="build" groupWeight="118"
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* 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.
@@ -75,7 +75,7 @@ public class BuildoutFacet extends LibraryContributingFacet<BuildoutFacetConfigu
@NotNull final BuildoutFacetConfiguration configuration, Facet underlyingFacet) {
super(facetType, module, name, configuration, underlyingFacet);
VirtualFileManager.getInstance().addVirtualFileListener(new VirtualFileAdapter() {
VirtualFileManager.getInstance().addVirtualFileListener(new VirtualFileListener() {
@Override
public void contentsChanged(@NotNull VirtualFileEvent event) {
if (Comparing.equal(event.getFile(), getScript())) {
@@ -99,7 +99,7 @@ public class BuildoutFacet extends LibraryContributingFacet<BuildoutFacetConfigu
bin.refresh(false, false);
}
final String exe;
if (SystemInfo.isWindows || SystemInfo.isOS2) {
if (SystemInfo.isWindows) {
exe = "buildout.exe";
}
else {
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* 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.
@@ -51,7 +51,7 @@ public class PythonModulePathCache extends PythonPathCache implements Disposable
clearCache();
}
});
VirtualFileManager.getInstance().addVirtualFileListener(new MyVirtualFileAdapter(), this);
VirtualFileManager.getInstance().addVirtualFileListener(new MyVirtualFileListener(), this);
updateCacheForSdk(module);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* 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.
@@ -15,7 +15,6 @@
*/
package com.jetbrains.python.psi.resolve;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.vfs.*;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.QualifiedName;
@@ -27,7 +26,6 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author yole
@@ -76,7 +74,7 @@ public abstract class PythonPathCache {
myQNameCache.put(vFile.getUrl(), new ArrayList<>(qNames));
}
protected class MyVirtualFileAdapter extends VirtualFileAdapter {
protected class MyVirtualFileListener implements VirtualFileListener {
@Override
public void fileCreated(@NotNull VirtualFileEvent event) {
clearCache();
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* 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.
@@ -94,7 +94,7 @@ public class PythonSdkPathCache extends PythonPathCache implements Disposable {
myBuiltins.set(null);
}
}, this);
VirtualFileManager.getInstance().addVirtualFileListener(new MyVirtualFileAdapter(), this);
VirtualFileManager.getInstance().addVirtualFileListener(new MyVirtualFileListener(), this);
}
@Override
@@ -375,7 +375,7 @@ public final class PythonSdkType extends SdkType {
*/
@Nullable
public static File findExecutableFile(File parent, String name) {
if (SystemInfo.isWindows || SystemInfo.isOS2) {
if (SystemInfo.isWindows) {
for (String suffix : WINDOWS_EXECUTABLE_SUFFIXES) {
File file = new File(parent, name + "." + suffix);
if (file.exists()) return file;