diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCProjectComponent.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCProjectComponent.java index b5a23ba917ed..3dec53619b9f 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCProjectComponent.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCProjectComponent.java @@ -1,17 +1,32 @@ 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; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.io.FileUtilRt; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.jetbrains.edu.learning.StudyProjectComponent; import com.jetbrains.edu.learning.StudyTaskManager; +import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.Course; +import com.jetbrains.edu.learning.courseFormat.Lesson; +import com.jetbrains.edu.learning.courseFormat.Task; +import com.jetbrains.edu.learning.courseFormat.TaskFile; import org.jetbrains.annotations.NotNull; import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; public class CCProjectComponent extends AbstractProjectComponent { + private static final Logger LOG = Logger.getInstance(CCProjectComponent.class); private final CCVirtualFileListener myTaskFileLifeListener = new CCVirtualFileListener(); private final Project myProject; @@ -20,23 +35,67 @@ public class CCProjectComponent extends AbstractProjectComponent { myProject = project; } - public void initComponent() { - VirtualFileManager.getInstance().addVirtualFileListener(myTaskFileLifeListener); - } - public void migrateIfNeeded() { Course studyCourse = StudyTaskManager.getInstance(myProject).getCourse(); - Course course = CCProjectService.getInstance(myProject).getCourse(); - if (studyCourse == null && course != null) { - course.setCourseMode(CCUtils.COURSE_MODE); + if (studyCourse == null) { + Course oldCourse = CCProjectService.getInstance(myProject).getCourse(); + if (oldCourse == null) { + return; + } + StudyTaskManager.getInstance(myProject).setCourse(oldCourse); + 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, course.getName() + "-" + myProject.getName()); - course.setCourseDirectory(courseDir.getPath()); - StudyTaskManager.getInstance(myProject).setCourse(course); - StudyProjectComponent.getInstance(myProject).registerStudyToolWindow(course); + File courseDir = new File(coursesDir, oldCourse.getName() + "-" + myProject.getName()); + oldCourse.setCourseDirectory(courseDir.getPath()); + StudyProjectComponent.getInstance(myProject).registerStudyToolWindow(oldCourse); + transformFiles(oldCourse, myProject); } } + private static void transformFiles(Course course, Project project) { + List files = getAllAnswerTaskFiles(course, project); + for (VirtualFile answerFile : files) { + ApplicationManager.getApplication().runWriteAction(() -> { + String answerName = answerFile.getName(); + String name = FileUtil.getNameWithoutExtension(FileUtil.getNameWithoutExtension(answerName)) + "." + FileUtilRt.getExtension(answerName); + VirtualFile file = answerFile.getParent().findChild(name); + try { + if (file != null) { + file.delete(CCProjectComponent.class); + } + answerFile.rename(CCProjectComponent.class, name); + } + catch (IOException e) { + LOG.error(e); + } + }); + } + } + + + private static List getAllAnswerTaskFiles(@NotNull Course course, @NotNull Project project) { + List result = new ArrayList<>(); + for (Lesson lesson : course.getLessons()) { + for (Task task : lesson.getTaskList()) { + for (Map.Entry entry : task.getTaskFiles().entrySet()) { + String name = entry.getKey(); + String answerName = FileUtil.getNameWithoutExtension(name) + CCUtils.ANSWER_EXTENSION_DOTTED + FileUtilRt.getExtension(name); + String taskPath = FileUtil.join(project.getBasePath(), EduNames.LESSON + lesson.getIndex(), EduNames.TASK + task.getIndex()); + VirtualFile taskFile = LocalFileSystem.getInstance().findFileByPath(FileUtil.join(taskPath, answerName)); + if (taskFile == null) { + taskFile = LocalFileSystem.getInstance().findFileByPath(FileUtil.join(taskPath, EduNames.SRC, answerName)); + } + if (taskFile!= null) { + result.add(taskFile); + } + } + } + } + return result; + } + @NotNull public String getComponentName() { return "CCProjectComponent"; diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCProjectService.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCProjectService.java index 2ff6f8051033..44ed180d0901 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCProjectService.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCProjectService.java @@ -19,14 +19,37 @@ import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; +import com.intellij.openapi.editor.Document; import com.intellij.openapi.project.Project; -import com.intellij.util.xmlb.XmlSerializerUtil; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.io.FileUtilRt; +import com.intellij.util.xmlb.XmlSerializer; +import com.intellij.util.xmlb.annotations.Transient; +import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.courseFormat.Course; +import org.jdom.Element; import org.jetbrains.annotations.NotNull; +import java.util.Map; + +import static com.jetbrains.edu.learning.StudySerializationUtils.*; +import static com.jetbrains.edu.learning.StudySerializationUtils.Xml.*; + +/** + * @deprecated since version 3 + */ @State(name = "CCProjectService", storages = @Storage("course_service.xml")) -public class CCProjectService implements PersistentStateComponent { +public class CCProjectService implements PersistentStateComponent { private Course myCourse; + @Transient private final Project myProject; + + public CCProjectService() { + this(null); + } + + public CCProjectService(Project project) { + myProject = project; + } public Course getCourse() { return myCourse; @@ -37,14 +60,39 @@ public class CCProjectService implements PersistentStateComponent taskFiles = getChildMap(task, TASK_FILES); + for (Map.Entry entry : taskFiles.entrySet()) { + Element taskFileElement = entry.getValue(); + String name = entry.getKey(); + String answerName = FileUtil.getNameWithoutExtension(name) + CCUtils.ANSWER_EXTENSION_DOTTED + FileUtilRt.getExtension(name); + Document document = StudyUtils.getDocument(myProject.getBasePath(), lessonIndex, taskIndex, answerName); + if (document == null) { + continue; + } + for (Element placeholder : getChildList(taskFileElement, ANSWER_PLACEHOLDERS)) { + Element lineElement = getChildWithName(placeholder, LINE); + int line = lineElement != null ? Integer.valueOf(lineElement.getAttributeValue(VALUE)) : 0; + Element startElement = getChildWithName(placeholder, START); + int start = startElement != null ? Integer.valueOf(startElement.getAttributeValue(VALUE)) : 0; + int offset = document.getLineStartOffset(line) + start; + addChildWithName(placeholder, OFFSET, offset); + addChildWithName(placeholder, "useLength", "false"); + } + } + } + } + XmlSerializer.deserializeInto(this, state); } public static CCProjectService getInstance(@NotNull Project project) { diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCUtils.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCUtils.java index e1504ce29ede..5ab441fd84bf 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCUtils.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCUtils.java @@ -34,6 +34,7 @@ import java.io.IOException; import java.util.*; public class CCUtils { + public static final String ANSWER_EXTENSION_DOTTED = ".answer."; private static final Logger LOG = Logger.getInstance(CCUtils.class); public static final String GENERATED_FILES_FOLDER = ".coursecreator"; public static final String COURSE_MODE = "Course Creator"; diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCVirtualFileListener.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCVirtualFileListener.java index 5f5922edf027..36b36d02e096 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCVirtualFileListener.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCVirtualFileListener.java @@ -1,6 +1,5 @@ package com.jetbrains.edu.coursecreator; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectUtil; import com.intellij.openapi.vfs.VirtualFile; @@ -17,8 +16,6 @@ import org.jetbrains.annotations.NotNull; public class CCVirtualFileListener extends VirtualFileAdapter { - private static final Logger LOG = Logger.getInstance(CCVirtualFileListener.class); - @Override public void fileCreated(@NotNull VirtualFileEvent event) { VirtualFile createdFile = event.getFile(); diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCAddAnswerPlaceholder.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCAddAnswerPlaceholder.java index 254d64dd534c..41275862f6f0 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCAddAnswerPlaceholder.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCAddAnswerPlaceholder.java @@ -30,10 +30,10 @@ public class CCAddAnswerPlaceholder extends CCAnswerPlaceholderAction { } - private static boolean arePlaceholdersIntersect(@NotNull final TaskFile taskFile, @NotNull final Document document, int start, int end) { + private static boolean arePlaceholdersIntersect(@NotNull final TaskFile taskFile, int start, int end) { List answerPlaceholders = taskFile.getAnswerPlaceholders(); for (AnswerPlaceholder existingAnswerPlaceholder : answerPlaceholders) { - int twStart = existingAnswerPlaceholder.getRealStartOffset(document); + int twStart = existingAnswerPlaceholder.getOffset(); int twEnd = existingAnswerPlaceholder.getPossibleAnswerLength() + twStart; if ((start >= twStart && start < twEnd) || (end > twStart && end <= twEnd) || (twStart >= start && twStart < end) || (twEnd > start && twEnd <= end)) { @@ -54,13 +54,12 @@ public class CCAddAnswerPlaceholder extends CCAnswerPlaceholderAction { } final SelectionModel model = editor.getSelectionModel(); - final int start = model.getSelectionStart(); - final int lineNumber = document.getLineNumber(start); - int realStart = start - document.getLineStartOffset(lineNumber); + final int offset = model.getSelectionStart(); final AnswerPlaceholder answerPlaceholder = new AnswerPlaceholder(); - answerPlaceholder.setLine(lineNumber); - answerPlaceholder.setStart(realStart); + + answerPlaceholder.setOffset(offset); answerPlaceholder.setUseLength(false); + String selectedText = model.getSelectedText(); answerPlaceholder.setPossibleAnswer(selectedText); @@ -103,7 +102,8 @@ public class CCAddAnswerPlaceholder extends CCAnswerPlaceholderAction { for (int i = 0; i < placeholders.size(); i++) { AnswerPlaceholder fromPlaceholder = placeholders.get(i); - taskFile.getAnswerPlaceholders().get(i).setInitialState(fromPlaceholder); + AnswerPlaceholder.MyInitialState state = fromPlaceholder.getInitialState(); + taskFile.getAnswerPlaceholders().get(i).setInitialState(new AnswerPlaceholder.MyInitialState(state.getOffset(), state.getLength())); } } @@ -161,7 +161,7 @@ public class CCAddAnswerPlaceholder extends CCAnswerPlaceholderAction { } int start = selectionModel.getSelectionStart(); int end = selectionModel.getSelectionEnd(); - return !arePlaceholdersIntersect(state.getTaskFile(), editor.getDocument(), start, end); + return !arePlaceholdersIntersect(state.getTaskFile(), start, end); } private static boolean canDeletePlaceholder(@NotNull CCState state) { diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCAnswerPlaceholderAction.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCAnswerPlaceholderAction.java index e1633044d5e6..4a0479403094 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCAnswerPlaceholderAction.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCAnswerPlaceholderAction.java @@ -44,9 +44,9 @@ abstract public class CCAnswerPlaceholderAction extends DumbAwareAction { if (taskFile == null) { return null; } - AnswerPlaceholder answerPlaceholder = taskFile.getAnswerPlaceholder(editor.getDocument(), - editor.getCaretModel().getLogicalPosition(), - true); + AnswerPlaceholder answerPlaceholder = taskFile.getAnswerPlaceholder( + editor.getCaretModel().getOffset() + ); return new CCState(taskFile, answerPlaceholder, psiFile, editor, project); } diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCFromCourseArchive.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCFromCourseArchive.java index 40a81611150e..ccfe1298a4a0 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCFromCourseArchive.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCFromCourseArchive.java @@ -17,18 +17,17 @@ import com.intellij.openapi.fileChooser.FileChooserDescriptor; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.intellij.platform.templates.github.ZipUtil; import com.jetbrains.edu.coursecreator.CCUtils; +import com.jetbrains.edu.learning.StudySerializationUtils; import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.core.EduDocumentListener; 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.oldCourseFormat.OldCourse; import org.jetbrains.annotations.NotNull; import java.io.*; @@ -62,19 +61,17 @@ public class CCFromCourseArchive extends DumbAwareAction { Reader reader = null; try { ZipUtil.unzip(null, new File(basePath), new File(virtualFile.getPath()), null, null, true); - reader = new InputStreamReader(new FileInputStream(new File(basePath, EduNames.COURSE_META_FILE))); - Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); + File courseMetaFile = new File(basePath, EduNames.COURSE_META_FILE); + reader = new InputStreamReader(new FileInputStream(courseMetaFile)); + Gson gson = new GsonBuilder() + .registerTypeAdapter(Course.class, new StudySerializationUtils.Json.CourseTypeAdapter(courseMetaFile)) + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .create(); Course course = gson.fromJson(reader, Course.class); - if (course == null || course.getLessons().isEmpty() || StringUtil.isEmptyOrSpaces(course.getLessons().get(0).getName())) { - try { - reader.close(); - } - catch (IOException e) { - LOG.error(e.getMessage()); - } - reader = new InputStreamReader(new FileInputStream(new File(basePath, EduNames.COURSE_META_FILE))); - OldCourse oldCourse = gson.fromJson(reader, OldCourse.class); - course = EduUtils.transformOldCourse(oldCourse); + + if (course == null) { + Messages.showErrorDialog("This course is incompatible with current version", "Failed to Unpack Course"); + return; } StudyTaskManager.getInstance(project).setCourse(course); @@ -152,7 +149,7 @@ public class CCFromCourseArchive extends DumbAwareAction { private static void replaceAnswerPlaceholder(@NotNull final Project project, @NotNull final Document document, @NotNull final AnswerPlaceholder answerPlaceholder) { - final int offset = answerPlaceholder.getRealStartOffset(document); + final int offset = answerPlaceholder.getOffset(); CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> { final String text = document.getText(TextRange.create(offset, offset + answerPlaceholder.getRealLength())); answerPlaceholder.setTaskText(text); diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyAnswerPlaceholderExtendWordHandler.java b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyAnswerPlaceholderExtendWordHandler.java index ad364dfc02f2..210a8e766f7e 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyAnswerPlaceholderExtendWordHandler.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyAnswerPlaceholderExtendWordHandler.java @@ -37,8 +37,7 @@ public class StudyAnswerPlaceholderExtendWordHandler implements ExtendWordSelect return null; } Editor editor = FileEditorManager.getInstance(e.getProject()).getSelectedTextEditor(); - return editor == null ? null : taskFile.getAnswerPlaceholder(document, - editor.offsetToLogicalPosition(offset)); + return editor == null ? null : taskFile.getAnswerPlaceholder(offset); } @@ -55,10 +54,7 @@ public class StudyAnswerPlaceholderExtendWordHandler implements ExtendWordSelect public List select(PsiElement e, CharSequence editorText, int cursorOffset, Editor editor) { AnswerPlaceholder placeholder = getAnswerPlaceholder(e, cursorOffset); assert placeholder != null; - VirtualFile file = e.getContainingFile().getVirtualFile(); - Document document = FileDocumentManager.getInstance().getDocument(file); - assert document != null; - int startOffset = placeholder.getRealStartOffset(document); + int startOffset = placeholder.getOffset(); return Collections.singletonList(new TextRange(startOffset, startOffset + placeholder.getRealLength())); } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/StudySerializationUtils.java b/python/educational-core/student/src/com/jetbrains/edu/learning/StudySerializationUtils.java new file mode 100644 index 000000000000..83b00b10d66b --- /dev/null +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/StudySerializationUtils.java @@ -0,0 +1,385 @@ +package com.jetbrains.edu.learning; + +import com.google.gson.*; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.containers.hash.HashMap; +import com.jetbrains.edu.learning.core.EduNames; +import com.jetbrains.edu.learning.courseFormat.Course; +import com.jetbrains.edu.learning.courseFormat.StudyStatus; +import com.jetbrains.edu.learning.courseFormat.TaskFile; +import org.jdom.Attribute; +import org.jdom.Element; +import org.jdom.output.XMLOutputter; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class StudySerializationUtils { + + public static final String PLACEHOLDERS = "placeholders"; + public static final String LINE = "line"; + public static final String START = "start"; + public static final String OFFSET = "offset"; + public static final String TEXT = "text"; + public static final String LESSONS = "lessons"; + public static final String COURSE = "course"; + public static final String COURSE_TITLED = "Course"; + public static final String STATUS = "status"; + public static final String AUTHOR = "author"; + public static final String AUTHORS = "authors"; + public static final String MY_INITIAL_START = "myInitialStart"; + + private StudySerializationUtils() { + } + + public static class Xml { + public final static String COURSE_ELEMENT = "courseElement"; + public final static String MAIN_ELEMENT = "StudyTaskManager"; + public static final String MAP = "map"; + public static final String KEY = "key"; + public static final String VALUE = "value"; + public static final String NAME = "name"; + public static final String LIST = "list"; + public static final String OPTION = "option"; + public static final String INDEX = "index"; + public static final String STUDY_STATUS_MAP = "myStudyStatusMap"; + public static final String TASK_STATUS_MAP = "myTaskStatusMap"; + public static final String LENGTH = "length"; + public static final String ANSWER_PLACEHOLDERS = "answerPlaceholders"; + public static final String TASK_LIST = "taskList"; + public static final String TASK_FILES = "taskFiles"; + public static final String INITIAL_STATE = "initialState"; + public static final String MY_INITIAL_STATE = "MyInitialState"; + public static final String MY_LINE = "myLine"; + public static final String MY_START = "myStart"; + public static final String MY_LENGTH = "myLength"; + public static final String AUTHOR_TITLED = "Author"; + public static final String FIRST_NAME = "first_name"; + public static final String SECOND_NAME = "second_name"; + public static final String MY_INITIAL_LINE = "myInitialLine"; + public static final String MY_INITIAL_LENGTH = "myInitialLength"; + public static final String ANSWER_PLACEHOLDER = "AnswerPlaceholder"; + public static final String TASK_WINDOWS = "taskWindows"; + public static final String RESOURCE_PATH = "resourcePath"; + public static final String COURSE_DIRECTORY = "courseDirectory"; + + private Xml() { + } + + public static int getVersion(Element element) { + if (element.getChild(COURSE_ELEMENT) != null) { + return 1; + } + + final Element taskManager = element.getChild(MAIN_ELEMENT); + + Element versionElement = getChildWithName(taskManager, "VERSION"); + if (versionElement == null) { + return -1; + } + + return Integer.valueOf(versionElement.getAttributeValue("value")); + } + + public static Element convertToSecondVersion(Element element) { + final Element oldCourseElement = element.getChild(COURSE_ELEMENT); + Element state = new Element(MAIN_ELEMENT); + + Element course = addChildWithName(state, COURSE, oldCourseElement.clone()); + course.setName(COURSE_TITLED); + + Element author = getChildWithName(course, AUTHOR); + String authorString = author.getAttributeValue(VALUE); + course.removeContent(author); + + String[] names = authorString.split(" ", 2); + Element authorElement = new Element(AUTHOR_TITLED); + addChildWithName(authorElement, FIRST_NAME, names[0]); + addChildWithName(authorElement, SECOND_NAME, names.length == 1 ? "" : names[1]); + + addChildList(course, AUTHORS, Arrays.asList(authorElement)); + + Element courseDirectoryElement = getChildWithName(course, RESOURCE_PATH); + renameElement(courseDirectoryElement, COURSE_DIRECTORY); + + for (Element lesson : getChildList(course, LESSONS)) { + incrementIndex(lesson); + for (Element task : getChildList(lesson, TASK_LIST)) { + incrementIndex(task); + Map taskFiles = getChildMap(task, TASK_FILES); + for (Element taskFile : taskFiles.values()) { + renameElement(getChildWithName(taskFile, TASK_WINDOWS), ANSWER_PLACEHOLDERS); + for (Element placeholder : getChildList(taskFile, ANSWER_PLACEHOLDERS)) { + placeholder.setName(ANSWER_PLACEHOLDER); + + Element initialState = new Element(MY_INITIAL_STATE); + addChildWithName(placeholder, INITIAL_STATE, initialState); + addChildWithName(initialState, MY_LINE, getChildWithName(placeholder, MY_INITIAL_LINE).getAttributeValue(VALUE)); + addChildWithName(initialState, MY_START, getChildWithName(placeholder, MY_INITIAL_START).getAttributeValue(VALUE)); + addChildWithName(initialState, MY_LENGTH, getChildWithName(placeholder, MY_INITIAL_LENGTH).getAttributeValue(VALUE)); + } + } + + } + } + element.removeContent(); + element.addContent(state); + return element; + } + + public static Map fillStatusMap(Element taskManagerElement, String mapName, XMLOutputter outputter) { + Map sourceMap = getChildMap(taskManagerElement, mapName); + Map destMap = new HashMap<>(); + for (Map.Entry entry : sourceMap.entrySet()) { + String status = entry.getValue(); + if (status.equals(StudyStatus.Unchecked.toString())) { + continue; + } + destMap.put(outputter.outputString(entry.getKey()), status); + } + return destMap; + } + + public static Element convertToThirdVersion(Element state, Project project) { + Element taskManagerElement = state.getChild(MAIN_ELEMENT); + XMLOutputter outputter = new XMLOutputter(); + + Map placeholderTextToStatus = fillStatusMap(taskManagerElement, STUDY_STATUS_MAP, outputter); + Map taskFileToStatusMap = fillStatusMap(taskManagerElement, TASK_STATUS_MAP, outputter); + + Element courseElement = getChildWithName(taskManagerElement, COURSE).getChild(COURSE_TITLED); + for (Element lesson : getChildList(courseElement, LESSONS)) { + int lessonIndex = getAsInt(lesson, INDEX); + for (Element task : getChildList(lesson, TASK_LIST)) { + String taskStatus = null; + int taskIndex = getAsInt(task, INDEX); + Map taskFiles = getChildMap(task, TASK_FILES); + for (Map.Entry entry : taskFiles.entrySet()) { + Element taskFileElement = entry.getValue(); + String taskFileText = outputter.outputString(taskFileElement); + String taskFileStatus = taskFileToStatusMap.get(taskFileText); + if (taskFileStatus != null && (taskStatus == null || taskFileStatus.equals(StudyStatus.Failed.toString()))) { + taskStatus = taskFileStatus; + } + Document document = StudyUtils.getDocument(project.getBasePath(), lessonIndex, taskIndex, entry.getKey()); + if (document == null) { + continue; + } + for (Element placeholder : getChildList(taskFileElement, ANSWER_PLACEHOLDERS)) { + taskStatus = addStatus(outputter, placeholderTextToStatus, taskStatus, placeholder); + addOffset(document, placeholder); + addInitialState(document, placeholder); + } + } + if (taskStatus != null) { + addChildWithName(task, STATUS, taskStatus); + } + } + } + return state; + } + + public static String addStatus(XMLOutputter outputter, + Map placeholderTextToStatus, + String taskStatus, + Element placeholder) { + String placeholderText = outputter.outputString(placeholder); + String status = placeholderTextToStatus.get(placeholderText); + if (status != null) { + addChildWithName(placeholder, STATUS, status); + if (taskStatus == null || status.equals(StudyStatus.Failed.toString())) { + taskStatus = status; + } + } + return taskStatus; + } + + public static void addInitialState(Document document, Element placeholder) { + Element initialState = getChildWithName(placeholder, INITIAL_STATE).getChild(MY_INITIAL_STATE); + int initialLine = getAsInt(initialState, MY_LINE); + int initialStart = getAsInt(initialState, MY_START); + int initialOffset = document.getLineStartOffset(initialLine) + initialStart; + addChildWithName(initialState, OFFSET, initialOffset); + renameElement(getChildWithName(initialState, MY_LENGTH), LENGTH); + } + + public static void addOffset(Document document, Element placeholder) { + int line = getAsInt(placeholder, LINE); + int start = getAsInt(placeholder, START); + int offset = document.getLineStartOffset(line) + start; + addChildWithName(placeholder, OFFSET, offset); + } + + public static int getAsInt(Element element, String name) { + return Integer.valueOf(getChildWithName(element, name).getAttributeValue(VALUE)); + } + + public static void incrementIndex(Element element) { + Element index = getChildWithName(element, INDEX); + int indexValue = Integer.parseInt(index.getAttributeValue(VALUE)); + changeValue(index, indexValue + 1); + } + + public static void renameElement(Element element, String newName) { + element.setAttribute(NAME, newName); + } + + public static void changeValue(Element element, Object newValue) { + element.setAttribute(VALUE, newValue.toString()); + } + + public static Element addChildWithName(Element parent, String name, Element value) { + Element child = new Element(OPTION); + child.setAttribute(NAME, name); + child.addContent(value); + parent.addContent(child); + return value; + } + + public static Element addChildWithName(Element parent, String name, Object value) { + Element child = new Element(OPTION); + child.setAttribute(NAME, name); + child.setAttribute(VALUE, value.toString()); + parent.addContent(child); + return child; + } + + public static Element addChildList(Element parent, String name, List elements) { + Element listElement = new Element(LIST); + for (Element element : elements) { + listElement.addContent(element); + } + return addChildWithName(parent, name, listElement); + } + + public static List getChildList(Element parent, String name) { + Element listParent = getChildWithName(parent, name); + if (listParent != null) { + Element list = listParent.getChild(LIST); + if (list != null) { + return list.getChildren(); + } + } + return Collections.emptyList(); + } + + @Nullable + public static Element getChildWithName(Element parent, String name) { + for (Element child : parent.getChildren()) { + Attribute attribute = child.getAttribute(NAME); + if (attribute == null) { + continue; + } + if (name.equals(attribute.getValue())) { + return child; + } + } + return null; + } + + public static Map getChildMap(Element element, String name) { + Element mapParent = getChildWithName(element, name); + if (mapParent != null) { + Element map = mapParent.getChild(MAP); + if (map != null) { + HashMap result = new HashMap(); + for (Element entry : map.getChildren()) { + Object key = entry.getAttribute(KEY) == null ? entry.getChild(KEY).getChildren().get(0) : entry.getAttributeValue(KEY); + Object value = entry.getAttribute(VALUE) == null ? entry.getChild(VALUE).getChildren().get(0) : entry.getAttributeValue(VALUE); + result.put(key, value); + } + return result; + } + } + return Collections.emptyMap(); + } + } + + public static class Json { + + public static final String TASK_LIST = "task_list"; + public static final String TASK_FILES = "task_files"; + + private Json() { + } + + public static class CourseTypeAdapter implements JsonDeserializer { + + private final File myCourseFile; + + public CourseTypeAdapter(File courseFile) { + myCourseFile = courseFile; + } + + @Override + public Course deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonObject courseObject = json.getAsJsonObject(); + JsonArray lessons = courseObject.getAsJsonArray(LESSONS); + for (int lessonIndex = 1; lessonIndex <= lessons.size(); lessonIndex++) { + JsonObject lessonObject = lessons.get(lessonIndex - 1).getAsJsonObject(); + JsonArray tasks = lessonObject.getAsJsonArray(TASK_LIST); + for (int taskIndex = 1; taskIndex <= tasks.size(); taskIndex++) { + JsonObject taskObject = tasks.get(taskIndex - 1).getAsJsonObject(); + for (Map.Entry taskFile : taskObject.getAsJsonObject(TASK_FILES).entrySet()) { + String name = taskFile.getKey(); + String filePath = FileUtil.join(myCourseFile.getParent(), EduNames.LESSON + lessonIndex, EduNames.TASK + taskIndex, name); + VirtualFile resourceFile = LocalFileSystem.getInstance().findFileByIoFile(new File(filePath)); + if (resourceFile == null) { + continue; + } + Document document = FileDocumentManager.getInstance().getDocument(resourceFile); + if (document == null) { + continue; + } + JsonObject taskFileObject = taskFile.getValue().getAsJsonObject(); + JsonArray placeholders = taskFileObject.getAsJsonArray(PLACEHOLDERS); + for (JsonElement placeholder : placeholders) { + JsonObject placeholderObject = placeholder.getAsJsonObject(); + if (placeholderObject.getAsJsonPrimitive(OFFSET) != null) { + break; + } + int line = placeholderObject.getAsJsonPrimitive(LINE).getAsInt(); + int start = placeholderObject.getAsJsonPrimitive(START).getAsInt(); + int offset = document.getLineStartOffset(line) + start; + placeholderObject.addProperty(OFFSET, offset); + } + } + } + } + return new GsonBuilder().create().fromJson(json, Course.class); + } + } + + public static class StepicTaskFileAdapter implements JsonDeserializer { + + @Override + public TaskFile deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonObject taskFileObject = json.getAsJsonObject(); + JsonArray placeholders = taskFileObject.getAsJsonArray(PLACEHOLDERS); + for (JsonElement placeholder : placeholders) { + JsonObject placeholderObject = placeholder.getAsJsonObject(); + int line = placeholderObject.getAsJsonPrimitive(LINE).getAsInt(); + int start = placeholderObject.getAsJsonPrimitive(START).getAsInt(); + if (line == -1) { + placeholderObject.addProperty(OFFSET, start); + } else { + Document document = EditorFactory.getInstance().createDocument(taskFileObject.getAsJsonPrimitive(TEXT).getAsString()); + placeholderObject.addProperty(OFFSET, document.getLineStartOffset(line) + start); + } + } + return new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create().fromJson(json, TaskFile.class); + } + } + } +} diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyTaskManager.java b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyTaskManager.java index 2690d4b86660..ff870e726fac 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyTaskManager.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyTaskManager.java @@ -1,24 +1,26 @@ package com.jetbrains.edu.learning; +import com.intellij.openapi.application.PathManager; import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtil; import com.intellij.ui.JBColor; import com.intellij.util.containers.hash.HashMap; import com.intellij.util.xmlb.XmlSerializer; import com.intellij.util.xmlb.annotations.Transient; -import com.jetbrains.edu.learning.core.EduUtils; import com.jetbrains.edu.learning.courseFormat.*; -import com.jetbrains.edu.learning.oldCourseFormat.OldCourse; import com.jetbrains.edu.learning.stepic.StepicUser; import com.jetbrains.edu.learning.ui.StudyToolWindow; import org.jdom.Element; 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; @@ -31,22 +33,30 @@ import java.util.Map; @State(name = "StudySettings", storages = @Storage("study_project.xml")) public class StudyTaskManager implements PersistentStateComponent, DumbAware { + private static final Logger LOG = Logger.getInstance(StudyTaskManager.class); + public static final int CURRENT_VERSION = 3; + private StepicUser myUser; private Course myCourse; - private OldCourse myOldCourse; - public int VERSION = 2; - public StepicUser myUser; - public Map myStudyStatusMap = new HashMap<>(); - public Map myTaskStatusMap = new HashMap<>(); + public int VERSION = 3; + public Map> myUserTests = new HashMap<>(); public List myInvisibleFiles = new ArrayList<>(); + public boolean myShouldUseJavaFx = StudyUtils.hasJavaFx(); private StudyToolWindow.StudyToolWindowMode myToolWindowMode = StudyToolWindow.StudyToolWindowMode.TEXT; private boolean myTurnEditingMode = false; - private StudyTaskManager() { + @Transient private final Project myProject; + + public StudyTaskManager(Project project) { + myProject = project; } - public void setCourse(final Course course) { + public StudyTaskManager() { + this(null); + } + + public void setCourse(Course course) { myCourse = course; } @@ -85,97 +95,8 @@ public class StudyTaskManager implements PersistentStateComponent, Dumb } } - - public void setStatus(Task task, StudyStatus status) { - task.setStatus(status); - for (TaskFile taskFile : task.getTaskFiles().values()) { - setStatus(taskFile, status); - } - } - - public void setStatus(TaskFile file, StudyStatus status) { - for (AnswerPlaceholder answerPlaceholder : file.getAnswerPlaceholders()) { - setStatus(answerPlaceholder, status); - } - } - - public StudyStatus getStatus(AnswerPlaceholder placeholder) { - StudyStatus status = placeholder.getStatus(); - if (status != StudyStatus.Uninitialized) return status; - - status = myStudyStatusMap.get(placeholder); - if (status == null) { - status = StudyStatus.Unchecked; - } - placeholder.setStatus(status); - return status; - } - - - public StudyStatus getStatus(@NotNull final Lesson lesson) { - for (Task task : lesson.getTaskList()) { - StudyStatus taskStatus = getStatus(task); - if (taskStatus == StudyStatus.Unchecked || taskStatus == StudyStatus.Failed) { - return StudyStatus.Unchecked; - } - } - return StudyStatus.Solved; - } - - public StudyStatus getStatus(@NotNull final Task task) { - StudyStatus taskStatus = task.getStatus(); - if (taskStatus != StudyStatus.Uninitialized) return taskStatus; - - for (TaskFile taskFile : task.getTaskFiles().values()) { - StudyStatus taskFileStatus = getStatus(taskFile); - if (taskFileStatus == StudyStatus.Unchecked) { - task.setStatus(StudyStatus.Unchecked); - removeObsoleteTaskStatus(task); - return StudyStatus.Unchecked; - } - if (taskFileStatus == StudyStatus.Failed) { - task.setStatus(StudyStatus.Failed); - removeObsoleteTaskStatus(task); - return StudyStatus.Failed; - } - } - task.setStatus(StudyStatus.Solved); - removeObsoleteTaskStatus(task); - return StudyStatus.Solved; - } - - private void removeObsoleteTaskStatus(Task task) { - for (TaskFile taskFile: task.taskFiles.values()) { - myTaskStatusMap.remove(taskFile); - - for (AnswerPlaceholder answerPlaceholder: taskFile.getAnswerPlaceholders()) { - myStudyStatusMap.remove(answerPlaceholder); - } - } - - } - - private StudyStatus getStatus(@NotNull final TaskFile file) { - if (file.getAnswerPlaceholders().isEmpty()) { - if (myTaskStatusMap == null) return StudyStatus.Solved; - return myTaskStatusMap.get(file); - - } - for (AnswerPlaceholder answerPlaceholder : file.getAnswerPlaceholders()) { - StudyStatus placeholderStatus = getStatus(answerPlaceholder); - if (placeholderStatus == StudyStatus.Failed) { - return StudyStatus.Failed; - } - if (placeholderStatus == StudyStatus.Unchecked) { - return StudyStatus.Unchecked; - } - } - return StudyStatus.Solved; - } - - public JBColor getColor(@NotNull final AnswerPlaceholder placeholder) { - final StudyStatus status = getStatus(placeholder); + final StudyStatus status = placeholder.getStatus(); if (status == StudyStatus.Solved) { return JBColor.GREEN; } @@ -186,7 +107,7 @@ public class StudyTaskManager implements PersistentStateComponent, Dumb } public boolean hasFailedAnswerPlaceholders(@NotNull final TaskFile taskFile) { - return taskFile.getAnswerPlaceholders().size() > 0 && getStatus(taskFile) == StudyStatus.Failed; + return taskFile.getAnswerPlaceholders().size() > 0 && taskFile.hasFailedPlaceholders(); } @Nullable @@ -194,7 +115,7 @@ public class StudyTaskManager implements PersistentStateComponent, Dumb public Element getState() { Element el = new Element("taskManager"); if (myCourse != null) { - Element courseElement = new Element(MAIN_ELEMENT); + Element courseElement = new Element(StudySerializationUtils.Xml.MAIN_ELEMENT); XmlSerializer.serializeInto(this, courseElement); el.addContent(courseElement); } @@ -203,46 +124,34 @@ public class StudyTaskManager implements PersistentStateComponent, Dumb @Override public void loadState(Element state) { - final Element mainElement = state.getChild(MAIN_ELEMENT); - if (mainElement != null) { - final StudyTaskManager taskManager = XmlSerializer.deserialize(mainElement, StudyTaskManager.class); - if (taskManager != null) { - myCourse = taskManager.myCourse; - myUserTests = taskManager.myUserTests; - myInvisibleFiles = taskManager.myInvisibleFiles; - myTaskStatusMap = taskManager.myTaskStatusMap; - myStudyStatusMap = taskManager.myStudyStatusMap; - myShouldUseJavaFx = taskManager.myShouldUseJavaFx; - myUser = taskManager.getUser(); - } + int version = StudySerializationUtils.Xml.getVersion(state); + if (version == -1) { + LOG.error("StudyTaskManager doesn't contain any version:\n" + state.getValue()); + return; } - final Element oldCourseElement = state.getChild(COURSE_ELEMENT); - if (oldCourseElement != null) { - myOldCourse = XmlSerializer.deserialize(oldCourseElement, OldCourse.class); - if (myOldCourse != null) { - myCourse = EduUtils.transformOldCourse(myOldCourse, pair -> { - setStatus(pair.first, pair.second); - return null; - }); - myOldCourse = null; - } + switch (version) { + case 1: + state = StudySerializationUtils.Xml.convertToSecondVersion(state); + case 2: + state = StudySerializationUtils.Xml.convertToThirdVersion(state, myProject); + //uncomment for future versions + //case 3: + //state = StudySerializationUtils.Xml.convertToForthVersion(state, myProject); } + + XmlSerializer.deserializeInto(this, state.getChild(StudySerializationUtils.Xml.MAIN_ELEMENT)); + VERSION = CURRENT_VERSION; if (myCourse != null) { myCourse.initCourse(true); + if (version != VERSION) { + String updatedCoursePath = FileUtil.join(PathManager.getConfigPath(), "courses", myCourse.getName()); + if (new File(updatedCoursePath).exists()) { + myCourse.setCourseDirectory(updatedCoursePath); + } + } } } - public static final String COURSE_ELEMENT = "courseElement"; - public static final String MAIN_ELEMENT = "StudyTaskManager"; - - public OldCourse getOldCourse() { - return myOldCourse; - } - - public void setOldCourse(OldCourse oldCourse) { - myOldCourse = oldCourse; - } - public static StudyTaskManager getInstance(@NotNull final Project project) { return ServiceManager.getService(project, StudyTaskManager.class); } @@ -279,13 +188,15 @@ public class StudyTaskManager implements PersistentStateComponent, Dumb myTurnEditingMode = turnEditingMode; } + @Transient public String getLogin() { if (myUser != null) { return myUser.getEmail(); } return ""; } - + + @Transient public void setLogin(String login) { if (myUser != null) { myUser.setEmail(login); diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java index 8cedfc1dadfa..d8d4276575bc 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java @@ -28,6 +28,7 @@ import com.intellij.openapi.ui.popup.Balloon; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.io.FileUtil; 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.wm.ToolWindow; @@ -680,4 +681,17 @@ public class StudyUtils { return new File(parent, EduNames.TASK_MD); } } + + @Nullable + public static Document getDocument(String basePath, int lessonIndex, int taskIndex, String fileName) { + String taskPath = FileUtil.join(basePath, EduNames.LESSON + lessonIndex, EduNames.TASK + taskIndex); + VirtualFile taskFile = LocalFileSystem.getInstance().findFileByPath(FileUtil.join(taskPath, fileName)); + if (taskFile == null) { + taskFile = LocalFileSystem.getInstance().findFileByPath(FileUtil.join(taskPath, EduNames.SRC, fileName)); + } + if (taskFile == null) { + return null; + } + return FileDocumentManager.getInstance().getDocument(taskFile); + } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyFillPlaceholdersAction.java b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyFillPlaceholdersAction.java index dced38434bda..0427f690c825 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyFillPlaceholdersAction.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyFillPlaceholdersAction.java @@ -34,7 +34,7 @@ public class StudyFillPlaceholdersAction extends AnAction { if (answer == null) { continue; } - int offset = placeholder.getRealStartOffset(document); + int offset = placeholder.getOffset(); document.deleteString(offset, offset + placeholder.getRealLength()); document.insertString(offset, answer); } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshAnswerPlaceholder.java b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshAnswerPlaceholder.java index b05088812f2c..987819c83ffe 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshAnswerPlaceholder.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshAnswerPlaceholder.java @@ -50,11 +50,11 @@ public class StudyRefreshAnswerPlaceholder extends DumbAwareAction { return; } AnswerPlaceholder.MyInitialState initialState = answerPlaceholder.getInitialState(); - int startOffset = patternDocument.getLineStartOffset(initialState.myLine) + initialState.myStart; - final String text = patternDocument.getText(new TextRange(startOffset, startOffset + initialState.myLength)); + 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.getRealStartOffset(document); + int offset = answerPlaceholder.getOffset(); document.deleteString(offset, offset + answerPlaceholder.getRealLength()); document.insertString(offset, text); }), NAME, null); @@ -96,6 +96,6 @@ public class StudyRefreshAnswerPlaceholder extends DumbAwareAction { } final Editor editor = studyState.getEditor(); final TaskFile taskFile = studyState.getTaskFile(); - return taskFile.getAnswerPlaceholder(editor.getDocument(), editor.getCaretModel().getLogicalPosition()); + return taskFile.getAnswerPlaceholder(editor.getCaretModel().getOffset()); } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyShowHintAction.java b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyShowHintAction.java index 75a1277c88d9..ab390c5b0405 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyShowHintAction.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyShowHintAction.java @@ -5,7 +5,6 @@ import com.intellij.codeInsight.documentation.DocumentationManager; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.KeyboardShortcut; import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.LogicalPosition; import com.intellij.openapi.keymap.KeymapUtil; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.JBPopup; @@ -14,11 +13,11 @@ import com.intellij.openapi.util.Disposer; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.StudyState; import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.StudyUtils; +import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; +import com.jetbrains.edu.learning.courseFormat.Course; import icons.InteractiveLearningIcons; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -54,8 +53,9 @@ public class StudyShowHintAction extends StudyActionWithShortcut { } PsiFile file = PsiManager.getInstance(project).findFile(studyState.getVirtualFile()); final Editor editor = studyState.getEditor(); - LogicalPosition pos = editor.getCaretModel().getLogicalPosition(); - AnswerPlaceholder answerPlaceholder = studyState.getTaskFile().getAnswerPlaceholder(editor.getDocument(), pos); + int offset = editor.getCaretModel().getOffset(); + AnswerPlaceholder answerPlaceholder = studyState.getTaskFile().getAnswerPlaceholder( + offset); if (file == null) { return; } @@ -64,7 +64,6 @@ public class StudyShowHintAction extends StudyActionWithShortcut { String hint = answerPlaceholder.getHint(); hintText = hint.isEmpty() ? HINT_NOT_AVAILABLE : hint; } - int offset = editor.getDocument().getLineStartOffset(pos.line) + pos.column; PsiElement element = file.findElementAt(offset); DocumentationManager documentationManager = DocumentationManager.getInstance(project); DocumentationComponent component = new DocumentationComponent(documentationManager); diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyWindowNavigationAction.java b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyWindowNavigationAction.java index cd0b5ccf1dbe..2bffbe8cafca 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyWindowNavigationAction.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyWindowNavigationAction.java @@ -2,14 +2,13 @@ package com.jetbrains.edu.learning.actions; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.LogicalPosition; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; +import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.navigation.StudyNavigator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -38,7 +37,7 @@ abstract public class StudyWindowNavigationAction extends StudyActionWithShortcu if (nextAnswerPlaceholder == null) { return; } - StudyNavigator.navigateToAnswerPlaceholder(selectedEditor, nextAnswerPlaceholder, selectedTaskFile); + StudyNavigator.navigateToAnswerPlaceholder(selectedEditor, nextAnswerPlaceholder); selectedEditor.getSelectionModel().removeSelection(); } } @@ -47,8 +46,7 @@ abstract public class StudyWindowNavigationAction extends StudyActionWithShortcu @Nullable private static AnswerPlaceholder getSelectedAnswerPlaceholder(@NotNull final Editor editor, @NotNull final TaskFile file) { - LogicalPosition position = editor.getCaretModel().getLogicalPosition(); - return file.getAnswerPlaceholder(editor.getDocument(), position); + return file.getAnswerPlaceholder(editor.getCaretModel().getOffset()); } @Nullable diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java index 965347321167..90b6110df848 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java @@ -49,7 +49,7 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun myTask = studyState.getTask(); myTaskDir = studyState.getTaskDir(); myTaskManger = StudyTaskManager.getInstance(myProject); - myStatusBeforeCheck = myTaskManger.getStatus(myTask); + myStatusBeforeCheck = myTask.getStatus(); } @Override @@ -67,7 +67,7 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun @Override public void onCancel() { - myTaskManger.setStatus(myTask, myStatusBeforeCheck); + myTask.setStatus(myStatusBeforeCheck); clearState(); } @@ -156,9 +156,8 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun } protected void onTaskFailed(String message) { - myTaskManger.setStatus(myTask, StudyStatus.Failed); final Course course = StudyTaskManager.getInstance(myProject).getCourse(); - + myTask.setStatus(StudyStatus.Failed); if (course != null) { if (course.isAdaptive()) { ApplicationManager.getApplication().invokeLater( @@ -175,9 +174,8 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun } protected void onTaskSolved(String message) { - myTaskManger.setStatus(myTask, StudyStatus.Solved); final Course course = StudyTaskManager.getInstance(myProject).getCourse(); - + myTask.setStatus(StudyStatus.Solved); if (course != null) { if (course.isAdaptive()) { ApplicationManager.getApplication().invokeLater( diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java index b91bc4773ba4..7e62ccd1c038 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java @@ -156,7 +156,7 @@ public class StudyCheckUtils { if (!answerPlaceholder.isValid(document)) { continue; } - final int start = answerPlaceholder.getRealStartOffset(document); + final int start = answerPlaceholder.getOffset(); final int end = start + answerPlaceholder.getRealLength(); final String text = answerPlaceholder.getPossibleAnswer(); document.replaceString(start, end, text); diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudySmartChecker.java b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudySmartChecker.java index c75904d0e3a5..9621efbd7d39 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudySmartChecker.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudySmartChecker.java @@ -54,10 +54,10 @@ public class StudySmartChecker { TaskFile.copy(answerTaskFile, windowTaskFile); EduDocumentListener listener = new EduDocumentListener(windowTaskFile); windowDocument.addDocumentListener(listener); - int start = placeholder.getRealStartOffset(windowDocument); + int start = placeholder.getOffset(); int end = start + placeholder.getRealLength(); final AnswerPlaceholder userAnswerPlaceholder = usersTaskFile.getAnswerPlaceholders().get(placeholder.getIndex()); - int userStart = userAnswerPlaceholder.getRealStartOffset(usersDocument); + int userStart = userAnswerPlaceholder.getOffset(); int userEnd = userStart + userAnswerPlaceholder.getRealLength(); String text = usersDocument.getText(new TextRange(userStart, userEnd)); windowDocument.replaceString(start, end, text); diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduAnswerPlaceholderPainter.java b/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduAnswerPlaceholderPainter.java index 81fcf85fb989..3902b942af00 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduAnswerPlaceholderPainter.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduAnswerPlaceholderPainter.java @@ -36,7 +36,10 @@ public class EduAnswerPlaceholderPainter { EffectType.BOXED, Font.PLAIN); final Project project = editor.getProject(); assert project != null; - final int startOffset = placeholder.getRealStartOffset(document); + final int startOffset = placeholder.getOffset(); + if (startOffset == - 1) { + return; + } final int length = placeholder.getRealLength(); final int endOffset = startOffset + length; textAttributes.setEffectColor(color); @@ -66,7 +69,7 @@ public class EduAnswerPlaceholderPainter { DocumentImpl documentImpl = (DocumentImpl)document; List blocks = documentImpl.getGuardedBlocks(); if (!placeholder.isValid(document)) return; - int start = placeholder.getRealStartOffset(document); + int start = placeholder.getOffset(); final int length = placeholder.getRealLength(); int end = start + length; if (start != 0) { diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduDocumentListener.java b/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduDocumentListener.java index 8e43bf57b26b..7105b4662b30 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduDocumentListener.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduDocumentListener.java @@ -39,10 +39,9 @@ public class EduDocumentListener extends DocumentAdapter { return; } myTaskFile.setHighlightErrors(true); - Document document = e.getDocument(); myAnswerPlaceholders.clear(); for (AnswerPlaceholder answerPlaceholder : myTaskFile.getAnswerPlaceholders()) { - int twStart = answerPlaceholder.getRealStartOffset(document); + int twStart = answerPlaceholder.getOffset(); int length = answerPlaceholder.getRealLength(); int twEnd = twStart + length; myAnswerPlaceholders.add(new AnswerPlaceholderWrapper(answerPlaceholder, twStart, twEnd)); @@ -70,11 +69,9 @@ public class EduDocumentListener extends DocumentAdapter { twEnd += change; } AnswerPlaceholder answerPlaceholder = answerPlaceholderWrapper.getAnswerPlaceholder(); - int line = document.getLineNumber(twStart); - int start = twStart - document.getLineStartOffset(line); int length = twEnd - twStart; - answerPlaceholder.setLine(line); - answerPlaceholder.setStart(start); + answerPlaceholder.setOffset(twStart); + answerPlaceholder.setLength(length); if (!answerPlaceholder.getUseLength()) { answerPlaceholder.setPossibleAnswer(document.getText(TextRange.create(twStart, twStart + length))); } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduUtils.java b/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduUtils.java index fb5df916db6c..ecfa1ec3a136 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduUtils.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduUtils.java @@ -4,33 +4,24 @@ import com.intellij.ide.SaveAndSyncHandler; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.Presentation; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.PathManager; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.intellij.psi.PsiDirectory; -import com.intellij.util.Function; -import com.intellij.util.containers.HashMap; import com.jetbrains.edu.learning.courseFormat.*; -import com.jetbrains.edu.learning.oldCourseFormat.OldCourse; -import com.jetbrains.edu.learning.oldCourseFormat.TaskWindow; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.imageio.ImageIO; -import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; -import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.Map; @@ -95,7 +86,7 @@ public class EduUtils { printWriter.println("#educational_plugin_window = "); continue; } - int start = answerPlaceholder.getRealStartOffset(document); + int start = answerPlaceholder.getOffset(); final String windowDescription = document.getText(new TextRange(start, start + length)); printWriter.println("#educational_plugin_window = " + windowDescription); } @@ -186,8 +177,9 @@ public class EduUtils { taskFile.sortAnswerPlaceholders(); for (int i = taskFile.getAnswerPlaceholders().size() - 1; i >= 0; i--) { final AnswerPlaceholder answerPlaceholder = taskFile.getAnswerPlaceholders().get(i); - if (answerPlaceholder.getRealStartOffset(document) > document.getTextLength() || answerPlaceholder.getRealStartOffset(document) + answerPlaceholder.getPossibleAnswerLength() > document.getTextLength()) { - LOG.error("Wrong startOffset: " + answerPlaceholder.getRealStartOffset(document) + "; document: " + file.getPath()); + int offset = answerPlaceholder.getOffset(); + if (offset > document.getTextLength() || offset + answerPlaceholder.getPossibleAnswerLength() > document.getTextLength()) { + LOG.error("Wrong startOffset: " + answerPlaceholder.getOffset() + "; document: " + file.getPath()); return; } replaceAnswerPlaceholder(project, document, answerPlaceholder); @@ -200,7 +192,7 @@ public class EduUtils { @NotNull final Document document, @NotNull final AnswerPlaceholder answerPlaceholder) { final String taskText = answerPlaceholder.getTaskText(); - final int offset = answerPlaceholder.getRealStartOffset(document); + final int offset = answerPlaceholder.getOffset(); CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> { document.replaceString(offset, offset + answerPlaceholder.getPossibleAnswerLength(), taskText); FileDocumentManager.getInstance().saveDocument(document); @@ -246,76 +238,6 @@ public class EduUtils { return lesson.getTask(directory.getName()); } - @NotNull - public static Course transformOldCourse(@NotNull final OldCourse oldCourse) { - return transformOldCourse(oldCourse, null); - } - - @NotNull - public static Course transformOldCourse(@NotNull final OldCourse oldCourse, - @Nullable Function, Void> setStatus) { - Course course = new Course(); - course.setDescription(oldCourse.description); - course.setName(oldCourse.name); - course.setAuthors(new String[]{oldCourse.author}); - - String updatedCoursePath = FileUtil.join(PathManager.getConfigPath(), "courses", oldCourse.name); - if (new File(updatedCoursePath).exists()) { - course.setCourseDirectory(FileUtil.toSystemIndependentName(updatedCoursePath)); - } - final ArrayList lessons = new ArrayList(); - for (com.jetbrains.edu.learning.oldCourseFormat.Lesson oldLesson : oldCourse.lessons) { - final Lesson lesson = new Lesson(); - lesson.setName(oldLesson.name); - lesson.setIndex(oldLesson.myIndex + 1); - - final ArrayList tasks = new ArrayList(); - for (com.jetbrains.edu.learning.oldCourseFormat.Task oldTask : oldLesson.taskList) { - final Task task = new Task(); - task.setIndex(oldTask.myIndex + 1); - task.setName(oldTask.name); - task.setLesson(lesson); - final HashMap taskFiles = new HashMap(); - for (Map.Entry entry : oldTask.taskFiles.entrySet()) { - final TaskFile taskFile = new TaskFile(); - final com.jetbrains.edu.learning.oldCourseFormat.TaskFile oldTaskFile = entry.getValue(); - taskFile.setIndex(oldTaskFile.myIndex); - taskFile.name = entry.getKey(); - - final ArrayList placeholders = new ArrayList(); - for (TaskWindow window : oldTaskFile.taskWindows) { - final AnswerPlaceholder placeholder = new AnswerPlaceholder(); - placeholder.setIndex(window.myIndex); - placeholder.setHint(window.hint); - placeholder.setLength(window.length); - placeholder.setLine(window.line); - placeholder.setPossibleAnswer(window.possibleAnswer); - placeholder.setStart(window.start); - placeholders.add(placeholder); - placeholder.setInitialState(new AnswerPlaceholder.MyInitialState(window.myInitialLine, - window.myInitialLength, - window.myInitialStart)); - if (setStatus != null) { - setStatus.fun(Pair.create(placeholder, window.myStatus)); - } - } - - taskFile.setAnswerPlaceholders(placeholders); - taskFiles.put(entry.getKey(), taskFile); - } - task.taskFiles = taskFiles; - tasks.add(task); - } - - lesson.taskList = tasks; - - lessons.add(lesson); - } - course.setLessons(lessons); - course.initCourse(true); - return course; - } - public static boolean isImage(String fileName) { final String[] readerFormatNames = ImageIO.getReaderFormatNames(); for (@NonNls String format : readerFormatNames) { diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholder.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholder.java index 72f2b422b7f8..23fb46888d43 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholder.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholder.java @@ -13,25 +13,34 @@ import org.jetbrains.annotations.Nullable; public class AnswerPlaceholder { - @Expose private int line = 0; - @Expose private int start = 0; @Expose private String hint = ""; @SerializedName("possible_answer") @Expose private String possibleAnswer = ""; - @Expose private int length = 0; + + @SerializedName("offset") + @Expose + private int myOffset = -1; + + @Expose private int length = -1; + private int myIndex = -1; private String myTaskText; private MyInitialState myInitialState; - private StudyStatus myStatus = StudyStatus.Uninitialized; + private StudyStatus myStatus = StudyStatus.Unchecked; private boolean mySelected = false; private boolean myUseLength = true; - @Transient private TaskFile myTaskFile; + + @Transient + private TaskFile myTaskFile; + + public AnswerPlaceholder() { + } public void initAnswerPlaceholder(final TaskFile file, boolean isRestarted) { if (!isRestarted) { - setInitialState(new MyInitialState(getLine(), getLength(), getStart())); + setInitialState(new MyInitialState(myOffset, length)); myStatus = file.getTask().getStatus(); } @@ -57,22 +66,6 @@ public class AnswerPlaceholder { this.length = length; } - public int getStart() { - return start; - } - - public void setStart(int start) { - this.start = start; - } - - public int getLine() { - return line; - } - - public void setLine(int line) { - this.line = line; - } - public String getHint() { return hint; } @@ -93,7 +86,7 @@ public class AnswerPlaceholder { return myInitialState; } - public void setInitialState(@NotNull final MyInitialState initialState) { + public void setInitialState(MyInitialState initialState) { myInitialState = initialState; } @@ -115,10 +108,6 @@ public class AnswerPlaceholder { myTaskFile = taskFile; } - public int getRealStartOffset(@NotNull final Document document) { - return document.getLineStartOffset(line) + start; - } - public int getPossibleAnswerLength() { return possibleAnswer.length(); } @@ -128,21 +117,16 @@ public class AnswerPlaceholder { } public boolean isValid(@NotNull final Document document, int length) { - boolean isLineValid = line < document.getLineCount() && line >= 0; - if (!isLineValid) return false; - boolean isStartValid = start >= 0 && start < document.getLineEndOffset(line); - boolean isLengthValid = (getRealStartOffset(document) + length) <= document.getTextLength(); - return isLengthValid && isStartValid; + return myOffset >= 0 && myOffset + length <= document.getTextLength(); } /** * Returns window to its initial state */ public void reset() { - line = myInitialState.myLine; - start = myInitialState.myStart; - length = myInitialState.myLength; - if (!getUseLength()) { + myOffset = myInitialState.getOffset(); + length = myInitialState.getLength(); + if (!myUseLength) { possibleAnswer = myTaskText; } } @@ -164,7 +148,7 @@ public class AnswerPlaceholder { } public void init() { - setInitialState(new MyInitialState(line, myTaskText.length(), start)); + setInitialState(new MyInitialState(myOffset, myTaskText.length())); } public boolean getUseLength() { @@ -182,53 +166,40 @@ public class AnswerPlaceholder { myUseLength = useLength; } - public void setInitialState(AnswerPlaceholder placeholder) { - setInitialState(new MyInitialState(placeholder.line, placeholder.length, placeholder.start)); + public int getOffset() { + return myOffset; + } + + public void setOffset(int offset) { + myOffset = offset; } public static class MyInitialState { - public int myLine = -1; - public int myLength = -1; - public int myStart = -1; + private int length = -1; + private int offset = -1; public MyInitialState() { } - public MyInitialState(int line, int length, int start) { - myLine = line; - myLength = length; - myStart = start; + public MyInitialState(int initialOffset, int length) { + this.offset = initialOffset; + this.length = length; + } + + public int getLength() { + return length; + } + + public void setLength(int length) { + this.length = length; + } + + public int getOffset() { + return offset; + } + + public void setOffset(int offset) { + this.offset = offset; } } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - AnswerPlaceholder that = (AnswerPlaceholder)o; - - if (getLine() != that.getLine()) return false; - if (getStart() != that.getStart()) return false; - if (getLength() != that.getLength()) return false; - if (getIndex() != that.getIndex()) return false; - if (getHint() != null ? !getHint().equals(that.getHint()) : that.getHint() != null) return false; - if (getPossibleAnswer() != null ? !getPossibleAnswer().equals(that.getPossibleAnswer()) : that.getPossibleAnswer() != null) - return false; - if (myTaskText != null ? !myTaskText.equals(that.myTaskText) : that.myTaskText != null) return false; - - return true; - } - - @Override - public int hashCode() { - int result = getLine(); - result = 31 * result + getStart(); - result = 31 * result + (getHint() != null ? getHint().hashCode() : 0); - result = 31 * result + (getPossibleAnswer() != null ? getPossibleAnswer().hashCode() : 0); - result = 31 * result + getLength(); - result = 31 * result + getIndex(); - result = 31 * result + (myTaskText != null ? myTaskText.hashCode() : 0); - return result; - } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholderComparator.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholderComparator.java index 09926490d8e2..90cc4d8f8ba7 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholderComparator.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholderComparator.java @@ -5,12 +5,7 @@ import java.util.Comparator; public class AnswerPlaceholderComparator implements Comparator { @Override public int compare(AnswerPlaceholder o1, AnswerPlaceholder answerPlaceholder) { - final int line = o1.getLine(); - int lineDiff = line - answerPlaceholder.getLine(); - if (lineDiff == 0) { - return o1.getStart() - answerPlaceholder.getStart(); - } - return lineDiff; + return o1.getOffset() - answerPlaceholder.getOffset(); } @Override diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Course.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Course.java index 03dbc110882c..bfd62a40236b 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Course.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Course.java @@ -4,7 +4,6 @@ import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; import com.intellij.lang.Language; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.util.Function; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.core.EduUtils; import com.jetbrains.edu.learning.stepic.StepicUser; @@ -29,6 +28,9 @@ public class Course { @Expose private String courseType = EduNames.PYCHARM; @Expose private String courseMode = EduNames.STUDY; //this field is used to distinguish study and course creator modes + public Course() { + } + /** * Initializes state of course */ diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Lesson.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Lesson.java index 4110dd621c28..48103d22f78b 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Lesson.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Lesson.java @@ -12,12 +12,27 @@ import java.util.List; public class Lesson implements StudyItem { @Expose public int id; - @Expose private int myIndex = -1; // index is visible to user number of lesson from 1 to lesson number - @Expose @Transient public List steps; - @Expose @SerializedName("title") private String name; - @Expose @SerializedName("task_list") public List taskList = new ArrayList(); + @Transient public List steps; @Transient public List tags; - @Transient private Course myCourse = null; + @Transient + Boolean is_public; + + @Expose + @SerializedName("title") + private String name; + + @Expose + @SerializedName("task_list") + public List taskList = new ArrayList(); + + @Transient + private Course myCourse = null; + + // index is visible to user number of lesson from 1 to lesson number + @Expose private int myIndex = -1; + + public Lesson() { + } public void initLesson(final Course course, boolean isRestarted) { setCourse(course); @@ -74,4 +89,12 @@ public class Lesson implements StudyItem { return null; } + public StudyStatus getStatus() { + for (Task task : taskList) { + if (task.getStatus() != StudyStatus.Solved) { + return StudyStatus.Unchecked; + } + } + return StudyStatus.Solved; + } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/StudyStatus.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/StudyStatus.java index e1849da8461b..b4e1b97587bc 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/StudyStatus.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/StudyStatus.java @@ -1,5 +1,5 @@ package com.jetbrains.edu.learning.courseFormat; public enum StudyStatus { - Unchecked, Solved, Failed, Uninitialized + Unchecked, Solved, Failed } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Task.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Task.java index 8cb75e13ee3c..bbd515843705 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Task.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Task.java @@ -20,13 +20,22 @@ import java.util.Map; * Implementation of task which contains task files, tests, input file for tests */ public class Task implements StudyItem { - @Expose private String name; - @Expose private String text; + @Expose + private String name; + + // index is visible to user number of task from 1 to task number + @Expose private int myIndex; + @Expose private StudyStatus myStatus = StudyStatus.Unchecked; + @Expose private int myStepicId; - @Expose private int myIndex; // index is visible to user number of task from 1 to task number - @Expose @SerializedName("task_files") public Map taskFiles = new HashMap(); - @Expose private Map testsText = new HashMap(); - @Expose private StudyStatus myStatus = StudyStatus.Uninitialized; + + @Expose + @SerializedName("task_files") + public Map taskFiles = new HashMap(); + + @Expose private String text; + private Map testsText = new HashMap(); + @Transient private Lesson myLesson; public Task() {} @@ -205,5 +214,10 @@ public class Task implements StudyItem { public void setStatus(StudyStatus status) { myStatus = status; + for (TaskFile taskFile : taskFiles.values()) { + for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) { + placeholder.setStatus(status); + } + } } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/TaskFile.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/TaskFile.java index 65ef81d5e6b5..81a126735ac1 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/TaskFile.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/TaskFile.java @@ -2,8 +2,6 @@ package com.jetbrains.edu.learning.courseFormat; import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.LogicalPosition; import com.intellij.util.xmlb.annotations.Transient; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -27,6 +25,9 @@ public class TaskFile { @Expose @SerializedName("placeholders") private List myAnswerPlaceholders = new ArrayList(); @Transient private Task myTask; + public TaskFile() { + } + public void initTaskFile(final Task task, boolean isRestarted) { setTask(task); final List answerPlaceholders = getAnswerPlaceholders(); @@ -70,32 +71,16 @@ public class TaskFile { } /** - * @param pos position in editor - * @return task window located in specified position or null if there is no task window in this position + * @param offset position in editor + * @return answer placeholder located in specified position or null if there is no task window in this position */ @Nullable - public AnswerPlaceholder getAnswerPlaceholder(@NotNull final Document document, @NotNull final LogicalPosition pos) { - return getAnswerPlaceholder(document, pos, false); - } - - @Nullable - public AnswerPlaceholder getAnswerPlaceholder(@NotNull final Document document, @NotNull final LogicalPosition pos, - boolean useAnswerLength) { - int line = pos.line; - if (line >= document.getLineCount()) { - return null; - } - int column = pos.column; - int offset = document.getLineStartOffset(line) + column; + public AnswerPlaceholder getAnswerPlaceholder(int offset) { for (AnswerPlaceholder placeholder : myAnswerPlaceholders) { - if (placeholder.getLine() <= line) { - int realStartOffset = placeholder.getRealStartOffset(document); - int placeholderLength = placeholder.getRealLength(); - final int length = placeholderLength > 0 ? placeholderLength : 0; - int endOffset = realStartOffset + length; - if (realStartOffset <= offset && offset <= endOffset) { - return placeholder; - } + int placeholderStart = placeholder.getOffset(); + int placeholderEnd = placeholderStart + placeholder.getRealLength(); + if (placeholderStart <= offset && offset <= placeholderEnd) { + return placeholder; } } return null; @@ -107,9 +92,8 @@ public class TaskFile { List answerPlaceholdersCopy = new ArrayList(sourceAnswerPlaceholders.size()); for (AnswerPlaceholder answerPlaceholder : sourceAnswerPlaceholders) { AnswerPlaceholder answerPlaceholderCopy = new AnswerPlaceholder(); - answerPlaceholderCopy.setLine(answerPlaceholder.getLine()); - answerPlaceholderCopy.setStart(answerPlaceholder.getStart()); answerPlaceholderCopy.setTaskText(answerPlaceholder.getTaskText()); + answerPlaceholderCopy.setOffset(answerPlaceholder.getOffset()); answerPlaceholderCopy.setLength(answerPlaceholder.getLength()); answerPlaceholderCopy.setPossibleAnswer(answerPlaceholder.getPossibleAnswer()); answerPlaceholderCopy.setIndex(answerPlaceholder.getIndex()); @@ -151,35 +135,12 @@ public class TaskFile { } } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - TaskFile that = (TaskFile)o; - - if (getIndex() != that.getIndex()) return false; - if (!name.equals(that.name)) return false; - - final List answerPlaceholders = getAnswerPlaceholders(); - final List thatAnswerPlaceholders = that.getAnswerPlaceholders(); - if (answerPlaceholders.size() != thatAnswerPlaceholders.size()) return false; - for (int i = 0; i < answerPlaceholders.size(); i++) { - final AnswerPlaceholder placeholder = answerPlaceholders.get(i); - final AnswerPlaceholder thatPlaceholder = thatAnswerPlaceholders.get(i); - if (!placeholder.equals(thatPlaceholder)) return false; - } - return true; - } - - @Override - public int hashCode() { - int result = getIndex(); - result = 31 * result + name.hashCode(); + public boolean hasFailedPlaceholders() { for (AnswerPlaceholder placeholder : myAnswerPlaceholders) { - result = 31 * result + placeholder.hashCode(); + if (placeholder.getStatus() == StudyStatus.Failed) { + return true; + } } - return result; + return false; } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java index 2cff3f89318d..7daa0028d87b 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java @@ -26,6 +26,7 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.util.containers.ContainerUtil; import com.jetbrains.edu.learning.StudyProjectComponent; +import com.jetbrains.edu.learning.StudySerializationUtils; import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.core.EduNames; @@ -132,7 +133,7 @@ public class StudyProjectGenerator { Reader reader = null; try { reader = new InputStreamReader(new FileInputStream(courseFile), "UTF-8"); - Gson gson = new GsonBuilder().create(); + Gson gson = new GsonBuilder().registerTypeAdapter(Course.class, new StudySerializationUtils.Json.CourseTypeAdapter(courseFile)).create(); final Course course = gson.fromJson(reader, Course.class); course.initCourse(isAdaptive); return course; diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/editor/StudyEditorFactoryListener.java b/python/educational-core/student/src/com/jetbrains/edu/learning/editor/StudyEditorFactoryListener.java index 8a9c17d0ef24..d640110af404 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/editor/StudyEditorFactoryListener.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/editor/StudyEditorFactoryListener.java @@ -42,11 +42,11 @@ public class StudyEditorFactoryListener implements EditorFactoryListener { final Editor editor = e.getEditor(); final Point point = e.getMouseEvent().getPoint(); final LogicalPosition pos = editor.xyToLogicalPosition(point); - final AnswerPlaceholder answerPlaceholder = myTaskFile.getAnswerPlaceholder(editor.getDocument(), pos); + final AnswerPlaceholder answerPlaceholder = myTaskFile.getAnswerPlaceholder(editor.logicalPositionToOffset(pos)); if (answerPlaceholder == null || answerPlaceholder.getSelected()) { return; } - int startOffset = answerPlaceholder.getRealStartOffset(editor.getDocument()); + int startOffset = answerPlaceholder.getOffset(); editor.getSelectionModel().setSelection(startOffset, startOffset + answerPlaceholder.getRealLength()); answerPlaceholder.setSelected(true); } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/navigation/StudyNavigator.java b/python/educational-core/student/src/com/jetbrains/edu/learning/navigation/StudyNavigator.java index b54809ef9323..75b2c7709001 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/navigation/StudyNavigator.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/navigation/StudyNavigator.java @@ -1,11 +1,9 @@ package com.jetbrains.edu.learning.navigation; import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.LogicalPosition; import com.intellij.openapi.project.Project; -import com.jetbrains.edu.learning.core.EduNames; -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.*; import org.jetbrains.annotations.NotNull; @@ -67,29 +65,26 @@ public class StudyNavigator { final Project project = editor.getProject(); if (project == null) return; for (AnswerPlaceholder answerPlaceholder : taskFile.getAnswerPlaceholders()) { - final StudyStatus status = StudyTaskManager.getInstance(project).getStatus(answerPlaceholder); - if (status != StudyStatus.Failed) { + if (answerPlaceholder.getStatus() != StudyStatus.Failed) { continue; } - navigateToAnswerPlaceholder(editor, answerPlaceholder, taskFile); + navigateToAnswerPlaceholder(editor, answerPlaceholder); break; } } - public static void navigateToAnswerPlaceholder(@NotNull final Editor editor, @NotNull final AnswerPlaceholder answerPlaceholder, - @NotNull final TaskFile taskFile) { + public static void navigateToAnswerPlaceholder(@NotNull final Editor editor, @NotNull final AnswerPlaceholder answerPlaceholder) { if (editor.isDisposed() || !answerPlaceholder.isValid(editor.getDocument())) { return; } - LogicalPosition placeholderStart = new LogicalPosition(answerPlaceholder.getLine(), answerPlaceholder.getStart()); - editor.getCaretModel().moveToLogicalPosition(placeholderStart); + editor.getCaretModel().moveToOffset(answerPlaceholder.getOffset()); } public static void navigateToFirstAnswerPlaceholder(@NotNull final Editor editor, @NotNull final TaskFile taskFile) { if (!taskFile.getAnswerPlaceholders().isEmpty()) { AnswerPlaceholder firstAnswerPlaceholder = StudyUtils.getFirst(taskFile.getAnswerPlaceholders()); - navigateToAnswerPlaceholder(editor, firstAnswerPlaceholder, taskFile); + navigateToAnswerPlaceholder(editor, firstAnswerPlaceholder); } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/Lesson.java b/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/Lesson.java deleted file mode 100644 index bdf1f45a36c9..000000000000 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/Lesson.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.jetbrains.edu.learning.oldCourseFormat; - -import java.util.ArrayList; -import java.util.List; - -public class Lesson { - public String name; - public List taskList = new ArrayList(); - public int myIndex = -1; - public LessonInfo myLessonInfo = new LessonInfo(); - -} diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/LessonInfo.java b/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/LessonInfo.java deleted file mode 100644 index b19031b335a3..000000000000 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/LessonInfo.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.jetbrains.edu.learning.oldCourseFormat; - -/** - * Implementation of class which contains information about student progress in current lesson - */ -public class LessonInfo { - public int myTaskNum; - public int myTaskFailed; - public int myTaskSolved; - public int myTaskUnchecked; - -} diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/OldCourse.java b/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/OldCourse.java deleted file mode 100644 index fe254359e2ef..000000000000 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/OldCourse.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.jetbrains.edu.learning.oldCourseFormat; - -import java.util.ArrayList; -import java.util.List; - -public class OldCourse { - - public List lessons = new ArrayList(); - public String description; - public String name; - public String myResourcePath = ""; - public String author; - public boolean myUpToDate = false; - -} \ No newline at end of file diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/Task.java b/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/Task.java deleted file mode 100644 index 43a41c815962..000000000000 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/Task.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.jetbrains.edu.learning.oldCourseFormat; - -import java.util.HashMap; -import java.util.Map; - -/** - * Implementation of task which contains task files, tests, input file for tests - */ -public class Task { - public String name; - public Map taskFiles = new HashMap(); - public int myIndex; -} diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/TaskFile.java b/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/TaskFile.java deleted file mode 100644 index be2c082f4e75..000000000000 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/TaskFile.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.jetbrains.edu.learning.oldCourseFormat; - -import com.intellij.util.xmlb.annotations.Transient; - -import java.util.ArrayList; -import java.util.List; - -public class TaskFile { - public List taskWindows = new ArrayList(); - @Transient - public int myIndex = -1; -} diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/TaskWindow.java b/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/TaskWindow.java deleted file mode 100644 index 50ac63949232..000000000000 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/TaskWindow.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.jetbrains.edu.learning.oldCourseFormat; - -import com.jetbrains.edu.learning.courseFormat.StudyStatus; - -/** - * Implementation of windows which user should type in - */ - - -public class TaskWindow { - public int line = 0; - public int start = 0; - public String hint = ""; - public String possibleAnswer = ""; - public int length = 0; - public int myIndex = -1; - public int myInitialLine = -1; - public int myInitialStart = -1; - public int myInitialLength = -1; - public StudyStatus myStatus = StudyStatus.Unchecked; -} \ No newline at end of file diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/StudyDirectoryNode.java b/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/StudyDirectoryNode.java index 6d71a8276b7f..c9821a704112 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/StudyDirectoryNode.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/StudyDirectoryNode.java @@ -43,7 +43,7 @@ public class StudyDirectoryNode extends PsiDirectoryNode { if (course == null) { return; } - if (valueName.equals(myProject.getName())) { + if (valueName.equals(myProject.getBaseDir().getName())) { data.clearText(); data.setIcon(InteractiveLearningIcons.Course); data.addText(course.getName(), new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, JBColor.BLACK)); @@ -89,9 +89,8 @@ public class StudyDirectoryNode extends PsiDirectoryNode { return name.contains(EduNames.SANDBOX_DIR) ? 0 : 3; } - private void setStudyAttributes(Lesson lesson, PresentationData data, String additionalName) { - StudyStatus taskStatus = StudyTaskManager.getInstance(myProject).getStatus(lesson); - switch (taskStatus) { + private static void setStudyAttributes(Lesson lesson, PresentationData data, String additionalName) { + switch (lesson.getStatus()) { case Unchecked: { updatePresentation(data, additionalName, JBColor.BLACK, InteractiveLearningIcons.Lesson); break; @@ -107,7 +106,7 @@ public class StudyDirectoryNode extends PsiDirectoryNode { } protected void setStudyAttributes(Task task, PresentationData data, String additionalName) { - StudyStatus taskStatus = StudyTaskManager.getInstance(myProject).getStatus(task); + StudyStatus taskStatus = task.getStatus(); switch (taskStatus) { case Unchecked: { updatePresentation(data, additionalName, JBColor.BLACK, InteractiveLearningIcons.Task); diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/CourseInfo.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/CourseInfo.java index 50f6c4119e8e..004b313054a1 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/CourseInfo.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/CourseInfo.java @@ -81,8 +81,8 @@ public class CourseInfo { public void setAuthors(List authors) { myAuthors = authors; for (StepicUser author : authors) { - if (author.id > 0) { - instructors.add(author.id); + if (author.getId() > 0) { + instructors.add(author.getId()); } } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java index b527720d4af3..642b0a160b3b 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java @@ -170,7 +170,7 @@ public class EduAdaptiveStepicConnector { final boolean recommendationReaction = user != null && postRecommendationReaction(project, String.valueOf(editor.getTaskFile().getTask().getLesson().id), - String.valueOf(user.id), reaction); + String.valueOf(user.getId()), reaction); if (recommendationReaction) { final Task task = getNextRecommendation(project, course); diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java index a335fc48b768..30429ed8e061 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java @@ -1,8 +1,6 @@ package com.jetbrains.edu.learning.stepic; -import com.google.gson.FieldNamingPolicy; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; +import com.google.gson.*; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.diagnostic.Logger; @@ -16,6 +14,7 @@ import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileFilter; import com.intellij.util.net.ssl.CertificateManager; +import com.jetbrains.edu.learning.StudySerializationUtils; import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.core.EduUtils; @@ -235,7 +234,7 @@ public class EduStepicConnector { if (statusLine.getStatusCode() != HttpStatus.SC_OK) { throw new IOException("Stepic returned non 200 status code " + responseString); } - Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); + Gson gson = new GsonBuilder().registerTypeAdapter(TaskFile.class, new StudySerializationUtils.Json.StepicTaskFileAdapter()).setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); return gson.fromJson(responseString, container); } @@ -718,6 +717,7 @@ public class EduStepicConnector { public static void postTask(final Project project, @NotNull final Task task, final int lessonId) { final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.STEP_SOURCES); setHeaders(request, "application/json"); + //TODO: register type adapter for task files here? final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create(); ApplicationManager.getApplication().invokeLater(() -> { final String requestBody = gson.toJson(new StepicWrappers.StepSourceWrapper(project, task, lessonId)); diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicUser.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicUser.java index 04f43de30d37..2196f9563d09 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicUser.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicUser.java @@ -10,16 +10,16 @@ import com.jetbrains.edu.learning.StudyTaskManager; public class StepicUser { private static final String STEPIC_SETTINGS_PASSWORD_KEY = "STEPIC_SETTINGS_PASSWORD_KEY"; private static final Logger LOG = Logger.getInstance(StepicUser.class); - int id; - String firstName; - String lastName; - String email; + private int id = -1; + private String myFirstName = ""; + private String myLastName = ""; + private String myEmail = ""; public StepicUser() { } public StepicUser(String email, String password) { - this.email = email; + this.myEmail = email; setPassword(password); } @@ -32,27 +32,27 @@ public class StepicUser { } public String getFirstName() { - return firstName; + return myFirstName; } public void setFirstName(String firstName) { - this.firstName = firstName; + this.myFirstName = firstName; } public String getLastName() { - return lastName; + return myLastName; } public void setLastName(String last_name) { - this.lastName = last_name; + this.myLastName = last_name; } public String getEmail() { - return email; + return myEmail; } public void setEmail(String email) { - this.email = email; + this.myEmail = email; } @Transient @@ -83,6 +83,6 @@ public class StepicUser { } public String getName() { - return StringUtil.join(new String[]{firstName, lastName}, " "); + return StringUtil.join(new String[]{myFirstName, myLastName}, " "); } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyProgressToolWindowFactory.java b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyProgressToolWindowFactory.java index eea24f44ca0d..d57ad24c70f8 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyProgressToolWindowFactory.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyProgressToolWindowFactory.java @@ -43,7 +43,7 @@ public class StudyProgressToolWindowFactory implements ToolWindowFactory, DumbAw for (Lesson lesson : lessons) { if (lesson.getName().equals(EduNames.PYCHARM_ADDITIONAL)) continue; taskNum += lesson.getTaskList().size(); - taskSolved += getSolvedTasks(lesson, taskManager); + taskSolved += getSolvedTasks(lesson); } String completedTasks = String.format("%d of %d tasks completed", taskSolved, taskNum); @@ -68,10 +68,10 @@ public class StudyProgressToolWindowFactory implements ToolWindowFactory, DumbAw contentPanel.add(statisticLabel); } - private static int getSolvedTasks(@NotNull final Lesson lesson, StudyTaskManager taskManager) { + private static int getSolvedTasks(@NotNull final Lesson lesson) { int solved = 0; for (Task task : lesson.getTaskList()) { - if (taskManager.getStatus(task) == StudyStatus.Solved) { + if (task.getStatus() == StudyStatus.Solved) { solved += 1; } } diff --git a/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java b/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java index 16bbdf9f218b..b3252a761426 100644 --- a/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java +++ b/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java @@ -95,12 +95,11 @@ public class PyStudyCheckAction extends StudyCheckAction { protected void onTaskFailed(String message) { ApplicationManager.getApplication().invokeLater(() -> { if (myTaskDir == null) return; - myTaskManger.setStatus(myTask, StudyStatus.Failed); + myTask.setStatus(StudyStatus.Failed); for (Map.Entry entry : myTask.getTaskFiles().entrySet()) { final String name = entry.getKey(); final TaskFile taskFile = entry.getValue(); if (taskFile.getAnswerPlaceholders().size() < 2) { - myTaskManger.setStatus(taskFile, StudyStatus.Failed); continue; } final Course course = myTaskManger.getCourse();