EDU-619 Replace line-offset placeholder representation with absolute offset

This commit is contained in:
Liana Bakradze
2016-06-19 21:46:56 +03:00
parent bac90f20ff
commit 1f0e3a7bd2
44 changed files with 785 additions and 585 deletions
@@ -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<VirtualFile> 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<VirtualFile> getAllAnswerTaskFiles(@NotNull Course course, @NotNull Project project) {
List<VirtualFile> result = new ArrayList<>();
for (Lesson lesson : course.getLessons()) {
for (Task task : lesson.getTaskList()) {
for (Map.Entry<String, TaskFile> 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";
@@ -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<CCProjectService> {
public class CCProjectService implements PersistentStateComponent<Element> {
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<CCProjectServi
}
@Override
public CCProjectService getState() {
return this;
public Element getState() {
return XmlSerializer.serialize(this);
}
@Override
public void loadState(CCProjectService state) {
XmlSerializerUtil.copyBean(state, this);
myCourse.initCourse(true);
public void loadState(Element state) {
Element courseElement = getChildWithName(state, COURSE).getChild(COURSE_TITLED);
for (Element lesson : getChildList(courseElement, LESSONS)) {
int lessonIndex = getAsInt(lesson, INDEX);
for (Element task : getChildList(lesson, TASK_LIST)) {
int taskIndex = getAsInt(task, INDEX);
Map<String, Element> taskFiles = getChildMap(task, TASK_FILES);
for (Map.Entry<String, Element> 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) {
@@ -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";
@@ -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();
@@ -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<AnswerPlaceholder> 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) {
@@ -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);
}
@@ -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);
@@ -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<TextRange> 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()));
}
}
@@ -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<String, Element> 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<String, String> fillStatusMap(Element taskManagerElement, String mapName, XMLOutputter outputter) {
Map<Element, String> sourceMap = getChildMap(taskManagerElement, mapName);
Map<String, String> destMap = new HashMap<>();
for (Map.Entry<Element, String> 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<String, String> placeholderTextToStatus = fillStatusMap(taskManagerElement, STUDY_STATUS_MAP, outputter);
Map<String, String> 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<String, Element> taskFiles = getChildMap(task, TASK_FILES);
for (Map.Entry<String, Element> 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<String, String> 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<Element> elements) {
Element listElement = new Element(LIST);
for (Element element : elements) {
listElement.addContent(element);
}
return addChildWithName(parent, name, listElement);
}
public static List<Element> 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 <K, V> Map<K, V> 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<Course> {
private final File myCourseFile;
public CourseTypeAdapter(File courseFile) {
myCourseFile = courseFile;
}
@Override
public Course deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject courseObject = json.getAsJsonObject();
JsonArray lessons = courseObject.getAsJsonArray(LESSONS);
for (int lessonIndex = 1; lessonIndex <= lessons.size(); lessonIndex++) {
JsonObject lessonObject = lessons.get(lessonIndex - 1).getAsJsonObject();
JsonArray tasks = lessonObject.getAsJsonArray(TASK_LIST);
for (int taskIndex = 1; taskIndex <= tasks.size(); taskIndex++) {
JsonObject taskObject = tasks.get(taskIndex - 1).getAsJsonObject();
for (Map.Entry<String, JsonElement> taskFile : taskObject.getAsJsonObject(TASK_FILES).entrySet()) {
String name = taskFile.getKey();
String filePath = FileUtil.join(myCourseFile.getParent(), EduNames.LESSON + lessonIndex, EduNames.TASK + taskIndex, name);
VirtualFile resourceFile = LocalFileSystem.getInstance().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<TaskFile> {
@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);
}
}
}
}
@@ -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<Element>, 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<AnswerPlaceholder, StudyStatus> myStudyStatusMap = new HashMap<>();
public Map<TaskFile, StudyStatus> myTaskStatusMap = new HashMap<>();
public int VERSION = 3;
public Map<Task, List<UserTest>> myUserTests = new HashMap<>();
public List<String> myInvisibleFiles = new ArrayList<>();
public boolean myShouldUseJavaFx = StudyUtils.hasJavaFx();
private StudyToolWindow.StudyToolWindowMode myToolWindowMode = StudyToolWindow.StudyToolWindowMode.TEXT;
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<Element>, 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<Element>, 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<Element>, 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<Element>, 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<Element>, 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);
@@ -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);
}
}
@@ -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);
}
@@ -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());
}
}
@@ -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);
@@ -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
@@ -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(
@@ -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);
@@ -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);
@@ -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<RangeMarker> 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) {
@@ -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)));
}
@@ -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<Pair<AnswerPlaceholder, StudyStatus>, 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<Lesson> lessons = new ArrayList<Lesson>();
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<Task> tasks = new ArrayList<Task>();
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<String, TaskFile> taskFiles = new HashMap<String, TaskFile>();
for (Map.Entry<String, com.jetbrains.edu.learning.oldCourseFormat.TaskFile> 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<AnswerPlaceholder> placeholders = new ArrayList<AnswerPlaceholder>();
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) {
@@ -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;
}
}
@@ -5,12 +5,7 @@ import java.util.Comparator;
public class AnswerPlaceholderComparator implements Comparator<AnswerPlaceholder> {
@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
@@ -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
*/
@@ -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<Integer> steps;
@Expose @SerializedName("title") private String name;
@Expose @SerializedName("task_list") public List<Task> taskList = new ArrayList<Task>();
@Transient public List<Integer> steps;
@Transient public List<String> tags;
@Transient private Course myCourse = null;
@Transient
Boolean is_public;
@Expose
@SerializedName("title")
private String name;
@Expose
@SerializedName("task_list")
public List<Task> taskList = new ArrayList<Task>();
@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;
}
}
@@ -1,5 +1,5 @@
package com.jetbrains.edu.learning.courseFormat;
public enum StudyStatus {
Unchecked, Solved, Failed, Uninitialized
Unchecked, Solved, Failed
}
@@ -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<String, TaskFile> taskFiles = new HashMap<String, TaskFile>();
@Expose private Map<String, String> testsText = new HashMap<String, String>();
@Expose private StudyStatus myStatus = StudyStatus.Uninitialized;
@Expose
@SerializedName("task_files")
public Map<String, TaskFile> taskFiles = new HashMap<String, TaskFile>();
@Expose private String text;
private Map<String, String> testsText = new HashMap<String, String>();
@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);
}
}
}
}
@@ -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<AnswerPlaceholder> myAnswerPlaceholders = new ArrayList<AnswerPlaceholder>();
@Transient private Task myTask;
public TaskFile() {
}
public void initTaskFile(final Task task, boolean isRestarted) {
setTask(task);
final List<AnswerPlaceholder> 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<AnswerPlaceholder> answerPlaceholdersCopy = new ArrayList<AnswerPlaceholder>(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<AnswerPlaceholder> answerPlaceholders = getAnswerPlaceholders();
final List<AnswerPlaceholder> 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;
}
}
@@ -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;
@@ -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);
}
@@ -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);
}
}
@@ -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<Task> taskList = new ArrayList<Task>();
public int myIndex = -1;
public LessonInfo myLessonInfo = new LessonInfo();
}
@@ -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;
}
@@ -1,15 +0,0 @@
package com.jetbrains.edu.learning.oldCourseFormat;
import java.util.ArrayList;
import java.util.List;
public class OldCourse {
public List<Lesson> lessons = new ArrayList<Lesson>();
public String description;
public String name;
public String myResourcePath = "";
public String author;
public boolean myUpToDate = false;
}
@@ -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<String, TaskFile> taskFiles = new HashMap<String, TaskFile>();
public int myIndex;
}
@@ -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<TaskWindow> taskWindows = new ArrayList<TaskWindow>();
@Transient
public int myIndex = -1;
}
@@ -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;
}
@@ -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);
@@ -81,8 +81,8 @@ public class CourseInfo {
public void setAuthors(List<StepicUser> authors) {
myAuthors = authors;
for (StepicUser author : authors) {
if (author.id > 0) {
instructors.add(author.id);
if (author.getId() > 0) {
instructors.add(author.getId());
}
}
}
@@ -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);
@@ -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));
@@ -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}, " ");
}
}
@@ -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;
}
}
@@ -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<String, TaskFile> 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();