EDU-894 Save all course content in course.json

EDU-891 Drop courses cache

First iteration
This commit is contained in:
Ekaterina Tuzova
2017-04-06 14:54:32 +03:00
committed by Ekaterina Tuzova
parent 6f92410b5f
commit 2168b64066
52 changed files with 685 additions and 1439 deletions
@@ -198,7 +198,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(name))) {
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(1);
return lesson;
}
}
@@ -48,7 +48,7 @@ public class CCVirtualFileListener implements VirtualFileListener {
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.getName())) {
return;
}
@@ -68,8 +68,6 @@ public class CCVirtualFileListener implements VirtualFileListener {
return;
}
CCUtils.createResourceFile(createdFile, course, taskVF);
task.addTaskFile(taskRelativePath, 1);
}
@@ -89,7 +87,7 @@ public class CCVirtualFileListener implements VirtualFileListener {
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 implements VirtualFileListener {
}
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,26 @@ 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);
additionalFiles(courseCopy);
generateJson(archiveFolder, courseCopy);
VirtualFileManager.getInstance().refreshWithoutFileWatcher(false);
packCourse(archiveFolder, locationDir, zipName, showMessage);
synchronize(project);
}
private void additionalFiles(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 +119,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) {
@@ -27,7 +27,10 @@ import com.jetbrains.edu.learning.StudySerializationUtils;
import com.jetbrains.edu.learning.StudyTaskManager;
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 org.jetbrains.annotations.NotNull;
@@ -35,8 +38,6 @@ 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());
@@ -80,8 +81,6 @@ public class CCFromCourseArchive extends DumbAwareAction {
}
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;
@@ -15,25 +15,22 @@ 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.coursecreator.stepik.CCStepicConnector;
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.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.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 {
@@ -66,21 +63,17 @@ public class CCGetCourseFromStepic extends DumbAwareAction {
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);
StudyGenerator.createCourse(course, baseDir);
}));
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;
@@ -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);
}
@@ -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.CourseInfo;
import com.jetbrains.edu.learning.courseFormat.Lesson;
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;
@@ -59,11 +59,11 @@ public class CCPushCourse extends DumbAwareAction {
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 Integer sectionId = sections.get(sections.size() - 1);
@@ -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.Lesson;
import com.jetbrains.edu.learning.stepic.EduStepicNames;
import org.jetbrains.annotations.NotNull;
@@ -77,11 +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 int lessonId = CCStepicConnector.postLesson(project, lesson);
final List<Integer> sections = info.getSections();
final Integer sectionId = sections.get(sections.size()-1);
CCStepicConnector.postUnit(lessonId, lesson.getIndex(), sectionId);
@@ -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();
@@ -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,13 +8,9 @@ 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.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;
@@ -24,7 +20,6 @@ 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;
@@ -65,13 +60,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();
@@ -110,55 +108,25 @@ public class CCStepicConnector {
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()));
}
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 +223,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));
@@ -279,7 +247,10 @@ public class CCStepicConnector {
}
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 +261,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));
@@ -311,7 +282,10 @@ public class CCStepicConnector {
final Lesson postedLesson = new Gson().fromJson(responseString, Course.class).getLessons().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,8 +21,6 @@ import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
public interface EduPluginConfigurator {
String EP_NAME = "Edu.pluginConfigurator";
LanguageExtension<EduPluginConfigurator> INSTANCE = new LanguageExtension<>(EP_NAME);
@@ -64,7 +62,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 +126,7 @@ public interface EduPluginConfigurator {
@Nullable String moduleDir) {
}
default String getBundledCoursePath() {
return null;
}
}
@@ -39,6 +39,7 @@ 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.stepic.EduStepicConnector;
@@ -49,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 {
@@ -150,15 +149,8 @@ public class StudyProjectComponent implements ProjectComponent {
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 (course == null) return;
flushCourse(course);
course.initCourse(false);
EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(course.getLanguageById());
@@ -175,14 +167,14 @@ public class StudyProjectComponent implements ProjectComponent {
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);
@@ -210,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);
}
@@ -231,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()) {
@@ -528,9 +528,9 @@ public class StudySerializationUtils {
stepOptionsJson = convertToSecondVersion(stepOptionsJson);
case 2:
stepOptionsJson = convertToThirdVersion(stepOptionsJson);
// uncomment for future versions
//case 3:
// stepOptionsJson = convertToFourthVersion(stepOptionsJson);
// uncomment for future versions
//case 3:
// stepOptionsJson = convertToFourthVersion(stepOptionsJson);
}
convertSubtaskInfosToMap(stepOptionsJson);
StepicWrappers.StepOptions stepOptions =
@@ -13,14 +13,12 @@ import com.intellij.util.xmlb.XmlSerializer;
import com.intellij.util.xmlb.annotations.Transient;
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;
@@ -39,7 +37,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;
@@ -150,12 +147,6 @@ public class StudyTaskManager implements PersistentStateComponent<Element>, Dumb
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) {
@@ -167,14 +158,6 @@ public class StudyTaskManager implements PersistentStateComponent<Element>, Dumb
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();
@@ -623,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");
@@ -880,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) {
@@ -18,13 +18,13 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Date;
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;
@@ -35,8 +35,7 @@ public class Course {
private String courseType = EduNames.PYCHARM;
private 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 +47,18 @@ public class Course {
}
public List<Lesson> getLessons() {
return lessons;
return getLessons(false);
}
public List<Lesson> getLessons(boolean withAadditional) {
return withAadditional ? 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,6 +67,10 @@ 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();
@@ -118,14 +127,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;
}
@@ -1,7 +1,6 @@
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;
@@ -10,7 +9,6 @@ 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
@@ -152,10 +150,11 @@ public class CourseInfo {
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);
CourseInfo courseInfo = new CourseInfo();
courseInfo.setName(course.getName());
courseInfo.setDescription(course.getDescription());
courseInfo.setAuthors(course.getAuthors());
return courseInfo;
}
public List<Integer> getSections() {
@@ -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;
@@ -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,88 @@ 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()));
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);
}
private 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());
}
}
private 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());
}
}
private 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,62 +1,48 @@
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.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> myLocalCourses = new ArrayList<>();
private List<Integer> myEnrolledCoursesIds = new ArrayList<>();
protected CourseInfo mySelectedCourseInfo;
@@ -97,39 +83,24 @@ 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()) {
final CourseInfo info = mySelectedCourseInfo;
for (Course course : myLocalCourses) {
if (course.getName().equals(info.getName())){
course.initCourse(false);
return course;
}
return getCourseFromStepic(project);
}
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);
}
@@ -140,7 +111,6 @@ public class StudyProjectGenerator {
return execCancelable(() -> {
final Course course = EduStepicConnector.getCourse(project, mySelectedCourseInfo);
if (StudyUtils.isCourseValid(course)) {
flushCourse(course);
course.initCourse(false);
}
return course;
@@ -148,267 +118,12 @@ 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());
}
public 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 = execCancelable(() -> EduStepicConnector.getCourses(StepicUpdateSettings.getInstance().getUser()));
}
if (force || myCourses.isEmpty()) {
myCourses = execCancelable(() -> EduStepicConnector.getCourses(StudySettings.getInstance().getUser()));
flushCache(myCourses);
}
if (myCourses.isEmpty() || (myCourses.size() == 1 && myCourses.contains(CourseInfo.INVALID_COURSE))) {
if (force || myCourses == null || myCourses.isEmpty() || (myCourses.size() == 1 && myCourses.contains(CourseInfo.INVALID_COURSE))) {
myCourses = Collections.singletonList(getBundledIntro());
}
sortCourses(myCourses);
@@ -461,78 +176,45 @@ public class StudyProjectGenerator {
}
}
public static CourseInfo getBundledIntro() {
final File introCourse = new File(OUR_COURSES_DIR, "Introduction to Python");
if (introCourse.exists()) {
return getCourseInfo(introCourse);
@Nullable
public CourseInfo getBundledIntro() {
final LanguageExtensionPoint<EduPluginConfigurator>[] extensions = Extensions.getExtensions(EduPluginConfigurator.EP_NAME, null);
for (LanguageExtensionPoint<EduPluginConfigurator> extension : extensions) {
final EduPluginConfigurator configurator = extension.getInstance();
final String path = configurator.getBundledCoursePath();
if (path != null) {
return getCourseInfo(path);
}
}
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);
}
}
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);
final CourseInfo courseInfo = getCourseInfo(zipFilePath);
if (courseInfo != null) {
myCourses.add(0, courseInfo);
}
return courseInfo;
}
@Nullable
public CourseInfo getCourseInfo(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();
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.TaskDeserializer())
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create();
Course course = gson.fromJson(jsonText, Course.class);
final CourseInfo courseInfo = CourseInfo.fromCourse(course);
if (courseInfo != null) {
myLocalCourses.add(course);
}
return courseName;
return courseInfo;
}
catch (IOException e) {
LOG.error("Failed to unzip course archive");
@@ -540,80 +222,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());
}
}
@@ -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,16 +7,17 @@ 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.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.jetbrains.edu.learning.StudySettings;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.checker.StudyExecutor;
@@ -27,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;
@@ -45,12 +45,14 @@ 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.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static com.jetbrains.edu.learning.stepic.EduStepicConnector.getStep;
@@ -67,7 +69,6 @@ public class EduAdaptiveStepicConnector {
private static final String CODE_TASK_TYPE = "code";
private static final String CHOICE_TYPE_TEXT = "choice";
private static final String TEXT_STEP_TYPE = "text";
private static final String DEFAULT_TASK_NAME = "code.py";
@Nullable
public static Task getNextRecommendation(@NotNull Project project, @NotNull Course course) {
@@ -78,7 +79,7 @@ public class EduAdaptiveStepicConnector {
return null;
}
StepicUser user = StudySettings.getInstance().getUser();
StepicUser user = StepicUpdateSettings.getInstance().getUser();
if (user == null) {
LOG.warn("User is null");
return null;
@@ -128,7 +129,16 @@ public class EduAdaptiveStepicConnector {
}
catch (IOException e) {
LOG.warn(e.getMessage());
ApplicationManager.getApplication().invokeLater(() -> StudyUtils.showErrorPopupOnToolbar(project, "Connection problems, Please, try again"));
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);
}
});
}
catch (URISyntaxException e) {
LOG.warn(e.getMessage());
@@ -194,8 +204,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(DEFAULT_TASK_NAME, taskFile);
taskFile.name = "code.py";
task.taskFiles.put("code.py", taskFile);
}
@Nullable
@@ -252,7 +262,9 @@ 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)));
@@ -276,100 +288,74 @@ 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 reactionToPost) {
public static void addNextRecommendedTask(@NotNull Project project,
@NotNull ProgressIndicator indicator,
int reaction) {
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 Lesson adaptive = editor.getTaskFile().getTask().getLesson();
final boolean reactionPosted = postRecommendationReaction(String.valueOf(adaptive.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 (course == null || editor == null || editor.getTaskFile() == null) {
return;
}
indicator.checkCanceled();
final StepicUser user = StepicUpdateSettings.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 Lesson lesson = course.getLessons().get(0);
if (lesson == null) return;
final boolean recommendationReaction = postRecommendationReaction(String.valueOf(lesson.getId()),
String.valueOf(user.getId()), reaction);
if (recommendationReaction) {
indicator.checkCanceled();
final Task task = getNextRecommendation(project, course);
if (task == null) {
ApplicationManager
.getApplication().invokeLater(() -> StudyUtils.showErrorPopupOnToolbar(project, "Couldn't load a new recommendation"));
return;
}
task.initTask(adaptive, false);
boolean replaceCurrentTask = reactionToPost == TOO_HARD_RECOMMENDATION_REACTION || reactionToPost == TOO_BORING_RECOMMENDATION_REACTION;
if (replaceCurrentTask) {
replaceCurrentTask(project, editor, task);
if (task != null) {
task.initTask(lesson, false);
if (reaction == TOO_HARD_RECOMMENDATION_REACTION || reaction == TOO_BORING_RECOMMENDATION_REACTION) {
replaceCurrentTask(project, task, lesson);
}
else {
addNewTask(project, course, task, lesson);
}
}
else {
addAsNextTask(project, editor, task);
ApplicationManager.getApplication().invokeLater(() -> {
final Balloon balloon = JBPopupFactory.getInstance().
createHtmlTextBalloonBuilder("Couldn't load a new recommendation", MessageType.ERROR, null).createBalloon();
StudyUtils.showCheckPopUp(project, balloon);
});
}
ApplicationManager.getApplication().invokeLater(() -> {
VirtualFileManager.getInstance().refreshWithoutFileWatcher(false);
ProjectView.getInstance(project).refresh();
StudyNavigator.navigateToTask(project, task);
});
}
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 adaptive = editor.getTaskFile().getTask().getLesson();
int taskIndex = adaptive.getTaskList().size();
task.setLesson(adaptive);
task.setIndex(taskIndex);
adaptive.getTaskList().set(taskIndex - 1, task);
copyTaskFileParameters(editor, task);
final String lessonName = EduNames.LESSON + adaptive.getIndex();
updateCache(course, task, lessonName);
updateProjectFiles(project, task, lessonName, taskIndex);
setToolWindowText(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 adaptive = editor.getTaskFile().getTask().getLesson();
adaptive.addTask(task);
task.setIndex(adaptive.getTaskList().size());
adaptive.initLesson(course, true);
final String lessonName = EduNames.LESSON + adaptive.getIndex();
updateCache(course, task, lessonName);
createFilesForNewTask(project, task, lessonName, course.getCourseDirectory());
}
private static void createFilesForNewTask(@NotNull Project project,
@NotNull Task task,
@NotNull String lessonName,
@NotNull String courseDirectory) {
private static void addNewTask(@NotNull Project project, Course course, Task task, Lesson lesson) {
final String lessonName = EduNames.LESSON + String.valueOf(lesson.getIndex());
lesson.addTask(task);
task.setIndex(lesson.getTaskList().size());
final VirtualFile lessonDir = project.getBaseDir().findChild(lessonName);
if (lessonDir != null) {
ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> {
try {
StudyGenerator.createTask(task, lessonDir, new File(courseDirectory, lessonName), project);
final String taskName = EduNames.TASK + String.valueOf(task.getIndex());
StudyGenerator.createTask(task, lessonDir);
lesson.initLesson(course, true);
StudyNavigator.navigateToTask(project, lessonName, taskName);
}
catch (IOException e) {
LOG.warn(e.getMessage());
@@ -378,56 +364,30 @@ public class EduAdaptiveStepicConnector {
}
}
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 replaceCurrentTask(@NotNull Project project, Task task, Lesson lesson) {
final Task unsolvedTask = lesson.getTaskList().get(lesson.getTaskList().size() - 1);
final String lessonName = EduNames.LESSON + String.valueOf(lesson.getIndex());
task.setLesson(unsolvedTask.getLesson());
task.setIndex(unsolvedTask.getIndex());
lesson.getTaskList().set(lesson.getTaskList().size() - 1, task);
ApplicationManager.getApplication().invokeLater(() -> StudyNavigator.navigateToTask(project, task));
private static void setToolWindowText(@NotNull Project project, @NotNull Task task) {
final String taskName = EduNames.TASK + String.valueOf(lesson.getTaskList().size());
final VirtualFile lessonDir = project.getBaseDir().findChild(lessonName);
if (lessonDir != null) {
try {
StudyGenerator.createTask(task, lessonDir);
}
catch (IOException e) {
LOG.warn(e.getMessage());
}
}
final StudyToolWindow window = StudyUtils.getStudyToolWindow(project);
if (window != null) {
window.setTaskText(StudyUtils.wrapTextToDisplayLatex(task.getTaskDescription()), task.getTaskDir(project), project);
window.setTaskText(StudyUtils.wrapTextToDisplayLatex(unsolvedTask.getTaskDescription()), unsolvedTask.getTaskDir(project), project);
}
}
private static void updateProjectFiles(@NotNull Project project, @NotNull Task task, @NotNull String lessonName, int taskIndex) {
final VirtualFile lessonDir = project.getBaseDir().findChild(lessonName);
if (lessonDir != null) {
final File taskResourceRoot = new File(lessonDir.getCanonicalPath(), EduNames.TASK + taskIndex);
StudyProjectGenerator.createFiles(taskResourceRoot, task.getTaskTexts());
StudyProjectGenerator.createFiles(taskResourceRoot, task.getTestsText());
}
}
private static void updateCache(@NotNull Course course, @NotNull Task task, @NotNull String lessonName) {
String taskName = EduNames.TASK + task.getIndex();
final File lessonDirectory = new File(course.getCourseDirectory(), lessonName);
final File taskDir = new File(lessonDirectory, taskName);
StudyProjectGenerator.flushTask(task, taskDir);
StudyProjectGenerator.flushCourseJson(course, new File(course.getCourseDirectory()));
}
private static void updateEditorText(@NotNull StudyEditor editor, @NotNull TaskFile newTaskFile) {
ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> {
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(DEFAULT_TASK_NAME, currentTaskFile);
StudyNavigator.navigateToTask(project, lessonName, taskName);
}
@NotNull
@@ -482,7 +442,7 @@ public class EduAdaptiveStepicConnector {
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(DEFAULT_TASK_NAME, taskFile);
task.taskFiles.put("code.py", taskFile);
}
return task;
}
@@ -543,7 +503,6 @@ public class EduAdaptiveStepicConnector {
return answer;
}
@NotNull
public static Pair<Boolean, String> checkCodeTask(@NotNull Project project, @NotNull Task task, @NotNull StepicUser user) {
int attemptId = -1;
try {
@@ -12,9 +12,10 @@ 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 org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class EduStepicUpdater {
@@ -50,24 +51,27 @@ public class EduStepicUpdater {
ActionCallback callback = new ActionCallback();
ApplicationManager.getApplication().executeOnPooledThread(() -> {
final List<CourseInfo> courses = EduStepicConnector.getCourses(null);
final List<CourseInfo> cachedCourses = StudyProjectGenerator.getCoursesFromCache();
StudyProjectGenerator.flushCache(courses);
StudySettings.getInstance().setLastTimeChecked(System.currentTimeMillis());
courses.removeAll(cachedCourses);
if (!courses.isEmpty() && !cachedCourses.isEmpty()) {
if (!courses.isEmpty()) {
List<CourseInfo> updated = new ArrayList<>();
for (CourseInfo course : courses) {
if (course.getUpdateDate().after(new Date(StepicUpdateSettings.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, CourseInfo::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 +83,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;
long timeToNextCheck = StepicUpdateSettings.getInstance().getLastTimeChecked() + CHECK_INTERVAL - System.currentTimeMillis();
return timeToNextCheck <= 0;
}
}
@@ -5,8 +5,6 @@ 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;
@@ -18,12 +16,10 @@ 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 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;
@@ -290,7 +290,6 @@ public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor {
myGenerator.setCourses(courses);
myAvailableCourses = courses;
StudyProjectGenerator.flushCache(myAvailableCourses, false);
}
private void addCoursesToCombobox(@NotNull List<CourseInfo> 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);
}
@@ -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();
@@ -125,7 +124,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;
}
@@ -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,12 +14,14 @@ 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;
@@ -65,9 +67,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 name) {
return name.contains("__pycache__") || name.endsWith(".pyc");
}
@Override
@@ -126,4 +127,17 @@ public class PyEduPluginConfigurator implements EduPluginConfigurator {
public StudyCheckAction getCheckAction() {
return new PyStudyCheckAction();
}
@Override
public String getBundledCoursePath() {
@NonNls String jarPath = PathUtil.getJarPathForClass(PyEduPluginConfigurator.class);
if (jarPath.endsWith(".jar")) {
final File jarFile = new File(jarPath);
File pluginBaseDir = jarFile.getParentFile();
return new File(new File(pluginBaseDir, "courses"), "Introduction to Python.zip").getPath();
}
return new File(new File(jarPath, "courses"), "Introduction to Python.zip").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,6 +23,7 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiManager;
import com.intellij.util.BooleanFunction;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.CourseInfo;
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
@@ -125,7 +126,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,11 +161,6 @@ 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) {
myGenerator.setSelectedCourse(course);
}
@@ -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,9 @@ 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);
}
}
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,9 +19,10 @@ 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.PyStudyDirectoryProjectGenerator;
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;
@@ -44,7 +45,8 @@ public class PyStudyIntroductionCourseAction extends AnAction {
if (projectDir.exists()) {
return;
}
if (StudyProjectGenerator.getBundledIntro() != null) {
final EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(PythonLanguage.getInstance());
if (configurator.getBundledCoursePath() != null) {
return;
}
Presentation presentation = e.getPresentation();
@@ -60,7 +62,9 @@ public class PyStudyIntroductionCourseAction extends AnAction {
}
else {
final PyStudyDirectoryProjectGenerator generator = new PyStudyDirectoryProjectGenerator();
CourseInfo introCourse = StudyProjectGenerator.getBundledIntro();
final EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(PythonLanguage.getInstance());
final String bundledCoursePath = configurator.getBundledCoursePath();
CourseInfo introCourse = generator.getGenerator().getCourseInfo(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")) {