mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Resolve merge conflicts
This commit is contained in:
+3
-1
@@ -17,6 +17,7 @@ package com.jetbrains.edu.coursecreator;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiDirectory;
|
||||
import com.intellij.psi.PsiElement;
|
||||
@@ -68,7 +69,8 @@ public class CCRefactoringElementListenerProvider implements RefactoringElementL
|
||||
|
||||
private static void tryToRenameTaskFile(PsiFile file, String oldName) {
|
||||
final PsiDirectory taskDir = file.getContainingDirectory();
|
||||
Course course = StudyTaskManager.getInstance(file.getProject()).getCourse();
|
||||
final Project project = file.getProject();
|
||||
Course course = StudyTaskManager.getInstance(project).getCourse();
|
||||
if (course == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
+1
-1
@@ -221,7 +221,7 @@ public class CCUtils {
|
||||
if (child == null) {
|
||||
continue;
|
||||
}
|
||||
Document patternDocument = StudyUtils.getPatternDocument(entry.getValue(), name);
|
||||
Document patternDocument = StudyUtils.getPatternDocument(project, entry.getValue(), name);
|
||||
Document document = FileDocumentManager.getInstance().getDocument(child);
|
||||
if (document == null || patternDocument == null) {
|
||||
return;
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ public class CCAddAnswerPlaceholder extends CCAnswerPlaceholderAction {
|
||||
}
|
||||
|
||||
private static void computeInitialState(Project project, PsiFile file, TaskFile taskFile, Document document) {
|
||||
Document patternDocument = StudyUtils.getPatternDocument(taskFile, file.getName());
|
||||
Document patternDocument = StudyUtils.getPatternDocument(project, taskFile, file.getName());
|
||||
if (patternDocument == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ public class CCHideFromStudent extends CCTaskFileActionBase {
|
||||
return;
|
||||
}
|
||||
String name = file.getName();
|
||||
VirtualFile patternFile = StudyUtils.getPatternFile(taskFile, name);
|
||||
VirtualFile patternFile = StudyUtils.getPatternFile(project, taskFile, name);
|
||||
ApplicationManager.getApplication().runWriteAction(() -> {
|
||||
if (patternFile != null) {
|
||||
try {
|
||||
|
||||
@@ -87,11 +87,10 @@
|
||||
<applicationConfigurable groupId="tools" instance="com.jetbrains.edu.learning.settings.StudyConfigurable"
|
||||
id="com.jetbrains.edu.learning.settings.StudyConfigurable"
|
||||
displayName="Education"/>
|
||||
<applicationService serviceInterface="com.jetbrains.edu.learning.stepic.StudySettings"
|
||||
serviceImplementation="com.jetbrains.edu.learning.stepic.StudySettings"/>
|
||||
|
||||
<toolWindow id="Task Description" anchor="right" factoryClass="com.jetbrains.edu.learning.ui.StudyToolWindowFactory" conditionClass="com.jetbrains.edu.learning.ui.StudyCondition"/>
|
||||
<toolWindow id="Course Progress" anchor="left" factoryClass="com.jetbrains.edu.learning.ui.StudyProgressToolWindowFactory" conditionClass="com.jetbrains.edu.learning.ui.StudyCondition"/>
|
||||
<toolWindow id="Test Results" anchor="bottom" factoryClass="com.jetbrains.edu.learning.ui.StudyTestResultsToolWindowFactory" conditionClass="com.jetbrains.edu.learning.ui.StudyCondition"/>
|
||||
<fileEditorProvider implementation="com.jetbrains.edu.learning.editor.StudyFileEditorProvider"/>
|
||||
<treeStructureProvider implementation="com.jetbrains.edu.learning.projectView.StudyTreeStructureProvider"/>
|
||||
<highlightErrorFilter implementation="com.jetbrains.edu.learning.editor.StudyHighlightErrorFilter"/>
|
||||
|
||||
+1
@@ -66,6 +66,7 @@ public abstract class StudyBasePluginConfigurator implements StudyPluginConfigur
|
||||
Task task = getTask(file);
|
||||
setTaskText(task, file.getParent());
|
||||
}
|
||||
toolWindow.setBottomComponent(null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
+38
-1
@@ -14,6 +14,7 @@ import com.intellij.util.xmlb.XmlSerializer;
|
||||
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;
|
||||
@@ -34,6 +35,7 @@ public class StudyTaskManager implements PersistentStateComponent<Element>, Dumb
|
||||
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 Map<Task, List<UserTest>> myUserTests = new HashMap<>();
|
||||
@@ -74,7 +76,7 @@ public class StudyTaskManager implements PersistentStateComponent<Element>, Dumb
|
||||
@NotNull
|
||||
public List<UserTest> getUserTests(@NotNull final Task task) {
|
||||
final List<UserTest> userTests = myUserTests.get(task);
|
||||
return userTests != null ? userTests : Collections.<UserTest>emptyList();
|
||||
return userTests != null ? userTests : Collections.emptyList();
|
||||
}
|
||||
|
||||
public void removeUserTest(@NotNull final Task task, @NotNull final UserTest userTest) {
|
||||
@@ -212,6 +214,7 @@ public class StudyTaskManager implements PersistentStateComponent<Element>, Dumb
|
||||
myTaskStatusMap = taskManager.myTaskStatusMap;
|
||||
myStudyStatusMap = taskManager.myStudyStatusMap;
|
||||
myShouldUseJavaFx = taskManager.myShouldUseJavaFx;
|
||||
myUser = taskManager.getUser();
|
||||
}
|
||||
}
|
||||
final Element oldCourseElement = state.getChild(COURSE_ELEMENT);
|
||||
@@ -276,4 +279,38 @@ public class StudyTaskManager implements PersistentStateComponent<Element>, Dumb
|
||||
public void setTurnEditingMode(boolean turnEditingMode) {
|
||||
myTurnEditingMode = turnEditingMode;
|
||||
}
|
||||
|
||||
public String getLogin() {
|
||||
if (myUser != null) {
|
||||
return myUser.getEmail();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
if (myUser != null) {
|
||||
return myUser.getPassword();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public void setLogin(String login) {
|
||||
if (myUser != null) {
|
||||
myUser.setEmail(login);
|
||||
}
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
if (myUser != null) {
|
||||
myUser.setPassword(password);
|
||||
}
|
||||
}
|
||||
|
||||
public StepicUser getUser() {
|
||||
return myUser;
|
||||
}
|
||||
|
||||
public void setUser(StepicUser user) {
|
||||
myUser = user;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,9 @@ import com.intellij.openapi.fileEditor.FileDocumentManager;
|
||||
import com.intellij.openapi.fileEditor.FileEditor;
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager;
|
||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.project.ProjectManager;
|
||||
import com.intellij.openapi.projectRoots.Sdk;
|
||||
import com.intellij.openapi.ui.popup.Balloon;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
@@ -36,6 +38,7 @@ import com.intellij.psi.PsiFile;
|
||||
import com.intellij.ui.JBColor;
|
||||
import com.intellij.ui.awt.RelativePoint;
|
||||
import com.intellij.ui.content.Content;
|
||||
import com.intellij.util.TimeoutUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.text.MarkdownUtil;
|
||||
@@ -47,6 +50,7 @@ import com.jetbrains.edu.learning.core.EduAnswerPlaceholderPainter;
|
||||
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.courseGeneration.StudyProjectGenerator;
|
||||
import com.jetbrains.edu.learning.editor.StudyEditor;
|
||||
import com.jetbrains.edu.learning.ui.StudyProgressToolWindowFactory;
|
||||
import com.jetbrains.edu.learning.ui.StudyToolWindow;
|
||||
@@ -61,6 +65,9 @@ import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
public class StudyUtils {
|
||||
private StudyUtils() {
|
||||
@@ -358,12 +365,12 @@ public class StudyUtils {
|
||||
|
||||
|
||||
@Nullable
|
||||
public static VirtualFile getPatternFile(@NotNull TaskFile taskFile, String name) {
|
||||
public static VirtualFile getPatternFile(@NotNull Project project, @NotNull TaskFile taskFile, String name) {
|
||||
Task task = taskFile.getTask();
|
||||
String lessonDir = EduNames.LESSON + String.valueOf(task.getLesson().getIndex());
|
||||
String taskDir = EduNames.TASK + String.valueOf(task.getIndex());
|
||||
Course course = task.getLesson().getCourse();
|
||||
File resourceFile = new File(course.getCourseDirectory());
|
||||
File resourceFile = getCourseDirectory(project, course);
|
||||
if (!resourceFile.exists()) {
|
||||
return null;
|
||||
}
|
||||
@@ -376,8 +383,8 @@ public class StudyUtils {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Document getPatternDocument(@NotNull final TaskFile taskFile, String name) {
|
||||
VirtualFile patternFile = getPatternFile(taskFile, name);
|
||||
public static Document getPatternDocument(@NotNull Project project, @NotNull final TaskFile taskFile, String name) {
|
||||
VirtualFile patternFile = getPatternFile(project, taskFile, name);
|
||||
if (patternFile == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -495,7 +502,7 @@ public class StudyUtils {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
public static String getTaskText(@NotNull final Project project) {
|
||||
TaskFile taskFile = getSelectedTaskFile(project);
|
||||
if (taskFile == null) {
|
||||
@@ -519,12 +526,33 @@ public class StudyUtils {
|
||||
}
|
||||
return taskFile;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Task getCurrentTask(@NotNull final Project project) {
|
||||
VirtualFile[] files = FileEditorManager.getInstance(project).getSelectedFiles();
|
||||
TaskFile taskFile = null;
|
||||
for (VirtualFile file : files) {
|
||||
taskFile = getTaskFile(project, file);
|
||||
if (taskFile != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (taskFile != null) {
|
||||
return taskFile.getTask();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void updateStudyToolWindow(Project project) {
|
||||
final StudyToolWindow studyToolWindow = getStudyToolWindow(project);
|
||||
if (studyToolWindow != null) {
|
||||
String taskText = getTaskText(project);
|
||||
studyToolWindow.setTaskText(taskText, null, project);
|
||||
if (taskText != null) {
|
||||
studyToolWindow.setTaskText(taskText, null, project);
|
||||
}
|
||||
else {
|
||||
LOG.warn("Task text is null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -532,6 +560,32 @@ public class StudyUtils {
|
||||
return StudyTaskManager.getInstance(project).getCourse() != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Project getStudyProject() {
|
||||
Project studyProject = null;
|
||||
Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
|
||||
for (Project project : openProjects) {
|
||||
if (StudyTaskManager.getInstance(project).getCourse() != null) {
|
||||
studyProject = project;
|
||||
}
|
||||
}
|
||||
return studyProject;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static File getCourseDirectory(@NotNull Project project, Course course) {
|
||||
final File courseDirectory;
|
||||
if (course.isAdaptive()) {
|
||||
courseDirectory = new File(StudyProjectGenerator.OUR_COURSES_DIR,
|
||||
StudyProjectGenerator.ADAPTIVE_COURSE_PREFIX + course.getName()
|
||||
+ "_" + StudyTaskManager.getInstance(project).getUser().getEmail());
|
||||
}
|
||||
else {
|
||||
courseDirectory = new File(StudyProjectGenerator.OUR_COURSES_DIR, course.getName());
|
||||
}
|
||||
return courseDirectory;
|
||||
}
|
||||
|
||||
public static boolean hasJavaFx() {
|
||||
try {
|
||||
Class.forName("javafx.application.Platform");
|
||||
@@ -575,6 +629,36 @@ public class StudyUtils {
|
||||
return null;
|
||||
}
|
||||
|
||||
// supposd to be called under progress
|
||||
@Nullable
|
||||
public static <T> T execCancelable(@NotNull final Callable<T> callable) {
|
||||
final Future<T> future = ApplicationManager.getApplication().executeOnPooledThread(callable);
|
||||
|
||||
while (!future.isCancelled() && !future.isDone()) {
|
||||
ProgressManager.checkCanceled();
|
||||
TimeoutUtil.sleep(500);
|
||||
}
|
||||
T result = null;
|
||||
try {
|
||||
result = future.get();
|
||||
}
|
||||
catch (InterruptedException | ExecutionException e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Task getTaskFromSelectedEditor(Project project) {
|
||||
final StudyEditor editor = getSelectedStudyEditor(project);
|
||||
Task task = null;
|
||||
if (editor != null) {
|
||||
final TaskFile file = editor.getTaskFile();
|
||||
task = file.getTask();
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
private static String convertToHtml(@NotNull final String content) {
|
||||
ArrayList<String> lines = ContainerUtil.newArrayList(content.split("\n|\r|\r\n"));
|
||||
MarkdownUtil.replaceHeaders(lines);
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ public class StudyRefreshAnswerPlaceholder extends DumbAwareAction {
|
||||
}
|
||||
StudyEditor studyEditor = StudyUtils.getSelectedStudyEditor(project);
|
||||
final StudyState studyState = new StudyState(studyEditor);
|
||||
Document patternDocument = StudyUtils.getPatternDocument(answerPlaceholder.getTaskFile(), studyState.getVirtualFile().getName());
|
||||
Document patternDocument = StudyUtils.getPatternDocument(project, answerPlaceholder.getTaskFile(), studyState.getVirtualFile().getName());
|
||||
if (patternDocument == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
+4
-3
@@ -81,7 +81,7 @@ public class StudyRefreshTaskFileAction extends StudyActionWithShortcut {
|
||||
@NotNull final Project project,
|
||||
TaskFile taskFile,
|
||||
String name) {
|
||||
if (!resetDocument(document, taskFile, name)) {
|
||||
if (!resetDocument(project, document, taskFile, name)) {
|
||||
return false;
|
||||
}
|
||||
taskFile.getTask().setStatus(StudyStatus.Unchecked);
|
||||
@@ -110,10 +110,11 @@ public class StudyRefreshTaskFileAction extends StudyActionWithShortcut {
|
||||
}
|
||||
|
||||
|
||||
private static boolean resetDocument(@NotNull final Document document,
|
||||
private static boolean resetDocument(@NotNull final Project project,
|
||||
@NotNull final Document document,
|
||||
@NotNull final TaskFile taskFile,
|
||||
String fileName) {
|
||||
final Document patternDocument = StudyUtils.getPatternDocument(taskFile, fileName);
|
||||
final Document patternDocument = StudyUtils.getPatternDocument(project, taskFile, fileName);
|
||||
if (patternDocument == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
+112
-34
@@ -8,6 +8,7 @@ import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.MessageType;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
@@ -17,11 +18,13 @@ import com.jetbrains.edu.learning.StudyTaskManager;
|
||||
import com.jetbrains.edu.learning.StudyUtils;
|
||||
import com.jetbrains.edu.learning.actions.StudyAfterCheckAction;
|
||||
import com.jetbrains.edu.learning.core.EduUtils;
|
||||
import com.jetbrains.edu.learning.courseFormat.Course;
|
||||
import com.jetbrains.edu.learning.courseFormat.StudyStatus;
|
||||
import com.jetbrains.edu.learning.courseFormat.Task;
|
||||
import com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector;
|
||||
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
|
||||
import com.jetbrains.edu.learning.stepic.StudySettings;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroundable {
|
||||
|
||||
@@ -70,57 +73,132 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun
|
||||
|
||||
@Override
|
||||
public void run(@NotNull ProgressIndicator indicator) {
|
||||
final Course course = StudyTaskManager.getInstance(myProject).getCourse();
|
||||
if (course != null && course.isAdaptive()) {
|
||||
checkForAdaptiveCourse(indicator);
|
||||
}
|
||||
else {
|
||||
checkForEduCourse(indicator);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkForEduCourse(@NotNull ProgressIndicator indicator) {
|
||||
final StudyTestsOutputParser.TestsOutput testsOutput = getTestOutput(indicator);
|
||||
|
||||
if (testsOutput != null) {
|
||||
if (testsOutput.isSuccess()) {
|
||||
onTaskSolved(testsOutput.getMessage());
|
||||
}
|
||||
else {
|
||||
onTaskFailed(testsOutput.getMessage());
|
||||
}
|
||||
runAfterTaskCheckedActions();
|
||||
postAttemptToStepic(testsOutput);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private StudyTestsOutputParser.TestsOutput getTestOutput(@NotNull ProgressIndicator indicator) {
|
||||
final CapturingProcessHandler handler = new CapturingProcessHandler(myTestProcess, null, myCommandLine);
|
||||
final ProcessOutput output = handler.runProcessWithProgressIndicator(indicator);
|
||||
if (indicator.isCanceled()) {
|
||||
ApplicationManager.getApplication().invokeLater(
|
||||
() -> StudyCheckUtils.showTestResultPopUp("Check cancelled", MessageType.WARNING.getPopupBackground(), myProject));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
final StudyTestsOutputParser.TestsOutput testsOutput = StudyTestsOutputParser.getTestsOutput(output);
|
||||
String stderr = output.getStderr();
|
||||
if (!stderr.isEmpty()) {
|
||||
ApplicationManager.getApplication().invokeLater(() ->
|
||||
StudyCheckUtils.showTestResultPopUp("Failed to launch checking",
|
||||
MessageType.WARNING.getPopupBackground(),
|
||||
myProject));
|
||||
//log error output of tests
|
||||
LOG.info("#educational " + stderr);
|
||||
return;
|
||||
final Course course = StudyTaskManager.getInstance(myProject).getCourse();
|
||||
if (course != null) {
|
||||
final StudyTestsOutputParser.TestsOutput testsOutput = StudyTestsOutputParser.getTestsOutput(output, course.isAdaptive());
|
||||
String stderr = output.getStderr();
|
||||
if (!stderr.isEmpty()) {
|
||||
ApplicationManager.getApplication().invokeLater(() ->
|
||||
StudyCheckUtils.showTestResultPopUp("Failed to launch checking",
|
||||
MessageType.WARNING.getPopupBackground(),
|
||||
myProject));
|
||||
//log error output of tests
|
||||
LOG.info("#educational " + stderr);
|
||||
return null;
|
||||
}
|
||||
return testsOutput;
|
||||
}
|
||||
|
||||
postAttemptToStepic(testsOutput);
|
||||
|
||||
|
||||
if (testsOutput.isSuccess()) {
|
||||
onTaskSolved(testsOutput);
|
||||
}
|
||||
else {
|
||||
onTaskFailed(testsOutput);
|
||||
}
|
||||
runAfterTaskSolvedActions();
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void onTaskFailed(StudyTestsOutputParser.TestsOutput testsOutput) {
|
||||
private void checkForAdaptiveCourse(ProgressIndicator indicator) {
|
||||
final StudyTestsOutputParser.TestsOutput testOutput = getTestOutput(indicator);
|
||||
if (testOutput != null) {
|
||||
if (testOutput.isSuccess()) {
|
||||
final Pair<Boolean, String> pair = EduAdaptiveStepicConnector.checkTask(myProject, myTask);
|
||||
if (pair != null && !(!pair.getFirst() && pair.getSecond().isEmpty())) {
|
||||
if (pair.getFirst()) {
|
||||
onTaskSolved("Congratulations! Remote tests passed.");
|
||||
if (myStatusBeforeCheck != StudyStatus.Solved) {
|
||||
EduAdaptiveStepicConnector.addNextRecommendedTask(myProject, 2);
|
||||
}
|
||||
}
|
||||
else {
|
||||
final String checkMessage = pair.getSecond();
|
||||
onTaskFailed(checkMessage);
|
||||
}
|
||||
runAfterTaskCheckedActions();
|
||||
}
|
||||
else {
|
||||
ApplicationManager.getApplication().invokeLater(() -> StudyCheckUtils.showTestResultPopUp("Failed to launch checking",
|
||||
MessageType.WARNING
|
||||
.getPopupBackground(),
|
||||
myProject));
|
||||
}
|
||||
}
|
||||
else {
|
||||
onTaskFailed(testOutput.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onTaskFailed(String message) {
|
||||
myTaskManger.setStatus(myTask, StudyStatus.Failed);
|
||||
ApplicationManager.getApplication().invokeLater(
|
||||
() -> StudyCheckUtils.showTestResultPopUp(testsOutput.getMessage(), MessageType.ERROR.getPopupBackground(), myProject));
|
||||
final Course course = StudyTaskManager.getInstance(myProject).getCourse();
|
||||
|
||||
if (course != null) {
|
||||
if (course.isAdaptive()) {
|
||||
ApplicationManager.getApplication().invokeLater(
|
||||
() -> {
|
||||
StudyCheckUtils.showTestResultPopUp("Failed", MessageType.ERROR.getPopupBackground(), myProject);
|
||||
StudyCheckUtils.showTestResultsToolWindow(myProject, message, false);
|
||||
});
|
||||
}
|
||||
else {
|
||||
ApplicationManager.getApplication()
|
||||
.invokeLater(() -> StudyCheckUtils.showTestResultPopUp(message, MessageType.ERROR.getPopupBackground(), myProject));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onTaskSolved(StudyTestsOutputParser.TestsOutput testsOutput) {
|
||||
protected void onTaskSolved(String message) {
|
||||
myTaskManger.setStatus(myTask, StudyStatus.Solved);
|
||||
ApplicationManager.getApplication().invokeLater(
|
||||
() -> StudyCheckUtils.showTestResultPopUp(testsOutput.getMessage(), MessageType.INFO.getPopupBackground(), myProject));
|
||||
final Course course = StudyTaskManager.getInstance(myProject).getCourse();
|
||||
|
||||
if (course != null) {
|
||||
if (course.isAdaptive()) {
|
||||
ApplicationManager.getApplication().invokeLater(
|
||||
() -> {
|
||||
StudyCheckUtils.showTestResultPopUp("Congratulations!", MessageType.INFO.getPopupBackground(), myProject);
|
||||
StudyCheckUtils.showTestResultsToolWindow(myProject, message, true);
|
||||
});
|
||||
}
|
||||
else {
|
||||
ApplicationManager.getApplication()
|
||||
.invokeLater(() -> StudyCheckUtils.showTestResultPopUp(message, MessageType.INFO.getPopupBackground(), myProject));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void runAfterTaskSolvedActions() {
|
||||
private void runAfterTaskCheckedActions() {
|
||||
StudyPluginConfigurator configurator = StudyUtils.getConfigurator(myProject);
|
||||
if (configurator != null) {
|
||||
StudyAfterCheckAction[] checkActions = configurator.getAfterCheckActions();
|
||||
if (checkActions != null) {
|
||||
for (StudyAfterCheckAction action: checkActions) {
|
||||
for (StudyAfterCheckAction action : checkActions) {
|
||||
action.run(myProject, myTask, myStatusBeforeCheck);
|
||||
}
|
||||
}
|
||||
@@ -130,8 +208,8 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun
|
||||
}
|
||||
}
|
||||
|
||||
protected void postAttemptToStepic(StudyTestsOutputParser.TestsOutput testsOutput) {
|
||||
final StudySettings studySettings = StudySettings.getInstance();
|
||||
protected void postAttemptToStepic(@NotNull StudyTestsOutputParser.TestsOutput testsOutput) {
|
||||
final StudyTaskManager studySettings = StudyTaskManager.getInstance(myProject);
|
||||
final String login = studySettings.getLogin();
|
||||
final String password = StringUtil.isEmptyOrSpaces(login) ? "" : studySettings.getPassword();
|
||||
EduStepicConnector.postAttempt(myTask, testsOutput.isSuccess(), login, password);
|
||||
|
||||
+46
-17
@@ -1,5 +1,6 @@
|
||||
package com.jetbrains.edu.learning.checker;
|
||||
|
||||
import com.intellij.execution.ui.ConsoleViewContentType;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
@@ -15,24 +16,26 @@ import com.intellij.openapi.ui.popup.BalloonBuilder;
|
||||
import com.intellij.openapi.ui.popup.JBPopupFactory;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.wm.IdeFocusManager;
|
||||
import com.intellij.openapi.wm.IdeFrame;
|
||||
import com.intellij.openapi.wm.WindowManager;
|
||||
import com.intellij.openapi.wm.*;
|
||||
import com.intellij.openapi.wm.ex.StatusBarEx;
|
||||
import com.intellij.openapi.wm.ex.WindowManagerEx;
|
||||
import com.intellij.ui.content.Content;
|
||||
import com.jetbrains.edu.learning.StudyState;
|
||||
import com.jetbrains.edu.learning.StudyTaskManager;
|
||||
import com.jetbrains.edu.learning.StudyUtils;
|
||||
import com.jetbrains.edu.learning.core.EduDocumentListener;
|
||||
import com.jetbrains.edu.learning.core.EduNames;
|
||||
import com.jetbrains.edu.learning.core.EduUtils;
|
||||
import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
|
||||
import com.jetbrains.edu.learning.courseFormat.Task;
|
||||
import com.jetbrains.edu.learning.courseFormat.TaskFile;
|
||||
import com.jetbrains.edu.learning.StudyState;
|
||||
import com.jetbrains.edu.learning.StudyTaskManager;
|
||||
import com.jetbrains.edu.learning.StudyUtils;
|
||||
import com.jetbrains.edu.learning.editor.StudyEditor;
|
||||
import com.jetbrains.edu.learning.navigation.StudyNavigator;
|
||||
import com.jetbrains.edu.learning.ui.StudyTestResultsToolWindowFactory;
|
||||
import com.jetbrains.python.console.PythonConsoleView;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
@@ -61,9 +64,9 @@ public class StudyCheckUtils {
|
||||
}
|
||||
|
||||
public static void navigateToFailedPlaceholder(@NotNull final StudyState studyState,
|
||||
@NotNull final Task task,
|
||||
@NotNull final VirtualFile taskDir,
|
||||
@NotNull final Project project) {
|
||||
@NotNull final Task task,
|
||||
@NotNull final VirtualFile taskDir,
|
||||
@NotNull final Project project) {
|
||||
TaskFile selectedTaskFile = studyState.getTaskFile();
|
||||
Editor editor = studyState.getEditor();
|
||||
TaskFile taskFileToNavigate = selectedTaskFile;
|
||||
@@ -109,10 +112,10 @@ public class StudyCheckUtils {
|
||||
|
||||
|
||||
public static void runSmartTestProcess(@NotNull final VirtualFile taskDir,
|
||||
@NotNull final StudyTestRunner testRunner,
|
||||
final String taskFileName,
|
||||
@NotNull final TaskFile taskFile,
|
||||
@NotNull final Project project) {
|
||||
@NotNull final StudyTestRunner testRunner,
|
||||
final String taskFileName,
|
||||
@NotNull final TaskFile taskFile,
|
||||
@NotNull final Project project) {
|
||||
final TaskFile answerTaskFile = new TaskFile();
|
||||
answerTaskFile.name = taskFileName;
|
||||
final VirtualFile virtualFile = taskDir.findChild(taskFileName);
|
||||
@@ -135,11 +138,10 @@ public class StudyCheckUtils {
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static VirtualFile getCopyWithAnswers(@NotNull final VirtualFile taskDir,
|
||||
@NotNull final VirtualFile file,
|
||||
@NotNull final TaskFile source,
|
||||
@NotNull final TaskFile target) {
|
||||
@NotNull final VirtualFile file,
|
||||
@NotNull final TaskFile source,
|
||||
@NotNull final TaskFile target) {
|
||||
VirtualFile copy = null;
|
||||
try {
|
||||
|
||||
@@ -191,4 +193,31 @@ public class StudyCheckUtils {
|
||||
EduUtils.flushWindows(taskFile, virtualFile, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void showTestResultsToolWindow(@NotNull final Project project, @NotNull final String message, boolean solved) {
|
||||
final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project);
|
||||
ToolWindow window = toolWindowManager.getToolWindow("Test Results");
|
||||
if (window == null) {
|
||||
toolWindowManager.registerToolWindow("Test Results", true, ToolWindowAnchor.BOTTOM);
|
||||
window = toolWindowManager.getToolWindow("Test Results");
|
||||
new StudyTestResultsToolWindowFactory().createToolWindowContent(project, window);
|
||||
}
|
||||
|
||||
final Content[] contents = window.getContentManager().getContents();
|
||||
for (Content content : contents) {
|
||||
final JComponent component = content.getComponent();
|
||||
if (component instanceof PythonConsoleView) {
|
||||
((PythonConsoleView)component).clear();
|
||||
if (!solved) {
|
||||
((PythonConsoleView)component).print(message, ConsoleViewContentType.ERROR_OUTPUT);
|
||||
}
|
||||
else {
|
||||
((PythonConsoleView)component).print(message, ConsoleViewContentType.NORMAL_OUTPUT);
|
||||
}
|
||||
window.setAvailable(true, () -> {});
|
||||
window.show(() -> {});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-8
@@ -14,6 +14,7 @@ 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.AnswerPlaceholder;
|
||||
import com.jetbrains.edu.learning.courseFormat.Course;
|
||||
import com.jetbrains.edu.learning.courseFormat.StudyStatus;
|
||||
import com.jetbrains.edu.learning.courseFormat.TaskFile;
|
||||
import com.jetbrains.edu.learning.StudyTaskManager;
|
||||
@@ -65,14 +66,17 @@ public class StudySmartChecker {
|
||||
Process smartTestProcess = testRunner.createCheckProcess(project, windowCopy.getPath());
|
||||
final CapturingProcessHandler handler = new CapturingProcessHandler(smartTestProcess, null, windowCopy.getPath());
|
||||
final ProcessOutput output = handler.runProcess();
|
||||
boolean res = StudyTestsOutputParser.getTestsOutput(output).isSuccess();
|
||||
StudyTaskManager.getInstance(project).setStatus(userAnswerPlaceholder, res ? StudyStatus.Solved : StudyStatus.Failed);
|
||||
StudyUtils.deleteFile(windowCopy);
|
||||
if (fileWindows != null) {
|
||||
StudyUtils.deleteFile(fileWindows);
|
||||
}
|
||||
if (!resourceFile.delete()) {
|
||||
LOG.error("failed to delete", resourceFile.getPath());
|
||||
final Course course = StudyTaskManager.getInstance(project).getCourse();
|
||||
if (course != null) {
|
||||
boolean res = StudyTestsOutputParser.getTestsOutput(output, course.isAdaptive()).isSuccess();
|
||||
StudyTaskManager.getInstance(project).setStatus(userAnswerPlaceholder, res ? StudyStatus.Solved : StudyStatus.Failed);
|
||||
StudyUtils.deleteFile(windowCopy);
|
||||
if (fileWindows != null) {
|
||||
StudyUtils.deleteFile(fileWindows);
|
||||
}
|
||||
if (!resourceFile.delete()) {
|
||||
LOG.error("failed to delete", resourceFile.getPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+25
-5
@@ -3,8 +3,10 @@ package com.jetbrains.edu.learning.checker;
|
||||
import com.intellij.execution.process.ProcessOutput;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class StudyTestsOutputParser {
|
||||
private static final String ourStudyPrefix = "#educational_plugin";
|
||||
private static final String STUDY_PREFIX = "#educational_plugin";
|
||||
public static final String TEST_OK = "test OK";
|
||||
private static final String TEST_FAILED = "FAILED + ";
|
||||
private static final String CONGRATS_MESSAGE = "CONGRATS_MESSAGE ";
|
||||
@@ -29,10 +31,12 @@ public class StudyTestsOutputParser {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static TestsOutput getTestsOutput(@NotNull final ProcessOutput processOutput) {
|
||||
public static TestsOutput getTestsOutput(@NotNull final ProcessOutput processOutput, final boolean isAdaptive) {
|
||||
String congratulations = CONGRATULATIONS;
|
||||
for (String line : processOutput.getStdoutLines()) {
|
||||
if (line.startsWith(ourStudyPrefix)) {
|
||||
final List<String> lines = processOutput.getStdoutLines();
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
final String line = lines.get(i);
|
||||
if (line.startsWith(STUDY_PREFIX)) {
|
||||
if (line.contains(TEST_OK)) {
|
||||
continue;
|
||||
}
|
||||
@@ -42,7 +46,23 @@ public class StudyTestsOutputParser {
|
||||
}
|
||||
|
||||
if (line.contains(TEST_FAILED)) {
|
||||
return new TestsOutput(false, line.substring(line.indexOf(TEST_FAILED) + TEST_FAILED.length()));
|
||||
if (!isAdaptive) {
|
||||
return new TestsOutput(false, line.substring(line.indexOf(TEST_FAILED) + TEST_FAILED.length()));
|
||||
}
|
||||
else {
|
||||
final StringBuilder builder = new StringBuilder(line.substring(line.indexOf(TEST_FAILED) + TEST_FAILED.length()) + "\n");
|
||||
for (int j = i + 1; j < lines.size(); j++) {
|
||||
final String failedTextLine = lines.get(j);
|
||||
if (!failedTextLine.contains(STUDY_PREFIX) || !failedTextLine.contains(CONGRATS_MESSAGE)) {
|
||||
builder.append(failedTextLine);
|
||||
builder.append("\n");
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return new TestsOutput(false, builder.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+32
-20
@@ -7,31 +7,27 @@ 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.CourseInfo;
|
||||
import com.jetbrains.edu.learning.stepic.StepicUser;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Course {
|
||||
@Expose
|
||||
private List<Lesson> lessons = new ArrayList<Lesson>();
|
||||
|
||||
@Expose private List<Lesson> lessons = new ArrayList<Lesson>();
|
||||
@Expose private List<StepicUser> authors = new ArrayList<StepicUser>();
|
||||
@Expose private String description;
|
||||
@Expose private String name;
|
||||
private String myCourseDirectory = "";
|
||||
@Expose private List<CourseInfo.Author> authors = new ArrayList<CourseInfo.Author>();
|
||||
private boolean myUpToDate;
|
||||
@Expose private String myCourseDirectory = "";
|
||||
@Expose private int id;
|
||||
@Expose private boolean myUpToDate;
|
||||
@Expose private boolean isAdaptive;
|
||||
@Expose @SerializedName("language") private String myLanguage = "Python";
|
||||
|
||||
@Expose @SerializedName("language")
|
||||
private String myLanguage = "Python";
|
||||
|
||||
//this field is used to distinguish ordinary and CheckIO projects
|
||||
//this field is used to distinguish ordinary and CheckIO projects,
|
||||
//"PyCharm" is used here for historical reasons
|
||||
private String courseType = EduNames.PYCHARM;
|
||||
|
||||
//this field is used to distinguish study and course creator modes
|
||||
private String courseMode = EduNames.STUDY;
|
||||
@Expose private String courseType = EduNames.PYCHARM;
|
||||
@Expose private String courseMode = EduNames.STUDY; //this field is used to distinguish study and course creator modes
|
||||
|
||||
/**
|
||||
* Initializes state of course
|
||||
@@ -72,20 +68,20 @@ public class Course {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<CourseInfo.Author> getAuthors() {
|
||||
public List<StepicUser> getAuthors() {
|
||||
return authors;
|
||||
}
|
||||
|
||||
public static String getAuthorsString(@NotNull List<CourseInfo.Author> authors) {
|
||||
public static String getAuthorsString(@NotNull List<StepicUser> authors) {
|
||||
return StringUtil.join(authors, author -> author.getName(), ", ");
|
||||
}
|
||||
|
||||
public void setAuthors(String[] authors) {
|
||||
this.authors = new ArrayList<CourseInfo.Author>();
|
||||
this.authors = new ArrayList<StepicUser>();
|
||||
for (String name : authors) {
|
||||
final List<String> pair = StringUtil.split(name, " ");
|
||||
if (!pair.isEmpty())
|
||||
this.authors.add(new CourseInfo.Author(pair.get(0), pair.size() > 1 ? pair.get(1) : ""));
|
||||
this.authors.add(new StepicUser(pair.get(0), pair.size() > 1 ? pair.get(1) : ""));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +129,7 @@ public class Course {
|
||||
myLanguage = language;
|
||||
}
|
||||
|
||||
public void setAuthors(List<CourseInfo.Author> authors) {
|
||||
public void setAuthors(List<StepicUser> authors) {
|
||||
this.authors = authors;
|
||||
}
|
||||
|
||||
@@ -146,6 +142,22 @@ public class Course {
|
||||
this.courseType = courseType;
|
||||
}
|
||||
|
||||
public boolean isAdaptive() {
|
||||
return isAdaptive;
|
||||
}
|
||||
|
||||
public void setAdaptive(boolean adaptive) {
|
||||
isAdaptive = adaptive;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getCourseMode() {
|
||||
return courseMode;
|
||||
}
|
||||
|
||||
+7
-19
@@ -11,25 +11,13 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Lesson implements StudyItem {
|
||||
public int id;
|
||||
@Transient
|
||||
public List<Integer> steps;
|
||||
@Transient
|
||||
public List<String> tags;
|
||||
@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
|
||||
private int myIndex = -1;
|
||||
@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<String> tags;
|
||||
@Transient private Course myCourse = null;
|
||||
|
||||
public void initLesson(final Course course, boolean isRestarted) {
|
||||
setCourse(course);
|
||||
|
||||
+7
-16
@@ -20,22 +20,13 @@ 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;
|
||||
|
||||
// index is visible to user number of task from 1 to task number
|
||||
private int myIndex;
|
||||
private StudyStatus myStatus = StudyStatus.Uninitialized;
|
||||
|
||||
private int myStepicId;
|
||||
|
||||
@Expose
|
||||
@SerializedName("task_files")
|
||||
public Map<String, TaskFile> taskFiles = new HashMap<String, TaskFile>();
|
||||
|
||||
private String text;
|
||||
private Map<String, String> testsText = new HashMap<String, String>();
|
||||
|
||||
@Expose private String name;
|
||||
@Expose private String text;
|
||||
@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;
|
||||
@Transient private Lesson myLesson;
|
||||
|
||||
public Task() {}
|
||||
|
||||
+7
-12
@@ -18,19 +18,14 @@ import java.util.List;
|
||||
*/
|
||||
|
||||
public class TaskFile {
|
||||
@SerializedName("placeholders")
|
||||
@Expose
|
||||
private List<AnswerPlaceholder> myAnswerPlaceholders = new ArrayList<AnswerPlaceholder>();
|
||||
private int myIndex = -1;
|
||||
|
||||
@Expose
|
||||
public String name;
|
||||
@Expose
|
||||
public String text;
|
||||
@Expose public String name;
|
||||
@Expose public String text;
|
||||
@Expose private int myIndex = -1;
|
||||
@Expose private boolean myUserCreated = false;
|
||||
@Expose private boolean myTrackChanges = true;
|
||||
@Expose private boolean myHighlightErrors = false;
|
||||
@Expose @SerializedName("placeholders") private List<AnswerPlaceholder> myAnswerPlaceholders = new ArrayList<AnswerPlaceholder>();
|
||||
@Transient private Task myTask;
|
||||
private boolean myUserCreated = false;
|
||||
private boolean myTrackChanges = true;
|
||||
private boolean myHighlightErrors = false;
|
||||
|
||||
public void initTaskFile(final Task task, boolean isRestarted) {
|
||||
setTask(task);
|
||||
|
||||
+26
-26
@@ -4,13 +4,13 @@ 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.learning.StudyTaskManager;
|
||||
import com.jetbrains.edu.learning.StudyUtils;
|
||||
import com.jetbrains.edu.learning.core.EduNames;
|
||||
import com.jetbrains.edu.learning.courseFormat.Course;
|
||||
import com.jetbrains.edu.learning.courseFormat.Lesson;
|
||||
import com.jetbrains.edu.learning.courseFormat.Task;
|
||||
import com.jetbrains.edu.learning.courseFormat.TaskFile;
|
||||
import com.jetbrains.edu.learning.StudyTaskManager;
|
||||
import com.jetbrains.edu.learning.StudyUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
@@ -106,30 +106,30 @@ public class StudyGenerator {
|
||||
public static void createCourse(@NotNull final Course course, @NotNull final VirtualFile baseDir, @NotNull final File resourceRoot,
|
||||
@NotNull final Project project) {
|
||||
|
||||
try {
|
||||
final List<Lesson> lessons = course.getLessons();
|
||||
for (int i = 1; i <= lessons.size(); i++) {
|
||||
Lesson lesson = lessons.get(i - 1);
|
||||
lesson.setIndex(i);
|
||||
createLesson(lesson, baseDir, resourceRoot, project);
|
||||
}
|
||||
baseDir.createChildDirectory(project, EduNames.SANDBOX_DIR);
|
||||
File[] files = resourceRoot.listFiles(
|
||||
(dir, name) -> !name.contains(EduNames.LESSON) && !name.equals(EduNames.COURSE_META_FILE) && !name.equals(EduNames.HINTS));
|
||||
for (File file : files) {
|
||||
File dir = new File(baseDir.getPath(), file.getName());
|
||||
if (file.isDirectory()) {
|
||||
FileUtil.copyDir(file, dir);
|
||||
continue;
|
||||
}
|
||||
|
||||
FileUtil.copy(file, dir);
|
||||
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.error(e);
|
||||
}
|
||||
try {
|
||||
final List<Lesson> lessons = course.getLessons();
|
||||
for (int i = 1; i <= lessons.size(); i++) {
|
||||
Lesson lesson = lessons.get(i - 1);
|
||||
lesson.setIndex(i);
|
||||
createLesson(lesson, baseDir, resourceRoot, project);
|
||||
}
|
||||
baseDir.createChildDirectory(project, EduNames.SANDBOX_DIR);
|
||||
File[] files = resourceRoot.listFiles(
|
||||
(dir, name) -> !name.contains(EduNames.LESSON) && !name.equals(EduNames.COURSE_META_FILE) && !name.equals(EduNames.HINTS));
|
||||
for (File file : files) {
|
||||
File dir = new File(baseDir.getPath(), file.getName());
|
||||
if (file.isDirectory()) {
|
||||
FileUtil.copyDir(file, dir);
|
||||
continue;
|
||||
}
|
||||
|
||||
FileUtil.copy(file, dir);
|
||||
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+110
-58
@@ -8,10 +8,12 @@ import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.PathManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.project.DumbModePermission;
|
||||
import com.intellij.openapi.project.DumbService;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.util.ThrowableComputable;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
@@ -22,38 +24,40 @@ import com.intellij.platform.templates.github.ZipUtil;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiManager;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.edu.learning.StudyProjectComponent;
|
||||
import com.jetbrains.edu.learning.StudyTaskManager;
|
||||
import com.jetbrains.edu.learning.StudyUtils;
|
||||
import com.jetbrains.edu.learning.core.EduNames;
|
||||
import com.jetbrains.edu.learning.core.EduUtils;
|
||||
import com.jetbrains.edu.learning.courseFormat.Course;
|
||||
import com.jetbrains.edu.learning.courseFormat.Lesson;
|
||||
import com.jetbrains.edu.learning.courseFormat.Task;
|
||||
import com.jetbrains.edu.learning.courseFormat.TaskFile;
|
||||
import com.jetbrains.edu.learning.StudyProjectComponent;
|
||||
import com.jetbrains.edu.learning.StudyTaskManager;
|
||||
import com.jetbrains.edu.learning.StudyUtils;
|
||||
import com.jetbrains.edu.learning.stepic.CourseInfo;
|
||||
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
|
||||
import com.jetbrains.edu.learning.stepic.StepicUser;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
import static com.jetbrains.edu.learning.StudyUtils.execCancelable;
|
||||
|
||||
public class StudyProjectGenerator {
|
||||
private static final Logger LOG = Logger.getInstance(StudyProjectGenerator.class.getName());
|
||||
private final List<SettingsListener> myListeners = ContainerUtil.newArrayList();
|
||||
protected static final File ourCoursesDir = new File(PathManager.getConfigPath(), "courses");
|
||||
private static final String CACHE_NAME = "courseNames.txt";
|
||||
private List<CourseInfo> myCourses = new ArrayList<>();
|
||||
protected CourseInfo mySelectedCourseInfo;
|
||||
private static final String COURSE_NAME_ATTRIBUTE = "name";
|
||||
private static final String COURSE_DESCRIPTION = "description";
|
||||
public static final String AUTHOR_ATTRIBUTE = "authors";
|
||||
public static final String LANGUAGE_ATTRIBUTE = "language";
|
||||
public static final String ADAPTIVE_COURSE_PREFIX = "__AdaptivePyCharmPython__";
|
||||
public static final File OUR_COURSES_DIR = new File(PathManager.getConfigPath(), "courses");
|
||||
private static final Logger LOG = Logger.getInstance(StudyProjectGenerator.class.getName());
|
||||
private static final String COURSE_NAME_ATTRIBUTE = "name";
|
||||
private static final String COURSE_DESCRIPTION = "description";
|
||||
private static final String CACHE_NAME = "courseNames.txt";
|
||||
private final List<SettingsListener> myListeners = ContainerUtil.newArrayList();
|
||||
public StepicUser myUser;
|
||||
private List<CourseInfo> myCourses = new ArrayList<>();
|
||||
protected CourseInfo mySelectedCourseInfo;
|
||||
|
||||
public void setCourses(List<CourseInfo> courses) {
|
||||
myCourses = courses;
|
||||
@@ -64,48 +68,67 @@ public class StudyProjectGenerator {
|
||||
}
|
||||
|
||||
public void generateProject(@NotNull final Project project, @NotNull final VirtualFile baseDir) {
|
||||
final Course course = getCourse();
|
||||
StudyTaskManager.getInstance(project).setUser(myUser);
|
||||
final Course course = getCourse(project);
|
||||
if (course == null) {
|
||||
LOG.warn("Course is null");
|
||||
return;
|
||||
}
|
||||
final File courseDirectory = StudyUtils.getCourseDirectory(project, course);
|
||||
StudyTaskManager.getInstance(project).setCourse(course);
|
||||
ApplicationManager.getApplication().invokeLater(
|
||||
() -> DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND,
|
||||
() -> ApplicationManager.getApplication().runWriteAction(() -> {
|
||||
course.initCourse(false);
|
||||
final File courseDirectory = new File(ourCoursesDir, course.getName());
|
||||
StudyGenerator.createCourse(course, baseDir, courseDirectory, project);
|
||||
course.setCourseDirectory(new File(ourCoursesDir, mySelectedCourseInfo.getName()).getAbsolutePath());
|
||||
course.setCourseDirectory(courseDirectory.getAbsolutePath());
|
||||
VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
|
||||
StudyProjectComponent.getInstance(project).registerStudyToolWindow(course);
|
||||
openFirstTask(course, project);
|
||||
})));
|
||||
}
|
||||
|
||||
protected Course getCourse() {
|
||||
Reader reader = null;
|
||||
try {
|
||||
final File courseFile = new File(new File(ourCoursesDir, mySelectedCourseInfo.getName()), EduNames.COURSE_META_FILE);
|
||||
if (courseFile.exists()) {
|
||||
reader = new InputStreamReader(new FileInputStream(courseFile), "UTF-8");
|
||||
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
|
||||
final Course course = gson.fromJson(reader, Course.class);
|
||||
course.initCourse(false);
|
||||
return course;
|
||||
@Nullable
|
||||
protected Course getCourse(@NotNull final Project project) {
|
||||
final File courseFile = new File(new File(OUR_COURSES_DIR, mySelectedCourseInfo.getName()), EduNames.COURSE_META_FILE);
|
||||
if (courseFile.exists()) {
|
||||
return readCourseFromCache(courseFile, false);
|
||||
}
|
||||
else if (myUser != null) {
|
||||
final File adaptiveCourseFile = new File(new File(OUR_COURSES_DIR, ADAPTIVE_COURSE_PREFIX +
|
||||
mySelectedCourseInfo.getName() + "_" +
|
||||
myUser.getEmail()), EduNames.COURSE_META_FILE);
|
||||
if (adaptiveCourseFile.exists()) {
|
||||
return readCourseFromCache(adaptiveCourseFile, true);
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException | UnsupportedEncodingException e) {
|
||||
LOG.error(e);
|
||||
final Course course = EduStepicConnector.getCourse(project, mySelectedCourseInfo);
|
||||
if (course != null) {
|
||||
flushCourse(project, course);
|
||||
course.initCourse(false);
|
||||
}
|
||||
return course;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Course readCourseFromCache(@NotNull File courseFile, boolean isAdaptive) {
|
||||
Reader reader = null;
|
||||
try {
|
||||
reader = new InputStreamReader(new FileInputStream(courseFile), "UTF-8");
|
||||
Gson gson = new GsonBuilder().create();
|
||||
final Course course = gson.fromJson(reader, Course.class);
|
||||
course.initCourse(isAdaptive);
|
||||
return course;
|
||||
}
|
||||
catch (UnsupportedEncodingException e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
finally {
|
||||
StudyUtils.closeSilently(reader);
|
||||
}
|
||||
final Course course = EduStepicConnector.getCourse(mySelectedCourseInfo);
|
||||
if (course != null) {
|
||||
flushCourse(course);
|
||||
}
|
||||
return course;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void openFirstTask(@NotNull final Course course, @NotNull final Project project) {
|
||||
@@ -131,7 +154,8 @@ public class StudyProjectGenerator {
|
||||
final PsiFile file = PsiManager.getInstance(project).findFile(activeVirtualFile);
|
||||
ProjectView.getInstance(project).select(file, activeVirtualFile, true);
|
||||
FileEditorManager.getInstance(project).openFile(activeVirtualFile, true);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
String first = StudyUtils.getFirst(taskFiles.keySet());
|
||||
if (first != null) {
|
||||
NewVirtualFile firstFile = ((VirtualDirectoryImpl)taskDir).refreshAndFindChild(first);
|
||||
@@ -142,8 +166,8 @@ public class StudyProjectGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
public void flushCourse(@NotNull final Course course) {
|
||||
final File courseDirectory = new File(ourCoursesDir, course.getName());
|
||||
public static void flushCourse(@NotNull final Project project, @NotNull final Course course) {
|
||||
final File courseDirectory = StudyUtils.getCourseDirectory(project, course);
|
||||
FileUtil.createDirectory(courseDirectory);
|
||||
flushCourseJson(course, courseDirectory);
|
||||
|
||||
@@ -208,7 +232,6 @@ public class StudyProjectGenerator {
|
||||
else {
|
||||
FileUtil.writeToFile(file, taskFile.text);
|
||||
}
|
||||
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.error("ERROR copying file " + name);
|
||||
@@ -217,9 +240,12 @@ public class StudyProjectGenerator {
|
||||
final Map<String, String> testsText = task.getTestsText();
|
||||
for (Map.Entry<String, String> entry : testsText.entrySet()) {
|
||||
final File testsFile = new File(taskDirectory, entry.getKey());
|
||||
if (testsFile.exists()) {
|
||||
FileUtil.delete(testsFile);
|
||||
}
|
||||
FileUtil.createIfDoesntExist(testsFile);
|
||||
try {
|
||||
FileUtil.writeToFile(testsFile, entry.getValue());
|
||||
FileUtil.writeToFile(testsFile, entry.getValue());
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.error("ERROR copying tests file");
|
||||
@@ -235,8 +261,8 @@ public class StudyProjectGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
private static void flushCourseJson(@NotNull final Course course, @NotNull final File courseDirectory) {
|
||||
final Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||
public static void flushCourseJson(@NotNull final Course course, @NotNull final File courseDirectory) {
|
||||
final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create();
|
||||
final String json = gson.toJson(course);
|
||||
final File courseJson = new File(courseDirectory, EduNames.COURSE_META_FILE);
|
||||
final FileOutputStream fileOutputStream;
|
||||
@@ -269,7 +295,7 @@ public class StudyProjectGenerator {
|
||||
*/
|
||||
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
|
||||
public static void flushCache(List<CourseInfo> courses) {
|
||||
File cacheFile = new File(ourCoursesDir, CACHE_NAME);
|
||||
File cacheFile = new File(OUR_COURSES_DIR, CACHE_NAME);
|
||||
PrintWriter writer = null;
|
||||
try {
|
||||
if (!createCacheFile(cacheFile)) return;
|
||||
@@ -290,8 +316,8 @@ public class StudyProjectGenerator {
|
||||
}
|
||||
|
||||
private static boolean createCacheFile(File cacheFile) throws IOException {
|
||||
if (!ourCoursesDir.exists()) {
|
||||
final boolean created = ourCoursesDir.mkdirs();
|
||||
if (!OUR_COURSES_DIR.exists()) {
|
||||
final boolean created = OUR_COURSES_DIR.mkdirs();
|
||||
if (!created) {
|
||||
LOG.error("Cannot flush courses cache. Can't create courses directory");
|
||||
return false;
|
||||
@@ -307,12 +333,13 @@ public class StudyProjectGenerator {
|
||||
return true;
|
||||
}
|
||||
|
||||
public List<CourseInfo> getCourses(boolean force) {
|
||||
if (ourCoursesDir.exists()) {
|
||||
// Supposed to be called under progress
|
||||
public List<CourseInfo> getCoursesAsynchronouslyIfNeeded(boolean force) {
|
||||
if (OUR_COURSES_DIR.exists()) {
|
||||
myCourses = getCoursesFromCache();
|
||||
}
|
||||
if (force || myCourses.isEmpty()) {
|
||||
myCourses = EduStepicConnector.getCourses();
|
||||
myCourses = execCancelable(EduStepicConnector::getCourses);
|
||||
flushCache(myCourses);
|
||||
}
|
||||
if (myCourses.isEmpty()) {
|
||||
@@ -321,6 +348,22 @@ public class StudyProjectGenerator {
|
||||
return myCourses;
|
||||
}
|
||||
|
||||
public List<CourseInfo> getCoursesUnderProgress(boolean force, @NotNull final String progressTitle, @NotNull final Project project) {
|
||||
try {
|
||||
return ProgressManager.getInstance()
|
||||
.runProcessWithProgressSynchronously(new ThrowableComputable<List<CourseInfo>, RuntimeException>() {
|
||||
@Override
|
||||
public List<CourseInfo> compute() throws RuntimeException {
|
||||
ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true);
|
||||
return getCoursesAsynchronouslyIfNeeded(force);
|
||||
}
|
||||
}, progressTitle, true, project);
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
return Collections.singletonList(CourseInfo.INVALID_COURSE);
|
||||
}
|
||||
}
|
||||
|
||||
public void addSettingsStateListener(@NotNull SettingsListener listener) {
|
||||
myListeners.add(listener);
|
||||
}
|
||||
@@ -336,7 +379,7 @@ public class StudyProjectGenerator {
|
||||
}
|
||||
|
||||
public static List<CourseInfo> getBundledIntro() {
|
||||
final File introCourse = new File(ourCoursesDir, "Introduction to Python");
|
||||
final File introCourse = new File(OUR_COURSES_DIR, "Introduction to Python");
|
||||
if (introCourse.exists()) {
|
||||
final CourseInfo courseInfo = getCourseInfo(introCourse);
|
||||
|
||||
@@ -347,7 +390,7 @@ public class StudyProjectGenerator {
|
||||
|
||||
public static List<CourseInfo> getCoursesFromCache() {
|
||||
List<CourseInfo> courses = new ArrayList<>();
|
||||
final File cacheFile = new File(ourCoursesDir, CACHE_NAME);
|
||||
final File cacheFile = new File(OUR_COURSES_DIR, CACHE_NAME);
|
||||
if (!cacheFile.exists()) {
|
||||
return courses;
|
||||
}
|
||||
@@ -360,7 +403,9 @@ public class StudyProjectGenerator {
|
||||
while ((line = reader.readLine()) != null) {
|
||||
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
|
||||
final CourseInfo courseInfo = gson.fromJson(line, CourseInfo.class);
|
||||
courses.add(courseInfo);
|
||||
if (!courseInfo.isAdaptive()) {
|
||||
courses.add(courseInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException | JsonSyntaxException e) {
|
||||
@@ -369,7 +414,8 @@ public class StudyProjectGenerator {
|
||||
finally {
|
||||
StudyUtils.closeSilently(reader);
|
||||
}
|
||||
} finally {
|
||||
}
|
||||
finally {
|
||||
StudyUtils.closeSilently(inputStream);
|
||||
}
|
||||
}
|
||||
@@ -378,6 +424,7 @@ public class StudyProjectGenerator {
|
||||
}
|
||||
return courses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds course from zip archive to courses
|
||||
*
|
||||
@@ -389,12 +436,14 @@ public class StudyProjectGenerator {
|
||||
try {
|
||||
String fileName = file.getName();
|
||||
String unzippedName = fileName.substring(0, fileName.indexOf("."));
|
||||
File courseDir = new File(ourCoursesDir, unzippedName);
|
||||
File courseDir = new File(OUR_COURSES_DIR, unzippedName);
|
||||
ZipUtil.unzip(null, courseDir, file, null, null, true);
|
||||
CourseInfo courseName = addCourse(myCourses, courseDir);
|
||||
flushCache(myCourses);
|
||||
if (courseName != null && !courseName.getName().equals(unzippedName)) {
|
||||
courseDir.renameTo(new File(ourCoursesDir, courseName.getName()));
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
courseDir.renameTo(new File(OUR_COURSES_DIR, courseName.getName()));
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
courseDir.delete();
|
||||
}
|
||||
return courseName;
|
||||
@@ -409,7 +458,6 @@ public class StudyProjectGenerator {
|
||||
/**
|
||||
* Adds course to courses specified in params
|
||||
*
|
||||
*
|
||||
* @param courses
|
||||
* @param courseDir must be directory containing course file
|
||||
* @return added course name or null if course is invalid
|
||||
@@ -431,6 +479,7 @@ public class StudyProjectGenerator {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses course json meta file and finds course name
|
||||
*
|
||||
@@ -462,10 +511,13 @@ public class StudyProjectGenerator {
|
||||
courseInfo.setName(courseName);
|
||||
courseInfo.setDescription(courseDescription);
|
||||
courseInfo.setType("pycharm " + language);
|
||||
final ArrayList<CourseInfo.Author> authors = new ArrayList<>();
|
||||
final ArrayList<StepicUser> authors = new ArrayList<>();
|
||||
for (JsonElement author : courseAuthors) {
|
||||
final JsonObject authorAsJsonObject = author.getAsJsonObject();
|
||||
authors.add(new CourseInfo.Author(authorAsJsonObject.get("first_name").getAsString(), authorAsJsonObject.get("last_name").getAsString()));
|
||||
final StepicUser stepicUser = new StepicUser();
|
||||
stepicUser.setFirstName(authorAsJsonObject.get("first_name").getAsString());
|
||||
stepicUser.setLastName(authorAsJsonObject.get("last_name").getAsString());
|
||||
authors.add(stepicUser);
|
||||
}
|
||||
courseInfo.setAuthors(authors);
|
||||
}
|
||||
|
||||
+6
-6
@@ -5,9 +5,9 @@ import com.intellij.openapi.fileEditor.impl.text.PsiAwareTextEditorImpl;
|
||||
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.jetbrains.edu.learning.StudyUtils;
|
||||
import com.jetbrains.edu.learning.core.EduDocumentListener;
|
||||
import com.jetbrains.edu.learning.courseFormat.TaskFile;
|
||||
import com.jetbrains.edu.learning.StudyUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -21,6 +21,11 @@ public class StudyEditor extends PsiAwareTextEditorImpl {
|
||||
private final TaskFile myTaskFile;
|
||||
private static final Map<Document, EduDocumentListener> myDocumentListeners = new HashMap<Document, EduDocumentListener>();
|
||||
|
||||
public StudyEditor(@NotNull final Project project, @NotNull final VirtualFile file) {
|
||||
super(project, file, TextEditorProvider.getInstance());
|
||||
myTaskFile = StudyUtils.getTaskFile(project, file);
|
||||
}
|
||||
|
||||
public TaskFile getTaskFile() {
|
||||
return myTaskFile;
|
||||
}
|
||||
@@ -30,11 +35,6 @@ public class StudyEditor extends PsiAwareTextEditorImpl {
|
||||
myDocumentListeners.put(document, listener);
|
||||
}
|
||||
|
||||
public StudyEditor(@NotNull final Project project, @NotNull final VirtualFile file) {
|
||||
super(project, file, TextEditorProvider.getInstance());
|
||||
myTaskFile = StudyUtils.getTaskFile(project, file);
|
||||
}
|
||||
|
||||
public static void removeListener(Document document) {
|
||||
final EduDocumentListener listener = myDocumentListeners.get(document);
|
||||
if (listener != null) {
|
||||
|
||||
+4
-4
@@ -8,14 +8,14 @@ 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.learning.courseFormat.TaskFile;
|
||||
import com.jetbrains.edu.learning.StudyUtils;
|
||||
import com.jetbrains.edu.learning.courseFormat.TaskFile;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
class StudyFileEditorProvider implements FileEditorProvider, DumbAware {
|
||||
static final private String EDITOR_TYPE_ID = "StudyEditor";
|
||||
final private FileEditorProvider defaultTextEditorProvider = TextEditorProvider.getInstance();
|
||||
public class StudyFileEditorProvider implements FileEditorProvider, DumbAware {
|
||||
public static final String EDITOR_TYPE_ID = "StudyEditor";
|
||||
private final FileEditorProvider defaultTextEditorProvider = TextEditorProvider.getInstance();
|
||||
|
||||
@Override
|
||||
public boolean accept(@NotNull Project project, @NotNull VirtualFile file) {
|
||||
|
||||
+44
-21
@@ -3,6 +3,7 @@ package com.jetbrains.edu.learning.stepic;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -12,29 +13,26 @@ import java.util.List;
|
||||
* 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;
|
||||
@SerializedName("course_format")
|
||||
//course type in format "pycharm <language>"
|
||||
private String myType = "pycharm Python";
|
||||
public static CourseInfo INVALID_COURSE = new CourseInfo();
|
||||
|
||||
@SerializedName("title") private String myName;
|
||||
int id;
|
||||
boolean isAdaptive;
|
||||
boolean isPublic;
|
||||
List<Integer> sections;
|
||||
List<Integer> instructors = new ArrayList<Integer>();
|
||||
|
||||
List<Author> myAuthors = new ArrayList<Author>();
|
||||
int id;
|
||||
|
||||
public static CourseInfo INVALID_COURSE = new CourseInfo();
|
||||
List<StepicUser> myAuthors = new ArrayList<>();
|
||||
@SerializedName("summary") private String myDescription;
|
||||
@SerializedName("course_format") private String myType = "pycharm Python"; //course type in format "pycharm <language>"
|
||||
@Nullable private String username;
|
||||
|
||||
public String getName() {
|
||||
return myName;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<Author> getAuthors() {
|
||||
public List<StepicUser> getAuthors() {
|
||||
return myAuthors;
|
||||
}
|
||||
|
||||
@@ -48,7 +46,7 @@ public class CourseInfo {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return myName;
|
||||
return getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -57,17 +55,26 @@ public class CourseInfo {
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
CourseInfo that = (CourseInfo)o;
|
||||
if (that.getName() == null || that.getDescription() == null) return false;
|
||||
return that.getName().equals(myName)
|
||||
return that.getName().equals(getName())
|
||||
&& that.getDescription().equals(myDescription);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = myName != null ? myName.hashCode() : 0;
|
||||
int result = getName() != null ? getName().hashCode() : 0;
|
||||
result = 31 * result + (myDescription != null ? myDescription.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(@Nullable String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public static class Author {
|
||||
int id;
|
||||
String first_name = "";
|
||||
@@ -93,18 +100,18 @@ public class CourseInfo {
|
||||
myName = name;
|
||||
}
|
||||
|
||||
public void setAuthors(List<Author> authors) {
|
||||
public void setAuthors(List<StepicUser> authors) {
|
||||
myAuthors = authors;
|
||||
for (Author author : authors) {
|
||||
for (StepicUser author : authors) {
|
||||
if (author.id > 0) {
|
||||
instructors.add(author.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addAuthor(Author author) {
|
||||
public void addAuthor(StepicUser author) {
|
||||
if (myAuthors == null) {
|
||||
myAuthors = new ArrayList<Author>();
|
||||
myAuthors = new ArrayList<>();
|
||||
}
|
||||
myAuthors.add(author);
|
||||
}
|
||||
@@ -116,4 +123,20 @@ public class CourseInfo {
|
||||
public void setType(String type) {
|
||||
myType = type;
|
||||
}
|
||||
|
||||
public boolean isAdaptive() {
|
||||
return isAdaptive;
|
||||
}
|
||||
|
||||
public void setAdaptive(boolean adaptive) {
|
||||
isAdaptive = adaptive;
|
||||
}
|
||||
|
||||
public boolean isPublic() {
|
||||
return isPublic;
|
||||
}
|
||||
|
||||
public void setPublic(boolean aPublic) {
|
||||
isPublic = aPublic;
|
||||
}
|
||||
}
|
||||
|
||||
+511
@@ -0,0 +1,511 @@
|
||||
package com.jetbrains.edu.learning.stepic;
|
||||
|
||||
import com.google.gson.FieldNamingPolicy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.intellij.ide.projectView.ProjectView;
|
||||
import com.intellij.lang.Language;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.projectRoots.Sdk;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VfsUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.vfs.VirtualFileManager;
|
||||
import com.jetbrains.edu.learning.StudyTaskManager;
|
||||
import com.jetbrains.edu.learning.StudyUtils;
|
||||
import com.jetbrains.edu.learning.checker.StudyExecutor;
|
||||
import com.jetbrains.edu.learning.core.EduNames;
|
||||
import com.jetbrains.edu.learning.courseFormat.*;
|
||||
import com.jetbrains.edu.learning.courseGeneration.StudyGenerator;
|
||||
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
|
||||
import com.jetbrains.edu.learning.editor.StudyEditor;
|
||||
import com.jetbrains.edu.learning.ui.StudyToolWindow;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.StatusLine;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.utils.URIBuilder;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.jetbrains.edu.learning.stepic.EduStepicConnector.*;
|
||||
|
||||
public class EduAdaptiveStepicConnector {
|
||||
public static final String PYTHON27 = "python27";
|
||||
public static final String PYTHON3 = "python3";
|
||||
|
||||
private static final Logger LOG = Logger.getInstance(EduAdaptiveStepicConnector.class);
|
||||
private static final String STEPIC_URL = "https://stepic.org/";
|
||||
private static final String STEPIC_API_URL = STEPIC_URL + "api/";
|
||||
private static final String RECOMMENDATIONS_URL = "recommendations";
|
||||
private static final String CONTENT_TYPE_APPL_JSON = "application/json";
|
||||
private static final String LESSON_URL = "lessons/";
|
||||
private static final String RECOMMENDATION_REACTIONS_URL = "recommendation-reactions";
|
||||
private static final String ATTEMPTS_URL = "attempts";
|
||||
private static final String SUBMISSION_URL = "submissions";
|
||||
private static final String ASSIGNMENT_URL = "/assignments";
|
||||
private static final String VIEWS_URL = "/views";
|
||||
private static final String UNITS_URL = "/units";
|
||||
private static final String DEFAULT_TASKFILE_NAME = "code.py";
|
||||
|
||||
@Nullable
|
||||
public static Task getNextRecommendation(@NotNull final Project project, @NotNull Course course) {
|
||||
try {
|
||||
final CloseableHttpClient client = getHttpClient(project);
|
||||
final URI uri = new URIBuilder(STEPIC_API_URL + RECOMMENDATIONS_URL)
|
||||
.addParameter("course", String.valueOf(course.getId()))
|
||||
.build();
|
||||
final HttpGet request = new HttpGet(uri);
|
||||
setHeaders(request, CONTENT_TYPE_APPL_JSON);
|
||||
|
||||
final CloseableHttpResponse response = client.execute(request);
|
||||
final StatusLine statusLine = response.getStatusLine();
|
||||
final HttpEntity responseEntity = response.getEntity();
|
||||
final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
|
||||
|
||||
if (statusLine.getStatusCode() == HttpStatus.SC_OK) {
|
||||
final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
|
||||
final StepicWrappers.RecommendationWrapper recomWrapper = gson.fromJson(responseString, StepicWrappers.RecommendationWrapper.class);
|
||||
|
||||
if (recomWrapper.recommendations.length != 0) {
|
||||
final StepicWrappers.Recommendation recommendation = recomWrapper.recommendations[0];
|
||||
final String lessonId = recommendation.lesson;
|
||||
final StepicWrappers.LessonContainer
|
||||
lessonContainer = getFromStepic(LESSON_URL + lessonId, StepicWrappers.LessonContainer.class);
|
||||
if (lessonContainer.lessons.size() == 1) {
|
||||
final Lesson realLesson = lessonContainer.lessons.get(0);
|
||||
course.getLessons().get(0).id = Integer.parseInt(lessonId);
|
||||
|
||||
viewAllSteps(client, realLesson.id);
|
||||
|
||||
for (int stepId : realLesson.steps) {
|
||||
final StepicWrappers.Step step = getStep(stepId);
|
||||
if (step.name.equals("code")) {
|
||||
return getTaskFromStep(project, stepId, step, realLesson.getName());
|
||||
}
|
||||
}
|
||||
|
||||
LOG.warn("Got a lesson without code part as a recommendation");
|
||||
}
|
||||
else {
|
||||
LOG.warn("Got unexpected number of lessons: " + lessonContainer.lessons.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IOException("Stepic returned non 200 status code: " + responseString);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
catch (URISyntaxException e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void viewAllSteps(CloseableHttpClient client, int lessonId) throws URISyntaxException, IOException {
|
||||
final URI unitsUrl = new URIBuilder(UNITS_URL).addParameter("lesson", String.valueOf(lessonId)).build();
|
||||
final StepicWrappers.UnitContainer unitContainer = getFromStepic(unitsUrl.toString(), StepicWrappers.UnitContainer.class);
|
||||
if (unitContainer.units.size() != 1) {
|
||||
LOG.warn("Got unexpected numbers of units: " + unitContainer.units.size());
|
||||
return;
|
||||
}
|
||||
|
||||
final URIBuilder builder = new URIBuilder(ASSIGNMENT_URL);
|
||||
for (Integer step : unitContainer.units.get(0).assignments) {
|
||||
builder.addParameter("ids[]", String.valueOf(step));
|
||||
}
|
||||
final URI assignmentUrl = builder.build();
|
||||
final StepicWrappers.AssignmentsWrapper assignments = getFromStepic(assignmentUrl.toString(), StepicWrappers.AssignmentsWrapper.class);
|
||||
if (assignments.assignments.size() > 0) {
|
||||
for (StepicWrappers.Assignment assignment : assignments.assignments) {
|
||||
final HttpPost post = new HttpPost(STEPIC_API_URL + VIEWS_URL);
|
||||
final StepicWrappers.ViewsWrapper viewsWrapper = new StepicWrappers.ViewsWrapper(assignment.id, assignment.step);
|
||||
post.setEntity(new StringEntity(new Gson().toJson(viewsWrapper)));
|
||||
setHeaders(post, CONTENT_TYPE_APPL_JSON);
|
||||
final CloseableHttpResponse viewPostResult = client.execute(post);
|
||||
if (viewPostResult.getStatusLine().getStatusCode() != HttpStatus.SC_CREATED) {
|
||||
LOG.warn("Error while Views post, code: " + viewPostResult.getStatusLine().getStatusCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOG.warn("Got assignments of incorrect length: " + assignments.assignments.size());
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean postRecommendationReaction(@NotNull final Project project, @NotNull final String lessonId,
|
||||
@NotNull final String user, int reaction) {
|
||||
final HttpPost post = new HttpPost(STEPIC_API_URL + RECOMMENDATION_REACTIONS_URL);
|
||||
final String json = new Gson()
|
||||
.toJson(new StepicWrappers.RecommendationReactionWrapper(new StepicWrappers.RecommendationReaction(reaction, user, lessonId)));
|
||||
post.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));
|
||||
final CloseableHttpClient client = getHttpClient(project);
|
||||
setHeaders(post, CONTENT_TYPE_APPL_JSON);
|
||||
try {
|
||||
final CloseableHttpResponse execute = client.execute(post);
|
||||
return execute.getStatusLine().getStatusCode() == HttpStatus.SC_CREATED;
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void addNextRecommendedTask(@NotNull final Project project, int reaction) {
|
||||
final StudyEditor editor = StudyUtils.getSelectedStudyEditor(project);
|
||||
final Course course = StudyTaskManager.getInstance(project).getCourse();
|
||||
if (course != null && editor != null && editor.getTaskFile() != null) {
|
||||
final StepicUser user = StudyTaskManager.getInstance(project).getUser();
|
||||
|
||||
final boolean recommendationReaction =
|
||||
user != null && postRecommendationReaction(project, String.valueOf(editor.getTaskFile().getTask().getLesson().id),
|
||||
String.valueOf(user.id), reaction);
|
||||
if (recommendationReaction) {
|
||||
final Task task = getNextRecommendation(project, course);
|
||||
|
||||
if (task != null) {
|
||||
final Lesson adaptive = course.getLessons().get(0);
|
||||
final Task unsolvedTask = adaptive.getTaskList().get(adaptive.getTaskList().size() - 1);
|
||||
if (reaction == 0 || reaction == -1) {
|
||||
unsolvedTask.setName(task.getName());
|
||||
unsolvedTask.setStepicId(task.getStepicId());
|
||||
unsolvedTask.setText(task.getText());
|
||||
unsolvedTask.getTestsText().clear();
|
||||
final Map<String, String> testsText = task.getTestsText();
|
||||
for (String testName : testsText.keySet()) {
|
||||
unsolvedTask.addTestsTexts(testName, testsText.get(testName));
|
||||
}
|
||||
final Map<String, TaskFile> taskFiles = task.getTaskFiles();
|
||||
if (taskFiles.size() == 1) {
|
||||
final TaskFile taskFile = editor.getTaskFile();
|
||||
taskFile.text = ((TaskFile)taskFiles.values().toArray()[0]).text;
|
||||
ApplicationManager.getApplication().invokeLater(() ->
|
||||
ApplicationManager.getApplication().runWriteAction(() ->
|
||||
editor.getEditor()
|
||||
.getDocument()
|
||||
.setText(
|
||||
taskFiles.get(
|
||||
DEFAULT_TASKFILE_NAME).text)));
|
||||
}
|
||||
else {
|
||||
LOG.warn("Got task without unexpected number of task files: " + taskFiles.size());
|
||||
}
|
||||
|
||||
final File lessonDirectory = new File(course.getCourseDirectory(), EduNames.LESSON + String.valueOf(adaptive.getIndex()));
|
||||
final File taskDirectory = new File(lessonDirectory, EduNames.TASK + String.valueOf(adaptive.getTaskList().size()));
|
||||
StudyProjectGenerator.flushTask(task, taskDirectory);
|
||||
StudyProjectGenerator.flushCourseJson(course, new File(course.getCourseDirectory()));
|
||||
final VirtualFile lessonDir = project.getBaseDir().findChild(EduNames.LESSON + String.valueOf(adaptive.getIndex()));
|
||||
|
||||
if (lessonDir != null) {
|
||||
createTestFiles(course, task, unsolvedTask, lessonDir);
|
||||
}
|
||||
final StudyToolWindow window = StudyUtils.getStudyToolWindow(project);
|
||||
if (window != null) {
|
||||
window.setTaskText(unsolvedTask.getText(), unsolvedTask.getTaskDir(project), project);
|
||||
}
|
||||
}
|
||||
else {
|
||||
adaptive.addTask(task);
|
||||
task.setIndex(adaptive.getTaskList().size());
|
||||
final VirtualFile lessonDir = project.getBaseDir().findChild(EduNames.LESSON + String.valueOf(adaptive.getIndex()));
|
||||
|
||||
if (lessonDir != null) {
|
||||
ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> {
|
||||
try {
|
||||
StudyGenerator.createTask(task, lessonDir, new File(course.getCourseDirectory(), lessonDir.getName()), project);
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
final File lessonDirectory = new File(course.getCourseDirectory(), EduNames.LESSON + String.valueOf(adaptive.getIndex()));
|
||||
StudyProjectGenerator.flushLesson(lessonDirectory, adaptive);
|
||||
StudyProjectGenerator.flushCourseJson(course, new File(course.getCourseDirectory()));
|
||||
adaptive.initLesson(course, true);
|
||||
}
|
||||
}
|
||||
ApplicationManager.getApplication().invokeLater(() -> {
|
||||
VirtualFileManager.getInstance().refreshWithoutFileWatcher(false);
|
||||
ProjectView.getInstance(project).refresh();
|
||||
});
|
||||
}
|
||||
else {
|
||||
LOG.warn("Recommendation reactions weren't posted");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void createTestFiles(Course course, Task task, Task unsolvedTask, VirtualFile lessonDir) {
|
||||
ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> {
|
||||
try {
|
||||
final VirtualFile taskDir = VfsUtil
|
||||
.findFileByIoFile(new File(lessonDir.getCanonicalPath(), EduNames.TASK + unsolvedTask.getIndex()), true);
|
||||
final File resourceRoot = new File(course.getCourseDirectory(), lessonDir.getName());
|
||||
File newResourceRoot = null;
|
||||
if (taskDir != null) {
|
||||
newResourceRoot = new File(resourceRoot, taskDir.getName());
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOG.warn("Task directory is null");
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Task getTaskFromStep(Project project,
|
||||
int lessonID,
|
||||
@NotNull final StepicWrappers.Step step, @NotNull String name) {
|
||||
final Task task = new Task();
|
||||
task.setName(name);
|
||||
task.setStepicId(lessonID);
|
||||
task.setText(step.text);
|
||||
task.setStatus(StudyStatus.Unchecked);
|
||||
if (step.options.samples != null) {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
for (List<String> sample : step.options.samples) {
|
||||
if (sample.size() == 2) {
|
||||
builder.append("<b>Sample Input:</b><br>");
|
||||
builder.append(StringUtil.replace(sample.get(0), "\n", "<br>"));
|
||||
builder.append("<br>");
|
||||
builder.append("<b>Sample Output:</b><br>");
|
||||
builder.append(StringUtil.replace(sample.get(1), "\n", "<br>"));
|
||||
builder.append("<br><br>");
|
||||
}
|
||||
}
|
||||
task.setText(task.getText() + "<br>" + builder.toString());
|
||||
}
|
||||
|
||||
if (step.options.executionMemoryLimit != null && step.options.executionTimeLimit != null) {
|
||||
String builder = "<b>Memory limit</b>: " +
|
||||
step.options.executionMemoryLimit + " Mb" +
|
||||
"<br>" +
|
||||
"<b>Time limit</b>: " +
|
||||
step.options.executionTimeLimit + "s" +
|
||||
"<br><br>";
|
||||
task.setText(task.getText() + builder);
|
||||
}
|
||||
|
||||
if (step.options.test != null) {
|
||||
for (StepicWrappers.TestFileWrapper wrapper : step.options.test) {
|
||||
task.addTestsTexts(wrapper.name, wrapper.text);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (step.options.samples != null) {
|
||||
createTestFileFromSamples(task, step.options.samples);
|
||||
}
|
||||
}
|
||||
|
||||
task.taskFiles = new HashMap<String, TaskFile>(); // TODO: it looks like we don't need taskFiles as map anymore
|
||||
if (step.options.files != null) {
|
||||
for (TaskFile taskFile : step.options.files) {
|
||||
task.taskFiles.put(taskFile.name, taskFile);
|
||||
}
|
||||
}
|
||||
else {
|
||||
final TaskFile taskFile = new TaskFile();
|
||||
taskFile.name = "code";
|
||||
final String templateForTask = getCodeTemplateForTask(step.options.codeTemplates, task, project);
|
||||
taskFile.text = templateForTask == null ? "# write your answer here \n" : templateForTask;
|
||||
task.taskFiles.put("code.py", taskFile);
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
private static String getCodeTemplateForTask(@Nullable StepicWrappers.CodeTemplatesWrapper codeTemplates,
|
||||
@NotNull final Task task, @NotNull final Project project) {
|
||||
|
||||
if (codeTemplates != null) {
|
||||
final String languageString = getLanguageString(task, project);
|
||||
if (languageString != null) {
|
||||
return codeTemplates.getTemplateForLanguage(languageString);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Pair<Boolean, String> checkTask(@NotNull final Project project, @NotNull final Task task) {
|
||||
int attemptId = -1;
|
||||
try {
|
||||
attemptId = getAttemptId(project, task, ATTEMPTS_URL);
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
if (attemptId != -1) {
|
||||
final Editor editor = StudyUtils.getSelectedEditor(project);
|
||||
String language = getLanguageString(task, project);
|
||||
if (editor != null && language != null) {
|
||||
final CloseableHttpClient client = getHttpClient(project);
|
||||
StepicWrappers.ResultSubmissionWrapper wrapper = postResultsForCheck(client, attemptId, language, editor.getDocument().getText());
|
||||
|
||||
final StepicUser user = StudyTaskManager.getInstance(project).getUser();
|
||||
if (user != null) {
|
||||
final int id = user.getId();
|
||||
wrapper = getCheckResults(attemptId, id, client, wrapper);
|
||||
if (wrapper.submissions.length == 1) {
|
||||
final boolean isSolved = !wrapper.submissions[0].status.equals("wrong");
|
||||
return Pair.create(isSolved, wrapper.submissions[0].hint);
|
||||
}
|
||||
else {
|
||||
LOG.warn("Got a submission wrapper with incorrect submissions number: " + wrapper.submissions.length);
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOG.warn("User is null");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOG.warn("Got an incorrect attempt id: " + attemptId);
|
||||
}
|
||||
return Pair.create(false, "");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static StepicWrappers.ResultSubmissionWrapper postResultsForCheck(@NotNull final CloseableHttpClient client,
|
||||
final int attemptId,
|
||||
@NotNull final String language,
|
||||
@NotNull final String text) {
|
||||
final CloseableHttpResponse response;
|
||||
try {
|
||||
final StepicWrappers.SubmissionToPostWrapper submissionToPostWrapper =
|
||||
new StepicWrappers.SubmissionToPostWrapper(String.valueOf(attemptId), language, text);
|
||||
final HttpPost httpPost = new HttpPost(STEPIC_API_URL + SUBMISSION_URL);
|
||||
setHeaders(httpPost, CONTENT_TYPE_APPL_JSON);
|
||||
try {
|
||||
httpPost.setEntity(new StringEntity(new Gson().toJson(submissionToPostWrapper)));
|
||||
}
|
||||
catch (UnsupportedEncodingException e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
response = client.execute(httpPost);
|
||||
return new Gson().fromJson(EntityUtils.toString(response.getEntity()), StepicWrappers.ResultSubmissionWrapper.class);
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static StepicWrappers.ResultSubmissionWrapper getCheckResults(int attemptId,
|
||||
int id,
|
||||
CloseableHttpClient client,
|
||||
StepicWrappers.ResultSubmissionWrapper wrapper) {
|
||||
try {
|
||||
while (wrapper.submissions.length == 1 && wrapper.submissions[0].status.equals("evaluation")) {
|
||||
TimeUnit.MILLISECONDS.sleep(500);
|
||||
final URI submissionURI = new URIBuilder(STEPIC_API_URL + SUBMISSION_URL)
|
||||
.addParameter("attempt", String.valueOf(attemptId))
|
||||
.addParameter("order", "desc")
|
||||
.addParameter("user", String.valueOf(id))
|
||||
.build();
|
||||
final HttpGet httpGet = new HttpGet(submissionURI);
|
||||
setHeaders(httpGet, CONTENT_TYPE_APPL_JSON);
|
||||
final CloseableHttpResponse httpResponse = client.execute(httpGet);
|
||||
final String entity = EntityUtils.toString(httpResponse.getEntity());
|
||||
wrapper = new Gson().fromJson(entity, StepicWrappers.ResultSubmissionWrapper.class);
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
catch (URISyntaxException e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getLanguageString(@NotNull Task task, @NotNull Project project) {
|
||||
final Language pythonLanguage = Language.findLanguageByID("Python");
|
||||
if (pythonLanguage != null) {
|
||||
final Sdk language = StudyExecutor.INSTANCE.forLanguage(pythonLanguage).findSdk(project);
|
||||
if (language != null) {
|
||||
final String versionString = language.getVersionString();
|
||||
if (versionString != null) {
|
||||
final List<String> versionStringParts = StringUtil.split(versionString, " ");
|
||||
if (versionStringParts.size() == 2) {
|
||||
return versionStringParts.get(1).startsWith("2") ? PYTHON27 : PYTHON3;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
StudyUtils.showNoSdkNotification(task, project);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int getAttemptId(@NotNull final Project project, @NotNull Task task, @NotNull final String attempts) throws IOException {
|
||||
final StepicWrappers.AttemptToPostWrapper attemptWrapper = new StepicWrappers.AttemptToPostWrapper(task.getStepicId());
|
||||
|
||||
final HttpPost post = new HttpPost(STEPIC_API_URL + attempts);
|
||||
post.setEntity(new StringEntity(new Gson().toJson(attemptWrapper)));
|
||||
|
||||
final CloseableHttpClient client = getHttpClient(project);
|
||||
setHeaders(post, CONTENT_TYPE_APPL_JSON);
|
||||
final CloseableHttpResponse httpResponse = client.execute(post);
|
||||
final String entity = EntityUtils.toString(httpResponse.getEntity());
|
||||
final StepicWrappers.AttemptContainer container =
|
||||
new Gson().fromJson(entity, StepicWrappers.AttemptContainer.class);
|
||||
return (container.attempts != null && !container.attempts.isEmpty()) ? container.attempts.get(0).id : -1;
|
||||
}
|
||||
|
||||
private static void createTestFileFromSamples(@NotNull final Task task,
|
||||
@NotNull final List<List<String>> samples) {
|
||||
|
||||
String testText = "from test_helper import check_samples\n\n" +
|
||||
"if __name__ == '__main__':\n" +
|
||||
" check_samples(samples=" + new GsonBuilder().create().toJson(samples) + ")";
|
||||
task.addTestsTexts("tests.py", testText);
|
||||
}
|
||||
}
|
||||
+137
-369
@@ -3,7 +3,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.annotations.Expose;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ModalityState;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
@@ -17,6 +16,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.StudyTaskManager;
|
||||
import com.jetbrains.edu.learning.core.EduNames;
|
||||
import com.jetbrains.edu.learning.core.EduUtils;
|
||||
import com.jetbrains.edu.learning.courseFormat.Course;
|
||||
@@ -24,10 +24,7 @@ import com.jetbrains.edu.learning.courseFormat.Lesson;
|
||||
import com.jetbrains.edu.learning.courseFormat.Task;
|
||||
import com.jetbrains.edu.learning.courseFormat.TaskFile;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.StatusLine;
|
||||
import org.apache.http.*;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.*;
|
||||
import org.apache.http.cookie.Cookie;
|
||||
@@ -40,7 +37,6 @@ import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.message.BasicHeader;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.apache.http.protocol.HTTP;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -49,7 +45,6 @@ import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
@@ -57,9 +52,9 @@ import java.security.cert.X509Certificate;
|
||||
import java.util.*;
|
||||
|
||||
public class EduStepicConnector {
|
||||
private static final Logger LOG = Logger.getInstance(EduStepicConnector.class.getName());
|
||||
private static final String stepicUrl = "https://stepic.org/";
|
||||
private static final String stepicApiUrl = stepicUrl + "api/";
|
||||
private static final Logger LOG = Logger.getInstance(EduStepicConnector.class.getName());
|
||||
private static String ourCSRFToken = "";
|
||||
private static CloseableHttpClient ourClient;
|
||||
|
||||
@@ -70,15 +65,21 @@ public class EduStepicConnector {
|
||||
private EduStepicConnector() {
|
||||
}
|
||||
|
||||
public static boolean login(@NotNull final String user, @NotNull final String password) {
|
||||
public static StepicUser login(@NotNull final String username, @NotNull final String password) {
|
||||
initializeClient();
|
||||
return postCredentials(user, password);
|
||||
if (postCredentials(username, password)) {
|
||||
final StepicWrappers.AuthorWrapper stepicUserWrapper = getCurrentUser();
|
||||
if (stepicUserWrapper != null && stepicUserWrapper.users.size() == 1) {
|
||||
return stepicUserWrapper.users.get(0);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static AuthorWrapper getCurrentUser() {
|
||||
public static StepicWrappers.AuthorWrapper getCurrentUser() {
|
||||
try {
|
||||
return getFromStepic("stepics/1", AuthorWrapper.class);
|
||||
return getFromStepic("stepics/1", StepicWrappers.AuthorWrapper.class);
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.warn("Couldn't get author info");
|
||||
@@ -90,7 +91,7 @@ public class EduStepicConnector {
|
||||
final HttpPost userRequest = new HttpPost(stepicApiUrl + "users");
|
||||
initializeClient();
|
||||
setHeaders(userRequest, "application/json");
|
||||
String requestBody = new Gson().toJson(new UserWrapper(user, password));
|
||||
String requestBody = new Gson().toJson(new StepicWrappers.UserWrapper(user, password));
|
||||
userRequest.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
|
||||
|
||||
try {
|
||||
@@ -109,38 +110,47 @@ public class EduStepicConnector {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void initializeClient() {
|
||||
final HttpGet request = new HttpGet(stepicUrl);
|
||||
request.addHeader(new BasicHeader("referer", "https://stepic.org"));
|
||||
request.addHeader(new BasicHeader("content-type", "application/json"));
|
||||
public static void initializeClient() {
|
||||
if (ourClient == null) {
|
||||
final HttpGet request = new HttpGet(stepicUrl);
|
||||
request.addHeader(new BasicHeader("referer", "https://stepic.org"));
|
||||
request.addHeader(new BasicHeader("content-type", "application/json"));
|
||||
|
||||
|
||||
HttpClientBuilder builder = HttpClients.custom().setSslcontext(CertificateManager.getInstance().getSslContext()).setMaxConnPerRoute(100000).
|
||||
setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE);
|
||||
ourCookieStore = new BasicCookieStore();
|
||||
HttpClientBuilder builder =
|
||||
HttpClients.custom().setSslcontext(CertificateManager.getInstance().getSslContext()).setMaxConnPerRoute(100000).
|
||||
setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE);
|
||||
ourCookieStore = new BasicCookieStore();
|
||||
|
||||
try {
|
||||
// Create a trust manager that does not validate certificate for this connection
|
||||
TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
|
||||
public X509Certificate[] getAcceptedIssuers() { return null; }
|
||||
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
|
||||
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
|
||||
}};
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(null, trustAllCerts, new SecureRandom());
|
||||
ourClient = builder.setDefaultCookieStore(ourCookieStore).setSslcontext(sslContext).build();
|
||||
try {
|
||||
// Create a trust manager that does not validate certificate for this connection
|
||||
TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return null;
|
||||
}
|
||||
|
||||
ourClient.execute(request);
|
||||
saveCSRFToken();
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.error(e.getMessage());
|
||||
}
|
||||
catch (NoSuchAlgorithmException e) {
|
||||
LOG.error(e.getMessage());
|
||||
}
|
||||
catch (KeyManagementException e) {
|
||||
LOG.error(e.getMessage());
|
||||
public void checkClientTrusted(X509Certificate[] certs, String authType) {
|
||||
}
|
||||
|
||||
public void checkServerTrusted(X509Certificate[] certs, String authType) {
|
||||
}
|
||||
}};
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(null, trustAllCerts, new SecureRandom());
|
||||
ourClient = builder.setDefaultCookieStore(ourCookieStore).setSslcontext(sslContext).build();
|
||||
|
||||
ourClient.execute(request);
|
||||
saveCSRFToken();
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.error(e.getMessage());
|
||||
}
|
||||
catch (NoSuchAlgorithmException e) {
|
||||
LOG.error(e.getMessage());
|
||||
}
|
||||
catch (KeyManagementException e) {
|
||||
LOG.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,14 +174,7 @@ public class EduStepicConnector {
|
||||
nvps.add(new BasicNameValuePair("password", password));
|
||||
nvps.add(new BasicNameValuePair("remember", "on"));
|
||||
|
||||
try {
|
||||
request.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
|
||||
}
|
||||
catch (UnsupportedEncodingException e) {
|
||||
LOG.error(e.getMessage());
|
||||
ourClient = null;
|
||||
return false;
|
||||
}
|
||||
request.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8));
|
||||
|
||||
setHeaders(request, "application/x-www-form-urlencoded");
|
||||
|
||||
@@ -182,7 +185,8 @@ public class EduStepicConnector {
|
||||
if (line.getStatusCode() != HttpStatus.SC_MOVED_TEMPORARILY) {
|
||||
final HttpEntity responseEntity = response.getEntity();
|
||||
final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
|
||||
LOG.error("Failed to login " + responseString);
|
||||
LOG.warn("Failed to login: " + line.getStatusCode() + line.getReasonPhrase());
|
||||
LOG.debug("Failed to login " + responseString);
|
||||
ourClient = null;
|
||||
return false;
|
||||
}
|
||||
@@ -195,7 +199,7 @@ public class EduStepicConnector {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static <T> T getFromStepic(String link, final Class<T> container) throws IOException {
|
||||
static <T> T getFromStepic(String link, final Class<T> container) throws IOException {
|
||||
final HttpGet request = new HttpGet(stepicApiUrl + link);
|
||||
if (ourClient == null) {
|
||||
initializeClient();
|
||||
@@ -212,6 +216,15 @@ public class EduStepicConnector {
|
||||
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
|
||||
return gson.fromJson(responseString, container);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static CloseableHttpClient getHttpClient(@NotNull final Project project) {
|
||||
if (ourClient == null) {
|
||||
login(project);
|
||||
initializeClient();
|
||||
}
|
||||
return ourClient;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<CourseInfo> getCourses() {
|
||||
@@ -226,21 +239,21 @@ public class EduStepicConnector {
|
||||
catch (IOException e) {
|
||||
LOG.error("Cannot load course list " + e.getMessage());
|
||||
}
|
||||
return Collections.emptyList();
|
||||
return Collections.singletonList(CourseInfo.INVALID_COURSE);
|
||||
}
|
||||
|
||||
private static boolean addCoursesFromStepic(List<CourseInfo> result, int pageNumber) throws IOException {
|
||||
final String url = pageNumber == 0 ? "courses" : "courses?page=" + String.valueOf(pageNumber);
|
||||
final CoursesContainer coursesContainer = getFromStepic(url, CoursesContainer.class);
|
||||
final StepicWrappers.CoursesContainer coursesContainer = getFromStepic(url, StepicWrappers.CoursesContainer.class);
|
||||
final List<CourseInfo> courseInfos = coursesContainer.courses;
|
||||
for (CourseInfo info : courseInfos) {
|
||||
final String courseType = info.getType();
|
||||
if (StringUtil.isEmptyOrSpaces(courseType)) continue;
|
||||
if (!info.isAdaptive() && StringUtil.isEmptyOrSpaces(courseType)) continue;
|
||||
final List<String> typeLanguage = StringUtil.split(courseType, " ");
|
||||
if (typeLanguage.size() == 2 && PYCHARM_PREFIX.equals(typeLanguage.get(0))) {
|
||||
|
||||
// TODO: should adaptive course be of PyCharmType ?
|
||||
if (info.isAdaptive() || (typeLanguage.size() == 2 && PYCHARM_PREFIX.equals(typeLanguage.get(0)))) {
|
||||
for (Integer instructor : info.instructors) {
|
||||
final CourseInfo.Author author = getFromStepic("users/" + String.valueOf(instructor), AuthorWrapper.class).users.get(0);
|
||||
final StepicUser author = getFromStepic("users/" + String.valueOf(instructor), StepicWrappers.AuthorWrapper.class).users.get(0);
|
||||
info.addAuthor(author);
|
||||
}
|
||||
|
||||
@@ -250,34 +263,55 @@ public class EduStepicConnector {
|
||||
return coursesContainer.meta.containsKey("has_next") && coursesContainer.meta.get("has_next") == Boolean.TRUE;
|
||||
}
|
||||
|
||||
public static Course getCourse(@NotNull final CourseInfo info) {
|
||||
public static Course getCourse(@NotNull final Project project, @NotNull final CourseInfo info) {
|
||||
final Course course = new Course();
|
||||
course.setAuthors(info.getAuthors());
|
||||
course.setDescription(info.getDescription());
|
||||
course.setName(info.getName());
|
||||
String courseType = info.getType();
|
||||
course.setLanguage(courseType.substring(PYCHARM_PREFIX.length() + 1));
|
||||
course.setAdaptive(info.isAdaptive());
|
||||
course.setId(info.id);
|
||||
course.setUpToDate(true); // TODO: get from stepic
|
||||
try {
|
||||
for (Integer section : info.sections) {
|
||||
course.addLessons(getLessons(section));
|
||||
|
||||
if (!course.isAdaptive()) {
|
||||
String courseType = info.getType();
|
||||
course.setName(info.getName());
|
||||
course.setLanguage(courseType.substring(PYCHARM_PREFIX.length() + 1));
|
||||
try {
|
||||
for (Integer section : info.sections) {
|
||||
course.addLessons(getLessons(section));
|
||||
}
|
||||
return course;
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.error("IOException " + e.getMessage());
|
||||
}
|
||||
return course;
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.error("IOException " + e.getMessage());
|
||||
else {
|
||||
final Lesson lesson = new Lesson();
|
||||
course.setName(info.getName());
|
||||
//TODO: more specific name?
|
||||
lesson.setName("Adaptive");
|
||||
course.addLesson(lesson);
|
||||
final Task recommendation = EduAdaptiveStepicConnector.getNextRecommendation(project, course);
|
||||
if (recommendation != null) {
|
||||
lesson.addTask(recommendation);
|
||||
}
|
||||
|
||||
return course;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<Lesson> getLessons(int sectionId) throws IOException {
|
||||
final SectionContainer sectionContainer = getFromStepic("sections/" + String.valueOf(sectionId), SectionContainer.class);
|
||||
final StepicWrappers.SectionContainer
|
||||
sectionContainer = getFromStepic("sections/" + String.valueOf(sectionId), StepicWrappers.SectionContainer.class);
|
||||
List<Integer> unitIds = sectionContainer.sections.get(0).units;
|
||||
final List<Lesson> lessons = new ArrayList<Lesson>();
|
||||
for (Integer unitId : unitIds) {
|
||||
UnitContainer unit = getFromStepic("units/" + String.valueOf(unitId), UnitContainer.class);
|
||||
StepicWrappers.UnitContainer
|
||||
unit = getFromStepic("units/" + String.valueOf(unitId), StepicWrappers.UnitContainer.class);
|
||||
int lessonID = unit.units.get(0).lesson;
|
||||
LessonContainer lesson = getFromStepic("lessons/" + String.valueOf(lessonID), LessonContainer.class);
|
||||
StepicWrappers.LessonContainer
|
||||
lesson = getFromStepic("lessons/" + String.valueOf(lessonID), StepicWrappers.LessonContainer.class);
|
||||
Lesson realLesson = lesson.lessons.get(0);
|
||||
realLesson.taskList = new ArrayList<Task>();
|
||||
for (Integer s : realLesson.steps) {
|
||||
@@ -291,13 +325,13 @@ public class EduStepicConnector {
|
||||
}
|
||||
|
||||
private static void createTask(Lesson lesson, Integer stepicId) throws IOException {
|
||||
final Step step = getStep(stepicId);
|
||||
final StepicWrappers.Step step = getStep(stepicId);
|
||||
if (!step.name.equals(PYCHARM_PREFIX)) return;
|
||||
final Task task = new Task();
|
||||
task.setStepicId(stepicId);
|
||||
task.setName(step.options != null ? step.options.title : PYCHARM_PREFIX);
|
||||
task.setText(step.text);
|
||||
for (TestFileWrapper wrapper : step.options.test) {
|
||||
for (StepicWrappers.TestFileWrapper wrapper : step.options.test) {
|
||||
task.addTestsTexts(wrapper.name, wrapper.text);
|
||||
}
|
||||
|
||||
@@ -310,8 +344,8 @@ public class EduStepicConnector {
|
||||
lesson.taskList.add(task);
|
||||
}
|
||||
|
||||
public static Step getStep(Integer step) throws IOException {
|
||||
return getFromStepic("steps/" + String.valueOf(step), StepContainer.class).steps.get(0).block;
|
||||
public static StepicWrappers.Step getStep(Integer step) throws IOException {
|
||||
return getFromStepic("steps/" + String.valueOf(step), StepicWrappers.StepContainer.class).steps.get(0).block;
|
||||
}
|
||||
|
||||
|
||||
@@ -334,14 +368,13 @@ public class EduStepicConnector {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
final boolean success = login(login, password);
|
||||
if (!success) return;
|
||||
if (login(login, password) == null) return;
|
||||
}
|
||||
}
|
||||
|
||||
final HttpPost attemptRequest = new HttpPost(stepicApiUrl + "attempts");
|
||||
setHeaders(attemptRequest, "application/json");
|
||||
String attemptRequestBody = new Gson().toJson(new AttemptWrapper(task.getStepicId()));
|
||||
String attemptRequestBody = new Gson().toJson(new StepicWrappers.AttemptWrapper(task.getStepicId()));
|
||||
attemptRequest.setEntity(new StringEntity(attemptRequestBody, ContentType.APPLICATION_JSON));
|
||||
|
||||
try {
|
||||
@@ -352,12 +385,12 @@ public class EduStepicConnector {
|
||||
if (statusLine.getStatusCode() != HttpStatus.SC_CREATED) {
|
||||
LOG.error("Failed to make attempt " + attemptResponseString);
|
||||
}
|
||||
final AttemptWrapper.Attempt attempt = new Gson().fromJson(attemptResponseString, AttemptContainer.class).attempts.get(0);
|
||||
final StepicWrappers.AttemptWrapper.Attempt attempt = new Gson().fromJson(attemptResponseString, StepicWrappers.AttemptContainer.class).attempts.get(0);
|
||||
|
||||
final Map<String, TaskFile> taskFiles = task.getTaskFiles();
|
||||
final ArrayList<SolutionFile> files = new ArrayList<SolutionFile>();
|
||||
final ArrayList<StepicWrappers.SolutionFile> files = new ArrayList<StepicWrappers.SolutionFile>();
|
||||
for (TaskFile fileEntry : taskFiles.values()) {
|
||||
files.add(new SolutionFile(fileEntry.name, fileEntry.text));
|
||||
files.add(new StepicWrappers.SolutionFile(fileEntry.name, fileEntry.text));
|
||||
}
|
||||
postSubmission(passed, attempt, files);
|
||||
}
|
||||
@@ -366,11 +399,11 @@ public class EduStepicConnector {
|
||||
}
|
||||
}
|
||||
|
||||
private static void postSubmission(boolean passed, AttemptWrapper.Attempt attempt, ArrayList<SolutionFile> files) throws IOException {
|
||||
private static void postSubmission(boolean passed, StepicWrappers.AttemptWrapper.Attempt attempt, ArrayList<StepicWrappers.SolutionFile> files) throws IOException {
|
||||
final HttpPost request = new HttpPost(stepicApiUrl + "submissions");
|
||||
setHeaders(request, "application/json");
|
||||
|
||||
String requestBody = new Gson().toJson(new SubmissionWrapper(attempt.id, passed ? "1" : "0", files));
|
||||
String requestBody = new Gson().toJson(new StepicWrappers.SubmissionWrapper(attempt.id, passed ? "1" : "0", files));
|
||||
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
|
||||
final CloseableHttpResponse response = ourClient.execute(request);
|
||||
final HttpEntity responseEntity = response.getEntity();
|
||||
@@ -398,15 +431,15 @@ public class EduStepicConnector {
|
||||
indicator.setText("Uploading course to " + stepicUrl);
|
||||
final HttpPost request = new HttpPost(stepicApiUrl + "courses");
|
||||
if (ourClient == null || !relogin) {
|
||||
if (!login()) return;
|
||||
if (!login(project)) return;
|
||||
}
|
||||
final AuthorWrapper user = getCurrentUser();
|
||||
final StepicWrappers.AuthorWrapper user = getCurrentUser();
|
||||
if (user != null) {
|
||||
course.setAuthors(user.users);
|
||||
}
|
||||
|
||||
setHeaders(request, "application/json");
|
||||
String requestBody = new Gson().toJson(new CourseWrapper(course));
|
||||
String requestBody = new Gson().toJson(new StepicWrappers.CourseWrapper(course));
|
||||
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
|
||||
|
||||
try {
|
||||
@@ -416,13 +449,13 @@ public class EduStepicConnector {
|
||||
final StatusLine line = response.getStatusLine();
|
||||
if (line.getStatusCode() != HttpStatus.SC_CREATED) {
|
||||
if (!relogin) {
|
||||
login();
|
||||
login(project);
|
||||
postCourse(project, course, true, indicator);
|
||||
}
|
||||
LOG.error("Failed to push " + responseString);
|
||||
return;
|
||||
}
|
||||
final CourseInfo postedCourse = new Gson().fromJson(responseString, CoursesContainer.class).courses.get(0);
|
||||
final CourseInfo postedCourse = new Gson().fromJson(responseString, StepicWrappers.CoursesContainer.class).courses.get(0);
|
||||
|
||||
final int sectionId = postModule(postedCourse.id, 1, String.valueOf(postedCourse.getName()));
|
||||
int position = 1;
|
||||
@@ -439,14 +472,13 @@ public class EduStepicConnector {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean login() {
|
||||
final String login = StudySettings.getInstance().getLogin();
|
||||
private static boolean login(@NotNull final Project project) {
|
||||
final String login = StudyTaskManager.getInstance(project).getLogin();
|
||||
if (StringUtil.isEmptyOrSpaces(login)) {
|
||||
return showLoginDialog();
|
||||
}
|
||||
else {
|
||||
boolean success = login(login, StudySettings.getInstance().getPassword());
|
||||
if (!success) {
|
||||
if (login(login, StudyTaskManager.getInstance(project).getPassword()) == null) {
|
||||
return showLoginDialog();
|
||||
}
|
||||
}
|
||||
@@ -498,8 +530,8 @@ public class EduStepicConnector {
|
||||
private static void postUnit(int lessonId, int position, int sectionId) {
|
||||
final HttpPost request = new HttpPost(stepicApiUrl + "units");
|
||||
setHeaders(request, "application/json");
|
||||
final UnitWrapper unitWrapper = new UnitWrapper();
|
||||
unitWrapper.unit = new Unit();
|
||||
final StepicWrappers.UnitWrapper unitWrapper = new StepicWrappers.UnitWrapper();
|
||||
unitWrapper.unit = new StepicWrappers.Unit();
|
||||
unitWrapper.unit.lesson = lessonId;
|
||||
unitWrapper.unit.position = position;
|
||||
unitWrapper.unit.section = sectionId;
|
||||
@@ -524,11 +556,11 @@ public class EduStepicConnector {
|
||||
private static int postModule(int courseId, int position, @NotNull final String title) {
|
||||
final HttpPost request = new HttpPost(stepicApiUrl + "sections");
|
||||
setHeaders(request, "application/json");
|
||||
final Section section = new Section();
|
||||
final StepicWrappers.Section section = new StepicWrappers.Section();
|
||||
section.course = courseId;
|
||||
section.title = title;
|
||||
section.position = position;
|
||||
final SectionWrapper sectionContainer = new SectionWrapper();
|
||||
final StepicWrappers.SectionWrapper sectionContainer = new StepicWrappers.SectionWrapper();
|
||||
sectionContainer.section = section;
|
||||
String requestBody = new Gson().toJson(sectionContainer);
|
||||
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
|
||||
@@ -541,7 +573,8 @@ public class EduStepicConnector {
|
||||
if (line.getStatusCode() != HttpStatus.SC_CREATED) {
|
||||
LOG.error("Failed to push " + responseString);
|
||||
}
|
||||
final Section postedSection = new Gson().fromJson(responseString, SectionContainer.class).sections.get(0);
|
||||
final StepicWrappers.Section
|
||||
postedSection = new Gson().fromJson(responseString, StepicWrappers.SectionContainer.class).sections.get(0);
|
||||
return postedSection.id;
|
||||
}
|
||||
catch (IOException e) {
|
||||
@@ -550,17 +583,17 @@ public class EduStepicConnector {
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static int updateLesson(Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
|
||||
public static int updateLesson(@NotNull final Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
|
||||
final HttpPut request = new HttpPut(stepicApiUrl + "lessons/" + String.valueOf(lesson.id));
|
||||
if (ourClient == null) {
|
||||
if (!login()) {
|
||||
if (!login(project)) {
|
||||
LOG.error("Failed to push lesson");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
setHeaders(request, "application/json");
|
||||
String requestBody = new Gson().toJson(new LessonWrapper(lesson));
|
||||
String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson));
|
||||
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
|
||||
|
||||
try {
|
||||
@@ -589,14 +622,14 @@ public class EduStepicConnector {
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static int postLesson(Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
|
||||
public static int postLesson(@NotNull final Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
|
||||
final HttpPost request = new HttpPost(stepicApiUrl + "lessons");
|
||||
if (ourClient == null) {
|
||||
login();
|
||||
login(project);
|
||||
}
|
||||
|
||||
setHeaders(request, "application/json");
|
||||
String requestBody = new Gson().toJson(new LessonWrapper(lesson));
|
||||
String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson));
|
||||
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
|
||||
|
||||
try {
|
||||
@@ -646,7 +679,7 @@ public class EduStepicConnector {
|
||||
setHeaders(request, "application/json");
|
||||
final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create();
|
||||
ApplicationManager.getApplication().invokeLater(() -> {
|
||||
final String requestBody = gson.toJson(new StepSourceWrapper(project, task, lessonId));
|
||||
final String requestBody = gson.toJson(new StepicWrappers.StepSourceWrapper(project, task, lessonId));
|
||||
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
|
||||
|
||||
try {
|
||||
@@ -664,274 +697,9 @@ public class EduStepicConnector {
|
||||
});
|
||||
}
|
||||
|
||||
private static void setHeaders(@NotNull final HttpRequestBase request, String contentType) {
|
||||
static void setHeaders(@NotNull final HttpRequestBase request, String contentType) {
|
||||
request.addHeader(new BasicHeader("referer", stepicUrl));
|
||||
request.addHeader(new BasicHeader("X-CSRFToken", ourCSRFToken));
|
||||
request.addHeader(new BasicHeader("content-type", contentType));
|
||||
}
|
||||
|
||||
private static class StepContainer {
|
||||
List<StepSource> steps;
|
||||
}
|
||||
|
||||
private static class Step {
|
||||
@Expose StepOptions options;
|
||||
@Expose String text;
|
||||
@Expose String name = "pycharm";
|
||||
@Expose StepOptions source;
|
||||
|
||||
public static Step fromTask(Project project, @NotNull final Task task) {
|
||||
final Step step = new Step();
|
||||
step.text = task.getTaskText(project);
|
||||
step.source = StepOptions.fromTask(project, task);
|
||||
return step;
|
||||
}
|
||||
}
|
||||
|
||||
private static class StepOptions {
|
||||
@Expose List<TestFileWrapper> test;
|
||||
@Expose String title;
|
||||
@Expose List<TaskFile> files;
|
||||
@Expose String text;
|
||||
|
||||
public static StepOptions fromTask(final Project project, @NotNull final Task task) {
|
||||
final StepOptions source = new StepOptions();
|
||||
setTests(task, source, project);
|
||||
source.files = new ArrayList<TaskFile>();
|
||||
source.title = task.getName();
|
||||
for (final Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
|
||||
final TaskFile taskFile = new TaskFile();
|
||||
TaskFile.copy(entry.getValue(), taskFile);
|
||||
ApplicationManager.getApplication().runWriteAction(() -> {
|
||||
final VirtualFile taskDir = task.getTaskDir(project);
|
||||
assert taskDir != null;
|
||||
VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
|
||||
assert ideaDir != null;
|
||||
EduUtils.createStudentFileFromAnswer(project, ideaDir, taskDir, entry.getKey(), taskFile);
|
||||
});
|
||||
taskFile.name = entry.getKey();
|
||||
|
||||
VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
|
||||
if (ideaDir == null) return null;
|
||||
final VirtualFile file = ideaDir.findChild(taskFile.name);
|
||||
try {
|
||||
if (file != null) {
|
||||
if (EduUtils.isImage(taskFile.name)) {
|
||||
taskFile.text = Base64.encodeBase64URLSafeString(FileUtil.loadBytes(file.getInputStream()));
|
||||
}
|
||||
else {
|
||||
taskFile.text = FileUtil.loadTextAndClose(file.getInputStream());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.error("Can't find file " + file.getPath());
|
||||
}
|
||||
|
||||
source.files.add(taskFile);
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
private static void setTests(@NotNull final Task task, @NotNull final StepOptions source, @NotNull final Project project) {
|
||||
final Map<String, String> testsText = task.getTestsText();
|
||||
if (testsText.isEmpty()) {
|
||||
ApplicationManager.getApplication().runReadAction(() -> {
|
||||
source.test = Collections.singletonList(new TestFileWrapper(EduNames.TESTS_FILE, task.getTestsText(project)));
|
||||
});
|
||||
}
|
||||
else {
|
||||
source.test = new ArrayList<TestFileWrapper>();
|
||||
for (Map.Entry<String, String> entry : testsText.entrySet()) {
|
||||
source.test.add(new TestFileWrapper(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class CoursesContainer {
|
||||
public List<CourseInfo> courses;
|
||||
public Map meta;
|
||||
}
|
||||
|
||||
static class StepSourceWrapper {
|
||||
@Expose
|
||||
StepSource stepSource;
|
||||
|
||||
public StepSourceWrapper(Project project, Task task, int lessonId) {
|
||||
stepSource = new StepSource(project, task, lessonId);
|
||||
}
|
||||
}
|
||||
|
||||
static class CourseWrapper {
|
||||
CourseInfo course;
|
||||
|
||||
public CourseWrapper(Course course) {
|
||||
this.course = new CourseInfo();
|
||||
this.course.setName(course.getName());
|
||||
this.course.setDescription(course.getDescription());
|
||||
this.course.setAuthors(course.getAuthors());
|
||||
}
|
||||
}
|
||||
|
||||
static class LessonWrapper {
|
||||
Lesson lesson;
|
||||
|
||||
public LessonWrapper(Lesson lesson) {
|
||||
this.lesson = new Lesson();
|
||||
this.lesson.setName(lesson.getName());
|
||||
this.lesson.id = lesson.id;
|
||||
this.lesson.steps = new ArrayList<Integer>();
|
||||
}
|
||||
}
|
||||
|
||||
static class LessonContainer {
|
||||
List<Lesson> lessons;
|
||||
}
|
||||
|
||||
static class StepSource {
|
||||
@Expose Step block;
|
||||
@Expose int position = 0;
|
||||
@Expose int lesson = 0;
|
||||
|
||||
public StepSource(Project project, Task task, int lesson) {
|
||||
this.lesson = lesson;
|
||||
position = task.getIndex();
|
||||
block = Step.fromTask(project, task);
|
||||
}
|
||||
}
|
||||
|
||||
static class TestFileWrapper {
|
||||
@Expose private final String name;
|
||||
@Expose private final String text;
|
||||
|
||||
public TestFileWrapper(String name, String text) {
|
||||
this.name = name;
|
||||
this.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
static class Section {
|
||||
List<Integer> units;
|
||||
int course;
|
||||
String title;
|
||||
int position;
|
||||
int id;
|
||||
}
|
||||
|
||||
static class SectionWrapper {
|
||||
Section section;
|
||||
}
|
||||
|
||||
static class SectionContainer {
|
||||
List<Section> sections;
|
||||
List<Lesson> lessons;
|
||||
|
||||
List<Unit> units;
|
||||
}
|
||||
|
||||
static class Unit {
|
||||
int id;
|
||||
int section;
|
||||
int lesson;
|
||||
int position;
|
||||
}
|
||||
|
||||
static class UnitContainer {
|
||||
|
||||
List<Unit> units;
|
||||
}
|
||||
|
||||
static class UnitWrapper{
|
||||
Unit unit;
|
||||
}
|
||||
|
||||
|
||||
static class AttemptWrapper {
|
||||
static class Attempt {
|
||||
public Attempt(int step) {
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
int step;
|
||||
int id;
|
||||
}
|
||||
public AttemptWrapper(int step) {
|
||||
attempt = new Attempt(step);
|
||||
}
|
||||
|
||||
Attempt attempt;
|
||||
}
|
||||
|
||||
static class AttemptContainer {
|
||||
List<AttemptWrapper.Attempt> attempts;
|
||||
}
|
||||
|
||||
static class SolutionFile {
|
||||
String name;
|
||||
String text;
|
||||
|
||||
public SolutionFile(String name, String text) {
|
||||
this.name = name;
|
||||
this.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
static class AuthorWrapper {
|
||||
List<CourseInfo.Author> users;
|
||||
}
|
||||
|
||||
static class SubmissionWrapper {
|
||||
Submission submission;
|
||||
|
||||
|
||||
public SubmissionWrapper(int attempt, String score, ArrayList<SolutionFile> files) {
|
||||
submission = new Submission(score, attempt, files);
|
||||
}
|
||||
|
||||
static class Submission {
|
||||
int attempt;
|
||||
|
||||
private final Reply reply;
|
||||
|
||||
public Submission(String score, int attempt, ArrayList<SolutionFile> files) {
|
||||
reply = new Reply(files, score);
|
||||
this.attempt = attempt;
|
||||
}
|
||||
|
||||
static class Reply {
|
||||
String score;
|
||||
List<SolutionFile> solution;
|
||||
|
||||
public Reply(ArrayList<SolutionFile> files, String score) {
|
||||
this.score = score;
|
||||
solution = files;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class User {
|
||||
String first_name;
|
||||
String last_name;
|
||||
String email;
|
||||
String password;
|
||||
|
||||
public User(String user, String password) {
|
||||
email = user;
|
||||
this.password = password;
|
||||
this.first_name = user;
|
||||
this.last_name = user;
|
||||
}
|
||||
}
|
||||
|
||||
static class UserWrapper {
|
||||
User user;
|
||||
|
||||
public UserWrapper(String user, String password) {
|
||||
this.user = new User(user, password);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-6
@@ -14,6 +14,7 @@ public class LoginDialog extends DialogWrapper {
|
||||
myLoginPanel = new LoginPanel(this);
|
||||
setTitle("Login to Stepic");
|
||||
setOKButtonText("Login");
|
||||
setTitle("Login to Stepic");
|
||||
init();
|
||||
}
|
||||
|
||||
@@ -40,14 +41,12 @@ public class LoginDialog extends DialogWrapper {
|
||||
@Override
|
||||
protected void doOKAction() {
|
||||
AuthDataHolder authData = myLoginPanel.getAuthData();
|
||||
final boolean success = EduStepicConnector.login(authData.email, authData.password);
|
||||
if (!success) {
|
||||
setErrorText("Login failed");
|
||||
final StepicUser user = EduStepicConnector.login(authData.email, authData.password);
|
||||
if (user != null) {
|
||||
super.doOKAction();
|
||||
}
|
||||
else {
|
||||
StudySettings.getInstance().setLogin(authData.email);
|
||||
StudySettings.getInstance().setPassword(authData.password);
|
||||
super.doOKAction();
|
||||
setErrorText("Login failed");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
package com.jetbrains.edu.learning.stepic;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager;
|
||||
import com.intellij.openapi.fileEditor.FileEditorManagerEvent;
|
||||
import com.intellij.openapi.fileEditor.FileEditorManagerListener;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.progress.Task;
|
||||
import com.intellij.openapi.progress.util.ProgressIndicatorBase;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import com.jetbrains.edu.learning.StudyUtils;
|
||||
import com.jetbrains.edu.learning.courseFormat.StudyStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
|
||||
public class StepicAdaptiveReactionsPanel extends JPanel {
|
||||
private final ReactionButtonPanel myHardPanel;
|
||||
private final ReactionButtonPanel myBoringPanel;
|
||||
private final Project myProject;
|
||||
private static final int TOO_HARD_REACTION = 0;
|
||||
private static final int TOO_BORING_REACTION = -1;
|
||||
private static final String HARD_REACTION = "Too Hard";
|
||||
private static final String BORING_REACTION = "Too Boring";
|
||||
private static final String SOLVED_TASK_TOOLTIP = "Task Is Solved";
|
||||
private static final String HARD_LABEL_TOOLTIP = "Click To Get An Easier Task";
|
||||
private static final String BORING_LABEL_TOOLTIP = "Click To Get A More Challenging Task";
|
||||
|
||||
public StepicAdaptiveReactionsPanel(@NotNull final Project project) {
|
||||
myProject = project;
|
||||
setLayout(new GridBagLayout());
|
||||
setBackground(UIUtil.getTextFieldBackground());
|
||||
|
||||
myHardPanel = new ReactionButtonPanel(HARD_REACTION, HARD_LABEL_TOOLTIP, SOLVED_TASK_TOOLTIP, TOO_HARD_REACTION);
|
||||
myBoringPanel = new ReactionButtonPanel(BORING_REACTION, BORING_LABEL_TOOLTIP, SOLVED_TASK_TOOLTIP, TOO_BORING_REACTION);
|
||||
addFileListener();
|
||||
|
||||
final GridBagConstraints c = new GridBagConstraints();
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.gridx = 0;
|
||||
c.gridy = 0;
|
||||
add(Box.createVerticalStrut(3), c);
|
||||
c.gridx = 1;
|
||||
c.gridy = 1;
|
||||
add(Box.createHorizontalStrut(3), c);
|
||||
c.weightx = 1;
|
||||
c.gridx = 2;
|
||||
add(myHardPanel, c);
|
||||
c.gridx = 3;
|
||||
c.weightx = 0;
|
||||
add(Box.createHorizontalStrut(3), c);
|
||||
c.weightx = 1;
|
||||
c.gridx = 4;
|
||||
add(myBoringPanel, c);
|
||||
c.gridx = 5;
|
||||
c.weightx = 0;
|
||||
add(Box.createHorizontalStrut(3), c);
|
||||
}
|
||||
|
||||
public void setEnabledRecursive(final boolean isEnabled) {
|
||||
ApplicationManager.getApplication().invokeLater(() -> {
|
||||
super.setEnabled(isEnabled);
|
||||
myHardPanel.setEnabledRecursive(isEnabled);
|
||||
myBoringPanel.setEnabledRecursive(isEnabled);
|
||||
});
|
||||
}
|
||||
|
||||
private void addFileListener() {
|
||||
final FileEditorManagerListener editorManagerListener = new FileEditorManagerListener() {
|
||||
@Override
|
||||
public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
|
||||
final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getTaskFromSelectedEditor(myProject);
|
||||
final boolean isEnabled = task != null && task.getStatus() != StudyStatus.Solved;
|
||||
StepicAdaptiveReactionsPanel.this.setEnabledRecursive(isEnabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void selectionChanged(@NotNull FileEditorManagerEvent event) {
|
||||
final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getTaskFromSelectedEditor(myProject);
|
||||
final boolean isEnabled = task != null && task.getStatus() != StudyStatus.Solved;
|
||||
StepicAdaptiveReactionsPanel.this.setEnabledRecursive(isEnabled);
|
||||
}
|
||||
};
|
||||
myProject.getMessageBus().connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, editorManagerListener);
|
||||
}
|
||||
|
||||
private class ReactionButtonPanel extends JPanel {
|
||||
private final JPanel myButtonPanel;
|
||||
private final JLabel myLabel;
|
||||
|
||||
public ReactionButtonPanel(@NotNull final String text,
|
||||
@NotNull final String enabledTooltip,
|
||||
@NotNull final String disabledTooltip,
|
||||
int reaction) {
|
||||
com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getTaskFromSelectedEditor(myProject);
|
||||
final boolean isEnabled = task != null && task.getStatus() != StudyStatus.Solved;
|
||||
|
||||
myLabel = new JLabel(text);
|
||||
|
||||
myButtonPanel = new JPanel();
|
||||
myButtonPanel.setLayout(new BoxLayout(myButtonPanel, BoxLayout.PAGE_AXIS));
|
||||
myButtonPanel.setToolTipText(isEnabled ? enabledTooltip : disabledTooltip);
|
||||
myButtonPanel.add(Box.createVerticalStrut(5));
|
||||
myButtonPanel.add(myLabel);
|
||||
myButtonPanel.add(Box.createVerticalStrut(5));
|
||||
|
||||
setEnabledRecursive(isEnabled);
|
||||
|
||||
setLayout(new GridBagLayout());
|
||||
setBorder(BorderFactory.createEtchedBorder());
|
||||
add(myButtonPanel);
|
||||
addMouseListener(() -> EduAdaptiveStepicConnector.addNextRecommendedTask(myProject, reaction));
|
||||
}
|
||||
|
||||
private void addMouseListener(@NotNull Runnable onClickAction) {
|
||||
final ReactionMouseAdapter mouseAdapter = new ReactionMouseAdapter(onClickAction, this);
|
||||
this.addMouseListener(mouseAdapter);
|
||||
myButtonPanel.addMouseListener(mouseAdapter);
|
||||
myLabel.addMouseListener(mouseAdapter);
|
||||
}
|
||||
|
||||
|
||||
public void setEnabledRecursive(final boolean isEnabled) {
|
||||
ApplicationManager.getApplication().invokeLater(() -> {
|
||||
super.setEnabled(isEnabled);
|
||||
myButtonPanel.setEnabled(isEnabled);
|
||||
myLabel.setEnabled(isEnabled);
|
||||
});
|
||||
}
|
||||
|
||||
private class ReactionMouseAdapter extends MouseAdapter {
|
||||
private final Runnable myClickAction;
|
||||
private final ReactionButtonPanel myPanel;
|
||||
|
||||
public ReactionMouseAdapter(@NotNull final Runnable onClickAction, @NotNull final ReactionButtonPanel mainPanel) {
|
||||
myClickAction = onClickAction;
|
||||
myPanel = mainPanel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getClickCount() == 1) {
|
||||
final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getCurrentTask(myProject);
|
||||
if (task != null && task.getStatus() != StudyStatus.Solved) {
|
||||
final ProgressIndicatorBase progress = new ProgressIndicatorBase();
|
||||
progress.setText("Loading Next Recommendation");
|
||||
ProgressManager.getInstance().run(new Task.Backgroundable(myProject,
|
||||
"Loading Next Recommendation") {
|
||||
@Override
|
||||
public void run(@NotNull ProgressIndicator indicator) {
|
||||
StepicAdaptiveReactionsPanel.this.setEnabledRecursive(false);
|
||||
myClickAction.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinished() {
|
||||
StepicAdaptiveReactionsPanel.this.setEnabledRecursive(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e) {
|
||||
final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getCurrentTask(myProject);
|
||||
if (task != null && task.getStatus() != StudyStatus.Solved && myPanel.isEnabled()) {
|
||||
setBackground(UIUtil.getButtonSelectColor());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e) {
|
||||
setBackground(UIUtil.getLabelBackground());
|
||||
}
|
||||
|
||||
private void setBackground(Color color) {
|
||||
myPanel.setBackground(color);
|
||||
myButtonPanel.setBackground(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+28
-10
@@ -15,8 +15,12 @@
|
||||
*/
|
||||
package com.jetbrains.edu.learning.stepic;
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.ui.DocumentAdapter;
|
||||
import com.jetbrains.edu.learning.StudyTaskManager;
|
||||
import com.jetbrains.edu.learning.StudyUtils;
|
||||
import com.jetbrains.edu.learning.settings.StudyOptionsProvider;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -31,6 +35,7 @@ import java.awt.event.FocusListener;
|
||||
|
||||
public class StepicStudyOptions implements StudyOptionsProvider {
|
||||
private static final String DEFAULT_PASSWORD_TEXT = "************";
|
||||
private static final Logger LOG = Logger.getInstance(StepicStudyOptions.class);
|
||||
private JTextField myLoginTextField;
|
||||
private JPasswordField myPasswordField;
|
||||
private JPanel myPane;
|
||||
@@ -98,13 +103,19 @@ public class StepicStudyOptions implements StudyOptionsProvider {
|
||||
myPasswordField.setText(StringUtil.isEmpty(password) ? null : password);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
final StudySettings studySettings = StudySettings.getInstance();
|
||||
setLogin(studySettings.getLogin());
|
||||
setPassword(DEFAULT_PASSWORD_TEXT);
|
||||
Project project = StudyUtils.getStudyProject();
|
||||
if (project != null) {
|
||||
final StudyTaskManager studySettings = StudyTaskManager.getInstance(project);
|
||||
setLogin(studySettings.getLogin());
|
||||
setPassword(DEFAULT_PASSWORD_TEXT);
|
||||
|
||||
resetCredentialsModification();
|
||||
resetCredentialsModification();
|
||||
}
|
||||
else {
|
||||
LOG.warn("No study object is opened");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -112,13 +123,20 @@ public class StepicStudyOptions implements StudyOptionsProvider {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
if (myCredentialsModified) {
|
||||
final StudySettings studySettings = StudySettings.getInstance();
|
||||
studySettings.setLogin(getLogin());
|
||||
studySettings.setPassword(getPassword());
|
||||
if (!StringUtil.isEmptyOrSpaces(getLogin()) && !StringUtil.isEmptyOrSpaces(getPassword())) {
|
||||
EduStepicConnector.login(getLogin(), getPassword());
|
||||
final Project project = StudyUtils.getStudyProject();
|
||||
if (project != null) {
|
||||
final StudyTaskManager studyTaskManager = StudyTaskManager.getInstance(project);
|
||||
studyTaskManager.setLogin(getLogin());
|
||||
studyTaskManager.setPassword(getPassword());
|
||||
if (!StringUtil.isEmptyOrSpaces(getLogin()) && !StringUtil.isEmptyOrSpaces(getPassword())) {
|
||||
EduStepicConnector.login(getLogin(), getPassword());
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOG.warn("No study object is opened");
|
||||
}
|
||||
}
|
||||
resetCredentialsModification();
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.jetbrains.edu.learning.stepic;
|
||||
|
||||
import com.intellij.ide.passwordSafe.PasswordSafe;
|
||||
import com.intellij.ide.passwordSafe.PasswordSafeException;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
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 last_name;
|
||||
String email;
|
||||
|
||||
public StepicUser() {
|
||||
}
|
||||
|
||||
public StepicUser(String email, String password) {
|
||||
this.email = email;
|
||||
setPassword(password);
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return last_name;
|
||||
}
|
||||
|
||||
public void setLastName(String last_name) {
|
||||
this.last_name = last_name;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
final String login = getEmail();
|
||||
if (StringUtil.isEmptyOrSpaces(login)) return "";
|
||||
|
||||
String password;
|
||||
try {
|
||||
password = PasswordSafe.getInstance().getPassword(null, StudyTaskManager.class, STEPIC_SETTINGS_PASSWORD_KEY + login);
|
||||
}
|
||||
catch (PasswordSafeException e) {
|
||||
LOG.info("Couldn't get password for key [" + STEPIC_SETTINGS_PASSWORD_KEY + "]", e);
|
||||
password = "";
|
||||
}
|
||||
|
||||
return StringUtil.notNullize(password);
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
try {
|
||||
PasswordSafe.getInstance().storePassword(null, StudyTaskManager.class, STEPIC_SETTINGS_PASSWORD_KEY + getEmail(), password);
|
||||
}
|
||||
catch (PasswordSafeException e) {
|
||||
LOG.info("Couldn't set password for key [" + STEPIC_SETTINGS_PASSWORD_KEY + getEmail() + "]", e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return StringUtil.join(new String[]{firstName, last_name}, " ");
|
||||
}
|
||||
}
|
||||
+421
@@ -0,0 +1,421 @@
|
||||
package com.jetbrains.edu.learning.stepic;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
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.learning.core.EduNames;
|
||||
import com.jetbrains.edu.learning.core.EduUtils;
|
||||
import com.jetbrains.edu.learning.courseFormat.Course;
|
||||
import com.jetbrains.edu.learning.courseFormat.Lesson;
|
||||
import com.jetbrains.edu.learning.courseFormat.Task;
|
||||
import com.jetbrains.edu.learning.courseFormat.TaskFile;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class StepicWrappers {
|
||||
private static final Logger LOG = Logger.getInstance(StepOptions.class);
|
||||
|
||||
static class StepContainer {
|
||||
List<StepSource> steps;
|
||||
}
|
||||
|
||||
public static class Step {
|
||||
@Expose StepOptions options;
|
||||
@Expose String text;
|
||||
@Expose String name = "pycharm";
|
||||
@Expose StepOptions source;
|
||||
|
||||
public static Step fromTask(Project project, @NotNull final Task task) {
|
||||
final Step step = new Step();
|
||||
step.text = task.getTaskText(project);
|
||||
step.source = StepOptions.fromTask(project, task);
|
||||
return step;
|
||||
}
|
||||
}
|
||||
|
||||
public static class StepOptions {
|
||||
@Expose List<TestFileWrapper> test;
|
||||
@Expose String title;
|
||||
@Expose List<TaskFile> files;
|
||||
@Expose String text;
|
||||
@Expose List<List<String>> samples;
|
||||
@Expose Integer executionMemoryLimit;
|
||||
@Expose Integer executionTimeLimit;
|
||||
@Expose CodeTemplatesWrapper codeTemplates;
|
||||
|
||||
public static StepOptions fromTask(final Project project, @NotNull final Task task) {
|
||||
final StepOptions source = new StepOptions();
|
||||
setTests(task, source, project);
|
||||
source.files = new ArrayList<TaskFile>();
|
||||
source.title = task.getName();
|
||||
for (final Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
|
||||
final TaskFile taskFile = new TaskFile();
|
||||
TaskFile.copy(entry.getValue(), taskFile);
|
||||
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final VirtualFile taskDir = task.getTaskDir(project);
|
||||
assert taskDir != null;
|
||||
VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
|
||||
assert ideaDir != null;
|
||||
EduUtils.createStudentFileFromAnswer(project, ideaDir, taskDir, entry.getKey(), taskFile);
|
||||
}
|
||||
});
|
||||
taskFile.name = entry.getKey();
|
||||
|
||||
VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
|
||||
if (ideaDir == null) return null;
|
||||
final VirtualFile file = ideaDir.findChild(taskFile.name);
|
||||
try {
|
||||
if (file != null) {
|
||||
if (EduUtils.isImage(taskFile.name)) {
|
||||
taskFile.text = Base64.encodeBase64URLSafeString(FileUtil.loadBytes(file.getInputStream()));
|
||||
}
|
||||
else {
|
||||
taskFile.text = FileUtil.loadTextAndClose(file.getInputStream());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.error("Can't find file " + file.getPath());
|
||||
}
|
||||
|
||||
source.files.add(taskFile);
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
private static void setTests(@NotNull final Task task, @NotNull final StepOptions source, @NotNull final Project project) {
|
||||
final Map<String, String> testsText = task.getTestsText();
|
||||
if (testsText.isEmpty()) {
|
||||
ApplicationManager.getApplication().runReadAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
source.test = Collections.singletonList(new TestFileWrapper(EduNames.TESTS_FILE, task.getTestsText(project)));
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
source.test = new ArrayList<TestFileWrapper>();
|
||||
for (Map.Entry<String, String> entry : testsText.entrySet()) {
|
||||
source.test.add(new TestFileWrapper(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class CodeTemplatesWrapper {
|
||||
String python3;
|
||||
String python27;
|
||||
|
||||
@Nullable
|
||||
public String getTemplateForLanguage(@NotNull final String langauge) {
|
||||
if (langauge.equals(EduAdaptiveStepicConnector.PYTHON27)) {
|
||||
return python27;
|
||||
}
|
||||
|
||||
if (langauge.equals(EduAdaptiveStepicConnector.PYTHON3)) {
|
||||
return python3;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static class CoursesContainer {
|
||||
public List<CourseInfo> courses;
|
||||
public Map meta;
|
||||
}
|
||||
|
||||
static class StepSourceWrapper {
|
||||
@Expose
|
||||
StepSource stepSource;
|
||||
|
||||
public StepSourceWrapper(Project project, Task task, int lessonId) {
|
||||
stepSource = new StepSource(project, task, lessonId);
|
||||
}
|
||||
}
|
||||
|
||||
static class CourseWrapper {
|
||||
CourseInfo course;
|
||||
|
||||
public CourseWrapper(Course course) {
|
||||
this.course = new CourseInfo();
|
||||
this.course.setName(course.getName());
|
||||
this.course.setDescription(course.getDescription());
|
||||
this.course.setAuthors(course.getAuthors());
|
||||
}
|
||||
}
|
||||
|
||||
static class LessonWrapper {
|
||||
Lesson lesson;
|
||||
|
||||
public LessonWrapper(Lesson lesson) {
|
||||
this.lesson = new Lesson();
|
||||
this.lesson.setName(lesson.getName());
|
||||
this.lesson.id = lesson.id;
|
||||
this.lesson.steps = new ArrayList<Integer>();
|
||||
}
|
||||
}
|
||||
|
||||
static class LessonContainer {
|
||||
List<Lesson> lessons;
|
||||
}
|
||||
|
||||
static class StepSource {
|
||||
@Expose Step block;
|
||||
@Expose int position = 0;
|
||||
@Expose int lesson = 0;
|
||||
|
||||
public StepSource(Project project, Task task, int lesson) {
|
||||
this.lesson = lesson;
|
||||
position = task.getIndex();
|
||||
block = Step.fromTask(project, task);
|
||||
}
|
||||
}
|
||||
|
||||
static class TestFileWrapper {
|
||||
@Expose public final String name;
|
||||
@Expose public final String text;
|
||||
|
||||
public TestFileWrapper(String name, String text) {
|
||||
this.name = name;
|
||||
this.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
static class Section {
|
||||
List<Integer> units;
|
||||
int course;
|
||||
String title;
|
||||
int position;
|
||||
int id;
|
||||
}
|
||||
|
||||
static class SectionWrapper {
|
||||
Section section;
|
||||
}
|
||||
|
||||
static class SectionContainer {
|
||||
List<Section> sections;
|
||||
List<Lesson> lessons;
|
||||
|
||||
List<Unit> units;
|
||||
}
|
||||
|
||||
static class Unit {
|
||||
int id;
|
||||
int section;
|
||||
int lesson;
|
||||
int position;
|
||||
List<Integer> assignments;
|
||||
}
|
||||
|
||||
static class UnitContainer {
|
||||
|
||||
List<Unit> units;
|
||||
}
|
||||
|
||||
static class UnitWrapper {
|
||||
Unit unit;
|
||||
}
|
||||
|
||||
static class AttemptWrapper {
|
||||
static class Attempt {
|
||||
public Attempt(int step) {
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
int step;
|
||||
int id;
|
||||
}
|
||||
|
||||
public AttemptWrapper(int step) {
|
||||
attempt = new Attempt(step);
|
||||
}
|
||||
|
||||
Attempt attempt;
|
||||
}
|
||||
|
||||
static class AttemptToPostWrapper {
|
||||
static class Attempt {
|
||||
int step;
|
||||
String dataset_url;
|
||||
String status;
|
||||
String time;
|
||||
String time_left;
|
||||
String user;
|
||||
String user_id;
|
||||
|
||||
public Attempt(int step) {
|
||||
this.step = step;
|
||||
}
|
||||
}
|
||||
|
||||
public AttemptToPostWrapper(int step) {
|
||||
attempt = new Attempt(step);
|
||||
}
|
||||
|
||||
Attempt attempt;
|
||||
}
|
||||
|
||||
static class AttemptContainer {
|
||||
List<AttemptWrapper.Attempt> attempts;
|
||||
}
|
||||
|
||||
static class SolutionFile {
|
||||
String name;
|
||||
String text;
|
||||
|
||||
public SolutionFile(String name, String text) {
|
||||
this.name = name;
|
||||
this.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
static class AuthorWrapper {
|
||||
List<StepicUser> users;
|
||||
}
|
||||
|
||||
static class SubmissionWrapper {
|
||||
Submission submission;
|
||||
|
||||
|
||||
public SubmissionWrapper(int attempt, String score, ArrayList<SolutionFile> files) {
|
||||
submission = new Submission(score, attempt, files);
|
||||
}
|
||||
|
||||
static class Submission {
|
||||
int attempt;
|
||||
private final Reply reply;
|
||||
|
||||
public Submission(String score, int attempt, ArrayList<SolutionFile> files) {
|
||||
reply = new Reply(files, score);
|
||||
this.attempt = attempt;
|
||||
}
|
||||
|
||||
static class Reply {
|
||||
String score;
|
||||
List<SolutionFile> solution;
|
||||
|
||||
public Reply(ArrayList<SolutionFile> files, String score) {
|
||||
this.score = score;
|
||||
solution = files;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class UserWrapper {
|
||||
StepicUser user;
|
||||
|
||||
public UserWrapper(String user, String password) {
|
||||
this.user = new StepicUser(user, password);
|
||||
}
|
||||
}
|
||||
|
||||
static class RecommendationReaction {
|
||||
int reaction;
|
||||
String user;
|
||||
String lesson;
|
||||
|
||||
public RecommendationReaction(int reaction, String user, String lesson) {
|
||||
this.reaction = reaction;
|
||||
this.user = user;
|
||||
this.lesson = lesson;
|
||||
}
|
||||
}
|
||||
|
||||
static class RecommendationReactionWrapper {
|
||||
RecommendationReaction recommendationReaction;
|
||||
|
||||
public RecommendationReactionWrapper(RecommendationReaction recommendationReaction) {
|
||||
this.recommendationReaction = recommendationReaction;
|
||||
}
|
||||
}
|
||||
|
||||
static class RecommendationWrapper {
|
||||
Recommendation[] recommendations;
|
||||
}
|
||||
|
||||
static class Recommendation {
|
||||
String id;
|
||||
String lesson;
|
||||
}
|
||||
|
||||
|
||||
static class SubmissionToPostWrapper {
|
||||
Submission submission;
|
||||
|
||||
public SubmissionToPostWrapper(@NotNull String attemptId, @NotNull String language, @NotNull String code) {
|
||||
submission = new Submission(attemptId, new Submission.Reply(language, code));
|
||||
}
|
||||
|
||||
static class Submission {
|
||||
String attempt;
|
||||
Reply reply;
|
||||
|
||||
public Submission(String attempt, Reply reply) {
|
||||
this.attempt = attempt;
|
||||
this.reply = reply;
|
||||
}
|
||||
|
||||
static class Reply {
|
||||
String language;
|
||||
String code;
|
||||
|
||||
public Reply(String language, String code) {
|
||||
this.language = language;
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class ResultSubmissionWrapper {
|
||||
ResultSubmission[] submissions;
|
||||
|
||||
static class ResultSubmission {
|
||||
int id;
|
||||
String status;
|
||||
String hint;
|
||||
}
|
||||
}
|
||||
|
||||
static class AssignmentsWrapper {
|
||||
List<Assignment> assignments;
|
||||
}
|
||||
|
||||
static class Assignment {
|
||||
int id;
|
||||
int step;
|
||||
}
|
||||
|
||||
static class ViewsWrapper {
|
||||
View view;
|
||||
|
||||
public ViewsWrapper(final int assignment, final int step) {
|
||||
this.view = new View(assignment, step);
|
||||
}
|
||||
}
|
||||
|
||||
static class View {
|
||||
int assignment;
|
||||
int step;
|
||||
|
||||
public View(int assignment, int step) {
|
||||
this.assignment = assignment;
|
||||
this.step = step;
|
||||
}
|
||||
}
|
||||
}
|
||||
-90
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jetbrains.edu.learning.stepic;
|
||||
|
||||
import com.intellij.ide.passwordSafe.PasswordSafe;
|
||||
import com.intellij.ide.passwordSafe.PasswordSafeException;
|
||||
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.util.text.StringUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@SuppressWarnings("MethodMayBeStatic")
|
||||
@State(name = "StudySettings", storages = @Storage("stepic_settings.xml"))
|
||||
public class StudySettings implements PersistentStateComponent<StudySettings.State> {
|
||||
|
||||
private State myState = new State();
|
||||
|
||||
public static class State {
|
||||
@Nullable public String LOGIN = null;
|
||||
}
|
||||
|
||||
private static final String STEPIC_SETTINGS_PASSWORD_KEY = "STEPIC_SETTINGS_PASSWORD_KEY";
|
||||
private static final Logger LOG = Logger.getInstance(StudySettings.class.getName());
|
||||
|
||||
public static StudySettings getInstance() {
|
||||
return ServiceManager.getService(StudySettings.class);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getPassword() {
|
||||
final String login = getLogin();
|
||||
if (StringUtil.isEmptyOrSpaces(login)) return "";
|
||||
|
||||
String password;
|
||||
try {
|
||||
password = PasswordSafe.getInstance().getPassword(null, StudySettings.class, STEPIC_SETTINGS_PASSWORD_KEY);
|
||||
}
|
||||
catch (PasswordSafeException e) {
|
||||
LOG.info("Couldn't get password for key [" + STEPIC_SETTINGS_PASSWORD_KEY + "]", e);
|
||||
password = "";
|
||||
}
|
||||
|
||||
return StringUtil.notNullize(password);
|
||||
}
|
||||
|
||||
public void setPassword(@NotNull String password) {
|
||||
try {
|
||||
PasswordSafe.getInstance().storePassword(null, StudySettings.class, STEPIC_SETTINGS_PASSWORD_KEY, password);
|
||||
}
|
||||
catch (PasswordSafeException e) {
|
||||
LOG.info("Couldn't set password for key [" + STEPIC_SETTINGS_PASSWORD_KEY + "]", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getLogin() {
|
||||
return myState.LOGIN;
|
||||
}
|
||||
|
||||
public void setLogin(@Nullable String login) {
|
||||
myState.LOGIN = login;
|
||||
}
|
||||
@Nullable
|
||||
@Override
|
||||
public StudySettings.State getState() {
|
||||
return myState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadState(StudySettings.State state) {
|
||||
myState = state;
|
||||
}
|
||||
}
|
||||
+4
@@ -40,6 +40,10 @@ public class StudyAddRemoteCourse {
|
||||
return myContentPanel;
|
||||
}
|
||||
|
||||
public JTextField getLoginField() {
|
||||
return myLoginField;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return String.valueOf(myPasswordField.getPassword());
|
||||
}
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
|
||||
class StudyBrowserWindow extends JFrame {
|
||||
public class StudyBrowserWindow extends JFrame {
|
||||
private static final Logger LOG = Logger.getInstance(StudyToolWindow.class);
|
||||
private static final String EVENT_TYPE_CLICK = "click";
|
||||
private JFXPanel myPanel;
|
||||
|
||||
+3
-2
@@ -18,6 +18,7 @@ import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.project.ProjectUtil;
|
||||
import com.jetbrains.edu.learning.StudyPluginConfigurator;
|
||||
import com.jetbrains.edu.learning.StudyUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
@@ -39,8 +40,8 @@ public class StudyJavaFxToolWindow extends StudyToolWindow {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setText(String text) {
|
||||
public void setText(@NotNull String text) {
|
||||
StudyPluginConfigurator configurator = StudyUtils.getConfigurator(ProjectUtil.guessCurrentProject(this));
|
||||
myBrowserWindow.loadContent(text, configurator);
|
||||
myBrowserWindow.loadContent(text, configurator);
|
||||
}
|
||||
}
|
||||
|
||||
+86
-32
@@ -3,8 +3,13 @@ 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.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.fileChooser.FileChooser;
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.project.DefaultProjectFactory;
|
||||
import com.intellij.openapi.project.DefaultProjectFactoryImpl;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.project.ProjectManager;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
@@ -14,18 +19,20 @@ import com.intellij.openapi.ui.popup.PopupStep;
|
||||
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.ui.AncestorListenerAdapter;
|
||||
import com.intellij.util.Consumer;
|
||||
import com.jetbrains.edu.learning.StudyUtils;
|
||||
import com.jetbrains.edu.learning.courseFormat.Course;
|
||||
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
|
||||
import com.jetbrains.edu.learning.stepic.CourseInfo;
|
||||
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
|
||||
import com.jetbrains.edu.learning.stepic.StudySettings;
|
||||
import com.jetbrains.edu.learning.stepic.StepicUser;
|
||||
import icons.InteractiveLearningIcons;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.AncestorEvent;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
@@ -36,10 +43,11 @@ import java.util.List;
|
||||
* author: liana
|
||||
* data: 7/31/14.
|
||||
*/
|
||||
public class StudyNewProjectPanel{
|
||||
public class StudyNewProjectPanel {
|
||||
private static final Logger LOG = Logger.getInstance(StudyNewProjectPanel.class);
|
||||
private List<CourseInfo> myAvailableCourses = new ArrayList<CourseInfo>();
|
||||
private JButton myBrowseButton;
|
||||
private JComboBox myCoursesComboBox;
|
||||
private JComboBox<CourseInfo> myCoursesComboBox;
|
||||
private JButton myRefreshButton;
|
||||
private JPanel myContentPanel;
|
||||
private JLabel myAuthorLabel;
|
||||
@@ -50,19 +58,37 @@ public class StudyNewProjectPanel{
|
||||
private static final String CONNECTION_ERROR = "<html>Failed to download courses.<br>Check your Internet connection.</html>";
|
||||
private static final String INVALID_COURSE = "Selected course is invalid";
|
||||
private FacetValidatorsManager myValidationManager;
|
||||
private boolean isComboboxInitialized;
|
||||
|
||||
public StudyNewProjectPanel(StudyProjectGenerator generator) {
|
||||
public StudyNewProjectPanel(@NotNull final StudyProjectGenerator generator) {
|
||||
myGenerator = generator;
|
||||
myAvailableCourses = myGenerator.getCourses(false);
|
||||
myBrowseButton.setPreferredSize(new Dimension(28, 28));
|
||||
myRefreshButton.setPreferredSize(new Dimension(28, 28));
|
||||
if (myAvailableCourses.isEmpty()) {
|
||||
initListeners();
|
||||
myRefreshButton.setVisible(true);
|
||||
myRefreshButton.putClientProperty("JButton.buttonType", "null");
|
||||
myRefreshButton.setIcon(AllIcons.Actions.Refresh);
|
||||
|
||||
myLabel.setPreferredSize(new JLabel("Project name").getPreferredSize());
|
||||
myContentPanel.addAncestorListener(new AncestorListenerAdapter() {
|
||||
@Override
|
||||
public void ancestorMoved(AncestorEvent event) {
|
||||
if (!isComboboxInitialized && myContentPanel.isVisible()) {
|
||||
isComboboxInitialized = true;
|
||||
initCoursesCombobox();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initCoursesCombobox() {
|
||||
myAvailableCourses =
|
||||
myGenerator.getCoursesUnderProgress(false, "Getting Available Courses", ProjectManager.getInstance().getDefaultProject());
|
||||
if (myAvailableCourses == null || myAvailableCourses.isEmpty()) {
|
||||
setError(CONNECTION_ERROR);
|
||||
}
|
||||
else {
|
||||
for (CourseInfo courseInfo : myAvailableCourses) {
|
||||
myCoursesComboBox.addItem(courseInfo);
|
||||
}
|
||||
addCoursesToCombobox(myAvailableCourses);
|
||||
final CourseInfo selectedCourse = StudyUtils.getFirst(myAvailableCourses);
|
||||
final String authorsString = Course.getAuthorsString(selectedCourse.getAuthors());
|
||||
myAuthorLabel.setText(!StringUtil.isEmptyOrSpaces(authorsString) ? "Author: " + authorsString : "");
|
||||
@@ -72,13 +98,6 @@ public class StudyNewProjectPanel{
|
||||
myGenerator.setSelectedCourse(selectedCourse);
|
||||
setOK();
|
||||
}
|
||||
initListeners();
|
||||
myRefreshButton.setVisible(true);
|
||||
myRefreshButton.putClientProperty("JButton.buttonType", "null");
|
||||
myRefreshButton.setIcon(AllIcons.Actions.Refresh);
|
||||
|
||||
myLabel.setPreferredSize(new JLabel("Project name").getPreferredSize());
|
||||
|
||||
}
|
||||
|
||||
private void setupBrowseButton() {
|
||||
@@ -177,21 +196,22 @@ public class StudyNewProjectPanel{
|
||||
private class RefreshActionListener implements ActionListener {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
refreshCoursesList();
|
||||
final List<CourseInfo> courses =
|
||||
myGenerator.getCoursesUnderProgress(true, "Refreshing Course List", DefaultProjectFactory.getInstance().getDefaultProject());
|
||||
if (courses != null) {
|
||||
refreshCoursesList(courses);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshCoursesList() {
|
||||
final List<CourseInfo> courses = myGenerator.getCourses(true);
|
||||
private void refreshCoursesList(@NotNull final List<CourseInfo> courses) {
|
||||
if (courses.isEmpty()) {
|
||||
setError(CONNECTION_ERROR);
|
||||
return;
|
||||
}
|
||||
myCoursesComboBox.removeAllItems();
|
||||
|
||||
for (CourseInfo courseInfo : courses) {
|
||||
myCoursesComboBox.addItem(courseInfo);
|
||||
}
|
||||
addCoursesToCombobox(courses);
|
||||
myGenerator.setSelectedCourse(StudyUtils.getFirst(courses));
|
||||
|
||||
myGenerator.setCourses(courses);
|
||||
@@ -199,6 +219,20 @@ public class StudyNewProjectPanel{
|
||||
StudyProjectGenerator.flushCache(myAvailableCourses);
|
||||
}
|
||||
|
||||
private void addCoursesToCombobox(@NotNull List<CourseInfo> courses) {
|
||||
for (CourseInfo courseInfo : courses) {
|
||||
if (!courseInfo.isAdaptive()) {
|
||||
myCoursesComboBox.addItem(courseInfo);
|
||||
}
|
||||
else {
|
||||
final boolean isLoggedIn = myGenerator.myUser != null;
|
||||
if (isLoggedIn) {
|
||||
myCoursesComboBox.addItem(courseInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles selecting course in combo box
|
||||
@@ -213,10 +247,11 @@ public class StudyNewProjectPanel{
|
||||
if (selectedCourse == null || selectedCourse.equals(CourseInfo.INVALID_COURSE)) {
|
||||
myAuthorLabel.setText("");
|
||||
myDescriptionLabel.setText("");
|
||||
setError(INVALID_COURSE);
|
||||
return;
|
||||
}
|
||||
final String authorsString = Course.getAuthorsString(selectedCourse.getAuthors());
|
||||
myAuthorLabel.setText(!StringUtil.isEmptyOrSpaces(authorsString) ?"Author: " + authorsString : "");
|
||||
myAuthorLabel.setText(!StringUtil.isEmptyOrSpaces(authorsString) ? "Author: " + authorsString : "");
|
||||
myCoursesComboBox.removeItem(CourseInfo.INVALID_COURSE);
|
||||
myDescriptionLabel.setText(selectedCourse.getDescription());
|
||||
myGenerator.setSelectedCourse(selectedCourse);
|
||||
@@ -238,6 +273,7 @@ public class StudyNewProjectPanel{
|
||||
|
||||
protected AddRemoteDialog() {
|
||||
super(null);
|
||||
setTitle("Login To Stepic");
|
||||
myRemoteCourse = new StudyAddRemoteCourse();
|
||||
init();
|
||||
}
|
||||
@@ -248,8 +284,15 @@ public class StudyNewProjectPanel{
|
||||
return myRemoteCourse.getContentPanel();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public JComponent getPreferredFocusedComponent() {
|
||||
return myRemoteCourse.getLoginField();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doOKAction() {
|
||||
super.doOKAction();
|
||||
if (StringUtil.isEmptyOrSpaces(myRemoteCourse.getLogin())) {
|
||||
myRemoteCourse.setError("Please, enter your login");
|
||||
return;
|
||||
@@ -259,15 +302,26 @@ public class StudyNewProjectPanel{
|
||||
return;
|
||||
}
|
||||
|
||||
super.doOKAction();
|
||||
final boolean isSuccess = EduStepicConnector.login(myRemoteCourse.getLogin(), myRemoteCourse.getPassword());
|
||||
if (!isSuccess) {
|
||||
setError("Failed to log in");
|
||||
}
|
||||
StudySettings.getInstance().setLogin(myRemoteCourse.getLogin());
|
||||
StudySettings.getInstance().setPassword(myRemoteCourse.getPassword());
|
||||
refreshCoursesList();
|
||||
ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
|
||||
ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true);
|
||||
|
||||
final StepicUser stepicUser = StudyUtils.execCancelable(() -> EduStepicConnector.login(myRemoteCourse.getLogin(),
|
||||
myRemoteCourse.getPassword()));
|
||||
if (stepicUser != null) {
|
||||
stepicUser.setEmail(myRemoteCourse.getLogin());
|
||||
stepicUser.setPassword(myRemoteCourse.getPassword());
|
||||
myGenerator.myUser = stepicUser;
|
||||
|
||||
|
||||
final List<CourseInfo> courses = myGenerator.getCoursesAsynchronouslyIfNeeded(true);
|
||||
if (courses != null) {
|
||||
ApplicationManager.getApplication().invokeLater(() -> refreshCoursesList(courses));
|
||||
}
|
||||
}
|
||||
else {
|
||||
setError("Failed to login");
|
||||
}
|
||||
}, "Signing In And Getting Stepic Course List", true, new DefaultProjectFactoryImpl().getDefaultProject());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -22,6 +22,7 @@ import com.intellij.ui.BrowserHyperlinkListener;
|
||||
import com.intellij.ui.ColorUtil;
|
||||
import com.intellij.ui.components.JBScrollPane;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
@@ -61,7 +62,7 @@ public class StudySwingToolWindow extends StudyToolWindow {
|
||||
return scrollPane;
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
public void setText(@NotNull String text) {
|
||||
myTaskTextPane.setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package com.jetbrains.edu.learning.ui
|
||||
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
|
||||
import com.intellij.openapi.fileEditor.FileEditorManagerListener
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.wm.ToolWindow
|
||||
import com.jetbrains.edu.learning.StudyUtils
|
||||
import com.jetbrains.python.console.PythonConsoleView
|
||||
|
||||
|
||||
class StudyTestResultsToolWindowFactory: StudyToolWindowFactory() {
|
||||
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
|
||||
val currentTask = StudyUtils.getCurrentTask(project)
|
||||
if (currentTask != null) {
|
||||
val sdk = StudyUtils.findSdk(currentTask, project)
|
||||
if (sdk != null) {
|
||||
val testResultsToolWindow = PythonConsoleView(project, "Local test results", sdk);
|
||||
testResultsToolWindow.isEditable = false
|
||||
testResultsToolWindow.isConsoleEditorEnabled = false
|
||||
testResultsToolWindow.prompt = null
|
||||
toolWindow.isToHideOnEmptyContent = true
|
||||
|
||||
val contentManager = toolWindow.contentManager
|
||||
val content = contentManager.factory.createContent(testResultsToolWindow.component, null, false)
|
||||
contentManager.addContent(content)
|
||||
|
||||
project.messageBus.connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, getFileEditorManagerListener(toolWindow))
|
||||
}
|
||||
else {
|
||||
StudyUtils.showNoSdkNotification(currentTask, project)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getFileEditorManagerListener(toolWindow: ToolWindow): FileEditorManagerListener {
|
||||
|
||||
return object : FileEditorManagerListener {
|
||||
|
||||
override fun fileOpened(source: FileEditorManager, file: VirtualFile) {
|
||||
}
|
||||
|
||||
override fun fileClosed(source: FileEditorManager, file: VirtualFile) {
|
||||
toolWindow.setAvailable(false, {})
|
||||
}
|
||||
|
||||
override fun selectionChanged(event: FileEditorManagerEvent) {
|
||||
toolWindow.setAvailable(false, {})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+16
-5
@@ -35,6 +35,7 @@ import com.intellij.util.ui.JBUI;
|
||||
import com.jetbrains.edu.learning.*;
|
||||
import com.jetbrains.edu.learning.courseFormat.Course;
|
||||
import com.jetbrains.edu.learning.courseFormat.TaskFile;
|
||||
import com.jetbrains.edu.learning.stepic.StepicAdaptiveReactionsPanel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
@@ -45,6 +46,7 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D
|
||||
private static final Logger LOG = Logger.getInstance(StudyToolWindow.class);
|
||||
private static final String TASK_INFO_ID = "taskInfo";
|
||||
private static final String EMPTY_TASK_TEXT = "Please, open any task to see task description";
|
||||
|
||||
private final JBCardLayout myCardLayout;
|
||||
private final JPanel myContentPanel;
|
||||
private final OnePixelSplitter mySplitPane;
|
||||
@@ -63,7 +65,14 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D
|
||||
JPanel toolbarPanel = createToolbarPanel(getActionGroup(project));
|
||||
setToolbar(toolbarPanel);
|
||||
|
||||
myContentPanel.add(TASK_INFO_ID, createTaskInfoPanel(project));
|
||||
final JPanel panel = new JPanel(new BorderLayout());
|
||||
final Course course = StudyTaskManager.getInstance(project).getCourse();
|
||||
if (course != null && course.isAdaptive()) {
|
||||
panel.add(new StepicAdaptiveReactionsPanel(project), BorderLayout.NORTH);
|
||||
}
|
||||
JComponent taskInfoPanel = createTaskInfoPanel(project);
|
||||
panel.add(taskInfoPanel, BorderLayout.CENTER);
|
||||
myContentPanel.add(TASK_INFO_ID, panel);
|
||||
mySplitPane.setFirstComponent(myContentPanel);
|
||||
addAdditionalPanels(project);
|
||||
myCardLayout.show(myContentPanel, TASK_INFO_ID);
|
||||
@@ -76,14 +85,15 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D
|
||||
project.getMessageBus().connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, listener);
|
||||
}
|
||||
|
||||
if (StudyTaskManager.getInstance(project).isTurnEditingMode() || StudyTaskManager.getInstance(project).getToolWindowMode() == StudyToolWindowMode.EDITING) {
|
||||
if (StudyTaskManager.getInstance(project).isTurnEditingMode() ||
|
||||
StudyTaskManager.getInstance(project).getToolWindowMode() == StudyToolWindowMode.EDITING) {
|
||||
TaskFile file = StudyUtils.getSelectedTaskFile(project);
|
||||
if (file != null) {
|
||||
VirtualFile taskDir = file.getTask().getTaskDir(project);
|
||||
setTaskText(taskText, taskDir, project);
|
||||
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
setTaskText(taskText, null, project);
|
||||
}
|
||||
}
|
||||
@@ -98,6 +108,7 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void dispose() {
|
||||
}
|
||||
|
||||
@@ -180,7 +191,7 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void setText(String text);
|
||||
protected abstract void setText(@NotNull String text);
|
||||
|
||||
public void setEmptyText(@NotNull Project project) {
|
||||
if (StudyTaskManager.getInstance(project).getToolWindowMode() == StudyToolWindowMode.EDITING) {
|
||||
|
||||
+1
-1
@@ -7,6 +7,7 @@ import com.intellij.openapi.wm.ToolWindow;
|
||||
import com.intellij.openapi.wm.ToolWindowFactory;
|
||||
import com.intellij.ui.content.Content;
|
||||
import com.intellij.ui.content.ContentManager;
|
||||
import com.jetbrains.edu.learning.StudyProjectComponent;
|
||||
import com.jetbrains.edu.learning.StudyTaskManager;
|
||||
import com.jetbrains.edu.learning.StudyUtils;
|
||||
import com.jetbrains.edu.learning.courseFormat.Course;
|
||||
@@ -23,7 +24,6 @@ public class StudyToolWindowFactory implements ToolWindowFactory, DumbAware {
|
||||
StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
|
||||
final Course course = taskManager.getCourse();
|
||||
if (course != null) {
|
||||
|
||||
final StudyToolWindow studyToolWindow;
|
||||
if (StudyUtils.hasJavaFx() && StudyTaskManager.getInstance(project).shouldUseJavaFx()) {
|
||||
studyToolWindow = new StudyJavaFxToolWindow();
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module" module-name="platform-api" />
|
||||
<orderEntry type="module" module-name="python-community" />
|
||||
<orderEntry type="module" module-name="xml" />
|
||||
<orderEntry type="library" name="markdownj" level="project" />
|
||||
</component>
|
||||
|
||||
@@ -9,7 +9,7 @@ def get_file_text(path):
|
||||
return text
|
||||
|
||||
|
||||
def get_file_output(encoding="utf-8", path=sys.argv[-1]):
|
||||
def get_file_output(encoding="utf-8", path=sys.argv[-1], arg_string=""):
|
||||
"""
|
||||
Returns answer file output
|
||||
:param encoding: to decode output in python3
|
||||
@@ -18,7 +18,13 @@ def get_file_output(encoding="utf-8", path=sys.argv[-1]):
|
||||
"""
|
||||
import subprocess
|
||||
|
||||
proc = subprocess.Popen([sys.executable, path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
proc = subprocess.Popen([sys.executable, path], stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT)
|
||||
if arg_string:
|
||||
for arg in arg_string.split("\n"):
|
||||
proc.stdin.write(bytearray(str(arg) + "\n", encoding))
|
||||
proc.stdin.flush()
|
||||
|
||||
return list(map(lambda x: str(x.decode(encoding)), proc.communicate()[0].splitlines()))
|
||||
|
||||
|
||||
@@ -31,7 +37,8 @@ def test_file_importable():
|
||||
parent = os.path.abspath(os.path.join(path, os.pardir))
|
||||
python_files = [f for f in os.listdir(parent) if os.path.isfile(os.path.join(parent, f)) and f.endswith(".py")]
|
||||
for python_file in python_files:
|
||||
if python_file == "tests.py": continue
|
||||
if python_file == "tests.py":
|
||||
continue
|
||||
check_importable_path(os.path.join(parent, python_file))
|
||||
return
|
||||
check_importable_path(path)
|
||||
@@ -187,6 +194,28 @@ def get_answer_placeholders():
|
||||
return windows
|
||||
|
||||
|
||||
def check_samples(samples=()):
|
||||
"""
|
||||
Check script output for all samples. Sample is a two element list, where the first is input and
|
||||
the second is output.
|
||||
"""
|
||||
for sample in samples:
|
||||
if len(sample) == 2:
|
||||
output = get_file_output(arg_string=str(sample[0]))
|
||||
if "\n".join(output) != sample[1]:
|
||||
failed(
|
||||
"Test from samples failed: \n \n"
|
||||
"Input:\n{}"
|
||||
"\n \n"
|
||||
"Expected:\n{}"
|
||||
"\n \n"
|
||||
"Your result:\n{}".format(str.strip(sample[0]), str.strip(sample[1]), "\n".join(output)))
|
||||
return
|
||||
set_congratulation_message("All test from samples passed. Now we are checking your solution on Stepic server.")
|
||||
|
||||
passed()
|
||||
|
||||
|
||||
def run_common_tests(error_text="Please, reload file and try again"):
|
||||
test_is_initial_text()
|
||||
test_is_not_empty()
|
||||
|
||||
+41
-26
@@ -15,12 +15,13 @@ import com.jetbrains.edu.learning.actions.StudyRunAction;
|
||||
import com.jetbrains.edu.learning.checker.StudyCheckTask;
|
||||
import com.jetbrains.edu.learning.checker.StudyCheckUtils;
|
||||
import com.jetbrains.edu.learning.checker.StudyTestRunner;
|
||||
import com.jetbrains.edu.learning.checker.StudyTestsOutputParser;
|
||||
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.Task;
|
||||
import com.jetbrains.edu.learning.courseFormat.TaskFile;
|
||||
import com.jetbrains.edu.learning.editor.StudyEditor;
|
||||
import com.jetbrains.edu.learning.ui.StudyToolWindow;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -41,8 +42,8 @@ public class PyStudyCheckAction extends StudyCheckAction {
|
||||
}
|
||||
if (StudyCheckUtils.hasBackgroundProcesses(project)) return;
|
||||
|
||||
|
||||
if (!runTask(project)) return;
|
||||
final Course course = StudyTaskManager.getInstance(project).getCourse();
|
||||
if (course != null && !course.isAdaptive() && !runTask(project)) return;
|
||||
|
||||
final Task task = studyState.getTask();
|
||||
final VirtualFile taskDir = studyState.getTaskDir();
|
||||
@@ -90,34 +91,48 @@ public class PyStudyCheckAction extends StudyCheckAction {
|
||||
final Process testProcess,
|
||||
final String commandLine) {
|
||||
return new StudyCheckTask(project, studyState, myCheckInProgress, testProcess, commandLine) {
|
||||
@Override
|
||||
protected void onTaskFailed(StudyTestsOutputParser.TestsOutput testsOutput) {
|
||||
ApplicationManager.getApplication().invokeLater(() -> {
|
||||
if (myTaskDir == null) return;
|
||||
myTaskManger.setStatus(myTask, 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;
|
||||
}
|
||||
if (EduNames.STUDY.equals(myTaskManger.getCourse().getCourseMode())) {
|
||||
CommandProcessor.getInstance().runUndoTransparentAction(() -> ApplicationManager.getApplication().runWriteAction(() -> StudyCheckUtils.runSmartTestProcess(myTaskDir, testRunner, name, taskFile, project)));
|
||||
}
|
||||
}
|
||||
StudyCheckUtils.showTestResultPopUp(testsOutput.getMessage(), MessageType.ERROR.getPopupBackground(), project);
|
||||
StudyCheckUtils.navigateToFailedPlaceholder(myStudyState, myTask, myTaskDir, project);
|
||||
});
|
||||
@Override
|
||||
protected void onTaskFailed(String message) {
|
||||
ApplicationManager.getApplication().invokeLater(() -> {
|
||||
if (myTaskDir == null) return;
|
||||
myTaskManger.setStatus(myTask, 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;
|
||||
}
|
||||
};
|
||||
if (EduNames.STUDY.equals(myTaskManger.getCourse().getCourseMode())) {
|
||||
CommandProcessor.getInstance().runUndoTransparentAction(() -> ApplicationManager.getApplication().runWriteAction(() -> {
|
||||
StudyCheckUtils.runSmartTestProcess(myTaskDir, testRunner, name, taskFile, project);
|
||||
}));
|
||||
}
|
||||
}
|
||||
final StudyToolWindow toolWindow = StudyUtils.getStudyToolWindow(project);
|
||||
if (toolWindow != null) {
|
||||
final Course course = StudyTaskManager.getInstance(project).getCourse();
|
||||
if (course != null) {
|
||||
if (course.isAdaptive()) {
|
||||
StudyCheckUtils.showTestResultPopUp("Failed", MessageType.ERROR.getPopupBackground(), project);
|
||||
StudyCheckUtils.showTestResultsToolWindow(project, message, false);
|
||||
}
|
||||
else {
|
||||
StudyCheckUtils.showTestResultPopUp(message, MessageType.ERROR.getPopupBackground(), project);
|
||||
}
|
||||
}
|
||||
StudyCheckUtils.navigateToFailedPlaceholder(myStudyState, myTask, myTaskDir, project);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
private static VirtualFile getTaskVirtualFile(@NotNull final StudyState studyState,
|
||||
@NotNull final Task task,
|
||||
@NotNull final VirtualFile taskDir) {
|
||||
@NotNull final Task task,
|
||||
@NotNull final VirtualFile taskDir) {
|
||||
VirtualFile taskVirtualFile = studyState.getVirtualFile();
|
||||
for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
|
||||
String name = entry.getKey();
|
||||
@@ -131,7 +146,7 @@ public class PyStudyCheckAction extends StudyCheckAction {
|
||||
}
|
||||
return taskVirtualFile;
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getActionId() {
|
||||
|
||||
+5
-3
@@ -6,17 +6,19 @@ import com.intellij.facet.ui.ValidationResult;
|
||||
import com.intellij.ide.fileTemplates.FileTemplate;
|
||||
import com.intellij.ide.fileTemplates.FileTemplateManager;
|
||||
import com.intellij.ide.fileTemplates.FileTemplateUtil;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.progress.ProcessCanceledException;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.project.ProjectManager;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.platform.DirectoryProjectGenerator;
|
||||
import com.intellij.psi.PsiDirectory;
|
||||
import com.intellij.psi.PsiManager;
|
||||
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
|
||||
import com.jetbrains.edu.learning.ui.StudyNewProjectPanel;
|
||||
import com.jetbrains.edu.learning.stepic.CourseInfo;
|
||||
import com.jetbrains.edu.learning.ui.StudyNewProjectPanel;
|
||||
import com.jetbrains.python.newProject.PythonProjectGenerator;
|
||||
import icons.InteractiveLearningPythonIcons;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
@@ -89,14 +91,14 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator imp
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
public void validate() {
|
||||
fireStateChanged();
|
||||
ApplicationManager.getApplication().invokeLater(()->fireStateChanged());
|
||||
}
|
||||
});
|
||||
return settingsPanel.getContentPanel();
|
||||
}
|
||||
|
||||
public List<CourseInfo> getCourses() {
|
||||
return myGenerator.getCourses(false);
|
||||
return myGenerator.getCoursesUnderProgress(false, "Getting Courses", ProjectManager.getInstance().getDefaultProject());
|
||||
}
|
||||
|
||||
public void setSelectedCourse(CourseInfo course) {
|
||||
|
||||
Reference in New Issue
Block a user