extracted course format from interactive learning

This commit is contained in:
Ekaterina Tuzova
2015-02-13 17:18:09 +03:00
parent af9648ed44
commit 493a22adb4
54 changed files with 1385 additions and 101 deletions
@@ -27,9 +27,10 @@ public class AnswerPlaceholder implements Comparable<AnswerPlaceholder> {
@Expose private String hint;
@Expose private String possible_answer;
@Expose private int length;
private int myIndex;
private String myTaskText;
private int myReplacementLength;
private int myIndex;
public String getHint() {
return hint;
+2
View File
@@ -7,5 +7,7 @@
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="platform-api" />
<orderEntry type="library" name="gson" level="project" />
</component>
</module>
@@ -5,10 +5,12 @@
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
<excludeFolder url="file://$MODULE_DIR$/src/com/jetbrains/edu/learning/course" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="lang-impl" />
<orderEntry type="library" name="gson" level="project" />
<orderEntry type="module" module-name="educational" />
</component>
</module>
@@ -0,0 +1,61 @@
package com.jetbrains.edu.learning;
import com.google.gson.annotations.SerializedName;
import java.util.List;
/**
* Implementation of class which contains information to be shawn in course description in tool window
* and when project is being created
*/
public class CourseInfo {
boolean is_public;
public List<Integer> sections;
@SerializedName("title")
private String myName;
@SerializedName("summary")
private String myDescription;
private String myAuthor;
public static CourseInfo INVALID_COURSE = new CourseInfo("", "", "");
public CourseInfo(String name, String author, String description) {
myName = name;
myAuthor = author;
myDescription = description;
}
public String getName() {
return myName;
}
public String getAuthor() {
return myAuthor;
}
public String getDescription() {
return myDescription;
}
@Override
public String toString() {
return myName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CourseInfo that = (CourseInfo)o;
return that.getName().equals(myName) && that.getAuthor().equals(myAuthor)
&& that.getDescription().equals(myDescription);
}
@Override
public int hashCode() {
int result = myName != null ? myName.hashCode() : 0;
result = 31 * result + (myAuthor != null ? myAuthor.hashCode() : 0);
result = 31 * result + (myDescription != null ? myDescription.hashCode() : 0);
return result;
}
}
@@ -0,0 +1,26 @@
package com.jetbrains.edu.learning;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.edu.courseFormat.Task;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class StudyCourseResourceManager {
/**
* Gets text of resource file such as test input file or task text in needed format
*
* @param fileName name of resource file which should exist in task directory
* @param wrapHTML if it's necessary to wrap text with html tags
* @return text of resource file wrapped with html tags if necessary
*/
@Nullable
public String getResourceText(@NotNull final Project project, @NotNull final Task task, @NotNull final String fileName, boolean wrapHTML) {
VirtualFile taskDir = task.getTaskDir(project);
if (taskDir != null) {
return StudyUtils.getFileText(taskDir.getCanonicalPath(), fileName, wrapHTML, "UTF-8");
}
return null;
}
}
@@ -5,8 +5,8 @@ import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.event.DocumentAdapter;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.impl.event.DocumentEventImpl;
import com.jetbrains.edu.learning.course.TaskFile;
import com.jetbrains.edu.learning.course.AnswerPlaceholder;
import com.jetbrains.edu.courseFormat.AnswerPlaceholder;
import com.jetbrains.edu.courseFormat.TaskFile;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
@@ -13,9 +13,10 @@ import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.problems.WolfTheProblemSolver;
import com.jetbrains.edu.learning.course.AnswerPlaceholder;
import com.jetbrains.edu.learning.course.TaskFile;
import com.jetbrains.edu.courseFormat.AnswerPlaceholder;
import com.jetbrains.edu.courseFormat.TaskFile;
import com.jetbrains.edu.learning.editor.StudyEditor;
import com.jetbrains.edu.learning.navigation.StudyNavigator;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
@@ -41,10 +42,10 @@ class StudyEditorFactoryListener implements EditorFactoryListener {
final AnswerPlaceholder answerPlaceholder = myTaskFile.getAnswerPlaceholder(editor.getDocument(), pos);
if (answerPlaceholder != null) {
myTaskFile.setSelectedAnswerPlaceholder(answerPlaceholder);
answerPlaceholder.draw(editor);
StudyPainter.drawAnswerPlaceholder(editor, answerPlaceholder);
}
else {
myTaskFile.drawAllWindows(editor);
StudyPainter.drawAllWindows(editor, myTaskFile);
}
}
}
@@ -70,11 +71,11 @@ class StudyEditorFactoryListener implements EditorFactoryListener {
final StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
final TaskFile taskFile = taskManager.getTaskFile(openedFile);
if (taskFile != null) {
taskFile.navigateToFirstTaskWindow(editor);
StudyNavigator.navigateToFirstTaskWindow(editor, taskFile);
editor.addEditorMouseListener(new WindowSelectionListener(taskFile));
StudyEditor.addDocumentListener(document, new StudyDocumentListener(taskFile));
WolfTheProblemSolver.getInstance(project).clearProblems(openedFile);
taskFile.drawAllWindows(editor);
StudyPainter.drawAllWindows(editor, taskFile);
}
}
}
@@ -22,7 +22,7 @@ import com.intellij.lang.LanguageExtension;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.edu.learning.course.Task;
import com.jetbrains.edu.courseFormat.Task;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -0,0 +1,138 @@
package com.jetbrains.edu.learning;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.edu.StudyNames;
import com.jetbrains.edu.courseFormat.Course;
import com.jetbrains.edu.courseFormat.Lesson;
import com.jetbrains.edu.courseFormat.Task;
import com.jetbrains.edu.courseFormat.TaskFile;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.List;
import java.util.Map;
public class StudyGenerator {
private StudyGenerator() {
}
private static final Logger LOG = Logger.getInstance(StudyGenerator.class.getName());
/**
* Creates task files in its task folder in project user created
*
* @param taskDir project directory of task which task file belongs to
* @param resourceRoot directory where original task file stored
* @throws IOException
*/
public static void createTaskFile(@NotNull final VirtualFile taskDir, @NotNull final File resourceRoot,
@NotNull final String name) throws IOException {
String systemIndependentName = FileUtil.toSystemIndependentName(name);
final int index = systemIndependentName.lastIndexOf("/");
if (index > 0) {
systemIndependentName = systemIndependentName.substring(index + 1);
}
File resourceFile = new File(resourceRoot, name);
File fileInProject = new File(taskDir.getPath(), systemIndependentName);
FileUtil.copy(resourceFile, fileInProject);
}
/**
* Creates task directory in its lesson folder in project user created
*
* @param lessonDir project directory of lesson which task belongs to
* @param resourceRoot directory where original task file stored
* @throws IOException
*/
public static void createTask(@NotNull final Task task, @NotNull final VirtualFile lessonDir, @NotNull final File resourceRoot,
@NotNull final Project project) throws IOException {
VirtualFile taskDir = lessonDir.createChildDirectory(project, StudyNames.TASK_DIR + Integer.toString(task.getIndex() + 1));
StudyUtils.markDirAsSourceRoot(taskDir, project);
File newResourceRoot = new File(resourceRoot, taskDir.getName());
int i = 0;
for (Map.Entry<String, TaskFile> taskFile : task.getTaskFiles().entrySet()) {
TaskFile taskFileContent = taskFile.getValue();
taskFileContent.setIndex(i);
i++;
createTaskFile(taskDir, newResourceRoot, taskFile.getKey());
}
File[] filesInTask = newResourceRoot.listFiles();
if (filesInTask != null) {
for (File file : filesInTask) {
String fileName = file.getName();
if (!task.isTaskFile(fileName)) {
File resourceFile = new File(newResourceRoot, fileName);
File fileInProject = new File(taskDir.getCanonicalPath(), fileName);
FileUtil.copy(resourceFile, fileInProject);
}
}
}
}
/**
* Creates lesson directory in its course folder in project user created
*
* @param courseDir project directory of course
* @param resourceRoot directory where original lesson stored
* @throws IOException
*/
public static void createLesson(@NotNull final Lesson lesson, @NotNull final VirtualFile courseDir, @NotNull final File resourceRoot,
@NotNull final Project project) throws IOException {
String lessonDirName = StudyNames.LESSON_DIR + Integer.toString(lesson.getIndex() + 1);
VirtualFile lessonDir = courseDir.createChildDirectory(project, lessonDirName);
final List<Task> taskList = lesson.getTaskList();
for (int i = 0; i < taskList.size(); i++) {
Task task = taskList.get(i);
task.setIndex(i);
createTask(task, lessonDir, new File(resourceRoot, lessonDir.getName()), project);
}
}
/**
* Creates course directory in project user created
*
* @param baseDir project directory
* @param resourceRoot directory where original course is stored
*/
public static void createCourse(@NotNull final Course course, @NotNull final VirtualFile baseDir, @NotNull final File resourceRoot,
@NotNull final Project project) {
ApplicationManager.getApplication().invokeLater(
new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
try {
final List<Lesson> lessons = course.getLessons();
for (int i = 0; i < lessons.size(); i++) {
Lesson lesson = lessons.get(i);
lesson.setIndex(i);
createLesson(lesson, baseDir, resourceRoot, project);
}
baseDir.createChildDirectory(this, StudyNames.SANDBOX_DIR);
File[] files = resourceRoot.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return !name.contains(StudyNames.LESSON_DIR) && !name.equals("course.json") && !name.equals("hints");
}
});
for (File file : files) {
FileUtil.copy(file, new File(baseDir.getPath(), file.getName()));
}
}
catch (IOException e) {
LOG.error(e);
}
}
});
}
});
}
}
@@ -4,7 +4,7 @@ import com.intellij.codeInsight.highlighting.HighlightErrorFilter;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiErrorElement;
import com.intellij.psi.PsiFile;
import com.jetbrains.edu.learning.course.TaskFile;
import com.jetbrains.edu.courseFormat.TaskFile;
import org.jetbrains.annotations.NotNull;
public class StudyHighlightErrorFilter extends HighlightErrorFilter{
@@ -0,0 +1,100 @@
package com.jetbrains.edu.learning;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.editor.actionSystem.EditorActionManager;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.editor.impl.DocumentImpl;
import com.intellij.openapi.editor.markup.*;
import com.intellij.ui.JBColor;
import com.jetbrains.edu.courseFormat.AnswerPlaceholder;
import com.jetbrains.edu.courseFormat.TaskFile;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.util.List;
public class StudyPainter {
private StudyPainter() {
}
/**
* Draw task window with color according to its status
*/
public static void drawAnswerPlaceholder(@NotNull final Editor editor, AnswerPlaceholder answerPlaceholder) {
Document document = editor.getDocument();
if (!answerPlaceholder.isValid(document)) {
return;
}
EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme();
final TextAttributes defaultTestAttributes = new TextAttributes(scheme.getDefaultForeground(), scheme.getDefaultBackground(), null,
EffectType.BOXED, Font.PLAIN);
final JBColor color = answerPlaceholder.getColor();
int startOffset = answerPlaceholder.getRealStartOffset(document);
RangeHighlighter
highlighter = editor.getMarkupModel().addRangeHighlighter(startOffset, startOffset + answerPlaceholder.getLength(), HighlighterLayer.LAST + 1,
defaultTestAttributes, HighlighterTargetArea.EXACT_RANGE);
highlighter.setCustomRenderer(new CustomHighlighterRenderer() {
@Override
public void paint(@NotNull Editor editor, @NotNull RangeHighlighter highlighter, @NotNull Graphics g) {
g.setColor(color);
Point point = editor.logicalPositionToXY(editor.offsetToLogicalPosition(highlighter.getStartOffset()));
Point pointEnd = editor.logicalPositionToXY(editor.offsetToLogicalPosition(highlighter.getEndOffset()));
g.drawRect(point.x, point.y - 2, (pointEnd.x - point.x), editor.getLineHeight() + 1);
}
});
editor.getCaretModel().moveToOffset(startOffset);
highlighter.setGreedyToLeft(true);
highlighter.setGreedyToRight(true);
}
public static void drawAllWindows(Editor editor, TaskFile taskFile) {
editor.getMarkupModel().removeAllHighlighters();
for (AnswerPlaceholder answerPlaceholder : taskFile.getAnswerPlaceholders()) {
drawAnswerPlaceholder(editor, answerPlaceholder);
}
final Document document = editor.getDocument();
EditorActionManager.getInstance()
.setReadonlyFragmentModificationHandler(document, new TaskWindowDeleteHandler(editor));
createGuardedBlocks(editor, taskFile);
editor.getColorsScheme().setColor(EditorColors.READONLY_FRAGMENT_BACKGROUND_COLOR, null);
}
/**
* Marks symbols adjacent to task windows as read-only fragments
*/
public static void createGuardedBlocks(@NotNull final Editor editor, TaskFile taskFile) {
final Document document = editor.getDocument();
if (document instanceof DocumentImpl) {
DocumentImpl documentImpl = (DocumentImpl)document;
List<RangeMarker> blocks = documentImpl.getGuardedBlocks();
for (AnswerPlaceholder answerPlaceholder : taskFile.getAnswerPlaceholders()) {
if (!answerPlaceholder.isValid(document)) {
return;
}
int start = answerPlaceholder.getRealStartOffset(document);
int end = start + answerPlaceholder.getLength();
if (start != 0) {
createGuardedBlock(editor, blocks, start - 1, start);
}
if (end != document.getTextLength()) {
createGuardedBlock(editor, blocks, end, end + 1);
}
}
}
}
private static void createGuardedBlock(Editor editor, List<RangeMarker> blocks, int start, int end) {
RangeHighlighter rh = editor.getMarkupModel()
.addRangeHighlighter(start, end, HighlighterLayer.LAST + 1, null, HighlighterTargetArea.EXACT_RANGE);
blocks.add(rh);
}
}
@@ -24,7 +24,10 @@ import com.intellij.openapi.wm.ex.ToolWindowManagerEx;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.edu.learning.course.*;
import com.jetbrains.edu.courseFormat.Course;
import com.jetbrains.edu.courseFormat.Lesson;
import com.jetbrains.edu.courseFormat.Task;
import com.jetbrains.edu.courseFormat.TaskFile;
import com.jetbrains.edu.learning.stepic.StudyStepicConnector;
import org.jetbrains.annotations.NotNull;
@@ -56,7 +59,7 @@ public class StudyProjectGenerator {
flushCourse(course);
course.init(false);
final File courseDirectory = new File(myCoursesDir, course.getName());
course.create(baseDir, courseDirectory, project);
StudyGenerator.createCourse(course, baseDir, courseDirectory, project);
course.setCourseDirectory(new File(myCoursesDir, mySelectedCourseInfo.getName()).getAbsolutePath());
VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
StudyTaskManager.getInstance(project).setCourse(course);
@@ -0,0 +1,85 @@
package com.jetbrains.edu.learning;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.process.CapturingProcessHandler;
import com.intellij.execution.process.ProcessOutput;
import com.intellij.openapi.application.ApplicationManager;
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.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.edu.StudyNames;
import com.jetbrains.edu.courseFormat.AnswerPlaceholder;
import com.jetbrains.edu.courseFormat.StudyStatus;
import com.jetbrains.edu.courseFormat.TaskFile;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
public class StudySmartChecker {
private StudySmartChecker() {
}
private static final Logger LOG = Logger.getInstance(StudySmartChecker.class);
public static void smartCheck(@NotNull final AnswerPlaceholder placeholder,
@NotNull final Project project,
@NotNull final VirtualFile answerFile,
@NotNull final TaskFile answerTaskFile,
@NotNull final TaskFile usersTaskFile,
@NotNull final StudyTestRunner testRunner,
@NotNull final VirtualFile virtualFile,
@NotNull final Document usersDocument) {
try {
final int index = placeholder.getIndex();
final VirtualFile windowCopy =
answerFile.copy(project, answerFile.getParent(), answerFile.getNameWithoutExtension() + index + StudyNames.WINDOW_POSTFIX);
final FileDocumentManager documentManager = FileDocumentManager.getInstance();
final Document windowDocument = documentManager.getDocument(windowCopy);
if (windowDocument != null) {
final File resourceFile = StudyUtils.copyResourceFile(virtualFile.getName(), windowCopy.getName(), project, usersTaskFile.getTask());
final TaskFile windowTaskFile = new TaskFile();
TaskFile.copy(answerTaskFile, windowTaskFile);
StudyDocumentListener listener = new StudyDocumentListener(windowTaskFile);
windowDocument.addDocumentListener(listener);
int start = placeholder.getRealStartOffset(windowDocument);
int end = start + placeholder.getLength();
final AnswerPlaceholder userAnswerPlaceholder = usersTaskFile.getAnswerPlaceholders().get(placeholder.getIndex());
int userStart = userAnswerPlaceholder.getRealStartOffset(usersDocument);
int userEnd = userStart + userAnswerPlaceholder.getLength();
String text = usersDocument.getText(new TextRange(userStart, userEnd));
windowDocument.replaceString(start, end, text);
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
documentManager.saveDocument(windowDocument);
}
});
VirtualFile fileWindows = StudyUtils.flushWindows(windowTaskFile, windowCopy);
Process smartTestProcess = testRunner.createCheckProcess(project, windowCopy.getPath());
final CapturingProcessHandler handler = new CapturingProcessHandler(smartTestProcess);
final ProcessOutput output = handler.runProcess();
boolean res = testRunner.getTestsOutput(output).equals(StudyTestRunner.TEST_OK);
userAnswerPlaceholder.setStatus(res ? StudyStatus.Solved : StudyStatus.Failed, StudyStatus.Unchecked);
StudyUtils.deleteFile(windowCopy);
if (fileWindows != null) {
StudyUtils.deleteFile(fileWindows);
}
if (!resourceFile.delete()) {
LOG.error("failed to delete", resourceFile.getPath());
}
}
}
catch (ExecutionException e) {
LOG.error(e);
}
catch (IOException e) {
LOG.error(e);
}
}
}
@@ -3,8 +3,8 @@ package com.jetbrains.edu.learning;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.edu.learning.course.Task;
import com.jetbrains.edu.learning.course.TaskFile;
import com.jetbrains.edu.courseFormat.Task;
import com.jetbrains.edu.courseFormat.TaskFile;
import com.jetbrains.edu.learning.editor.StudyEditor;
import org.jetbrains.annotations.Nullable;
@@ -27,11 +27,12 @@ import com.intellij.openapi.vfs.VirtualFileEvent;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.wm.*;
import com.intellij.util.xmlb.XmlSerializer;
import com.jetbrains.edu.StudyNames;
import com.jetbrains.edu.courseFormat.Course;
import com.jetbrains.edu.courseFormat.Lesson;
import com.jetbrains.edu.courseFormat.Task;
import com.jetbrains.edu.courseFormat.TaskFile;
import com.jetbrains.edu.learning.actions.*;
import com.jetbrains.edu.learning.course.Course;
import com.jetbrains.edu.learning.course.Lesson;
import com.jetbrains.edu.learning.course.Task;
import com.jetbrains.edu.learning.course.TaskFile;
import com.jetbrains.edu.learning.ui.StudyCondition;
import com.jetbrains.edu.learning.ui.StudyToolWindowFactory;
import org.jdom.Element;
@@ -326,7 +327,7 @@ public class StudyTaskManager implements ProjectComponent, PersistentStateCompon
return null;
}
final String taskDirName = taskDir.getName();
if (taskDirName.contains(Task.TASK_DIR)) {
if (taskDirName.contains(StudyNames.TASK_DIR)) {
final VirtualFile lessonDir = taskDir.getParent();
if (lessonDir != null) {
int lessonIndex = StudyUtils.getIndex(lessonDir.getName(), StudyNames.LESSON_DIR);
@@ -335,7 +336,7 @@ public class StudyTaskManager implements ProjectComponent, PersistentStateCompon
return null;
}
final Lesson lesson = lessons.get(lessonIndex);
int taskIndex = StudyUtils.getIndex(taskDirName, Task.TASK_DIR);
int taskIndex = StudyUtils.getIndex(taskDirName, StudyNames.TASK_DIR);
final List<Task> tasks = lesson.getTaskList();
if (!StudyUtils.indexIsValid(taskIndex, tasks)) {
return null;
@@ -352,8 +353,8 @@ public class StudyTaskManager implements ProjectComponent, PersistentStateCompon
public void fileCreated(@NotNull VirtualFileEvent event) {
final VirtualFile createdFile = event.getFile();
final VirtualFile taskDir = createdFile.getParent();
if (taskDir != null && taskDir.getName().contains(Task.TASK_DIR)) {
int taskIndex = StudyUtils.getIndex(taskDir.getName(), Task.TASK_DIR);
if (taskDir != null && taskDir.getName().contains(StudyNames.TASK_DIR)) {
int taskIndex = StudyUtils.getIndex(taskDir.getName(), StudyNames.TASK_DIR);
final VirtualFile lessonDir = taskDir.getParent();
if (lessonDir != null && lessonDir.getName().contains(StudyNames.LESSON_DIR)) {
int lessonIndex = StudyUtils.getIndex(lessonDir.getName(), StudyNames.LESSON_DIR);
@@ -4,7 +4,7 @@ import com.intellij.execution.ExecutionException;
import com.intellij.execution.process.ProcessOutput;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.edu.learning.course.Task;
import com.jetbrains.edu.courseFormat.Task;
import org.jetbrains.annotations.NotNull;
public abstract class StudyTestRunner {
@@ -29,7 +29,11 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.util.ui.UIUtil;
import com.jetbrains.edu.learning.course.*;
import com.jetbrains.edu.StudyNames;
import com.jetbrains.edu.courseFormat.AnswerPlaceholder;
import com.jetbrains.edu.courseFormat.Course;
import com.jetbrains.edu.courseFormat.Task;
import com.jetbrains.edu.courseFormat.TaskFile;
import com.jetbrains.edu.learning.editor.StudyEditor;
import com.jetbrains.edu.learning.ui.StudyToolWindowFactory;
import org.jetbrains.annotations.NotNull;
@@ -197,7 +201,7 @@ public class StudyUtils {
int taskNum = task.getIndex() + 1;
int lessonNum = task.getLesson().getIndex() + 1;
assert course != null;
final String pathToResource = FileUtil.join(course.getCourseDirectory(), StudyNames.LESSON_DIR + lessonNum, Task.TASK_DIR + taskNum);
final String pathToResource = FileUtil.join(course.getCourseDirectory(), StudyNames.LESSON_DIR + lessonNum, StudyNames.TASK_DIR + taskNum);
final File resourceFile = new File(pathToResource, copyName);
FileUtil.copy(new File(pathToResource, sourceName), resourceFile);
return resourceFile;
@@ -30,15 +30,13 @@ import com.intellij.openapi.wm.IdeFrame;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.openapi.wm.ex.StatusBarEx;
import com.intellij.openapi.wm.ex.WindowManagerEx;
import com.jetbrains.edu.learning.StudyDocumentListener;
import com.jetbrains.edu.learning.StudyState;
import com.jetbrains.edu.learning.StudyTestRunner;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.course.StudyStatus;
import com.jetbrains.edu.learning.course.Task;
import com.jetbrains.edu.learning.course.TaskFile;
import com.jetbrains.edu.learning.course.AnswerPlaceholder;
import com.jetbrains.edu.courseFormat.AnswerPlaceholder;
import com.jetbrains.edu.courseFormat.StudyStatus;
import com.jetbrains.edu.courseFormat.Task;
import com.jetbrains.edu.courseFormat.TaskFile;
import com.jetbrains.edu.learning.*;
import com.jetbrains.edu.learning.editor.StudyEditor;
import com.jetbrains.edu.learning.navigation.StudyNavigator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -98,7 +96,7 @@ public class StudyCheckAction extends DumbAwareAction {
FileEditor fileEditor = FileEditorManager.getInstance(project).getSelectedEditor(virtualFile);
if (fileEditor instanceof StudyEditor) {
StudyEditor studyEditor = (StudyEditor)fileEditor;
taskFile.drawAllWindows(studyEditor.getEditor());
StudyPainter.drawAllWindows(studyEditor.getEditor(), taskFile);
}
}
}
@@ -303,7 +301,8 @@ public class StudyCheckAction extends DumbAwareAction {
IdeFocusManager.getInstance(project).requestFocus(editorToNavigate.getContentComponent(), true);
}
});
taskFileToNavigate.navigateToFirstFailedTaskWindow(editor);
StudyNavigator.navigateToFirstFailedTaskWindow(editor, taskFileToNavigate);
}
private void runSmartTestProcess(@NotNull final VirtualFile taskDir,
@@ -325,7 +324,8 @@ public class StudyCheckAction extends DumbAwareAction {
if (!answerPlaceholder.isValid(document)) {
continue;
}
answerPlaceholder.smartCheck(project, answerFile, answerTaskFile, taskFile, testRunner, virtualFile, document);
StudySmartChecker.smartCheck(answerPlaceholder, project, answerFile, answerTaskFile, taskFile, testRunner,
virtualFile, document);
}
StudyUtils.deleteFile(answerFile);
}
@@ -20,12 +20,12 @@ import com.intellij.ui.tabs.TabInfo;
import com.intellij.ui.tabs.TabsListener;
import com.intellij.ui.tabs.impl.JBEditorTabs;
import com.intellij.util.PlatformIcons;
import com.jetbrains.edu.learning.StudyNames;
import com.jetbrains.edu.StudyNames;
import com.jetbrains.edu.courseFormat.Task;
import com.jetbrains.edu.courseFormat.TaskFile;
import com.jetbrains.edu.courseFormat.UserTest;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.course.Task;
import com.jetbrains.edu.learning.course.TaskFile;
import com.jetbrains.edu.learning.course.UserTest;
import com.jetbrains.edu.learning.editor.StudyEditor;
import com.jetbrains.edu.learning.ui.StudyTestContentPanel;
import org.jetbrains.annotations.NotNull;
@@ -62,7 +62,7 @@ public class StudyEditInputAction extends DumbAwareAction {
if (newSelection.getIcon() != null) {
int tabCount = tabbedPane.getTabCount();
VirtualFile taskDir = openedFile.getParent();
VirtualFile testsDir = taskDir.findChild(Task.USER_TESTS);
VirtualFile testsDir = taskDir.findChild(StudyNames.USER_TESTS);
assert testsDir != null;
UserTest userTest = createUserTest(testsDir, currentTask);
userTest.setEditable(true);
@@ -1,7 +1,8 @@
package com.jetbrains.edu.learning.actions;
import com.jetbrains.edu.learning.course.Task;
import com.jetbrains.edu.courseFormat.Task;
import com.jetbrains.edu.learning.editor.StudyEditor;
import com.jetbrains.edu.learning.navigation.StudyNavigator;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
@@ -23,6 +24,6 @@ public class StudyNextStudyTaskAction extends StudyTaskNavigationAction {
@Override
protected Task getTargetTask(@NotNull final Task sourceTask) {
return sourceTask.next();
return StudyNavigator.nextTask(sourceTask);
}
}
@@ -1,8 +1,8 @@
package com.jetbrains.edu.learning.actions;
import com.intellij.icons.AllIcons;
import com.jetbrains.edu.courseFormat.AnswerPlaceholder;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.course.AnswerPlaceholder;
import org.jetbrains.annotations.NotNull;
import java.util.List;
@@ -1,7 +1,7 @@
package com.jetbrains.edu.learning.actions;
import com.jetbrains.edu.courseFormat.AnswerPlaceholder;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.course.AnswerPlaceholder;
import icons.InteractiveLearningIcons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -1,8 +1,9 @@
package com.jetbrains.edu.learning.actions;
import com.jetbrains.edu.learning.course.Task;
import com.jetbrains.edu.courseFormat.Task;
import com.jetbrains.edu.learning.editor.StudyEditor;
import com.jetbrains.edu.learning.navigation.StudyNavigator;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
@@ -23,6 +24,6 @@ public class StudyPreviousStudyTaskAction extends StudyTaskNavigationAction {
@Override
protected Task getTargetTask(@NotNull final Task sourceTask) {
return sourceTask.prev();
return StudyNavigator.previousTask(sourceTask);
}
}
@@ -19,12 +19,15 @@ import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.IdeFocusManager;
import com.jetbrains.edu.learning.StudyNames;
import com.intellij.problems.WolfTheProblemSolver;
import com.jetbrains.edu.StudyNames;
import com.jetbrains.edu.courseFormat.*;
import com.jetbrains.edu.courseFormat.info.LessonInfo;
import com.jetbrains.edu.learning.StudyPainter;
import com.jetbrains.edu.learning.StudyState;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.course.*;
import com.jetbrains.edu.learning.editor.StudyEditor;
import com.jetbrains.edu.learning.navigation.StudyNavigator;
import org.jetbrains.annotations.NotNull;
import java.io.File;
@@ -63,15 +66,16 @@ public class StudyRefreshTaskFileAction extends DumbAwareAction {
}
WolfTheProblemSolver.getInstance(project).clearProblems(studyState.getVirtualFile());
taskFile.setHighlightErrors(false);
taskFile.drawAllWindows(editor);
taskFile.createGuardedBlocks(editor);
StudyPainter.drawAllWindows(editor, taskFile);
StudyPainter.createGuardedBlocks(editor, taskFile);
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
IdeFocusManager.getInstance(project).requestFocus(editor.getContentComponent(), true);
}
});
taskFile.navigateToFirstTaskWindow(editor);
StudyNavigator.navigateToFirstTaskWindow(editor, taskFile);
showBalloon(project, "You can start again now", MessageType.INFO);
}
@@ -121,7 +125,7 @@ public class StudyRefreshTaskFileAction extends DumbAwareAction {
clearDocument(document);
Task task = taskFile.getTask();
String lessonDir = StudyNames.LESSON_DIR + String.valueOf(task.getLesson().getIndex() + 1);
String taskDir = Task.TASK_DIR + String.valueOf(task.getIndex() + 1);
String taskDir = StudyNames.TASK_DIR + String.valueOf(task.getIndex() + 1);
Course course = task.getLesson().getCourse();
File resourceFile = new File(course.getCourseDirectory());
if (!resourceFile.exists()) {
@@ -14,10 +14,10 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.edu.courseFormat.Task;
import com.jetbrains.edu.courseFormat.TaskFile;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.course.Task;
import com.jetbrains.edu.learning.course.TaskFile;
import com.jetbrains.edu.learning.editor.StudyEditor;
import org.jetbrains.annotations.NotNull;
@@ -13,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.courseFormat.AnswerPlaceholder;
import com.jetbrains.edu.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.course.AnswerPlaceholder;
import com.jetbrains.edu.learning.course.Course;
import com.jetbrains.edu.learning.editor.StudyEditor;
import icons.InteractiveLearningIcons;
import org.jetbrains.annotations.NotNull;
@@ -14,10 +14,10 @@ import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowId;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.util.ui.tree.TreeUtil;
import com.jetbrains.edu.learning.StudyNames;
import com.jetbrains.edu.StudyNames;
import com.jetbrains.edu.courseFormat.Task;
import com.jetbrains.edu.courseFormat.TaskFile;
import com.jetbrains.edu.learning.StudyState;
import com.jetbrains.edu.learning.course.Task;
import com.jetbrains.edu.learning.course.TaskFile;
import com.jetbrains.edu.learning.editor.StudyEditor;
import org.jetbrains.annotations.NotNull;
@@ -60,7 +60,7 @@ abstract public class StudyTaskNavigationAction extends DumbAwareAction {
if (lessonDir == null) {
return;
}
String taskDirName = Task.TASK_DIR + String.valueOf(nextTaskIndex + 1);
String taskDirName = StudyNames.TASK_DIR + String.valueOf(nextTaskIndex + 1);
VirtualFile taskDir = lessonDir.findChild(taskDirName);
if (taskDir == null) {
return;
@@ -6,11 +6,12 @@ import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.edu.courseFormat.AnswerPlaceholder;
import com.jetbrains.edu.courseFormat.TaskFile;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.course.AnswerPlaceholder;
import com.jetbrains.edu.learning.course.TaskFile;
import com.jetbrains.edu.learning.editor.StudyEditor;
import com.jetbrains.edu.learning.navigation.StudyNavigator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -39,7 +40,7 @@ abstract public class StudyWindowNavigationAction extends DumbAwareAction {
if (nextAnswerPlaceholder == null) {
return;
}
selectedTaskFile.navigateToTaskWindow(selectedEditor, nextAnswerPlaceholder);
StudyNavigator.navigateToTaskWindow(selectedEditor, nextAnswerPlaceholder, selectedTaskFile);
selectedTaskFile.setSelectedAnswerPlaceholder(nextAnswerPlaceholder);
}
}
@@ -33,11 +33,11 @@ import com.intellij.ui.HideableTitledPanel;
import com.intellij.ui.JBColor;
import com.intellij.util.ui.EmptyClipboardOwner;
import com.intellij.util.ui.UIUtil;
import com.jetbrains.edu.courseFormat.Task;
import com.jetbrains.edu.courseFormat.TaskFile;
import com.jetbrains.edu.learning.StudyDocumentListener;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.actions.*;
import com.jetbrains.edu.learning.course.Task;
import com.jetbrains.edu.learning.course.TaskFile;
import icons.InteractiveLearningIcons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -8,8 +8,8 @@ import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.edu.courseFormat.TaskFile;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.course.TaskFile;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
@@ -0,0 +1,85 @@
package com.jetbrains.edu.learning.navigation;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.LogicalPosition;
import com.jetbrains.edu.courseFormat.*;
import com.jetbrains.edu.learning.StudyUtils;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class StudyNavigator {
private StudyNavigator() {
}
public static Task nextTask(@NotNull final Task task) {
Lesson currentLesson = task.getLesson();
List<Task> taskList = currentLesson.getTaskList();
if (task.getIndex() + 1 < taskList.size()) {
return taskList.get(task.getIndex() + 1);
}
Lesson nextLesson = nextLesson(currentLesson);
if (nextLesson == null) {
return null;
}
return StudyUtils.getFirst(nextLesson.getTaskList());
}
public static Task previousTask(@NotNull final Task task) {
Lesson currentLesson = task.getLesson();
if (task.getIndex() - 1 >= 0) {
return currentLesson.getTaskList().get(task.getIndex() - 1);
}
Lesson prevLesson = previousLesson(currentLesson);
if (prevLesson == null) {
return null;
}
//getting last task in previous lesson
return prevLesson.getTaskList().get(prevLesson.getTaskList().size() - 1);
}
public static Lesson nextLesson(@NotNull final Lesson lesson) {
List<Lesson> lessons = lesson.getCourse().getLessons();
if (lesson.getIndex() + 1 >= lessons.size()) {
return null;
}
return lessons.get(lesson.getIndex() + 1);
}
public static Lesson previousLesson(@NotNull final Lesson lesson) {
if (lesson.getIndex() - 1 < 0) {
return null;
}
return lesson.getCourse().getLessons().get(lesson.getIndex() - 1);
}
public static void navigateToFirstFailedTaskWindow(@NotNull final Editor editor, @NotNull final TaskFile taskFile) {
for (AnswerPlaceholder answerPlaceholder : taskFile.getAnswerPlaceholders()) {
if (answerPlaceholder.getStatus() != StudyStatus.Failed) {
continue;
}
navigateToTaskWindow(editor, answerPlaceholder, taskFile);
break;
}
}
public static void navigateToTaskWindow(@NotNull final Editor editor, @NotNull final AnswerPlaceholder answerPlaceholder,
@NotNull final TaskFile taskFile) {
if (!answerPlaceholder.isValid(editor.getDocument())) {
return;
}
taskFile.setSelectedAnswerPlaceholder(answerPlaceholder);
LogicalPosition taskWindowStart = new LogicalPosition(answerPlaceholder.getLine(), answerPlaceholder.getStart());
editor.getCaretModel().moveToLogicalPosition(taskWindowStart);
}
public static void navigateToFirstTaskWindow(@NotNull final Editor editor, @NotNull final TaskFile taskFile) {
if (!taskFile.getAnswerPlaceholders().isEmpty()) {
AnswerPlaceholder firstAnswerPlaceholder = StudyUtils.getFirst(taskFile.getAnswerPlaceholders());
navigateToTaskWindow(editor, firstAnswerPlaceholder, taskFile);
}
}
}
@@ -11,10 +11,10 @@ import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.ui.JBColor;
import com.intellij.ui.SimpleTextAttributes;
import com.jetbrains.edu.learning.StudyNames;
import com.jetbrains.edu.StudyNames;
import com.jetbrains.edu.courseFormat.*;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.course.*;
import icons.InteractiveLearningIcons;
import org.jetbrains.annotations.NotNull;
@@ -49,7 +49,7 @@ public class StudyDirectoryNode extends PsiDirectoryNode {
data.addText(" (" + valueName + ")", SimpleTextAttributes.GRAYED_ATTRIBUTES);
return;
}
if (valueName.contains(Task.TASK_DIR)) {
if (valueName.contains(StudyNames.TASK_DIR)) {
TaskFile file = null;
for (PsiElement child : myValue.getChildren()) {
VirtualFile virtualFile = child.getContainingFile().getVirtualFile();
@@ -69,10 +69,10 @@ public class StudyDirectoryNode extends PsiDirectoryNode {
setStudyAttributes(lesson, data, lesson.getName());
}
if (valueName.contains(Course.SANDBOX_DIR)) {
if (valueName.contains(StudyNames.SANDBOX_DIR)) {
if (myValue.getParent() != null) {
if (!myValue.getParent().getName().contains(Course.SANDBOX_DIR)) {
data.setPresentableText(Course.SANDBOX_DIR);
if (!myValue.getParent().getName().contains(StudyNames.SANDBOX_DIR)) {
data.setPresentableText(StudyNames.SANDBOX_DIR);
data.setIcon(InteractiveLearningIcons.Sandbox);
return;
}
@@ -84,14 +84,14 @@ public class StudyDirectoryNode extends PsiDirectoryNode {
@Override
public int getTypeSortWeight(boolean sortByType) {
String name = myValue.getName();
if (name.contains(StudyNames.LESSON_DIR) || name.contains(Task.TASK_DIR)) {
String logicalName = name.contains(StudyNames.LESSON_DIR) ? StudyNames.LESSON_DIR : Task.TASK_DIR;
if (name.contains(StudyNames.LESSON_DIR) || name.contains(StudyNames.TASK_DIR)) {
String logicalName = name.contains(StudyNames.LESSON_DIR) ? StudyNames.LESSON_DIR : StudyNames.TASK_DIR;
return StudyUtils.getIndex(name, logicalName) + 1;
}
return name.contains(Course.SANDBOX_DIR) ? 0 : 3;
return name.contains(StudyNames.SANDBOX_DIR) ? 0 : 3;
}
private static void setStudyAttributes(Stateful stateful, PresentationData data, String additionalName) {
private static void setStudyAttributes(StudyStateful stateful, PresentationData data, String additionalName) {
StudyStatus taskStatus = stateful.getStatus();
switch (taskStatus) {
case Unchecked: {
@@ -127,7 +127,7 @@ public class StudyDirectoryNode extends PsiDirectoryNode {
@Override
public void navigate(boolean requestFocus) {
if (myValue.getName().contains(Task.TASK_DIR)) {
if (myValue.getName().contains(StudyNames.TASK_DIR)) {
TaskFile taskFile = null;
VirtualFile virtualFile = null;
for (PsiElement child : myValue.getChildren()) {
@@ -170,7 +170,7 @@ public class StudyDirectoryNode extends PsiDirectoryNode {
@Override
public boolean expandOnDoubleClick() {
if (myValue.getName().contains(Task.TASK_DIR)) {
if (myValue.getName().contains(StudyNames.TASK_DIR)) {
return false;
}
return super.expandOnDoubleClick();
@@ -8,10 +8,9 @@ import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.jetbrains.edu.StudyNames;
import com.jetbrains.edu.courseFormat.TaskFile;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.course.Course;
import com.jetbrains.edu.learning.course.Task;
import com.jetbrains.edu.learning.course.TaskFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -33,7 +32,7 @@ public class StudyTreeStructureProvider implements TreeStructureProvider, DumbAw
if (project != null) {
if (node.getValue() instanceof PsiDirectory) {
final PsiDirectory nodeValue = (PsiDirectory)node.getValue();
if (!nodeValue.getName().contains(Task.USER_TESTS)) {
if (!nodeValue.getName().contains(StudyNames.USER_TESTS)) {
StudyDirectoryNode newNode = new StudyDirectoryNode(project, nodeValue, settings);
nodes.add(newNode);
}
@@ -51,7 +50,7 @@ public class StudyTreeStructureProvider implements TreeStructureProvider, DumbAw
}
final String parentName = parent.getName();
if (parentName != null) {
if (parentName.equals(Course.SANDBOX_DIR)) {
if (parentName.equals(StudyNames.SANDBOX_DIR)) {
nodes.add(node);
}
}
@@ -5,7 +5,11 @@ import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.util.io.HttpRequests;
import com.jetbrains.edu.learning.course.*;
import com.jetbrains.edu.courseFormat.Course;
import com.jetbrains.edu.courseFormat.Lesson;
import com.jetbrains.edu.courseFormat.Task;
import com.jetbrains.edu.courseFormat.TaskFile;
import com.jetbrains.edu.learning.CourseInfo;
import org.jetbrains.annotations.NotNull;
import java.io.BufferedReader;
@@ -3,9 +3,9 @@ package com.jetbrains.edu.learning.ui;
import com.intellij.facet.ui.FacetValidatorsManager;
import com.intellij.facet.ui.ValidationResult;
import com.intellij.icons.AllIcons;
import com.jetbrains.edu.learning.CourseInfo;
import com.jetbrains.edu.learning.StudyProjectGenerator;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.course.CourseInfo;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
@@ -3,7 +3,7 @@ package com.jetbrains.edu.learning.ui;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.ui.DocumentAdapter;
import com.intellij.ui.components.JBScrollPane;
import com.jetbrains.edu.learning.course.UserTest;
import com.jetbrains.edu.courseFormat.UserTest;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
@@ -7,11 +7,11 @@ import com.intellij.openapi.wm.ToolWindowFactory;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentFactory;
import com.intellij.util.ui.UIUtil;
import com.jetbrains.edu.courseFormat.Course;
import com.jetbrains.edu.courseFormat.Lesson;
import com.jetbrains.edu.courseFormat.StudyStatus;
import com.jetbrains.edu.courseFormat.info.LessonInfo;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.course.Course;
import com.jetbrains.edu.learning.course.Lesson;
import com.jetbrains.edu.learning.course.LessonInfo;
import com.jetbrains.edu.learning.course.StudyStatus;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
@@ -13,18 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.edu.learning;
package com.jetbrains.edu;
import org.jetbrains.annotations.NonNls;
@NonNls
public class StudyNames {
public static final String TASK_HTML = "task.html";
public static final String TASK_TESTS = "tests.py";
public static final String LESSON = "lesson";
public static final String TEST_HELPER = "test_helper.py";
public static final String USER_TESTER = "user_tester.py";
public static final String LESSON_DIR = "lesson";
public static final String TEST_TAB_NAME = "test";
public static final String USER_TEST_INPUT = "input";
public static final String USER_TEST_OUTPUT = "output";
public static final String WINDOW_POSTFIX = "_window.py";
public static final String TASK_DIR = "task";
public static final String USER_TESTS = "userTests";
public static final String SANDBOX_DIR = "Sandbox";
private StudyNames() {
}
@@ -0,0 +1,163 @@
package com.jetbrains.edu.courseFormat;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import com.intellij.openapi.editor.Document;
import com.intellij.ui.JBColor;
import org.jetbrains.annotations.NotNull;
/**
* Implementation of windows which user should type in
*/
public class AnswerPlaceholder implements Comparable, StudyStateful {
@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;
private int myIndex = -1;
private StudyStatus myStatus = StudyStatus.Unchecked;
private String myTaskText;
private MyInitialState myInitialState;
private TaskFile myTaskFile;
public void init(final TaskFile file, boolean isRestarted) {
if (!isRestarted) {
myInitialState = new MyInitialState(line, length, start);
}
myTaskFile = file;
}
public StudyStatus getStatus() {
return myStatus;
}
public void setStatus(StudyStatus status, StudyStatus oldStatus) {
myStatus = status;
}
public int getIndex() {
return myIndex;
}
public void setIndex(int index) {
myIndex = index;
}
public int getLength() {
return length;
}
public void setLength(int length) {
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;
}
public void setHint(@NotNull final String hint) {
this.hint = hint;
}
public String getPossibleAnswer() {
return possibleAnswer;
}
public void setPossibleAnswer(String possibleAnswer) {
this.possibleAnswer = possibleAnswer;
}
public MyInitialState getInitialState() {
return myInitialState;
}
public void setInitialState(@NotNull final MyInitialState initialState) {
myInitialState = initialState;
}
public TaskFile getTaskFile() {
return myTaskFile;
}
public JBColor getColor() {
if (myStatus == StudyStatus.Solved) {
return JBColor.GREEN;
}
if (myStatus == StudyStatus.Failed) {
return JBColor.RED;
}
return JBColor.BLUE;
}
public int getRealStartOffset(@NotNull final Document document) {
return document.getLineStartOffset(line) + start;
}
public boolean isValid(@NotNull final Document document) {
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;
}
@Override
public int compareTo(@NotNull Object o) {
AnswerPlaceholder answerPlaceholder = (AnswerPlaceholder)o;
if (answerPlaceholder.getTaskFile() != myTaskFile) {
throw new ClassCastException();
}
int lineDiff = line - answerPlaceholder.line;
if (lineDiff == 0) {
return start - answerPlaceholder.start;
}
return lineDiff;
}
/**
* Returns window to its initial state
*/
public void reset() {
myStatus = StudyStatus.Unchecked;
line = myInitialState.myLine;
start = myInitialState.myStart;
length = myInitialState.myLength;
}
private static class MyInitialState {
public int myLine = -1;
public int myLength = -1;
public int myStart = -1;
public MyInitialState() {
}
public MyInitialState(int line, int length, int start) {
myLine = line;
myLength = length;
myStart = start;
}
}
}
@@ -0,0 +1,83 @@
package com.jetbrains.edu.courseFormat;
import com.intellij.lang.Language;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public class Course {
public List<Lesson> lessons = new ArrayList<Lesson>();
private String description;
private String name;
private String myCourseDirectory = "";
private String author="";
private boolean myUpToDate;
private String myLanguage;
/**
* Initializes state of course
*/
public void init(boolean isRestarted) {
for (Lesson lesson : lessons) {
lesson.init(this, isRestarted);
}
}
public List<Lesson> getLessons() {
return lessons;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCourseDirectory() {
return myCourseDirectory;
}
public void setCourseDirectory(@NotNull final String courseDirectory) {
myCourseDirectory = courseDirectory;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public boolean isUpToDate() {
return myUpToDate;
}
public void setUpToDate(boolean upToDate) {
myUpToDate = upToDate;
}
public Language getLanguageById() {
return Language.findLanguageByID(myLanguage);
}
public String getLanguage() {
return myLanguage;
}
public void setLanguage(@NotNull final String language) {
myLanguage = language;
}
}
@@ -0,0 +1,82 @@
package com.jetbrains.edu.courseFormat;
import com.google.gson.annotations.SerializedName;
import com.intellij.util.xmlb.annotations.Transient;
import com.jetbrains.edu.courseFormat.info.LessonInfo;
import java.util.ArrayList;
import java.util.List;
public class Lesson implements StudyStateful {
@Transient
String id;
@Transient
public List<Integer> steps;
@Transient
public List<String> tags;
@Transient
Boolean is_public;
@SerializedName("title")
private String name;
public List<Task> taskList = new ArrayList<Task>();
private Course myCourse = null;
private int myIndex = -1;
private LessonInfo myLessonInfo = new LessonInfo();
public void init(final Course course, boolean isRestarted) {
myCourse = course;
myLessonInfo.setTaskNum(taskList.size());
myLessonInfo.setTaskUnchecked(taskList.size());
for (Task task : taskList) {
task.init(this, isRestarted);
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getIndex() {
return myIndex;
}
public void setIndex(int index) {
myIndex = index;
}
public LessonInfo getLessonInfo() {
return myLessonInfo;
}
@Transient
public StudyStatus getStatus() {
for (Task task : taskList) {
StudyStatus taskStatus = task.getStatus();
if (taskStatus == StudyStatus.Unchecked || taskStatus == StudyStatus.Failed) {
return StudyStatus.Unchecked;
}
}
return StudyStatus.Solved;
}
@Override
public void setStatus(StudyStatus status, StudyStatus oldStatus) {
for (Task task : taskList) {
task.setStatus(status, oldStatus);
}
}
public List<Task> getTaskList() {
return taskList;
}
public Course getCourse() {
return myCourse;
}
}
@@ -0,0 +1,6 @@
package com.jetbrains.edu.courseFormat;
public interface StudyStateful {
StudyStatus getStatus();
void setStatus(StudyStatus status, StudyStatus oldStatus);
}
@@ -0,0 +1,8 @@
package com.jetbrains.edu.courseFormat;
/**
* @see {@link AnswerPlaceholder#myStatus}
*/
public enum StudyStatus {
Unchecked, Solved, Failed
}
@@ -0,0 +1,141 @@
package com.jetbrains.edu.courseFormat;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.xmlb.annotations.Transient;
import com.jetbrains.edu.StudyNames;
import com.jetbrains.edu.courseFormat.info.LessonInfo;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Implementation of task which contains task files, tests, input file for tests
*/
public class Task implements StudyStateful {
private String name;
private int myIndex;
public Map<String, TaskFile> taskFiles = new HashMap<String, TaskFile>();
private String text;
private String testsText;
private Lesson myLesson;
private List<UserTest> userTests = new ArrayList<UserTest>();
/**
* Initializes state of task file
*
* @param lesson lesson which task belongs to
*/
public void init(final Lesson lesson, boolean isRestarted) {
myLesson = lesson;
for (TaskFile taskFile : taskFiles.values()) {
taskFile.init(this, isRestarted);
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getText() {
return text;
}
public void setText(@NotNull final String text) {
this.text = text;
}
public int getIndex() {
return myIndex;
}
public void setIndex(int index) {
myIndex = index;
}
public String getTestsText() {
return testsText;
}
public void setTestsText(@NotNull final String testsText) {
this.testsText = testsText;
}
public Map<String, TaskFile> getTaskFiles() {
return taskFiles;
}
@Transient
public StudyStatus getStatus() {
for (TaskFile taskFile : taskFiles.values()) {
StudyStatus taskFileStatus = taskFile.getStatus();
if (taskFileStatus == StudyStatus.Unchecked) {
return StudyStatus.Unchecked;
}
if (taskFileStatus == StudyStatus.Failed) {
return StudyStatus.Failed;
}
}
return StudyStatus.Solved;
}
public void setStatus(@NotNull final StudyStatus status, @NotNull final StudyStatus oldStatus) {
LessonInfo lessonInfo = myLesson.getLessonInfo();
if (status != oldStatus) {
lessonInfo.update(oldStatus, -1);
lessonInfo.update(status, +1);
}
for (TaskFile taskFile : taskFiles.values()) {
taskFile.setStatus(status, oldStatus);
}
}
public List<UserTest> getUserTests() {
return userTests;
}
public void setUserTests(@NotNull final List<UserTest> userTests) {
this.userTests = userTests;
}
public boolean isTaskFile(@NotNull final String fileName) {
return taskFiles.get(fileName) != null;
}
@Nullable
public TaskFile getFile(@NotNull final String fileName) {
return taskFiles.get(fileName);
}
public Lesson getLesson() {
return myLesson;
}
@Nullable
public VirtualFile getTaskDir(Project project) {
String lessonDirName = StudyNames.LESSON_DIR + String.valueOf(myLesson.getIndex() + 1);
String taskDirName = StudyNames.TASK_DIR + String.valueOf(myIndex + 1);
VirtualFile courseDir = project.getBaseDir();
if (courseDir != null) {
VirtualFile lessonDir = courseDir.findChild(lessonDirName);
if (lessonDir != null) {
return lessonDir.findChild(taskDirName);
}
}
return null;
}
}
@@ -0,0 +1,172 @@
package com.jetbrains.edu.courseFormat;
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;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Implementation of task file which contains task windows for student to type in and
* which is visible to student in project view
*/
public class TaskFile implements StudyStateful {
@SerializedName("placeholders")
private List<AnswerPlaceholder> myAnswerPlaceholders = new ArrayList<AnswerPlaceholder>();
private int myIndex = -1;
public String name;
public String text;
private Task myTask;
@Transient
private AnswerPlaceholder mySelectedAnswerPlaceholder = null;
private boolean myUserCreated = false;
private boolean myTrackChanges = true;
private boolean myHighlightErrors = false;
public void init(final Task task, boolean isRestarted) {
myTask = task;
for (AnswerPlaceholder answerPlaceholder : myAnswerPlaceholders) {
answerPlaceholder.init(this, isRestarted);
}
Collections.sort(myAnswerPlaceholders);
for (int i = 0; i < myAnswerPlaceholders.size(); i++) {
myAnswerPlaceholders.get(i).setIndex(i);
}
}
/**
* @return if all the windows in task file are marked as resolved
*/
@Transient
public StudyStatus getStatus() {
for (AnswerPlaceholder answerPlaceholder : myAnswerPlaceholders) {
StudyStatus windowStatus = answerPlaceholder.getStatus();
if (windowStatus == StudyStatus.Failed) {
return StudyStatus.Failed;
}
if (windowStatus == StudyStatus.Unchecked) {
return StudyStatus.Unchecked;
}
}
return StudyStatus.Solved;
}
public void setStatus(@NotNull final StudyStatus status, @NotNull final StudyStatus oldStatus) {
for (AnswerPlaceholder answerPlaceholder : myAnswerPlaceholders) {
answerPlaceholder.setStatus(status, oldStatus);
}
}
@Nullable
@Transient
public AnswerPlaceholder getSelectedAnswerPlaceholder() {
return mySelectedAnswerPlaceholder;
}
public void setSelectedAnswerPlaceholder(@NotNull final AnswerPlaceholder selectedAnswerPlaceholder) {
if (selectedAnswerPlaceholder.getTaskFile() == this) {
mySelectedAnswerPlaceholder = selectedAnswerPlaceholder;
}
else {
throw new IllegalArgumentException("Window may be set as selected only in task file which it belongs to");
}
}
public List<AnswerPlaceholder> getAnswerPlaceholders() {
return myAnswerPlaceholders;
}
public void setAnswerPlaceholders(List<AnswerPlaceholder> answerPlaceholders) {
this.myAnswerPlaceholders = answerPlaceholders;
}
public int getIndex() {
return myIndex;
}
public void setIndex(int index) {
myIndex = index;
}
public Task getTask() {
return myTask;
}
/**
* @param pos position in editor
* @return task window 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) {
int line = pos.line;
if (line >= document.getLineCount()) {
return null;
}
int column = pos.column;
int offset = document.getLineStartOffset(line) + column;
for (AnswerPlaceholder tw : myAnswerPlaceholders) {
if (tw.getLine() <= line) {
int twStartOffset = tw.getRealStartOffset(document);
final int length = tw.getLength() > 0 ? tw.getLength() : 0;
int twEndOffset = twStartOffset + length;
if (twStartOffset <= offset && offset <= twEndOffset) {
return tw;
}
}
}
return null;
}
public static void copy(@NotNull final TaskFile source, @NotNull final TaskFile target) {
List<AnswerPlaceholder> sourceAnswerPlaceholders = source.getAnswerPlaceholders();
List<AnswerPlaceholder> windowsCopy = new ArrayList<AnswerPlaceholder>(sourceAnswerPlaceholders.size());
for (AnswerPlaceholder answerPlaceholder : sourceAnswerPlaceholders) {
AnswerPlaceholder answerPlaceholderCopy = new AnswerPlaceholder();
answerPlaceholderCopy.setLine(answerPlaceholder.getLine());
answerPlaceholderCopy.setStart(answerPlaceholder.getStart());
answerPlaceholderCopy.setLength(answerPlaceholder.getLength());
answerPlaceholderCopy.setPossibleAnswer(answerPlaceholder.getPossibleAnswer());
answerPlaceholderCopy.setIndex(answerPlaceholder.getIndex());
windowsCopy.add(answerPlaceholderCopy);
}
target.setAnswerPlaceholders(windowsCopy);
}
public void setUserCreated(boolean userCreated) {
myUserCreated = userCreated;
}
public boolean isUserCreated() {
return myUserCreated;
}
public boolean hasFailedTaskWindows() {
return myAnswerPlaceholders.size() > 0 && getStatus() == StudyStatus.Failed;
}
public boolean isTrackChanges() {
return myTrackChanges;
}
public void setTrackChanges(boolean trackChanges) {
myTrackChanges = trackChanges;
}
public boolean isHighlightErrors() {
return myHighlightErrors;
}
public void setHighlightErrors(boolean highlightErrors) {
myHighlightErrors = highlightErrors;
}
}
@@ -0,0 +1,41 @@
package com.jetbrains.edu.courseFormat;
public class UserTest {
private String input;
private String output;
private StringBuilder myInputBuffer = new StringBuilder();
private StringBuilder myOutputBuffer = new StringBuilder();
private boolean myEditable = false;
public String getInput() {
return input;
}
public void setInput(String input) {
this.input = input;
}
public String getOutput() {
return output;
}
public void setOutput(String output) {
this.output = output;
}
public StringBuilder getInputBuffer() {
return myInputBuffer;
}
public StringBuilder getOutputBuffer() {
return myOutputBuffer;
}
public boolean isEditable() {
return myEditable;
}
public void setEditable(boolean editable) {
myEditable = editable;
}
}
@@ -0,0 +1,62 @@
package com.jetbrains.edu.courseFormat.info;
import com.jetbrains.edu.courseFormat.StudyStatus;
/**
* Implementation of class which contains information about student progress in current lesson
*/
public class LessonInfo {
private int myTaskNum;
private int myTaskFailed;
private int myTaskSolved;
private int myTaskUnchecked;
public int getTaskNum() {
return myTaskNum;
}
public void setTaskNum(int taskNum) {
myTaskNum = taskNum;
}
public int getTaskFailed() {
return myTaskFailed;
}
public void setTaskFailed(int taskFailed) {
myTaskFailed = taskFailed;
}
public int getTaskSolved() {
return myTaskSolved;
}
public void setTaskSolved(int taskSolved) {
myTaskSolved = taskSolved;
}
public int getTaskUnchecked() {
return myTaskUnchecked;
}
public void setTaskUnchecked(int taskUnchecked) {
myTaskUnchecked = taskUnchecked;
}
public void update(StudyStatus status, int delta) {
switch (status) {
case Solved: {
myTaskSolved += delta;
break;
}
case Failed: {
myTaskFailed += delta;
break;
}
case Unchecked: {
myTaskUnchecked += delta;
break;
}
}
}
}